From c83431e6f15d0b846d63a4cfabd2454adc79c0e8 Mon Sep 17 00:00:00 2001 From: kehiy Date: Mon, 9 Sep 2024 22:42:25 +0330 Subject: [PATCH 1/3] refactor(types): fix lint errors and wrong implementation. --- .golangci.yml | 10 +- types/envelope/envelope.go | 210 +++++++++++---------- types/envelope/envelope_test.go | 66 +++++-- types/event/event.go | 90 ++------- types/event/event_easyjson.go | 65 +++---- types/event/event_test.go | 227 ++++++++++++---------- types/filter/filter.go | 89 +++++++-- types/filter/filter_easyjson.go | 320 ++++++++++++++++---------------- types/filter/filter_test.go | 160 ++++++++++------ types/filter/filters.go | 30 +++ types/kind.go | 10 +- types/utils.go | 1 + 12 files changed, 738 insertions(+), 540 deletions(-) create mode 100644 types/filter/filters.go diff --git a/.golangci.yml b/.golangci.yml index d873b6c..c72470c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -47,7 +47,6 @@ linters: - misspell - musttag - nakedret - - nestif - nilerr - nilnil - nlreturn @@ -73,7 +72,6 @@ linters: - usestdlibvars - wastedassign - whitespace - # - wrapcheck - zerologlint - nonamedreturns @@ -85,7 +83,7 @@ linters-settings: enable-all: true disable: fieldalignment shadow: - strict: true + strict: true predeclared: # Comma-separated list of predeclared identifiers to not report on. @@ -106,7 +104,7 @@ linters-settings: nonamedreturns: # Report named error if it is assigned inside defer. # Default: false - report-error-in-defer: false + report-error-in-defer: false gocritic: disabled-checks: @@ -130,8 +128,8 @@ issues: - nestif - gocognit - forbidigo + - lll - linters: - govet - text: "shadow: declaration of \"err\" shadows" - \ No newline at end of file + text: 'shadow: declaration of "err" shadows' diff --git a/types/envelope/envelope.go b/types/envelope/envelope.go index a956e85..ed8226c 100644 --- a/types/envelope/envelope.go +++ b/types/envelope/envelope.go @@ -73,42 +73,47 @@ var ( ) type EventEnvelope struct { - SubscriptionID *string - Event event.Event + SubscriptionID string + Event *event.Event } -func (_ EventEnvelope) Label() string { return "EVENT" } +func (EventEnvelope) Label() string { return "EVENT" } -func (c EventEnvelope) String() string { - v, _ := json.Marshal(c) +func (ee EventEnvelope) String() string { + v, err := json.Marshal(ee) + if err != nil { + return "" + } return string(v) } -func (v *EventEnvelope) UnmarshalJSON(data []byte) error { +func (ee *EventEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() switch len(arr) { case 2: - return easyjson.Unmarshal([]byte(arr[1].Raw), &v.Event) + return easyjson.Unmarshal([]byte(arr[1].Raw), ee.Event) case 3: - v.SubscriptionID = &arr[1].Str + ee.SubscriptionID = arr[1].Str - return easyjson.Unmarshal([]byte(arr[2].Raw), &v.Event) + return easyjson.Unmarshal([]byte(arr[2].Raw), ee.Event) default: return fmt.Errorf("failed to decode EVENT envelope") } } -func (v EventEnvelope) MarshalJSON() ([]byte, error) { +func (ee EventEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["EVENT",`) - if v.SubscriptionID != nil { - w.RawString(`"` + *v.SubscriptionID + `",`) + + if ee.SubscriptionID != "" { + w.RawString(`"` + ee.SubscriptionID + `",`) } - v.Event.MarshalEasyJSON(&w) + + ee.Event.MarshalEasyJSON(&w) w.RawString(`]`) return w.BuildBytes() @@ -116,28 +121,22 @@ func (v EventEnvelope) MarshalJSON() ([]byte, error) { type ReqEnvelope struct { SubscriptionID string - Filters []filter.Filter + filter.Filters } -func (_ ReqEnvelope) Label() string { return "REQ" } - -func (c ReqEnvelope) String() string { - v, _ := json.Marshal(c) +func (ReqEnvelope) Label() string { return "REQ" } - return string(v) -} - -func (v *ReqEnvelope) UnmarshalJSON(data []byte) error { +func (re *ReqEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() if len(arr) < 3 { return fmt.Errorf("failed to decode REQ envelope: missing filters") } - v.SubscriptionID = arr[1].Str - v.Filters = make([]filter.Filter, len(arr)-2) + re.SubscriptionID = arr[1].Str + re.Filters = make(filter.Filters, len(arr)-2) f := 0 for i := 2; i < len(arr); i++ { - if err := easyjson.Unmarshal([]byte(arr[i].Raw), &v.Filters[f]); err != nil { + if err := easyjson.Unmarshal([]byte(arr[i].Raw), &re.Filters[f]); err != nil { return fmt.Errorf("%w -- on filter %d", err, f) } f++ @@ -146,11 +145,11 @@ func (v *ReqEnvelope) UnmarshalJSON(data []byte) error { return nil } -func (v ReqEnvelope) MarshalJSON() ([]byte, error) { +func (re ReqEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["REQ",`) - w.RawString(`"` + v.SubscriptionID + `"`) - for _, filter := range v.Filters { + w.RawString(`"` + re.SubscriptionID + `"`) + for _, filter := range re.Filters { w.RawString(`,`) filter.MarshalEasyJSON(&w) } @@ -161,24 +160,27 @@ func (v ReqEnvelope) MarshalJSON() ([]byte, error) { type CountEnvelope struct { SubscriptionID string - Filters []filter.Filter + Filters []*filter.Filter Count *int64 } -func (_ CountEnvelope) Label() string { return "COUNT" } -func (c CountEnvelope) String() string { - v, _ := json.Marshal(c) +func (CountEnvelope) Label() string { return "COUNT" } +func (ce CountEnvelope) String() string { + v, err := json.Marshal(ce) + if err != nil { + return "" + } return string(v) } -func (c *CountEnvelope) UnmarshalJSON(data []byte) error { +func (ce *CountEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() if len(arr) < 3 { return fmt.Errorf("failed to decode COUNT envelope: missing filters") } - c.SubscriptionID = arr[1].Str + ce.SubscriptionID = arr[1].Str if len(arr) < 3 { return fmt.Errorf("COUNT array must have at least 3 items") @@ -188,17 +190,17 @@ func (c *CountEnvelope) UnmarshalJSON(data []byte) error { Count *int64 `json:"count"` } if err := json.Unmarshal([]byte(arr[2].Raw), &countResult); err == nil && countResult.Count != nil { - c.Count = countResult.Count + ce.Count = countResult.Count return nil } - c.Filters = make([]filter.Filter, len(arr)-2) + ce.Filters = make([]*filter.Filter, len(arr)-2) f := 0 for i := 2; i < len(arr); i++ { item := []byte(arr[i].Raw) - if err := easyjson.Unmarshal(item, &c.Filters[f]); err != nil { + if err := easyjson.Unmarshal(item, ce.Filters[f]); err != nil { return fmt.Errorf("%w -- on filter %d", err, f) } @@ -208,16 +210,16 @@ func (c *CountEnvelope) UnmarshalJSON(data []byte) error { return nil } -func (v CountEnvelope) MarshalJSON() ([]byte, error) { +func (ce CountEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["COUNT",`) - w.RawString(`"` + v.SubscriptionID + `"`) - if v.Count != nil { + w.RawString(`"` + ce.SubscriptionID + `"`) + if ce.Count != nil { w.RawString(`,{"count":`) - w.RawString(strconv.FormatInt(*v.Count, 10)) + w.RawString(strconv.FormatInt(*ce.Count, 10)) w.RawString(`}`) } else { - for _, filter := range v.Filters { + for _, filter := range ce.Filters { w.RawString(`,`) filter.MarshalEasyJSON(&w) } @@ -229,28 +231,31 @@ func (v CountEnvelope) MarshalJSON() ([]byte, error) { type NoticeEnvelope string -func (_ NoticeEnvelope) Label() string { return "NOTICE" } -func (n NoticeEnvelope) String() string { - v, _ := json.Marshal(n) +func (NoticeEnvelope) Label() string { return "NOTICE" } +func (ne NoticeEnvelope) String() string { + v, err := json.Marshal(ne) + if err != nil { + return "" + } return string(v) } -func (v *NoticeEnvelope) UnmarshalJSON(data []byte) error { +func (ne *NoticeEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() if len(arr) < 2 { return fmt.Errorf("failed to decode NOTICE envelope") } - *v = NoticeEnvelope(arr[1].Str) + *ne = NoticeEnvelope(arr[1].Str) return nil } -func (v NoticeEnvelope) MarshalJSON() ([]byte, error) { +func (ne NoticeEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["NOTICE",`) - w.Raw(json.Marshal(string(v))) + w.Raw(json.Marshal(string(ne))) w.RawString(`]`) return w.BuildBytes() @@ -258,28 +263,31 @@ func (v NoticeEnvelope) MarshalJSON() ([]byte, error) { type EOSEEnvelope string -func (_ EOSEEnvelope) Label() string { return "EOSE" } -func (e EOSEEnvelope) String() string { - v, _ := json.Marshal(e) +func (EOSEEnvelope) Label() string { return "EOSE" } +func (ee EOSEEnvelope) String() string { + v, err := json.Marshal(ee) + if err != nil { + return "" + } return string(v) } -func (v *EOSEEnvelope) UnmarshalJSON(data []byte) error { +func (ee *EOSEEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() if len(arr) < 2 { return fmt.Errorf("failed to decode EOSE envelope") } - *v = EOSEEnvelope(arr[1].Str) + *ee = EOSEEnvelope(arr[1].Str) return nil } -func (v EOSEEnvelope) MarshalJSON() ([]byte, error) { +func (ee EOSEEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["EOSE",`) - w.Raw(json.Marshal(string(v))) + w.Raw(json.Marshal(string(ee))) w.RawString(`]`) return w.BuildBytes() @@ -287,19 +295,22 @@ func (v EOSEEnvelope) MarshalJSON() ([]byte, error) { type CloseEnvelope string -func (_ CloseEnvelope) Label() string { return "CLOSE" } -func (c CloseEnvelope) String() string { - v, _ := json.Marshal(c) +func (CloseEnvelope) Label() string { return "CLOSE" } +func (ce CloseEnvelope) String() string { + v, err := json.Marshal(ce) + if err != nil { + return "" + } return string(v) } -func (v *CloseEnvelope) UnmarshalJSON(data []byte) error { +func (ce *CloseEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() switch len(arr) { case 2: - *v = CloseEnvelope(arr[1].Str) + *ce = CloseEnvelope(arr[1].Str) return nil default: @@ -308,10 +319,10 @@ func (v *CloseEnvelope) UnmarshalJSON(data []byte) error { } } -func (v CloseEnvelope) MarshalJSON() ([]byte, error) { +func (ce CloseEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["CLOSE",`) - w.Raw(json.Marshal(string(v))) + w.Raw(json.Marshal(string(ce))) w.RawString(`]`) return w.BuildBytes() @@ -322,19 +333,22 @@ type ClosedEnvelope struct { Reason string } -func (_ ClosedEnvelope) Label() string { return "CLOSED" } -func (c ClosedEnvelope) String() string { - v, _ := json.Marshal(c) +func (ClosedEnvelope) Label() string { return "CLOSED" } +func (ce ClosedEnvelope) String() string { + v, err := json.Marshal(ce) + if err != nil { + return "" + } return string(v) } -func (v *ClosedEnvelope) UnmarshalJSON(data []byte) error { +func (ce *ClosedEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() switch len(arr) { case 3: - *v = ClosedEnvelope{arr[1].Str, arr[2].Str} + *ce = ClosedEnvelope{arr[1].Str, arr[2].Str} return nil default: @@ -343,12 +357,12 @@ func (v *ClosedEnvelope) UnmarshalJSON(data []byte) error { } } -func (v ClosedEnvelope) MarshalJSON() ([]byte, error) { +func (ce ClosedEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["CLOSED",`) - w.Raw(json.Marshal(string(v.SubscriptionID))) + w.Raw(json.Marshal(ce.SubscriptionID)) w.RawString(`,`) - w.Raw(json.Marshal(v.Reason)) + w.Raw(json.Marshal(ce.Reason)) w.RawString(`]`) return w.BuildBytes() @@ -360,37 +374,40 @@ type OKEnvelope struct { Reason string } -func (_ OKEnvelope) Label() string { return "OK" } -func (o OKEnvelope) String() string { - v, _ := json.Marshal(o) +func (OKEnvelope) Label() string { return "OK" } +func (oe OKEnvelope) String() string { + v, err := json.Marshal(oe) + if err != nil { + return "" + } return string(v) } -func (v *OKEnvelope) UnmarshalJSON(data []byte) error { +func (oe *OKEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() if len(arr) < 4 { return fmt.Errorf("failed to decode OK envelope: missing fields") } - v.EventID = arr[1].Str - v.OK = arr[2].Raw == "true" - v.Reason = arr[3].Str + oe.EventID = arr[1].Str + oe.OK = arr[2].Raw == "true" + oe.Reason = arr[3].Str return nil } -func (v OKEnvelope) MarshalJSON() ([]byte, error) { +func (oe OKEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["OK",`) - w.RawString(`"` + v.EventID + `",`) + w.RawString(`"` + oe.EventID + `",`) ok := "false" - if v.OK { + if oe.OK { ok = "true" } w.RawString(ok) w.RawString(`,`) - w.Raw(json.Marshal(v.Reason)) + w.Raw(json.Marshal(oe.Reason)) w.RawString(`]`) return w.BuildBytes() @@ -398,38 +415,43 @@ func (v OKEnvelope) MarshalJSON() ([]byte, error) { type AuthEnvelope struct { Challenge *string - Event event.Event + Event *event.Event } -func (_ AuthEnvelope) Label() string { return "AUTH" } -func (a AuthEnvelope) String() string { - v, _ := json.Marshal(a) +func (AuthEnvelope) Label() string { return "AUTH" } + +func (ae AuthEnvelope) String() string { + v, err := json.Marshal(ae) + if err != nil { + return "" + } return string(v) } -func (v *AuthEnvelope) UnmarshalJSON(data []byte) error { +func (ae *AuthEnvelope) UnmarshalJSON(data []byte) error { r := gjson.ParseBytes(data) arr := r.Array() if len(arr) < 2 { return fmt.Errorf("failed to decode Auth envelope: missing fields") } + if arr[1].IsObject() { - return easyjson.Unmarshal([]byte(arr[1].Raw), &v.Event) - } else { - v.Challenge = &arr[1].Str + return easyjson.Unmarshal([]byte(arr[1].Raw), ae.Event) } + ae.Challenge = &arr[1].Str + return nil } -func (v AuthEnvelope) MarshalJSON() ([]byte, error) { +func (ae AuthEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} w.RawString(`["AUTH",`) - if v.Challenge != nil { - w.Raw(json.Marshal(*v.Challenge)) + if ae.Challenge != nil { + w.Raw(json.Marshal(*ae.Challenge)) } else { - v.Event.MarshalEasyJSON(&w) + ae.Event.MarshalEasyJSON(&w) } w.RawString(`]`) diff --git a/types/envelope/envelope_test.go b/types/envelope/envelope_test.go index dce3607..631afa0 100644 --- a/types/envelope/envelope_test.go +++ b/types/envelope/envelope_test.go @@ -3,24 +3,66 @@ package envelope_test import ( "testing" + "github.com/dezh-tech/immortal/types" "github.com/dezh-tech/immortal/types/envelope" + "github.com/dezh-tech/immortal/types/filter" "github.com/stretchr/testify/assert" ) -// TODO::: write test for all cases. +type testCase struct { + Name string + Message []byte + ExpectedEnvelope envelope.Envelope +} -func TestEventEnvelopeEncodingAndDecoding(t *testing.T) { - eventEnvelopes := []string{ - `["EVENT","_",{"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"kind":1,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`, - } +var testCases = []testCase{ + { + Name: "nil", + Message: nil, + ExpectedEnvelope: nil, + }, + { + Name: "invalid string", + Message: []byte("invalid input"), + ExpectedEnvelope: nil, + }, + { + Name: "invalid string with a comma", + Message: []byte("invalid, input"), + ExpectedEnvelope: nil, + }, + { + Name: "CLOSED envelope", + Message: []byte(`["CLOSED",":1","error: we are broken"]`), + ExpectedEnvelope: &envelope.ClosedEnvelope{SubscriptionID: ":1", Reason: "error: we are broken"}, + }, + { + Name: "REQ envelope", + Message: []byte(`["REQ","million", {"kinds": [1]}, {"kinds": [30023 ], "#d": ["buteko", "batuke"]}]`), + ExpectedEnvelope: &envelope.ReqEnvelope{ + SubscriptionID: "million", + Filters: filter.Filters{{Kinds: []types.Kind{1}}, { + Kinds: []types.Kind{30023}, + Tags: map[string]types.Tag{"d": []string{"buteko", "batuke"}}, + }}, + }, + }, +} + +func TestEnvelope(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + parsedEnvelope := envelope.ParseMessage(tc.Message) + + if tc.ExpectedEnvelope == nil && parsedEnvelope == nil { + return + } - for _, raw := range eventEnvelopes { - var env envelope.EventEnvelope - err := env.UnmarshalJSON([]byte(raw)) - assert.NoError(t, err, "failed to parse event envelope json: %v", err) + if tc.ExpectedEnvelope == nil { + assert.NotNil(t, parsedEnvelope) + } - res, err := env.MarshalJSON() - assert.NoError(t, err, "failed to re marshal event as json: %v", err) - assert.Equal(t, raw, string(res)) + assert.Equal(t, tc.ExpectedEnvelope.String(), parsedEnvelope.String()) + }) } } diff --git a/types/event/event.go b/types/event/event.go index b18a48c..78e4f66 100644 --- a/types/event/event.go +++ b/types/event/event.go @@ -5,11 +5,9 @@ import ( "encoding/hex" "fmt" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/dezh-tech/immortal/types" - "github.com/dezh-tech/immortal/types/filter" "github.com/mailru/easyjson" - - "github.com/btcsuite/btcd/btcec/v2/schnorr" ) // Event represents an event structure defined on NIP-01. @@ -23,58 +21,6 @@ type Event struct { Signature string `json:"sig"` } -// Match checks if the event is match with given filter. -// Note: this method intended to be used for already open subscriptions and recently received events. -// For new subscriptions and queries for stored data use the database query and don't use this to verify the result. -func (e *Event) Match(f filter.Filter) bool { - if e == nil { - return false - } - - if f.IDs != nil && !types.ContainsString(e.ID, f.IDs) { - return false - } - - if f.Authors != nil && !types.ContainsString(e.PublicKey, f.Authors) { - return false - } - - if f.Kinds != nil && !types.ContainsKind(e.Kind, f.Kinds) { - return false - } - - if e.CreatedAt >= f.Since || e.CreatedAt <= f.Until { - return false - } - - for f, vals := range f.Tags { - for _, t := range e.Tags { - if len(t) < 2 { - continue - } - - if f != "#"+t[0] { // TODO:: should we replace + with strings.Builder? - return false - } - - var containsValue bool - for _, v := range vals { - if v == t[1] { - containsValue = true - - break - } - } - - if !containsValue { - return false - } - } - } - - return true -} - // Decode decodes a byte array into Event structure. func Decode(b []byte) (*Event, error) { e := new(Event) @@ -87,8 +33,8 @@ func Decode(b []byte) (*Event, error) { } // Encode encodes an Event to a byte array. -func (evt *Event) Encode() ([]byte, error) { - b, err := easyjson.Marshal(evt) +func (e *Event) Encode() ([]byte, error) { + b, err := easyjson.Marshal(e) if err != nil { return nil, err } @@ -96,7 +42,7 @@ func (evt *Event) Encode() ([]byte, error) { return b, nil } -func (evt *Event) Serialize() []byte { +func (e *Event) Serialize() []byte { // the serialization process is just putting everything into a JSON array // so the order is kept. See NIP-01 dst := make([]byte, 0) @@ -104,49 +50,47 @@ func (evt *Event) Serialize() []byte { // the header portion is easy to serialize // [0,"pubkey",created_at,kind,[ dst = append(dst, []byte( - fmt.Sprintf( + fmt.Sprintf( //nolint "[0,\"%s\",%d,%d,", - evt.PublicKey, - evt.CreatedAt, - evt.Kind, + e.PublicKey, + e.CreatedAt, + e.Kind, ))...) // tags - dst = types.MarshalTo(evt.Tags, dst) + dst = types.MarshalTo(e.Tags, dst) dst = append(dst, ',') // content needs to be escaped in general as it is user generated. - dst = types.EscapeString(dst, evt.Content) + dst = types.EscapeString(dst, e.Content) dst = append(dst, ']') return dst } // IsValid function validats an event Signature and ID. -func (e *Event) IsValid() (bool, error) { - // read and check pubkey +func (e *Event) IsValid() bool { pk, err := hex.DecodeString(e.PublicKey) if err != nil { - return false, fmt.Errorf("event pubkey '%s' is invalid hex: %w", e.PublicKey, err) + return false } pubkey, err := schnorr.ParsePubKey(pk) if err != nil { - return false, fmt.Errorf("event has invalid pubkey '%s': %w", e.PublicKey, err) + return false } - // read signature s, err := hex.DecodeString(e.Signature) if err != nil { - return false, fmt.Errorf("signature '%s' is invalid hex: %w", e.Signature, err) + return false } + sig, err := schnorr.ParseSignature(s) if err != nil { - return false, fmt.Errorf("failed to parse signature: %w", err) + return false } - // check signature hash := sha256.Sum256(e.Serialize()) - return sig.Verify(hash[:], pubkey), nil + return sig.Verify(hash[:], pubkey) } diff --git a/types/event/event_easyjson.go b/types/event/event_easyjson.go index 57d81bb..861f722 100644 --- a/types/event/event_easyjson.go +++ b/types/event/event_easyjson.go @@ -1,16 +1,15 @@ -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. - package event import ( json "encoding/json" + types "github.com/dezh-tech/immortal/types" easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" ) -// suppress unused package warning +// suppress unused package warning. var ( _ *json.RawMessage _ *jlexer.Lexer @@ -18,13 +17,14 @@ var ( _ easyjson.Marshaler ) -func easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(in *jlexer.Lexer, out *Event) { +func easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(in *jlexer.Lexer, out *Event) { //nolint isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { in.Consumed() } in.Skip() + return } in.Delim('{') @@ -34,15 +34,16 @@ func easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(in *jlexer.Lexer, if in.IsNull() { in.Skip() in.WantComma() + continue } switch key { case "id": - out.ID = string(in.String()) + out.ID = in.String() case "pubkey": - out.PublicKey = string(in.String()) + out.PublicKey = in.String() case "created_at": - out.CreatedAt = int64(in.Int64()) + out.CreatedAt = in.Int64() case "kind": out.Kind = types.Kind(in.Uint16()) case "tags": @@ -67,7 +68,7 @@ func easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(in *jlexer.Lexer, v1 = nil } else { in.Delim('[') - if v1 == nil { + if v1 == nil { //nolint if !in.IsDelim(']') { v1 = make(types.Tag, 0, 4) } else { @@ -77,8 +78,7 @@ func easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(in *jlexer.Lexer, v1 = (v1)[:0] } for !in.IsDelim(']') { - var v2 string - v2 = string(in.String()) + v2 := in.String() v1 = append(v1, v2) in.WantComma() } @@ -90,9 +90,9 @@ func easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(in *jlexer.Lexer, in.Delim(']') } case "content": - out.Content = string(in.String()) + out.Content = in.String() case "sig": - out.Signature = string(in.String()) + out.Signature = in.String() default: in.SkipRecursive() } @@ -103,24 +103,25 @@ func easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(in *jlexer.Lexer, in.Consumed() } } -func easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(out *jwriter.Writer, in Event) { + +func easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(out *jwriter.Writer, in Event) { //nolint out.RawByte('{') first := true _ = first { const prefix string = ",\"id\":" out.RawString(prefix[1:]) - out.String(string(in.ID)) + out.String(in.ID) } { const prefix string = ",\"pubkey\":" out.RawString(prefix) - out.String(string(in.PublicKey)) + out.String(in.PublicKey) } { const prefix string = ",\"created_at\":" out.RawString(prefix) - out.Int64(int64(in.CreatedAt)) + out.Int64(in.CreatedAt) } { const prefix string = ",\"kind\":" @@ -146,7 +147,7 @@ func easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(out *jwriter.Writ if v5 > 0 { out.RawByte(',') } - out.String(string(v6)) + out.String(v6) } out.RawByte(']') } @@ -157,36 +158,38 @@ func easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(out *jwriter.Writ { const prefix string = ",\"content\":" out.RawString(prefix) - out.String(string(in.Content)) + out.String(in.Content) } { const prefix string = ",\"sig\":" out.RawString(prefix) - out.String(string(in.Signature)) + out.String(in.Signature) } out.RawByte('}') } -// MarshalJSON supports json.Marshaler interface -func (v Event) MarshalJSON() ([]byte, error) { +// MarshalJSON supports json.Marshaler interface. +func (e Event) MarshalJSON() ([]byte, error) { //nolint w := jwriter.Writer{} - easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(&w, v) + easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(&w, e) + return w.Buffer.BuildBytes(), w.Error } -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v Event) MarshalEasyJSON(w *jwriter.Writer) { - easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(w, v) +// MarshalEasyJSON supports easyjson.Marshaler interface. +func (e Event) MarshalEasyJSON(w *jwriter.Writer) { //nolint + easyjsonF642ad3eEncodeGithubComDezhTechImmortalTypesEvent(w, e) } -// UnmarshalJSON supports json.Unmarshaler interface -func (v *Event) UnmarshalJSON(data []byte) error { +// UnmarshalJSON supports json.Unmarshaler interface. +func (e *Event) UnmarshalJSON(data []byte) error { //nolint r := jlexer.Lexer{Data: data} - easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(&r, v) + easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(&r, e) + return r.Error() } -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *Event) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(l, v) +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface. +func (e *Event) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjsonF642ad3eDecodeGithubComDezhTechImmortalTypesEvent(l, e) } diff --git a/types/event/event_test.go b/types/event/event_test.go index 7e14394..25a206b 100644 --- a/types/event/event_test.go +++ b/types/event/event_test.go @@ -5,123 +5,150 @@ import ( "github.com/dezh-tech/immortal/types" "github.com/dezh-tech/immortal/types/event" - "github.com/dezh-tech/immortal/types/filter" "github.com/stretchr/testify/assert" ) -// TODO::: Use table test. -// TODO::: Add error cases. - -var ( - validRawEvents = []string{ - `{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}`, - `{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524","extrakey":55}`, - `{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524","extrakey":"aaa"}`, - `{"kind":3,"id":"9e662bdd7d8abc40b5b15ee1ff5e9320efc87e9274d8d440c58e6eed2dddfbe2","pubkey":"373ebe3d45ec91977296a178d9f19f326c70631d2a1b0bbba5c5ecc2eb53b9e7","created_at":1644844224,"tags":[["p","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"],["p","75fc5ac2487363293bd27fb0d14fb966477d0f1dbc6361d37806a6a740eda91e"],["p","46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"]],"content":"{\"wss://nostr-pub.wellorder.net\":{\"read\":true,\"write\":true},\"wss://nostr.bitcoiner.social\":{\"read\":false,\"write\":true},\"wss://expensive-relay.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relayer.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relay.bitid.nz\":{\"read\":true,\"write\":true},\"wss://nostr.rocks\":{\"read\":true,\"write\":true}}","sig":"811355d3484d375df47581cb5d66bed05002c2978894098304f20b595e571b7e01b2efd906c5650080ffe49cf1c62b36715698e9d88b9e8be43029a2f3fa66be"}`, - `{"id":"6ea18dd9156305d7716348a459683642b0a35693c301d99665dc0bd4c58872a2","pubkey":"bd4ae3e67e29964d494172261dc45395c89f6bd2e774642e366127171dfb81f5","content":"OK, this is a test case for Immortal.","kind":1,"created_at":1725802966,"tags":[],"sig":"4ec9407243f41ca0b1b44e3b61ca2e43d8a20ed088357b075ee123a720ecd9b1734526d79f3c97cd77828fe1176e37104cce1270eb739499fcb202fced766e72","relays":[]}`, - `{"id":"6ea18dd9156305d7716348a459683642b0a35693c301d99665dc0bd4c58872a2","pubkey":"bd4ae3e67e29964d494172261dc45395c89f6bd2e774642e366127171dfb81f5","content":"OK, this is a test case for Immortal.","kind":1,"created_at":1725802966,"tags":[],"sig":"4ec9407243f41ca0b1b44e3b61ca2e43d8a20ed088357b075ee123a720ecd9b1734526d79f3c97cd77828fe1176e37104cce1270eb739499fcb202fced766e72","relays":[]}`, - `{"content":"GOOD MORNING.\n\nLIVE FREE.\n\nhttps://cdn.satellite.earth/fbd7f2d73469c95ca7ef0f6f66cf9456c4dcce5cb46d69dcc1d3243fe817faf3.mp4","created_at":1725802688,"id":"b8b2d7f724e3e774226ba84a621155a3656b58baf08c12c56f5452fe71b4fec9","kind":1,"pubkey":"04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9","sig":"c26537743dcd6a9adbec078d1394953ce55ca8c5af1b1d8a38ce716f946c5c6b59a3f9129b4df41ee537fcd332dbfddecd1126e30f65e1feff6dd426032a3e60","tags":[]}`, - `{"content":"{\"id\":\"1807a9e2d51e8e04fa6257fb1c5746df57c83ac6127b4c6462f9d986d5c98736\",\"kind\":1,\"pubkey\":\"c48e29f04b482cc01ca1f9ef8c86ef8318c059e0e9353235162f080f26e14c11\",\"content\":\" https:\\/\\/i.nostr.build\\/llzsmQs5gOtF1r8d.jpg \",\"tags\":[[\"imeta\",\"url https:\\/\\/i.nostr.build\\/llzsmQs5gOtF1r8d.jpg\",\"blurhash enQlLDaJysoy-;nMWBozs.kWKQtRs8ayenx^oyW;V[enNxV@i^WBfl\",\"dim 1259x1259\"],[\"r\",\"https:\\/\\/i.nostr.build\\/llzsmQs5gOtF1r8d.jpg\"]],\"sig\":\"5612c5a2ee8224e6d4b386698b1a9ae137cb9fa1c91ba7f15c4a14ac7896550f82caeffab7376e48e6fb81acf17597959c7658cabcf30c7484aa84cd740f2fee\",\"created_at\":1725639394}","created_at":1725802348,"id":"d2c8db3990efc74a56c4e9602bdcd5763e586b24b0708c14a171923f5e36e184","kind":6,"pubkey":"c48e29f04b482cc01ca1f9ef8c86ef8318c059e0e9353235162f080f26e14c11","sig":"5d3fd5cff3628905178c829416f793304688596e0304b95dd94976f55885adf2afe13d6120d932867c40e240429cdd2c4c1930b41e2beea852ce4626f51fc35a","tags":[["e","1807a9e2d51e8e04fa6257fb1c5746df57c83ac6127b4c6462f9d986d5c98736","","root"],["p","c48e29f04b482cc01ca1f9ef8c86ef8318c059e0e9353235162f080f26e14c11"]]}`, - `{"content":"Just add ReplyGuy now in Amethyst. \nnostr:nevent1qqsyjv4z7ns6frwnfrcn0lqk227chnrcnat476yaaadg8ev8jgn6p4gpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsygqpmhhz3xc696ggwn9rg2985s28vjnv45dtl25ctsspu74d59kn3spsgqqqqqqsa49ewc","created_at":1725799931,"id":"66e9072f5b2e7c4c6c6d8b9de2a81f78983549d2f56b7dc11737bba3a6a71408","kind":1,"pubkey":"01ddee289b1a2e90874ca3428a7a414764a6cad1abfaa985c201e7aada16d38c","sig":"2b17c30db9f7e246281d1dabcea12b40d19b6a6358d040a9b18a7b0fda72cca67adb260a7fe9b125a1ade5672cd922aa5a8d8d32fa4f18dc0efc80ae3f2deebe","tags":[["e","4932a2f4e1a48dd348f137fc1652bd8bcc789f575f689def5a83e5879227a0d5","","mention"],["p","01ddee289b1a2e90874ca3428a7a414764a6cad1abfaa985c201e7aada16d38c","","mention"],["q","4932a2f4e1a48dd348f137fc1652bd8bcc789f575f689def5a83e5879227a0d5"]]}`, - `{"content":"Kid: I want to be your age\nMe: no you don’t","created_at":1725799866,"id":"d4516dd8eda1c6235f6ca919b3ad6bdc7fe0a97d9bc7388b0444d9362f166522","kind":1,"pubkey":"1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411","sig":"17ec79e0e82e4ac3b3a32cfeac745774da90983d293435df9fa3225f31db87d4c855b2aee9d1c51a9bd64308262c1c7bf4274af20798b14b085d4dfc8f21ef9c","tags":[]}`, - `{"content":"Wen nostr hobby apps?","created_at":1725798384,"id":"43795c6b71168e6973223751d92f0904785a272eb40ae505243ae211cebddfa1","kind":1,"pubkey":"1bc70a0148b3f316da33fe3c89f23e3e71ac4ff998027ec712b905cd24f6a411","sig":"6b5309d3580b1f128279e66ea822cb153cfb7da14b1d130e86cb4855fb31b48d3f9ae6f5bd57732f96cf5914542c5f90d5fddb67e509fbd0efe534b41e568fdc","tags":[]}`, - `{"content":"GM from cicada\n\nhttps://video.nostr.build/66373d8edea1fadaacd10f4c5590729fb6e80f3f2279254d4c6b16cdbd80797f.mp4","created_at":1725798022,"id":"75b0fbee92009f70f57770aaf6ca993619af032b38d4b9abfa9bfe8d79c0f933","kind":1,"pubkey":"0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd","sig":"cf8bdc8e24e46c41aede59ca5a8766ead2c347b8a064b26e2ec30895a5f38ed6194c2bb8be11f6d17d5757de165364075c166ea1203777d299329f91ab245728","tags":[["imeta","url https://video.nostr.build/66373d8edea1fadaacd10f4c5590729fb6e80f3f2279254d4c6b16cdbd80797f.mp4","m video/mp4","x 6c0fe5d3347139a488f5f7ffcf179b5a61b28c65057eac951241d46c299db34d","ox 66373d8edea1fadaacd10f4c5590729fb6e80f3f2279254d4c6b16cdbd80797f","size 664508"]]}`, - } - - DecodedEvent *event.Event - EncodedEvent []byte +type TestCase struct { + RawEvent string + EventObject *event.Event + IsValidSig bool + IsValidData bool + Note string +} - events = []event.Event{ - { - ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", - PublicKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", - CreatedAt: 1644271588, +var testCases = []TestCase{ + { + RawEvent: `{"id":"a1d7ba3cdcc67a358186f85e5f2a02abd173877d484b76d1f1f22ee47d68293d","pubkey":"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245","created_at":1725890895,"kind":1,"tags":[],"content":"ReplyGuy never replies to me :( i feel left out","sig":"c2e6975905e41837343dc4b607dadf2895df457a0b8461b0f86d25506c4458c3fe83ed1f924715a0416412858fa5c51f3f3271361d729037f18d216b29618dda"}`, + EventObject: &event.Event{ + ID: "a1d7ba3cdcc67a358186f85e5f2a02abd173877d484b76d1f1f22ee47d68293d", + PublicKey: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", + CreatedAt: 1725890895, Kind: types.KindTextNote, Tags: []types.Tag{}, - Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?", - Signature: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524", + Content: "ReplyGuy never replies to me :( i feel left out", + Signature: "c2e6975905e41837343dc4b607dadf2895df457a0b8461b0f86d25506c4458c3fe83ed1f924715a0416412858fa5c51f3f3271361d729037f18d216b29618dda", }, - } - - EventValidation bool -) - -func TestDecode(t *testing.T) { - for _, e := range validRawEvents { - _, err := event.Decode([]byte(e)) - assert.NoError(t, err, "valid event must be decoded with no erros JSON") - } -} - -func BenchmarkDecode(b *testing.B) { - var decodedEvent *event.Event - for i := 0; i < b.N; i++ { - for _, e := range validRawEvents { - decodedEvent, _ = event.Decode([]byte(e)) - } - } - DecodedEvent = decodedEvent + IsValidSig: true, + IsValidData: true, + Note: "this is a valid event.", + }, + { + RawEvent: `{"content":"SUPER DOWN nostr:npub1h8nk2346qezka5cpm8jjh3yl5j88pf4ly2ptu7s6uu55wcfqy0wq36rpev","created_at":1725877943,"id":"a93df9f6746dfbd4de63196a36a0aa408dec8308fb55b3d5edcd22c953a4efb9","kind":1,"pubkey":"472be9f9264eea1254f2b2f7cd2da0c319dae4fe4cd649f0424e94234dcacf97","sig":"dd8e9478c52d086a793084dd092684344d3946b5f7f537573076530c07225870b732fa0e7214b0754e943e90340335756c8de5ab7a61c6f1375a4e5f340b6a26","tags":[["e","f6e8673a61ade88c087f45a6fa4f278e6e8b78dad2512a43b9e5a82e6df4ade4","","root"],["e","cec30d76c2599215b09f67f5d65f3b2786390bcfb51c97e8dab14c3d4e3c4e73","wss://relay.primal.net","reply"],["p","b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc"],["p","b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc"]]}`, + EventObject: &event.Event{ + ID: "a93df9f6746dfbd4de63196a36a0aa408dec8308fb55b3d5edcd22c953a4efb9", + PublicKey: "472be9f9264eea1254f2b2f7cd2da0c319dae4fe4cd649f0424e94234dcacf97", + CreatedAt: 1725877943, + Kind: types.KindTextNote, + Tags: []types.Tag{ + {"e", "f6e8673a61ade88c087f45a6fa4f278e6e8b78dad2512a43b9e5a82e6df4ade4", "", "root"}, + { + "e", + "cec30d76c2599215b09f67f5d65f3b2786390bcfb51c97e8dab14c3d4e3c4e73", + "wss://relay.primal.net", + "reply", + }, + { + "p", + "b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc", + }, + { + "p", + "b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc", + }, + }, + Content: "SUPER DOWN nostr:npub1h8nk2346qezka5cpm8jjh3yl5j88pf4ly2ptu7s6uu55wcfqy0wq36rpev", + Signature: "dd8e9478c52d086a793084dd092684344d3946b5f7f537573076530c07225870b732fa0e7214b0754e943e90340335756c8de5ab7a61c6f1375a4e5f340b6a26", + }, + IsValidSig: true, + IsValidData: true, + Note: "this is a valid event.", + }, + { + RawEvent: `{"content":"that’s a link to another website.","created_at":1725832414,"id":"594915a98c7f65b65a642e076463f5ac1319ae55d116c401528094e56023abf8","kind":1,"pubkey":"472be9f9264eea1254f2b2f7cd2da0c319dae4fe4cd649f0424e94234dcacf97","sig":"fc6d27f4cf775d7190a597fe67f7b0a4341ad044ae29d7d2b8daab060cf4e600a89521cbf0c370e00c8252b0ef4553294ebb56794cabe47ec7c16a800d857ca6","tags":[["e","b23ea78bd672a85faa84cdf4206231c217ae9034b9d82be78528279ffc87bbb9","","root"],["e","6ec00066a620631fb396d1756bdf56b397ea5626d88e71a8ec533e6ce99d595f","wss://a.nos.lol","reply"],["p","63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"]]}`, + EventObject: &event.Event{ + ID: "594915a98c7f65b65a642e076463f5ac1319ae55d116c401528094e56023abf8", + PublicKey: "472be9f9264eea1254f2b2f7cd2da0c319dae4fe4cd649f0424e94234dcacf97", + CreatedAt: 1725832414, + Kind: types.KindTextNote, + Tags: []types.Tag{ + { + "e", + "b23ea78bd672a85faa84cdf4206231c217ae9034b9d82be78528279ffc87bbb9", + "", + "root", + }, + { + "e", + "6ec00066a620631fb396d1756bdf56b397ea5626d88e71a8ec533e6ce99d595f", + "wss://a.nos.lol", + "reply", + }, + { + "p", + "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed", + }, + }, + Content: "that’s a link to another website.", + Signature: "fc6d27f4cf775d7190a597fe67f7b0a4341ad044ae29d7d2b8daab060cf4e600a89521cbf0c370e00c8252b0ef4553294ebb56794cabe47ec7c16a800d857ca6", + }, + IsValidSig: false, + IsValidData: true, + Note: "sig field is invalid.", + }, + { + RawEvent: `"content:"SUPER DO "np7e8dab14c3d4e3c4e73","wss://relay.primal.net","reply"],["p","b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc"],["p","b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc"]]}`, + EventObject: nil, + IsValidSig: true, + IsValidData: false, + Note: "data and encoding are invalid.", + }, } -func TestEncode(t *testing.T) { - events := make([]*event.Event, len(validRawEvents)) - for _, e := range validRawEvents { - decodedEvent, err := event.Decode([]byte(e)) - assert.NoError(t, err) - - events = append(events, decodedEvent) - } - - for _, e := range events { - _, err := e.Encode() - assert.NoError(t, err) - } -} +func TestEvent(t *testing.T) { + t.Run("Decode", func(t *testing.T) { + for _, tc := range testCases { + e, err := event.Decode([]byte(tc.RawEvent)) + if tc.IsValidData { + assert.NoError(t, err, tc.Note) -func BenchmarkEncode(b *testing.B) { - events := make([]*event.Event, len(validRawEvents)) - for _, e := range validRawEvents { - decodedEvent, _ := event.Decode([]byte(e)) - events = append(events, decodedEvent) - } + assert.Equal(t, tc.EventObject.ID, e.ID) + assert.Equal(t, tc.EventObject.CreatedAt, e.CreatedAt) + assert.Equal(t, tc.EventObject.Content, e.Content) + assert.Equal(t, tc.EventObject.Kind, e.Kind) + assert.Equal(t, tc.EventObject.PublicKey, e.PublicKey) + assert.Equal(t, tc.EventObject.Signature, e.Signature) - b.ResetTimer() + continue + } - var encodedEvent []byte - for i := 0; i < b.N; i++ { - for _, e := range events { - encodedEvent, _ = e.Encode() + assert.Error(t, err, tc.Note) } - } - EncodedEvent = encodedEvent -} - -// TODO::: add more test cases + benchmark. -func TestMatch(t *testing.T) { - f, err := filter.Decode([]byte(`{"kinds":[1, 2, 4],"authors":["3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","1d80e5588de010d137a67c42b03717595f5f510e73e42cfc48f31bae91844d59","ed4ca520e9929dfe9efdadf4011b53d30afd0678a09aa026927e60e7a45d9244"],"since":1677033299}`)) - assert.NoError(t, err) + }) - e, err := event.Decode([]byte(`{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}`)) - assert.NoError(t, err) + t.Run("Encode", func(t *testing.T) { + for _, tc := range testCases { + if tc.IsValidData { + e, err := tc.EventObject.Encode() + assert.NoError(t, err, tc.Note) + assert.Equal(t, len([]byte(tc.RawEvent)), len(e)) - assert.True(t, e.Match(*f)) -} + // assert.Equal(t, tc.RawEvent, string(e)) //TODO:: is that correct? + } + } + }) -func TestValidate(t *testing.T) { - for _, e := range events { - valid, err := e.IsValid() + t.Run("CheckSig", func(t *testing.T) { + for _, tc := range testCases { + if tc.IsValidData { + isValid := tc.EventObject.IsValid() + if tc.IsValidSig { + assert.True(t, isValid, tc.Note) - assert.NoError(t, err) - assert.True(t, valid) - } -} + continue + } -func BenchmarkValidate(b *testing.B) { - var eventValidation bool - for i := 0; i < b.N; i++ { - for _, e := range events { - eventValidation, _ = e.IsValid() + assert.False(t, isValid, tc.Note) + } } - } - EventValidation = eventValidation + }) } diff --git a/types/filter/filter.go b/types/filter/filter.go index 8a34dd0..ac32092 100644 --- a/types/filter/filter.go +++ b/types/filter/filter.go @@ -2,23 +2,80 @@ package filter import ( "github.com/dezh-tech/immortal/types" + "github.com/dezh-tech/immortal/types/event" "github.com/mailru/easyjson" ) // Filter defined the filter structure based on NIP-01 and NIP-50. type Filter struct { - IDs []string `json:"ids"` - Authors []string `json:"authors"` - Kinds []types.Kind `json:"kinds"` - Tags map[string]types.Tag `json:"tags"` - Since int64 `json:"since"` - Until int64 `json:"until"` - Limit uint16 `json:"limit"` - - // Sould we proxy Searchs to index server and elastic search? + IDs []string `json:"ids"` + Authors []string `json:"authors"` + Kinds []types.Kind `json:"kinds"` + Tags map[string]types.Tag + Since int64 `json:"since"` + Until int64 `json:"until"` + Limit uint16 `json:"limit"` + + // Should we proxy search to index server and elastic search? Search string `json:"search"` // Check NIP-50 } +// Match checks if the event is match with given filter. +// Note: this method intended to be used for already open subscriptions and recently received events. +// For new subscriptions and queries for stored data use the database query and don't use this to verify the result. +func (f *Filter) Match(e *event.Event) bool { + if e == nil { + return false + } + + if len(f.IDs) != 0 && !types.ContainsString(e.ID, f.IDs) { + return false + } + + if len(f.Authors) != 0 && !types.ContainsString(e.PublicKey, f.Authors) { + return false + } + + if len(f.Kinds) != 0 && !types.ContainsKind(e.Kind, f.Kinds) { + return false + } + + if f.Since != 0 && e.CreatedAt < f.Since { + return false + } + + if f.Until != 0 && e.CreatedAt > f.Until { + return false + } + + for f, vals := range f.Tags { + for _, t := range e.Tags { + if len(t) < 2 { + continue + } + + if f != "#"+t[0] { // TODO:: should we replace + with strings.Builder? + return false + } + + var containsValue bool + for _, v := range vals { + if v == t[1] { + containsValue = true + + break + } + } + + if !containsValue { + return false + } + } + } + + return true +} + // Decode decodes a byte array into event structure. func Decode(b []byte) (*Filter, error) { e := new(Filter) @@ -31,11 +88,21 @@ func Decode(b []byte) (*Filter, error) { } // Encode encodes an event to a byte array. -func (e *Filter) Encode() ([]byte, error) { - ee, err := easyjson.Marshal(e) +func (f *Filter) Encode() ([]byte, error) { + ee, err := easyjson.Marshal(f) if err != nil { return nil, err } return ee, nil } + +// String returns and string representation of encoded filter. +func (f *Filter) String() string { + ef, err := f.Encode() + if err != nil { + return "" + } + + return string(ef) +} diff --git a/types/filter/filter_easyjson.go b/types/filter/filter_easyjson.go index 8478254..29b735b 100644 --- a/types/filter/filter_easyjson.go +++ b/types/filter/filter_easyjson.go @@ -1,16 +1,15 @@ -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. - package filter import ( json "encoding/json" - types "github.com/dezh-tech/immortal/types" + + "github.com/dezh-tech/immortal/types" easyjson "github.com/mailru/easyjson" jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" ) -// suppress unused package warning +// suppress unused package warning. var ( _ *json.RawMessage _ *jlexer.Lexer @@ -18,15 +17,17 @@ var ( _ easyjson.Marshaler ) -func easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(in *jlexer.Lexer, out *Filter) { +func easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(in *jlexer.Lexer, out *Filter) { //nolint isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { in.Consumed() } in.Skip() + return } + out.Tags = make(map[string]types.Tag) in.Delim('{') for !in.IsDelim('}') { key := in.UnsafeFieldName(false) @@ -34,6 +35,7 @@ func easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(in *jlexer.Lexer if in.IsNull() { in.Skip() in.WantComma() + continue } switch key { @@ -45,7 +47,7 @@ func easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(in *jlexer.Lexer in.Delim('[') if out.IDs == nil { if !in.IsDelim(']') { - out.IDs = make([]string, 0, 4) + out.IDs = make([]string, 0, 20) } else { out.IDs = []string{} } @@ -53,106 +55,105 @@ func easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(in *jlexer.Lexer out.IDs = (out.IDs)[:0] } for !in.IsDelim(']') { - var v1 string - v1 = string(in.String()) + v1 := in.String() out.IDs = append(out.IDs, v1) in.WantComma() } in.Delim(']') } - case "authors": + case "kinds": if in.IsNull() { in.Skip() - out.Authors = nil + out.Kinds = nil } else { in.Delim('[') - if out.Authors == nil { + if out.Kinds == nil { if !in.IsDelim(']') { - out.Authors = make([]string, 0, 4) + out.Kinds = make([]types.Kind, 0, 8) } else { - out.Authors = []string{} + out.Kinds = []types.Kind{} } } else { - out.Authors = (out.Authors)[:0] + out.Kinds = (out.Kinds)[:0] } for !in.IsDelim(']') { - var v2 string - v2 = string(in.String()) - out.Authors = append(out.Authors, v2) + v2 := types.Kind(in.Int()) + out.Kinds = append(out.Kinds, v2) in.WantComma() } in.Delim(']') } - case "kinds": + case "authors": if in.IsNull() { in.Skip() - out.Kinds = nil + out.Authors = nil } else { in.Delim('[') - if out.Kinds == nil { + if out.Authors == nil { if !in.IsDelim(']') { - out.Kinds = make([]types.Kind, 0, 32) + out.Authors = make([]string, 0, 40) } else { - out.Kinds = []types.Kind{} + out.Authors = []string{} } } else { - out.Kinds = (out.Kinds)[:0] + out.Authors = (out.Authors)[:0] } for !in.IsDelim(']') { - var v3 types.Kind - v3 = types.Kind(in.Uint16()) - out.Kinds = append(out.Kinds, v3) + v3 := in.String() + out.Authors = append(out.Authors, v3) in.WantComma() } in.Delim(']') } - case "tags": + case "since": if in.IsNull() { in.Skip() + out.Since = 0 } else { - in.Delim('{') - out.Tags = make(map[string]types.Tag) - for !in.IsDelim('}') { - key := string(in.String()) - in.WantColon() - var v4 types.Tag - if in.IsNull() { - in.Skip() - v4 = nil - } else { - in.Delim('[') - if v4 == nil { - if !in.IsDelim(']') { - v4 = make(types.Tag, 0, 4) - } else { - v4 = types.Tag{} - } - } else { - v4 = (v4)[:0] - } - for !in.IsDelim(']') { - var v5 string - v5 = string(in.String()) - v4 = append(v4, v5) - in.WantComma() - } - in.Delim(']') - } - (out.Tags)[key] = v4 - in.WantComma() + if out.Since == 0 { + out.Since = *new(int64) //nolint } - in.Delim('}') + out.Since = in.Int64() } - case "since": - out.Since = int64(in.Int64()) case "until": - out.Until = int64(in.Int64()) + if in.IsNull() { + in.Skip() + out.Until = 0 + } else { + if out.Until == 0 { + out.Until = *new(int64) //nolint + } + out.Until = in.Int64() + } case "limit": - out.Limit = uint16(in.Uint16()) + out.Limit = in.Uint16() case "search": - out.Search = string(in.String()) + out.Search = in.String() default: - in.SkipRecursive() + if len(key) > 1 && key[0] == '#' { + tagValues := make([]string, 0, 40) + if !in.IsNull() { + in.Delim('[') + if out.Authors == nil { + if !in.IsDelim(']') { + tagValues = make([]string, 0, 4) + } else { + tagValues = []string{} + } + } else { + tagValues = (tagValues)[:0] + } + for !in.IsDelim(']') { + v3 := in.String() + tagValues = append(tagValues, v3) + in.WantComma() + } + in.Delim(']') + } + out.Tags[key[1:]] = tagValues + } else { + in.SkipRecursive() + } } in.WantComma() } @@ -161,133 +162,142 @@ func easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(in *jlexer.Lexer in.Consumed() } } -func easyjson4d398eaaEncodeGithubComDezhTechImmortalTypesFilter(out *jwriter.Writer, in Filter) { + +func easyjson4d398eaaEncodeGithubComDezhTechImmortalTypesFilter(out *jwriter.Writer, in Filter) { //nolint out.RawByte('{') first := true _ = first - { + if len(in.IDs) != 0 { const prefix string = ",\"ids\":" + first = false out.RawString(prefix[1:]) - if in.IDs == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v6, v7 := range in.IDs { - if v6 > 0 { - out.RawByte(',') - } - out.String(string(v7)) - } - out.RawByte(']') - } - } - { - const prefix string = ",\"authors\":" - out.RawString(prefix) - if in.Authors == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v8, v9 := range in.Authors { - if v8 > 0 { - out.RawByte(',') - } - out.String(string(v9)) + + out.RawByte('[') + for v4, v5 := range in.IDs { + if v4 > 0 { + out.RawByte(',') } - out.RawByte(']') + out.String(v5) } + out.RawByte(']') } - { + if len(in.Kinds) != 0 { const prefix string = ",\"kinds\":" - out.RawString(prefix) - if in.Kinds == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") + if first { + first = false + out.RawString(prefix[1:]) } else { - out.RawByte('[') - for v10, v11 := range in.Kinds { - if v10 > 0 { - out.RawByte(',') - } - out.Uint16(uint16(v11)) + out.RawString(prefix) + } + + out.RawByte('[') + for v6, v7 := range in.Kinds { + if v6 > 0 { + out.RawByte(',') } - out.RawByte(']') + out.Int(int(v7)) } + out.RawByte(']') } - { - const prefix string = ",\"tags\":" - out.RawString(prefix) - if in.Tags == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { - out.RawString(`null`) + if len(in.Authors) != 0 { + const prefix string = ",\"authors\":" + if first { + first = false + out.RawString(prefix[1:]) } else { - out.RawByte('{') - v12First := true - for v12Name, v12Value := range in.Tags { - if v12First { - v12First = false - } else { - out.RawByte(',') - } - out.String(string(v12Name)) - out.RawByte(':') - if v12Value == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v13, v14 := range v12Value { - if v13 > 0 { - out.RawByte(',') - } - out.String(string(v14)) - } - out.RawByte(']') - } + out.RawString(prefix) + } + out.RawByte('[') + for v8, v9 := range in.Authors { + if v8 > 0 { + out.RawByte(',') } - out.RawByte('}') + out.String(v9) } + out.RawByte(']') } - { + if in.Since != 0 { const prefix string = ",\"since\":" - out.RawString(prefix) - out.Int64(int64(in.Since)) + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int64(in.Since) } - { + if in.Until != 0 { const prefix string = ",\"until\":" - out.RawString(prefix) - out.Int64(int64(in.Until)) + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int64(in.Until) } - { + if in.Limit != 0 || in.Limit == 0 { const prefix string = ",\"limit\":" - out.RawString(prefix) - out.Uint16(uint16(in.Limit)) + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.Int(int(in.Limit)) } - { + if in.Search != "" { const prefix string = ",\"search\":" - out.RawString(prefix) - out.String(string(in.Search)) + if first { + first = false + out.RawString(prefix[1:]) + } else { + out.RawString(prefix) + } + out.String(in.Search) + } + for tag, values := range in.Tags { + const prefix string = ",\"authors\":" //nolint + if first { + first = false + out.RawString("\"#" + tag + "\":") + } else { + out.RawString(",\"#" + tag + "\":") + } + out.RawByte('[') + for i, v := range values { + if i > 0 { + out.RawByte(',') + } + out.String(v) + } + out.RawByte(']') } out.RawByte('}') } -// MarshalJSON supports json.Marshaler interface -func (v Filter) MarshalJSON() ([]byte, error) { +// MarshalJSON supports json.Marshaler interface. +func (f Filter) MarshalJSON() ([]byte, error) { //nolint w := jwriter.Writer{} - easyjson4d398eaaEncodeGithubComDezhTechImmortalTypesFilter(&w, v) + easyjson4d398eaaEncodeGithubComDezhTechImmortalTypesFilter(&w, f) + return w.Buffer.BuildBytes(), w.Error } -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v Filter) MarshalEasyJSON(w *jwriter.Writer) { - easyjson4d398eaaEncodeGithubComDezhTechImmortalTypesFilter(w, v) +// MarshalEasyJSON supports easyjson.Marshaler interface. +func (f Filter) MarshalEasyJSON(w *jwriter.Writer) { //nolint + easyjson4d398eaaEncodeGithubComDezhTechImmortalTypesFilter(w, f) } -// UnmarshalJSON supports json.Unmarshaler interface -func (v *Filter) UnmarshalJSON(data []byte) error { +// UnmarshalJSON supports json.Unmarshaler interface. +func (f *Filter) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} - easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(&r, v) + easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(&r, f) + return r.Error() } -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *Filter) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(l, v) +// UnmarshalEasyJSON supports easyjson.Unmarshaler interface. +func (f *Filter) UnmarshalEasyJSON(l *jlexer.Lexer) { + easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(l, f) } diff --git a/types/filter/filter_test.go b/types/filter/filter_test.go index 7c4420b..5048960 100644 --- a/types/filter/filter_test.go +++ b/types/filter/filter_test.go @@ -3,74 +3,128 @@ package filter_test import ( "testing" + "github.com/dezh-tech/immortal/types" + "github.com/dezh-tech/immortal/types/event" "github.com/dezh-tech/immortal/types/filter" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -// TODO::: Use table test. -// TODO::: Add error cases. +type TestCase struct { + RawFilter string + FilterObject *filter.Filter + IsValidData bool + Note string +} var ( - rawValidFilters = []string{ - `{"ids": ["abc"],"#e":["zzz"],"#something":["nothing","bab"],"since":1644254609,"search":"test"}`, - `{"ids": ["abc"],"#e":["zzz"],"limit":0,"#something":["nothing","bab"],"since":1644254609,"search":"test"}`, - `{"kinds":[1],"authors":["a8171781fd9e90ede3ea44ddca5d3abf828fe8eedeb0f3abb0dd3e563562e1fc","1d80e5588de010d137a67c42b03717595f5f510e73e42cfc48f31bae91844d59","ed4ca520e9929dfe9efdadf4011b53d30afd0678a09aa026927e60e7a45d9244"],"since":1677033299}`, - `{"kinds":[1,2,4],"until":12345678,"limit":0,"#fruit":["banana","mango"]}`, - `{"kinds":[1,2,4],"until":12345678,"#fruit":["banana","mango"]}`, + testCases = []TestCase{ + { + RawFilter: `{"ids": ["abc"],"#e":["zzz"],"limit":0,"#something":["nothing","bab"],"since":1644254609,"search":"test"}`, + FilterObject: &filter.Filter{ + IDs: []string{"abc"}, + Tags: map[string]types.Tag{ + "e": {"zzz"}, + "something": {"nothing", "bab"}, + }, + Since: 1644254609, + Search: "test", + }, + IsValidData: true, + Note: "this is a valid filter.", + }, + { + RawFilter: `{"kinds":[1],"authors":["a8171781fd9e90ede3ea44ddca5d3abf828fe8eedeb0f3abb0dd3e563562e1fc","1d80e5588de010d137a67c42b03717595f5f510e73e42cfc48f31bae91844d59","ed4ca520e9929dfe9efdadf4011b53d30afd0678a09aa026927e60e7a45d9244"],"since":1677033299}`, + FilterObject: &filter.Filter{ + Kinds: []types.Kind{types.KindTextNote}, + Authors: []string{ + "a8171781fd9e90ede3ea44ddca5d3abf828fe8eedeb0f3abb0dd3e563562e1fc", + "1d80e5588de010d137a67c42b03717595f5f510e73e42cfc48f31bae91844d59", + "ed4ca520e9929dfe9efdadf4011b53d30afd0678a09aa026927e60e7a45d9244", + }, + Tags: map[string]types.Tag{}, + Since: 1677033299, + }, + IsValidData: true, + Note: "this is a valid filter.", + }, + { + RawFilter: `{"kinds":[1,2,4],"until":12345678,"limit":0,"#fruit":["banana","mango"]}`, + FilterObject: &filter.Filter{ + Kinds: []types.Kind{1, 2, 4}, + Until: 12345678, + Limit: 0, + Tags: map[string]types.Tag{ + "fruit": {"banana", "mango"}, + }, + }, + IsValidData: true, + Note: "this is a valid filter.", + }, + { + RawFilter: `{"kinds":[1,2,4],"until":12345678,"#fruit":["banana","mango"]}`, + FilterObject: &filter.Filter{ + Kinds: []types.Kind{1, 2, 4}, + Until: 12345678, + Tags: map[string]types.Tag{ + "fruit": {"banana", "mango"}, + }, + }, + IsValidData: true, + Note: "this is a valid filter.", + }, + { + RawFilter: `{kinds":[1,2,4],"until":12345678#fruit":["banana","mango"]}`, + FilterObject: &filter.Filter{}, + IsValidData: false, + Note: "this is an invalid filter.", + }, } - EncodedFilter []byte - DecodedFilter *filter.Filter + // TODO::: Add more test cases for matchs. + testFilter = `{"kinds":[1],"authors":["1d80e5588de010d137a67c42b03717595f5f510e73e42cfc48f31bae91844d59"],"since":1677033299}` + testEvent = `{"id":"5a127c9c931f392f6afc7fdb74e8be01c34035314735a6b97d2cf360d13cfb94","pubkey":"1d80e5588de010d137a67c42b03717595f5f510e73e42cfc48f31bae91844d59","created_at":1677033299,"kind":1,"tags":[["t","japan"]],"content":"If you like my art,I'd appreciate a coin or two!!\nZap is welcome!! Thanks.\n\n\n#japan #bitcoin #art #bananaart\nhttps://void.cat/d/CgM1bzDgHUCtiNNwfX9ajY.webp","sig":"828497508487ca1e374f6b4f2bba7487bc09fccd5cc0d1baa82846a944f8c5766918abf5878a580f1e6615de91f5b57a32e34c42ee2747c983aaf47dbf2a0255"}` ) -func TestDencode(t *testing.T) { - for _, f := range rawValidFilters { - _, err := filter.Decode([]byte(f)) - assert.NoError(t, err) - } -} - -func BenchmarkDecode(b *testing.B) { - var decodedFilter *filter.Filter - for i := 0; i < b.N; i++ { - for _, f := range rawValidFilters { - decodedFilter, _ = filter.Decode([]byte(f)) - } - } - DecodedFilter = decodedFilter -} - -func TestEncode(t *testing.T) { - filters := make([]*filter.Filter, len(rawValidFilters)) +func TestFilter(t *testing.T) { + t.Run("Decode", func(t *testing.T) { + for i, tc := range testCases { + f, err := filter.Decode([]byte(tc.RawFilter)) + if tc.IsValidData { + assert.NoError(t, err, tc.Note) - for _, f := range rawValidFilters { - decodedFilter, err := filter.Decode([]byte(f)) - assert.NoError(t, err) + assert.Equal(t, tc.FilterObject.Authors, f.Authors) + assert.Equal(t, tc.FilterObject.IDs, f.IDs) + assert.Equal(t, tc.FilterObject.Kinds, f.Kinds) + assert.Equal(t, tc.FilterObject.Limit, f.Limit) + assert.Equal(t, tc.FilterObject.Search, f.Search) + assert.Equal(t, tc.FilterObject.Since, f.Since) + assert.Equal(t, tc.FilterObject.Tags, f.Tags, tc.RawFilter, "\n", i) + assert.Equal(t, tc.FilterObject.Until, f.Until) - filters = append(filters, decodedFilter) - } + continue + } - for _, f := range filters { - _, err := f.Encode() - assert.NoError(t, err) - } -} + assert.Error(t, err, tc.Note) + } + }) -func BenchmarkEncode(b *testing.B) { - filters := make([]*filter.Filter, len(rawValidFilters)) + t.Run("Encode", func(t *testing.T) { + for _, tc := range testCases { + if tc.IsValidData { + _, err := tc.FilterObject.Encode() + assert.NoError(t, err, tc.Note) + } + } + }) - for _, f := range rawValidFilters { - decodedFilter, _ := filter.Decode([]byte(f)) - filters = append(filters, decodedFilter) - } + t.Run("Match", func(t *testing.T) { + e, err := event.Decode([]byte(testEvent)) + require.NoError(t, err) - b.ResetTimer() + f, err := filter.Decode([]byte(testFilter)) + require.NoError(t, err) - var encodedFilter []byte - for i := 0; i < b.N; i++ { - for _, f := range filters { - encodedFilter, _ = f.Encode() - } - } - EncodedFilter = encodedFilter + assert.True(t, f.Match(e)) + }) } diff --git a/types/filter/filters.go b/types/filter/filters.go new file mode 100644 index 0000000..fee2b1b --- /dev/null +++ b/types/filter/filters.go @@ -0,0 +1,30 @@ +package filter + +import ( + "encoding/json" + + "github.com/dezh-tech/immortal/types/event" +) + +type Filters []Filter + +// String returns and string representation of encoded filters. +func (f Filters) String() string { + j, err := json.Marshal(f) + if err != nil { + return "" + } + + return string(j) +} + +// Match checks id the given event e is match with any of filters f. +func (f Filters) Match(e *event.Event) bool { + for _, filter := range f { + if filter.Match(e) { + return true + } + } + + return false +} diff --git a/types/kind.go b/types/kind.go index 2f49503..6fb2f41 100644 --- a/types/kind.go +++ b/types/kind.go @@ -12,7 +12,7 @@ const ( Ephemeral ParameterizedReplaceable - // Kinds + // Kinds. KindProfileMetadata Kind = 0 KindTextNote Kind = 1 KindRecommendServer Kind = 2 @@ -67,22 +67,22 @@ const ( KindSimpleGroupMembers Kind = 39002 ) -// IsRegular checks if the gived kind is in Regular range. +// IsRegular checks if the given kind is in Regular range. func (k Kind) IsRegular() bool { return 1000 <= k || k < 10000 || 4 <= k || k < 45 || k == 1 || k == 2 } -// IsReplaceable checks if the gived kind is in Replaceable range. +// IsReplaceable checks if the given kind is in Replaceable range. func (k Kind) IsReplaceable() bool { return 10000 <= k || k < 20000 || k == 0 || k == 3 } -// IsEphemeral checks if the gived kind is in Ephemeral range. +// IsEphemeral checks if the given kind is in Ephemeral range. func (k Kind) IsEphemeral() bool { return 20000 <= k || k < 30000 } -// IsParameterizedReplaceable checks if the gived kind is in ParameterizedReplaceable range. +// IsParameterizedReplaceable checks if the given kind is in ParameterizedReplaceable range. func (k Kind) IsParameterizedReplaceable() bool { return 30000 <= k || k < 40000 } diff --git a/types/utils.go b/types/utils.go index 7c8ba5d..a0b9c12 100644 --- a/types/utils.go +++ b/types/utils.go @@ -61,5 +61,6 @@ func EscapeString(dst []byte, s string) []byte { } } dst = append(dst, '"') + return dst } From 6ec64de128e29f6cc1ea5f38fce8284faefa6393 Mon Sep 17 00:00:00 2001 From: kehiy Date: Mon, 9 Sep 2024 22:45:37 +0330 Subject: [PATCH 2/3] refactor(types): fix lint errors and wrong implementation. --- types/filter/filter_easyjson.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/filter/filter_easyjson.go b/types/filter/filter_easyjson.go index 29b735b..ee795eb 100644 --- a/types/filter/filter_easyjson.go +++ b/types/filter/filter_easyjson.go @@ -77,7 +77,7 @@ func easyjson4d398eaaDecodeGithubComDezhTechImmortalTypesFilter(in *jlexer.Lexer out.Kinds = (out.Kinds)[:0] } for !in.IsDelim(']') { - v2 := types.Kind(in.Int()) + v2 := types.Kind(in.Int16()) out.Kinds = append(out.Kinds, v2) in.WantComma() } From 79895c6659514392c9cc87dbc3c374ea3a28b80f Mon Sep 17 00:00:00 2001 From: kehiy Date: Mon, 9 Sep 2024 22:50:24 +0330 Subject: [PATCH 3/3] refactor(types): fix lint errors and wrong implementation. --- .golangci.yml | 10 +++++++--- types/event/event.go | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c72470c..82dd973 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -50,6 +50,7 @@ linters: - nilerr - nilnil - nlreturn + - nestif - noctx - nolintlint - nosprintfhostport @@ -81,9 +82,8 @@ linters-settings: govet: enable-all: true - disable: fieldalignment - shadow: - strict: true + disable: + - fieldalignment predeclared: # Comma-separated list of predeclared identifiers to not report on. @@ -130,6 +130,10 @@ issues: - forbidigo - lll + - path: _easyjson.go + linters: + - nestif + - linters: - govet text: 'shadow: declaration of "err" shadows' diff --git a/types/event/event.go b/types/event/event.go index 78e4f66..2d64124 100644 --- a/types/event/event.go +++ b/types/event/event.go @@ -92,5 +92,6 @@ func (e *Event) IsValid() bool { hash := sha256.Sum256(e.Serialize()) + // TODO::: replace with libsecp256k1 (C++ version). return sig.Verify(hash[:], pubkey) }