Files
realy/event/json.go

276 lines
5.6 KiB
Go

package event
import (
"bytes"
"encoding/json"
"io"
"realy.lol/chk"
"realy.lol/ec/schnorr"
"realy.lol/errorf"
"realy.lol/hex"
"realy.lol/kind"
"realy.lol/sha256"
"realy.lol/tags"
"realy.lol/text"
"realy.lol/timestamp"
)
var (
jId = []byte("id")
jPubkey = []byte("pubkey")
jCreatedAt = []byte("created_at")
jKind = []byte("kind")
jTags = []byte("tags")
jContent = []byte("content")
jSig = []byte("sig")
)
// Marshal appends an event.T to a provided destination slice.
func (ev *T) Marshal(dst []byte) (b []byte) {
b = ev.marshalWithWhitespace(dst, false)
return
}
// marshalWithWhitespace adds tabs and newlines to make the JSON more readable
// for humans, if the on flag is set to true.
func (ev *T) marshalWithWhitespace(dst []byte, on bool) (b []byte) {
// open parentheses
dst = append(dst, '{')
// Id
if on {
dst = append(dst, '\n', '\t')
}
dst = text.JSONKey(dst, jId)
dst = text.AppendQuote(dst, ev.Id, hex.EncAppend)
dst = append(dst, ',')
// Pubkey
if on {
dst = append(dst, '\n', '\t')
}
dst = text.JSONKey(dst, jPubkey)
dst = text.AppendQuote(dst, ev.Pubkey, hex.EncAppend)
dst = append(dst, ',')
if on {
dst = append(dst, '\n', '\t')
}
// CreatedAt
dst = text.JSONKey(dst, jCreatedAt)
dst = ev.CreatedAt.Marshal(dst)
dst = append(dst, ',')
if on {
dst = append(dst, '\n', '\t')
}
// Kind
dst = text.JSONKey(dst, jKind)
dst = ev.Kind.Marshal(dst)
dst = append(dst, ',')
if on {
dst = append(dst, '\n', '\t')
}
// Tags
dst = text.JSONKey(dst, jTags)
dst = ev.Tags.Marshal(dst)
dst = append(dst, ',')
if on {
dst = append(dst, '\n', '\t')
}
// Content
dst = text.JSONKey(dst, jContent)
dst = text.AppendQuote(dst, ev.Content, text.NostrEscape)
dst = append(dst, ',')
if on {
dst = append(dst, '\n', '\t')
}
// jSig
dst = text.JSONKey(dst, jSig)
dst = text.AppendQuote(dst, ev.Sig, hex.EncAppend)
if on {
dst = append(dst, '\n')
}
// close parentheses
dst = append(dst, '}')
b = dst
return
}
// Marshal is a normal function that is the same as event.T Marshal method
// except you explicitly specify the receiver.
func Marshal(ev *T, dst []byte) (b []byte) { return ev.Marshal(dst) }
// Unmarshal an event from minified JSON into an event.T.
func (ev *T) Unmarshal(b []byte) (r []byte, err error) {
// this parser does not cope with whitespaces in valid places in json, so we
// scan first for linebreaks, as these indicate that it is probably not gona work and fall back to json.Unmarshal
for _, v := range b {
if v == '\n' {
// revert to json.Unmarshal
var j J
if err = json.Unmarshal(b, &j); chk.E(err) {
return
}
var e *T
if e, err = j.ToEvent(); chk.E(err) {
return
}
*ev = *e
return
}
}
key := make([]byte, 0, 9)
r = b
for ; len(r) > 0; r = r[1:] {
if r[0] == '{' {
r = r[1:]
goto BetweenKeys
}
}
goto eof
BetweenKeys:
for ; len(r) > 0; r = r[1:] {
if r[0] == '"' {
r = r[1:]
goto InKey
}
}
goto eof
InKey:
for ; len(r) > 0; r = r[1:] {
if r[0] == '"' {
r = r[1:]
goto InKV
}
key = append(key, r[0])
}
goto eof
InKV:
for ; len(r) > 0; r = r[1:] {
if r[0] == ':' {
r = r[1:]
goto InVal
}
}
goto eof
InVal:
switch key[0] {
case jId[0]:
if !bytes.Equal(jId, key) {
goto invalid
}
var id []byte
if id, r, err = text.UnmarshalHex(r); chk.E(err) {
return
}
if len(id) != sha256.Size {
err = errorf.E("invalid Id, require %d got %d", sha256.Size,
len(id))
return
}
ev.Id = id
goto BetweenKV
case jPubkey[0]:
if !bytes.Equal(jPubkey, key) {
goto invalid
}
var pk []byte
if pk, r, err = text.UnmarshalHex(r); chk.E(err) {
return
}
if len(pk) != schnorr.PubKeyBytesLen {
err = errorf.E("invalid pubkey, require %d got %d",
schnorr.PubKeyBytesLen, len(pk))
return
}
ev.Pubkey = pk
goto BetweenKV
case jKind[0]:
if !bytes.Equal(jKind, key) {
goto invalid
}
ev.Kind = kind.New(0)
if r, err = ev.Kind.Unmarshal(r); chk.E(err) {
return
}
goto BetweenKV
case jTags[0]:
if !bytes.Equal(jTags, key) {
goto invalid
}
ev.Tags = tags.New()
if r, err = ev.Tags.Unmarshal(r); chk.E(err) {
return
}
goto BetweenKV
case jSig[0]:
if !bytes.Equal(jSig, key) {
goto invalid
}
var sig []byte
if sig, r, err = text.UnmarshalHex(r); chk.E(err) {
return
}
if len(sig) != schnorr.SignatureSize {
err = errorf.E("invalid sig length, require %d got %d '%s'\n%s",
schnorr.SignatureSize, len(sig), r, b)
return
}
ev.Sig = sig
goto BetweenKV
case jContent[0]:
if key[1] == jContent[1] {
if !bytes.Equal(jContent, key) {
goto invalid
}
if ev.Content, r, err = text.UnmarshalQuoted(r); chk.T(err) {
return
}
goto BetweenKV
} else if key[1] == jCreatedAt[1] {
if !bytes.Equal(jCreatedAt, key) {
goto invalid
}
ev.CreatedAt = timestamp.New(uint(0))
if r, err = ev.CreatedAt.Unmarshal(r); chk.T(err) {
return
}
goto BetweenKV
} else {
goto invalid
}
default:
goto invalid
}
BetweenKV:
key = key[:0]
for ; len(r) > 0; r = r[1:] {
switch {
case len(r) == 0:
return
case r[0] == '}':
r = r[1:]
goto AfterClose
case r[0] == ',':
r = r[1:]
goto BetweenKeys
case r[0] == '"':
r = r[1:]
goto InKey
}
}
goto eof
AfterClose:
return
invalid:
err = errorf.E("invalid key,\n'%s'\n'%s'\n'%s'", string(b), string(b[:len(r)]),
string(r))
return
eof:
err = io.EOF
return
}
// Unmarshal is the same as the event.T Unmarshal method except you give it the
// event to marshal into instead of call it as a method of the type.
func Unmarshal(ev *T, b []byte) (r []byte, err error) { return ev.Unmarshal(b) }