1 // Package okenvelope is a codec for the OK message, which is an acknowledgement
2 // for an EVENT eventenvelope.Submission, containing true/false and if false a
3 // message with a machine readable error type as found in the messages package.
4 package okenvelope
5 6 import (
7 "io"
8 9 "next.orly.dev/pkg/nostr/encoders/envelopes"
10 "next.orly.dev/pkg/nostr/encoders/hex"
11 "next.orly.dev/pkg/nostr/encoders/text"
12 "next.orly.dev/pkg/nostr/interfaces/codec"
13 "next.orly.dev/pkg/nostr/utils/constraints"
14 "github.com/minio/sha256-simd"
15 "next.orly.dev/pkg/lol/chk"
16 "next.orly.dev/pkg/lol/errorf"
17 "next.orly.dev/pkg/lol/log"
18 )
19 20 // L is the label associated with this type of codec.Envelope.
21 const L = "OK"
22 23 // T is an OK envelope, used to signal acceptance or rejection, with a reason,
24 // to an eventenvelope.Submission.
25 type T struct {
26 EventID []byte
27 OK bool
28 Reason []byte
29 }
30 31 var _ codec.Envelope = (*T)(nil)
32 33 // New creates a new empty OK T.
34 func New() *T { return &T{} }
35 36 // NewFrom creates a new okenvelope.T with a string for the subscription.Id and
37 // the optional reason.
38 func NewFrom[V constraints.Bytes](eid V, ok bool, msg ...V) *T {
39 var m []byte
40 if len(msg) > 0 {
41 m = []byte(msg[0])
42 }
43 if len(eid) != sha256.Size {
44 log.W.F(
45 "event Subscription unexpected length, expect %d got %d",
46 len(eid), sha256.Size,
47 )
48 }
49 return &T{EventID: []byte(eid), OK: ok, Reason: m}
50 }
51 52 // Label returns the label of an okenvelope.T.
53 func (en *T) Label() string { return L }
54 55 // ReasonString returns the Reason in the form of a string.
56 func (en *T) ReasonString() string { return string(en.Reason) }
57 58 // Write the okenvelope.T to a provided io.Writer.
59 func (en *T) Write(w io.Writer) (err error) {
60 _, err = w.Write(en.Marshal(nil))
61 return
62 }
63 64 // Marshal a okenvelope.T from minified JSON, appending to a provided
65 // destination slice. Note that this ensures correct string escaping on the
66 // subscription.Id and Reason fields.
67 func (en *T) Marshal(dst []byte) (b []byte) {
68 var err error
69 _ = err
70 b = dst
71 b = envelopes.Marshal(
72 b, L,
73 func(bst []byte) (o []byte) {
74 o = bst
75 o = append(o, '"')
76 o = hex.EncAppend(o, en.EventID)
77 o = append(o, '"')
78 o = append(o, ',')
79 o = text.MarshalBool(o, en.OK)
80 o = append(o, ',')
81 o = append(o, '"')
82 o = text.NostrEscape(o, en.Reason)
83 o = append(o, '"')
84 return
85 },
86 )
87 return
88 }
89 90 // Unmarshal a okenvelope.T from minified JSON, returning the remainder after
91 // the end of the envelope. Note that this ensures the Reason and
92 // subscription.Id strings are correctly unescaped by NIP-01 escaping rules.
93 func (en *T) Unmarshal(b []byte) (r []byte, err error) {
94 r = b
95 var idBytes []byte
96 // Parse event id as quoted hex (NIP-20 compliant)
97 if idBytes, r, err = text.UnmarshalHex(r); err != nil {
98 return
99 }
100 if len(idBytes) != sha256.Size {
101 err = errorf.E(
102 "invalid size for Subscription, require %d got %d",
103 sha256.Size, len(idBytes),
104 )
105 return
106 }
107 en.EventID = idBytes
108 if r, err = text.Comma(r); chk.E(err) {
109 return
110 }
111 if r, en.OK, err = text.UnmarshalBool(r); chk.E(err) {
112 return
113 }
114 if r, err = text.Comma(r); chk.E(err) {
115 return
116 }
117 if en.Reason, r, err = text.UnmarshalQuoted(r); chk.E(err) {
118 return
119 }
120 if r, err = envelopes.SkipToTheEnd(r); chk.E(err) {
121 return
122 }
123 return
124 }
125 126 // Parse reads a OK envelope in minified JSON into a newly allocated
127 // okenvelope.T.
128 func Parse(b []byte) (t *T, rem []byte, err error) {
129 t = New()
130 if rem, err = t.Unmarshal(b); chk.E(err) {
131 return
132 }
133 return
134 }
135