158 lines
3.8 KiB
Go
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
|
|
}
|