event typechecker.
This commit is contained in:
4
go.mod
4
go.mod
@@ -26,6 +26,7 @@ require (
|
|||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||||
github.com/rs/cors v1.11.1
|
github.com/rs/cors v1.11.1
|
||||||
github.com/rs/zerolog v1.33.0
|
github.com/rs/zerolog v1.33.0
|
||||||
|
github.com/segmentio/encoding v0.5.3
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
github.com/tyler-smith/go-bip32 v1.0.0
|
github.com/tyler-smith/go-bip32 v1.0.0
|
||||||
@@ -38,6 +39,7 @@ require (
|
|||||||
golang.org/x/net v0.37.0
|
golang.org/x/net v0.37.0
|
||||||
golang.org/x/sync v0.12.0
|
golang.org/x/sync v0.12.0
|
||||||
golang.org/x/text v0.23.0
|
golang.org/x/text v0.23.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -82,6 +84,7 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||||
|
github.com/segmentio/asm v1.1.3 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
@@ -89,5 +92,4 @@ require (
|
|||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.2 // indirect
|
google.golang.org/protobuf v1.36.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -251,6 +251,10 @@ github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR
|
|||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||||
|
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
|
||||||
|
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
||||||
|
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
|
||||||
|
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
|||||||
32
pointers.go
32
pointers.go
@@ -122,9 +122,24 @@ type EntityPointer struct {
|
|||||||
|
|
||||||
// EntityPointerFromTag creates an EntityPointer from an "a" tag (but it doesn't check if the tag is really "a", it could be anything).
|
// EntityPointerFromTag creates an EntityPointer from an "a" tag (but it doesn't check if the tag is really "a", it could be anything).
|
||||||
func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
|
func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
|
||||||
spl := strings.SplitN(refTag[1], ":", 3)
|
pointer, err := ParseAddrString(refTag[1])
|
||||||
|
if err != nil {
|
||||||
|
return EntityPointer{}, fmt.Errorf("invalid addr '%s': %w", refTag[1], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(refTag) > 2 {
|
||||||
|
if relay := (refTag)[2]; IsValidRelayURL(relay) {
|
||||||
|
pointer.Relays = []string{relay}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseAddrString(addr string) (EntityPointer, error) {
|
||||||
|
spl := strings.SplitN(addr, ":", 3)
|
||||||
if len(spl) != 3 {
|
if len(spl) != 3 {
|
||||||
return EntityPointer{}, fmt.Errorf("invalid addr ref '%s'", refTag[1])
|
return EntityPointer{}, fmt.Errorf("invalid splits")
|
||||||
}
|
}
|
||||||
|
|
||||||
pk, err := PubKeyFromHex(spl[1])
|
pk, err := PubKeyFromHex(spl[1])
|
||||||
@@ -134,21 +149,14 @@ func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
|
|||||||
|
|
||||||
kind, err := strconv.Atoi(spl[0])
|
kind, err := strconv.Atoi(spl[0])
|
||||||
if err != nil || kind > (1<<16) {
|
if err != nil || kind > (1<<16) {
|
||||||
return EntityPointer{}, fmt.Errorf("invalid addr kind '%s'", spl[0])
|
return EntityPointer{}, fmt.Errorf("invalid kind")
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer := EntityPointer{
|
return EntityPointer{
|
||||||
Kind: Kind(kind),
|
Kind: Kind(kind),
|
||||||
PublicKey: pk,
|
PublicKey: pk,
|
||||||
Identifier: spl[2],
|
Identifier: spl[2],
|
||||||
}
|
}, nil
|
||||||
if len(refTag) > 2 {
|
|
||||||
if relay := (refTag)[2]; IsValidRelayURL(relay) {
|
|
||||||
pointer.Relays = []string{relay}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pointer, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchesEvent checks if the pointer matches an event.
|
// MatchesEvent checks if the pointer matches an event.
|
||||||
|
|||||||
175
schema/schema.go
Normal file
175
schema/schema.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"github.com/segmentio/encoding/json"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed schema.yaml
|
||||||
|
var schemaFile []byte
|
||||||
|
|
||||||
|
type Schema map[string]KindSchema
|
||||||
|
|
||||||
|
type KindSchema struct {
|
||||||
|
Content string `yaml:"content"`
|
||||||
|
Tags []tagSpec `yaml:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagSpec struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Prefix string `yaml:"prefix"`
|
||||||
|
Next *nextSpec `yaml:"next"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nextSpec struct {
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Required bool `yaml:"required"`
|
||||||
|
Either []string `yaml:"either"`
|
||||||
|
Next *nextSpec `yaml:"next"`
|
||||||
|
Variadic bool `yaml:"variadic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Validator struct {
|
||||||
|
Schema Schema
|
||||||
|
FailOnUnknown bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValidator(schemaData string) Validator {
|
||||||
|
schema := make(Schema)
|
||||||
|
if err := yaml.Unmarshal([]byte(schemaData), &schema); err != nil {
|
||||||
|
panic(fmt.Errorf("failed to parse schema.yaml: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Validator{Schema: schema}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnknownContent = fmt.Errorf("unknown content")
|
||||||
|
ErrUnknownKind = fmt.Errorf("unknown kind")
|
||||||
|
ErrInvalidContentJson = fmt.Errorf("invalid content json")
|
||||||
|
ErrEmptyTag = fmt.Errorf("empty tag")
|
||||||
|
ErrUnknownTagType = fmt.Errorf("unknown tag type")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *Validator) ValidateEvent(evt nostr.Event) error {
|
||||||
|
if sch, ok := v.Schema[strconv.FormatUint(uint64(evt.Kind), 10)]; ok {
|
||||||
|
switch sch.Content {
|
||||||
|
case "json":
|
||||||
|
if !json.Valid(unsafe.Slice(unsafe.StringData(evt.Content), len(evt.Content))) {
|
||||||
|
return ErrInvalidContentJson
|
||||||
|
}
|
||||||
|
case "free":
|
||||||
|
default:
|
||||||
|
if v.FailOnUnknown {
|
||||||
|
return ErrInvalidContentJson
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags:
|
||||||
|
for _, tag := range evt.Tags {
|
||||||
|
if len(tag) == 0 {
|
||||||
|
return ErrEmptyTag
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
specs:
|
||||||
|
for _, tagspec := range sch.Tags {
|
||||||
|
if tagspec.Name == tag[0] || (tagspec.Prefix != "" && strings.HasPrefix(tag[0], tagspec.Prefix)) {
|
||||||
|
if tagspec.Next != nil {
|
||||||
|
if err := v.validateNext(tag, 1, tagspec.Next); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue specs // see if there is another tagspec that matches this
|
||||||
|
} else {
|
||||||
|
continue tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue specs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastErr != nil {
|
||||||
|
// there was at least one failure for this tag and no further successes
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.FailOnUnknown {
|
||||||
|
return ErrUnknownKind
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var gitcommitdummydecoder = make([]byte, 20)
|
||||||
|
|
||||||
|
func (v *Validator) validateNext(tag nostr.Tag, index int, this *nextSpec) error {
|
||||||
|
if len(tag) <= index {
|
||||||
|
if this.Required {
|
||||||
|
return fmt.Errorf("invalid tag '%s', missing index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch this.Type {
|
||||||
|
case "id":
|
||||||
|
if _, err := nostr.IDFromHex(tag[index]); err != nil {
|
||||||
|
return fmt.Errorf("invalid id at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
case "pubkey":
|
||||||
|
if _, err := nostr.PubKeyFromHex(tag[index]); err != nil {
|
||||||
|
return fmt.Errorf("invalid pubkey at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
case "addr":
|
||||||
|
if _, err := nostr.ParseAddrString(tag[index]); err != nil {
|
||||||
|
return fmt.Errorf("invalid addr at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
case "kind":
|
||||||
|
if _, err := strconv.ParseUint(tag[index], 10, 16); err != nil {
|
||||||
|
return fmt.Errorf("invalid kind at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
case "relay":
|
||||||
|
if url, err := url.Parse(tag[index]); err != nil || (url.Scheme != "ws" && url.Scheme != "wss") {
|
||||||
|
return fmt.Errorf("invalid relay at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
case "constrained":
|
||||||
|
if !slices.Contains(this.Either, tag[index]) {
|
||||||
|
return fmt.Errorf("invalid constrained at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
case "gitcommit":
|
||||||
|
if len(tag[index]) != 40 {
|
||||||
|
return fmt.Errorf("invalid gitcommit at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
if _, err := hex.Decode(gitcommitdummydecoder, unsafe.Slice(unsafe.StringData(tag[index]), 40)); err != nil {
|
||||||
|
return fmt.Errorf("invalid gitcommit at tag '%s', index %d", tag[0], index)
|
||||||
|
}
|
||||||
|
case "free":
|
||||||
|
default:
|
||||||
|
if v.FailOnUnknown {
|
||||||
|
return ErrUnknownTagType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.Variadic {
|
||||||
|
// apply this same validation to all further items
|
||||||
|
if len(tag) >= index {
|
||||||
|
return v.validateNext(tag, index+1, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.Next != nil {
|
||||||
|
return v.validateNext(tag, index+1, this.Next)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
259
schema/schema.yaml
Normal file
259
schema/schema.yaml
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
_profile: &profile
|
||||||
|
type: pubkey
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
|
||||||
|
_event: &event
|
||||||
|
type: id
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
next:
|
||||||
|
type: pubkey
|
||||||
|
|
||||||
|
_addr: &addr
|
||||||
|
type: addr
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
|
||||||
|
_kind: &kind
|
||||||
|
type: kind
|
||||||
|
required: true
|
||||||
|
|
||||||
|
_dtag: &dtag
|
||||||
|
name: d
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
|
||||||
|
_atag: &atag
|
||||||
|
name: a
|
||||||
|
next: *addr
|
||||||
|
|
||||||
|
_ptag: &ptag
|
||||||
|
name: p
|
||||||
|
next: *profile
|
||||||
|
|
||||||
|
0:
|
||||||
|
content: json
|
||||||
|
|
||||||
|
1:
|
||||||
|
content: free
|
||||||
|
tags:
|
||||||
|
-
|
||||||
|
name: e
|
||||||
|
next:
|
||||||
|
type: id
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
next:
|
||||||
|
type: constrained
|
||||||
|
either:
|
||||||
|
- reply
|
||||||
|
- root
|
||||||
|
next:
|
||||||
|
type: pubkey
|
||||||
|
-
|
||||||
|
name: q
|
||||||
|
next:
|
||||||
|
type: id
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
next:
|
||||||
|
type: pubkey
|
||||||
|
-
|
||||||
|
name: q
|
||||||
|
next:
|
||||||
|
type: addr
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
-
|
||||||
|
name: p
|
||||||
|
next: *profile
|
||||||
|
|
||||||
|
1111:
|
||||||
|
content: free
|
||||||
|
tags:
|
||||||
|
-
|
||||||
|
name: A
|
||||||
|
next: *addr
|
||||||
|
-
|
||||||
|
name: a
|
||||||
|
next: *addr
|
||||||
|
-
|
||||||
|
name: E
|
||||||
|
next: *event
|
||||||
|
-
|
||||||
|
name: e
|
||||||
|
next: *event
|
||||||
|
-
|
||||||
|
name: I
|
||||||
|
next: &external
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: url
|
||||||
|
-
|
||||||
|
name: i
|
||||||
|
next: *external
|
||||||
|
-
|
||||||
|
name: K
|
||||||
|
next: *kind
|
||||||
|
-
|
||||||
|
name: K
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: k
|
||||||
|
next: *kind
|
||||||
|
-
|
||||||
|
name: P
|
||||||
|
next: *profile
|
||||||
|
-
|
||||||
|
name: p
|
||||||
|
next: *profile
|
||||||
|
|
||||||
|
10002:
|
||||||
|
content: empty
|
||||||
|
tags:
|
||||||
|
-
|
||||||
|
name: r
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: constrained
|
||||||
|
either:
|
||||||
|
- read
|
||||||
|
- write
|
||||||
|
|
||||||
|
9802:
|
||||||
|
content: free
|
||||||
|
tags:
|
||||||
|
-
|
||||||
|
name: p
|
||||||
|
next: *profile
|
||||||
|
-
|
||||||
|
name: e
|
||||||
|
next: *event
|
||||||
|
-
|
||||||
|
name: a
|
||||||
|
next: *addr
|
||||||
|
|
||||||
|
30617:
|
||||||
|
content: empty
|
||||||
|
tags:
|
||||||
|
- *dtag
|
||||||
|
-
|
||||||
|
name: name
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: description
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: web
|
||||||
|
next:
|
||||||
|
type: url
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: clone
|
||||||
|
next:
|
||||||
|
type: giturl
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: relays
|
||||||
|
next:
|
||||||
|
type: relay
|
||||||
|
variadic: true
|
||||||
|
-
|
||||||
|
name: r
|
||||||
|
next:
|
||||||
|
type: gitcommit
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: constrained
|
||||||
|
either:
|
||||||
|
- euc
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: maintainers
|
||||||
|
next:
|
||||||
|
type: pubkey
|
||||||
|
variadic: true
|
||||||
|
|
||||||
|
30618:
|
||||||
|
content: empty
|
||||||
|
tags:
|
||||||
|
- *dtag
|
||||||
|
-
|
||||||
|
prefix: "refs/"
|
||||||
|
next:
|
||||||
|
type: gitcommit
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: HEAD
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
|
||||||
|
1617:
|
||||||
|
content: free
|
||||||
|
tags:
|
||||||
|
- *atag
|
||||||
|
-
|
||||||
|
name: r
|
||||||
|
next:
|
||||||
|
type: gitcommit
|
||||||
|
required: true
|
||||||
|
- *ptag
|
||||||
|
-
|
||||||
|
name: t
|
||||||
|
next:
|
||||||
|
type: constrained
|
||||||
|
either:
|
||||||
|
- root
|
||||||
|
- root-revision
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: commit
|
||||||
|
next:
|
||||||
|
type: gitcommit
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: r
|
||||||
|
next:
|
||||||
|
type: gitcommit
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: parent-commit
|
||||||
|
next:
|
||||||
|
type: gitcommit
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: commit-pgp-sig
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
-
|
||||||
|
name: committer
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
|
next:
|
||||||
|
type: free
|
||||||
|
required: true
|
||||||
584
schema/schema_test.go
Normal file
584
schema/schema_test.go
Normal file
@@ -0,0 +1,584 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewValidator(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
require.NotNil(t, v.Schema)
|
||||||
|
require.False(t, v.FailOnUnknown) // default value
|
||||||
|
|
||||||
|
// test with some known kinds from schema.yaml
|
||||||
|
_, hasKind0 := v.Schema["0"] // profile metadata
|
||||||
|
require.True(t, hasKind0)
|
||||||
|
_, hasKind1 := v.Schema["1"] // text note
|
||||||
|
require.True(t, hasKind1)
|
||||||
|
_, hasKind1111 := v.Schema["1111"] // comment
|
||||||
|
require.True(t, hasKind1111)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewValidatorWithInvalidYAML(t *testing.T) {
|
||||||
|
require.Panics(t, func() {
|
||||||
|
NewValidator("invalid yaml content: [[[")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_BasicSuccess(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// kind 1 with free content and valid p tag
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 1,
|
||||||
|
Content: "hello world",
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_Kind0_JSONContent(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// valid JSON content
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 0,
|
||||||
|
Content: `{"name":"test","about":"description"}`,
|
||||||
|
}
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// invalid JSON content
|
||||||
|
evt.Content = "not-json-content"
|
||||||
|
err = v.ValidateEvent(evt)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, ErrInvalidContentJson, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_UnknownKind(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: nostr.Kind(39999),
|
||||||
|
Content: "test",
|
||||||
|
}
|
||||||
|
|
||||||
|
// should not fail when FailOnUnknown is false (default)
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// should fail when FailOnUnknown is true
|
||||||
|
v.FailOnUnknown = true
|
||||||
|
err = v.ValidateEvent(evt)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, ErrUnknownKind, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_EmptyTag(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 1,
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{}, // empty tag
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, ErrEmptyTag, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_ID(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tag nostr.Tag
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid id",
|
||||||
|
tag: nostr.Tag{"e", "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid id - too short",
|
||||||
|
tag: nostr.Tag{"e", "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c9"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid id - not hex",
|
||||||
|
tag: nostr.Tag{"e", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
next := &nextSpec{Type: "id", Required: true}
|
||||||
|
err := v.validateNext(tt.tag, 1, next)
|
||||||
|
if tt.valid {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_PubKey(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tag nostr.Tag
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid pubkey",
|
||||||
|
tag: nostr.Tag{"p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid pubkey - too short",
|
||||||
|
tag: nostr.Tag{"p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa45"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid pubkey - not hex",
|
||||||
|
tag: nostr.Tag{"p", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
next := &nextSpec{Type: "pubkey", Required: true}
|
||||||
|
err := v.validateNext(tt.tag, 1, next)
|
||||||
|
if tt.valid {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_Relay(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tag nostr.Tag
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid wss relay",
|
||||||
|
tag: nostr.Tag{"r", "wss://relay.example.com"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid ws relay",
|
||||||
|
tag: nostr.Tag{"r", "ws://relay.example.com"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid relay - http",
|
||||||
|
tag: nostr.Tag{"r", "http://relay.example.com"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid relay - https",
|
||||||
|
tag: nostr.Tag{"r", "https://relay.example.com"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid relay - malformed url",
|
||||||
|
tag: nostr.Tag{"r", "not-a-url"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
next := &nextSpec{Type: "relay", Required: true}
|
||||||
|
err := v.validateNext(tt.tag, 1, next)
|
||||||
|
if tt.valid {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_Kind(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tag nostr.Tag
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid kind",
|
||||||
|
tag: nostr.Tag{"k", "1"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid kind - large number",
|
||||||
|
tag: nostr.Tag{"k", "30023"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid kind - not number",
|
||||||
|
tag: nostr.Tag{"k", "not-a-number"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid kind - negative",
|
||||||
|
tag: nostr.Tag{"k", "-1"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid kind - too large",
|
||||||
|
tag: nostr.Tag{"k", "99999"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
next := &nextSpec{Type: "kind", Required: true}
|
||||||
|
err := v.validateNext(tt.tag, 1, next)
|
||||||
|
if tt.valid {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_Constrained(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tag nostr.Tag
|
||||||
|
allowed []string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid constrained value",
|
||||||
|
tag: nostr.Tag{"e", "someid", "somerelay", "reply"},
|
||||||
|
allowed: []string{"reply", "root"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid constrained value",
|
||||||
|
tag: nostr.Tag{"e", "someid", "somerelay", "invalid"},
|
||||||
|
allowed: []string{"reply", "root"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
next := &nextSpec{Type: "constrained", Required: true, Either: tt.allowed}
|
||||||
|
err := v.validateNext(tt.tag, 3, next)
|
||||||
|
if tt.valid {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_GitCommit(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tag nostr.Tag
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid git commit",
|
||||||
|
tag: nostr.Tag{"r", "a1b2c3d4e5f6789012345678901234567890abcd"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid git commit - too short",
|
||||||
|
tag: nostr.Tag{"r", "a1b2c3d4e5f6789012345678901234567890abc"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid git commit - too long",
|
||||||
|
tag: nostr.Tag{"r", "a1b2c3d4e5f6789012345678901234567890abcde"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid git commit - not hex",
|
||||||
|
tag: nostr.Tag{"r", "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
next := &nextSpec{Type: "gitcommit", Required: true}
|
||||||
|
err := v.validateNext(tt.tag, 1, next)
|
||||||
|
if tt.valid {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_Addr(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tag nostr.Tag
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid addr",
|
||||||
|
tag: nostr.Tag{"a", "30023:3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d:test"},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid addr - malformed",
|
||||||
|
tag: nostr.Tag{"a", "invalid-addr"},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
next := &nextSpec{Type: "addr", Required: true}
|
||||||
|
err := v.validateNext(tt.tag, 1, next)
|
||||||
|
if tt.valid {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_Free(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// free type should accept anything
|
||||||
|
tag := nostr.Tag{"test", "any value here", "even", "multiple", "values"}
|
||||||
|
next := &nextSpec{Type: "free", Required: true}
|
||||||
|
err := v.validateNext(tag, 1, next)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_UnknownType(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tag := nostr.Tag{"test", "value"}
|
||||||
|
next := &nextSpec{Type: "unknown-type", Required: true}
|
||||||
|
|
||||||
|
// should not fail when FailOnUnknown is false (default)
|
||||||
|
err := v.validateNext(tag, 1, next)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// should fail when FailOnUnknown is true
|
||||||
|
v.FailOnUnknown = true
|
||||||
|
err = v.validateNext(tag, 1, next)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, ErrUnknownTagType, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_RequiredField(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// test missing required field
|
||||||
|
tag := nostr.Tag{"test"} // only name, missing required value
|
||||||
|
next := &nextSpec{Type: "free", Required: true}
|
||||||
|
err := v.validateNext(tag, 1, next)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "missing index 1")
|
||||||
|
|
||||||
|
// test optional field
|
||||||
|
next = &nextSpec{Type: "free", Required: false}
|
||||||
|
err = v.validateNext(tag, 1, next)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateNext_Variadic(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// test variadic field with multiple values
|
||||||
|
tag := nostr.Tag{"test", "value1", "value2", "value3"}
|
||||||
|
next := &nextSpec{Type: "free", Variadic: true}
|
||||||
|
err := v.validateNext(tag, 1, next)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test variadic field with single value
|
||||||
|
tag = nostr.Tag{"test", "only-one-value"}
|
||||||
|
err = v.validateNext(tag, 1, next)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test variadic field with no values (should fail if required)
|
||||||
|
tag = nostr.Tag{"test"}
|
||||||
|
next = &nextSpec{Type: "free", Variadic: true, Required: true}
|
||||||
|
err = v.validateNext(tag, 1, next)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_Kind10002(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// kind 10002 (relay list metadata) with valid r tags
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 10002,
|
||||||
|
Content: "", // should be empty
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"r", "wss://relay1.example.com", "read"},
|
||||||
|
nostr.Tag{"r", "wss://relay2.example.com", "write"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test with invalid relay marker
|
||||||
|
evt.Tags = nostr.Tags{
|
||||||
|
nostr.Tag{"r", "wss://relay1.example.com", "invalid"},
|
||||||
|
}
|
||||||
|
err = v.ValidateEvent(evt)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// test with missing required r tags
|
||||||
|
evt.Tags = nostr.Tags{} // no r tags at all
|
||||||
|
err = v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err) // should pass as tags are not required by schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_Kind1_ETag(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// kind 1 with e tag (reply/root marker)
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 1,
|
||||||
|
Content: "this is a reply",
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"e", "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", "wss://relay.example.com", "reply", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test with invalid marker
|
||||||
|
evt.Tags = nostr.Tags{
|
||||||
|
nostr.Tag{"e", "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", "wss://relay.example.com", "invalid"},
|
||||||
|
}
|
||||||
|
err = v.ValidateEvent(evt)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_Kind30617_RepositoryAnnouncement(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// kind 30617 (repository announcement) with required tags
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 30617,
|
||||||
|
Content: "", // should be empty
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
nostr.Tag{"d", "my-repo"},
|
||||||
|
nostr.Tag{"name", "My Repository"},
|
||||||
|
nostr.Tag{"description", "A test repository"},
|
||||||
|
nostr.Tag{"web", "https://github.com/user/repo"},
|
||||||
|
nostr.Tag{"clone", "https://github.com/user/repo.git"},
|
||||||
|
nostr.Tag{"relays", "wss://relay1.example.com", "wss://relay2.example.com"},
|
||||||
|
nostr.Tag{"r", "a1b2c3d4e5f6789012345678901234567890abcd", "euc"},
|
||||||
|
nostr.Tag{"maintainers", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateEvent_MultiplePossibleTagSpecs(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
// test event with tags that could match multiple specs
|
||||||
|
// kind 1 has both "e" and "q" tags that can take different forms
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 1,
|
||||||
|
Content: "test content",
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
// this should match the q tag with addr format
|
||||||
|
nostr.Tag{"q", "30023:3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d:test", "wss://relay.example.com"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test q tag with id format (alternative spec)
|
||||||
|
evt.Tags = nostr.Tags{
|
||||||
|
nostr.Tag{"q", "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", "wss://relay.example.com", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"},
|
||||||
|
}
|
||||||
|
err = v.ValidateEvent(evt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchema_ErrorMessages(t *testing.T) {
|
||||||
|
v := NewValidator(string(schemaFile))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
event nostr.Event
|
||||||
|
expError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty tag error",
|
||||||
|
event: nostr.Event{
|
||||||
|
Kind: 1,
|
||||||
|
Tags: nostr.Tags{nostr.Tag{}},
|
||||||
|
},
|
||||||
|
expError: ErrEmptyTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown kind error when FailOnUnknown is true",
|
||||||
|
event: nostr.Event{
|
||||||
|
Kind: nostr.Kind(39999),
|
||||||
|
},
|
||||||
|
expError: ErrUnknownKind,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.expError == ErrUnknownKind {
|
||||||
|
v.FailOnUnknown = true
|
||||||
|
defer func() { v.FailOnUnknown = false }()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.ValidateEvent(tt.event)
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, tt.expError, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user