1 // Package countenvelope is an encoder for the COUNT request (client) and
2 // response (relay) message types.
3 package countenvelope
4 5 import (
6 "bytes"
7 "io"
8 9 "next.orly.dev/pkg/nostr/encoders/envelopes"
10 "next.orly.dev/pkg/nostr/encoders/filter"
11 "next.orly.dev/pkg/nostr/encoders/ints"
12 "next.orly.dev/pkg/nostr/encoders/text"
13 "next.orly.dev/pkg/nostr/interfaces/codec"
14 "next.orly.dev/pkg/nostr/utils/constraints"
15 "next.orly.dev/pkg/lol/chk"
16 "next.orly.dev/pkg/lol/errorf"
17 )
18 19 // L is the label associated with this type of codec.Envelope.
20 const L = "COUNT"
21 22 // Request is a COUNT envelope sent by a client to request a count of results.
23 // This is a stupid idea because it costs as much processing as fetching the
24 // events, but doesn't provide the means to actually get them (the HTTP API
25 // /filter does this by returning the actual event Ids).
26 type Request struct {
27 Subscription []byte
28 Filters filter.S
29 }
30 31 var _ codec.Envelope = (*Request)(nil)
32 33 // New creates a new Request with a standard style subscription.Id and empty filter.
34 func New() *Request { return new(Request) }
35 36 // NewRequest creates a new Request with a provided subscription.Id and
37 // filter.T.
38 func NewRequest(id []byte, filters filter.S) *Request {
39 return &Request{
40 Subscription: id,
41 Filters: filters,
42 }
43 }
44 45 // Label returns the label of a CLOSED envelope.
46 func (en *Request) Label() string { return L }
47 48 // Write the Request to a provided io.Writer.
49 func (en *Request) Write(w io.Writer) (err error) {
50 var b []byte
51 b = en.Marshal(b)
52 _, err = w.Write(b)
53 return
54 }
55 56 // Marshal a Request appended to the provided destination slice as minified
57 // JSON.
58 func (en *Request) Marshal(dst []byte) (b []byte) {
59 var err error
60 b = dst
61 b = envelopes.Marshal(
62 b, L,
63 func(bst []byte) (o []byte) {
64 o = bst
65 o = append(o, '"')
66 o = append(o, en.Subscription...)
67 o = append(o, '"')
68 o = append(o, ',')
69 for _, f := range en.Filters {
70 o = append(o, ',')
71 o = f.Marshal(o)
72 }
73 return
74 },
75 )
76 _ = err
77 return
78 }
79 80 // Unmarshal a Request from minified JSON, returning the remainder after the end
81 // of the envelope.
82 func (en *Request) Unmarshal(b []byte) (r []byte, err error) {
83 r = b
84 if en.Subscription, r, err = text.UnmarshalQuoted(r); chk.E(err) {
85 return
86 }
87 if r, err = en.Filters.Unmarshal(r); chk.E(err) {
88 return
89 }
90 if r, err = envelopes.SkipToTheEnd(r); chk.E(err) {
91 return
92 }
93 return
94 }
95 96 // ParseRequest reads a Request in minified JSON into a newly allocated Request.
97 func ParseRequest(b []byte) (t *Request, rem []byte, err error) {
98 t = New()
99 if rem, err = t.Unmarshal(b); chk.E(err) {
100 return
101 }
102 return
103 }
104 105 // Response is a COUNT Response returning a count and approximate flag
106 // associated with the REQ subscription.Id.
107 type Response struct {
108 Subscription []byte
109 Count int
110 Approximate bool
111 }
112 113 var _ codec.Envelope = (*Response)(nil)
114 115 // NewResponse creates a new empty countenvelope.Response with a standard formatted
116 // subscription.Id.
117 func NewResponse() *Response { return new(Response) }
118 119 // NewResponseFrom creates a new countenvelope.Response with provided string for the
120 // subscription.Id, a count and optional variadic approximate flag, which is
121 // otherwise false and does not get rendered into the JSON.
122 func NewResponseFrom[V constraints.Bytes](
123 s V, cnt int,
124 approx ...bool,
125 ) (res *Response, err error) {
126 var a bool
127 if len(approx) > 0 {
128 a = approx[0]
129 }
130 if len(s) < 0 || len(s) > 64 {
131 err = errorf.E("subscription id must be length > 0 and <= 64")
132 return
133 }
134 return &Response{[]byte(s), cnt, a}, nil
135 }
136 137 // Label returns the COUNT label associated with a Response.
138 func (en *Response) Label() string { return L }
139 140 // Write a Response to a provided io.Writer as minified JSON.
141 func (en *Response) Write(w io.Writer) (err error) {
142 _, err = w.Write(en.Marshal(nil))
143 return
144 }
145 146 // Marshal a countenvelope.Response envelope in minified JSON, appending to a
147 // provided destination slice.
148 func (en *Response) Marshal(dst []byte) (b []byte) {
149 b = dst
150 b = envelopes.Marshal(
151 b, L,
152 func(bst []byte) (o []byte) {
153 o = bst
154 o = append(o, '"')
155 o = append(o, en.Subscription...)
156 o = append(o, '"')
157 o = append(o, ',')
158 c := ints.New(en.Count)
159 o = c.Marshal(o)
160 if en.Approximate {
161 o = append(o, ',')
162 o = append(o, "true"...)
163 }
164 return
165 },
166 )
167 return
168 }
169 170 // Unmarshal a COUNT Response from minified JSON, returning the remainder after
171 // the end of the envelope.
172 func (en *Response) Unmarshal(b []byte) (r []byte, err error) {
173 r = b
174 if en.Subscription, r, err = text.UnmarshalQuoted(r); chk.E(err) {
175 return
176 }
177 if r, err = text.Comma(r); chk.E(err) {
178 return
179 }
180 i := ints.New(0)
181 if r, err = i.Unmarshal(r); chk.E(err) {
182 return
183 }
184 en.Count = int(i.N)
185 if len(r) > 0 {
186 if r[0] == ',' {
187 r = r[1:]
188 if bytes.HasPrefix(r, []byte("true")) {
189 en.Approximate = true
190 }
191 }
192 }
193 if r, err = envelopes.SkipToTheEnd(r); chk.E(err) {
194 return
195 }
196 return
197 }
198 199 // Parse reads a Count Response in minified JSON into a newly allocated
200 // countenvelope.Response.
201 func Parse(b []byte) (t *Response, rem []byte, err error) {
202 t = NewResponse()
203 if rem, err = t.Unmarshal(b); chk.E(err) {
204 return
205 }
206 return
207 }
208