countenvelope.go raw

   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