Files
transit/tag/tag.go
2025-05-21 22:42:51 -01:06

158 lines
3.8 KiB
Go

package tag
import (
"encoding/json"
"net/url"
"reflect"
"github.com/mleku/transit/chk"
"github.com/mleku/transit/errorf"
"github.com/mleku/transit/id"
"github.com/mleku/transit/pubkey"
)
type T [2]any
type U struct{ T }
// MarshalTypes is a list of types of tags that are valid. Note that this is all of the types that are
// valid. Additional tag keys may be added in the future but should never be taken away.
var MarshalTypes = map[string]reflect.Type{
"root": reflect.TypeOf(&id.I{}),
"parent": reflect.TypeOf(&id.I{}),
"event": reflect.TypeOf(&id.I{}),
"user": reflect.TypeOf(&pubkey.P{}),
"hashtag": reflect.TypeOf(""),
"url": reflect.TypeOf(""),
}
// UnmarshalTypes is a list of types of tags that are valid. Note that this is all of the types that are
// valid. Additional tag keys may be added in the future but should never be taken away.
var UnmarshalTypes = map[string]func() any{
"root": func() any { return id.I{} },
"parent": func() any { return id.I{} },
"event": func() any { return id.I{} },
"user": func() any { return pubkey.P{} },
"hashtag": func() any { return "" },
"url": func() any { return "" },
}
func New(key string, val any) (t *U) {
return &U{T{key, val}}
}
// GetId returns the correctly typed value or the ok value is false.
func (t *U) GetId() (i *id.I, ok bool) { i, ok = t.T[1].(*id.I); return }
// GetPubkey returns the correctly typed value or the ok value is false.
func (t *U) GetPubkey() (p *pubkey.P, ok bool) {
p, ok = t.T[1].(*pubkey.P)
return
}
// GetString returns the correctly typed value or the ok value is false.
func (t *U) GetString() (s string, ok bool) {
s, ok = t.T[1].(string)
return
}
func (t *U) MarshalJSON() (b []byte, err error) {
// first value must be a string
k, ok := t.T[0].(string)
if !ok {
err = errorf.E("key must be a string, got '%v'", k)
return
}
// second value must be one of MarshalTypes.
var found bool
for _, v := range MarshalTypes {
if v == reflect.TypeOf(t.T[1]) {
found = true
break
}
}
// url is a special case because it needs to be rendered using a stringer as it lacks a
// json.MarshalJSON implementation.
if k == "url" {
var ur string
if ur, ok = t.T[1].(string); !ok {
err = errorf.E("url key does not have a value of type string: %v",
reflect.TypeOf(t.T[1]))
return
}
if _, err = url.Parse(ur); chk.E(err) {
return
}
t.T[1] = ur
}
if k == "hashtag" {
var ht string
if ht, ok = t.T[1].(string); !ok {
err = errorf.E("hashtag key does not have a value of type []byte: %v",
reflect.TypeOf(t.T[1]))
return
}
t.T[1] = ht
}
if !found {
err = errorf.E("tag value is unknown type key: %s value: %v",
t.T[0], reflect.TypeOf(t.T[1]))
return
}
// marshal array into tag
tt := t.T
if b, err = json.Marshal(&tt); chk.E(err) {
return
}
return
}
func (t *U) UnmarshalJSON(b []byte) (err error) {
var kv []any
if err = json.Unmarshal(b, &kv); chk.E(err) {
return
}
// we only do the above to get the key name
var ok bool
var k string
if k, ok = kv[0].(string); !ok {
err = errorf.E("key must be a string, got '%v'", k)
return
}
t.T[0] = k
var vt func() any
if vt, ok = UnmarshalTypes[k]; !ok {
err = errorf.E("unknown key type '%s'", k)
return
}
tt := vt()
var vs string
if vs, ok = kv[1].(string); !ok {
err = errorf.E("value must be a string, got '%v'", k)
return
}
// decode in accordance with the key
switch ttt := tt.(type) {
case id.I:
vs = `"` + vs + `"`
if err = ttt.UnmarshalJSON([]byte(vs)); chk.E(err) {
return
}
t.T[1] = &ttt
case pubkey.P:
vs = `"` + vs + `"`
if err = ttt.UnmarshalJSON([]byte(vs)); chk.E(err) {
return
}
t.T[1] = &ttt
case string:
t.T[1] = vs
case *url.URL:
if ttt, err = url.Parse(vs); chk.E(err) {
return
}
t.T[1] = vs
}
return
}