From 0b239c9fe0f5b1358b1eb94f35ec5817b5f33434 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 23 Nov 2025 13:03:16 -0300 Subject: [PATCH] schema: support generic tags. --- schema/schema.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/schema/schema.go b/schema/schema.go index 4eea4bc..a5b36af 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -42,7 +42,10 @@ func fetchSchemaFromURL(schemaURL string) (string, error) { return string(body), nil } -type Schema map[string]KindSchema +type Schema struct { + GenericTags map[string]nextSpec `yaml:"generic_tags"` + Kinds map[string]KindSchema `yaml:"kinds"` +} type KindSchema struct { Content string `yaml:"content"` @@ -73,7 +76,10 @@ type Validator struct { } func NewValidatorFromBytes(schemaData []byte) (Validator, error) { - schema := make(Schema) + schema := Schema{ + GenericTags: make(map[string]nextSpec), + Kinds: make(map[string]KindSchema), + } if err := yaml.Unmarshal(schemaData, &schema); err != nil { return Validator{}, fmt.Errorf("failed to parse schema: %w", err) } @@ -210,7 +216,7 @@ func (v *Validator) ValidateEvent(evt nostr.Event) error { return ContentError{ErrDanglingSpace} } - if sch, ok := v.Schema[strconv.FormatUint(uint64(evt.Kind), 10)]; ok { + if sch, ok := v.Schema.Kinds[strconv.FormatUint(uint64(evt.Kind), 10)]; ok { switch sch.Content { case "json": if !json.Valid(unsafe.Slice(unsafe.StringData(evt.Content), len(evt.Content))) { @@ -233,10 +239,12 @@ func (v *Validator) ValidateEvent(evt nostr.Event) error { } var lastErr error + var tagWasValidated bool specs: for _, tagspec := range sch.Tags { if tagspec.Name == tag[0] || (tagspec.Prefix != "" && strings.HasPrefix(tag[0], tagspec.Prefix)) { if tagspec.Next != nil { + tagWasValidated = true if ii, err := v.validateNext(tag, 1, tagspec.Next); err != nil { lastErr = TagError{ti, ii, err} continue specs // see if there is another tagspec that matches this @@ -253,6 +261,15 @@ func (v *Validator) ValidateEvent(evt nostr.Event) error { // there was at least one failure for this tag and no further successes return lastErr } + + // when we don't find a specific tag validator for this kind, try a generic one + if !tagWasValidated { + if tagSpecNext, ok := v.Schema.GenericTags[tag[0]]; ok { + if ii, err := v.validateNext(tag, 1, &tagSpecNext); err != nil { + lastErr = TagError{ti, ii, err} + } + } + } } } @@ -279,7 +296,7 @@ func (v *Validator) findUnknownTypes(schema Schema) []string { var unknown []string visitedTypes := make([]string, 0, 10) - for _, kindSchema := range schema { + for _, kindSchema := range schema.Kinds { for _, tagSpec := range kindSchema.Tags { collectTypes(tagSpec.Next, visitedTypes, func(typeName string) { if _, ok := v.TypeValidators[typeName]; !ok { @@ -288,6 +305,13 @@ func (v *Validator) findUnknownTypes(schema Schema) []string { }) } } + for _, tagSpec := range schema.GenericTags { + collectTypes(tagSpec.Next, visitedTypes, func(typeName string) { + if _, ok := v.TypeValidators[typeName]; !ok { + unknown = append(unknown, typeName) + } + }) + } return unknown }