waveterm/pkg/waveobj/waveobj.go

330 lines
7.5 KiB
Go
Raw Permalink Normal View History

2024-05-26 02:37:05 +02:00
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package waveobj
import (
"encoding/json"
"fmt"
"reflect"
"regexp"
"strings"
2024-05-26 02:37:05 +02:00
"sync"
"github.com/google/uuid"
2024-05-26 02:37:05 +02:00
"github.com/mitchellh/mapstructure"
2024-09-05 23:25:45 +02:00
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
2024-05-26 02:37:05 +02:00
)
const (
2024-05-26 20:59:14 +02:00
OTypeKeyName = "otype"
OIDKeyName = "oid"
VersionKeyName = "version"
MetaKeyName = "meta"
2024-05-26 20:59:14 +02:00
OIDGoFieldName = "OID"
VersionGoFieldName = "Version"
MetaGoFieldName = "Meta"
2024-05-26 02:37:05 +02:00
)
2024-05-27 08:05:11 +02:00
type ORef struct {
// special JSON marshalling to string
OType string `json:"otype" mapstructure:"otype"`
OID string `json:"oid" mapstructure:"oid"`
}
func (oref ORef) String() string {
if oref.OType == "" || oref.OID == "" {
return ""
}
return fmt.Sprintf("%s:%s", oref.OType, oref.OID)
2024-05-27 08:05:11 +02:00
}
func (oref ORef) MarshalJSON() ([]byte, error) {
return json.Marshal(oref.String())
}
func (oref ORef) IsEmpty() bool {
// either being empty is not valid
return oref.OType == "" || oref.OID == ""
}
func (oref *ORef) UnmarshalJSON(data []byte) error {
var orefStr string
err := json.Unmarshal(data, &orefStr)
if err != nil {
return err
}
if len(orefStr) == 0 {
oref.OType = ""
oref.OID = ""
return nil
}
parsed, err := ParseORef(orefStr)
if err != nil {
return err
}
*oref = parsed
return nil
}
2024-06-12 02:42:10 +02:00
func MakeORef(otype string, oid string) ORef {
return ORef{
OType: otype,
OID: oid,
}
}
var otypeRe = regexp.MustCompile(`^[a-z]+$`)
func ParseORef(orefStr string) (ORef, error) {
fields := strings.Split(orefStr, ":")
if len(fields) != 2 {
return ORef{}, fmt.Errorf("invalid object reference: %q", orefStr)
}
otype := fields[0]
if !otypeRe.MatchString(otype) {
return ORef{}, fmt.Errorf("invalid object type: %q", otype)
}
if !ValidOTypes[otype] {
return ORef{}, fmt.Errorf("unknown object type: %q", otype)
}
oid := fields[1]
_, err := uuid.Parse(oid)
if err != nil {
return ORef{}, fmt.Errorf("invalid object id: %q", oid)
}
return ORef{OType: otype, OID: oid}, nil
}
2024-10-24 07:47:29 +02:00
func ParseORefNoErr(orefStr string) *ORef {
oref, err := ParseORef(orefStr)
if err != nil {
return nil
}
return &oref
}
2024-05-26 20:59:14 +02:00
type WaveObj interface {
GetOType() string // should not depend on object state (should work with nil value)
}
2024-05-26 02:37:05 +02:00
type waveObjDesc struct {
2024-05-26 20:59:14 +02:00
RType reflect.Type
OIDField reflect.StructField
VersionField reflect.StructField
MetaField reflect.StructField
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
var waveObjMap = sync.Map{}
var waveObjRType = reflect.TypeOf((*WaveObj)(nil)).Elem()
var metaMapRType = reflect.TypeOf(MetaMapType{})
2024-05-26 02:37:05 +02:00
2024-05-27 08:05:11 +02:00
func RegisterType(rtype reflect.Type) {
if rtype.Kind() != reflect.Ptr {
panic(fmt.Sprintf("wave object must be a pointer for %v", rtype))
}
if !rtype.Implements(waveObjRType) {
panic(fmt.Sprintf("wave object must implement WaveObj for %v", rtype))
}
waveObj := reflect.Zero(rtype).Interface().(WaveObj)
2024-05-26 20:59:14 +02:00
otype := waveObj.GetOType()
if otype == "" {
2024-05-27 08:05:11 +02:00
panic(fmt.Sprintf("otype is empty for %v", rtype))
2024-05-26 20:59:14 +02:00
}
oidField, found := rtype.Elem().FieldByName(OIDGoFieldName)
if !found {
2024-05-27 08:05:11 +02:00
panic(fmt.Sprintf("missing OID field for %v", rtype))
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
if oidField.Type.Kind() != reflect.String {
2024-05-27 08:05:11 +02:00
panic(fmt.Sprintf("OID field must be string for %v", rtype))
2024-05-26 02:37:05 +02:00
}
2024-08-28 07:02:21 +02:00
oidJsonTag := utilfn.GetJsonTag(oidField)
if oidJsonTag != OIDKeyName {
2024-05-27 08:05:11 +02:00
panic(fmt.Sprintf("OID field json tag must be %q for %v", OIDKeyName, rtype))
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
versionField, found := rtype.Elem().FieldByName(VersionGoFieldName)
if !found {
2024-05-27 08:05:11 +02:00
panic(fmt.Sprintf("missing Version field for %v", rtype))
2024-05-26 20:59:14 +02:00
}
if versionField.Type.Kind() != reflect.Int {
2024-05-27 08:05:11 +02:00
panic(fmt.Sprintf("Version field must be int for %v", rtype))
2024-05-26 20:59:14 +02:00
}
2024-08-28 07:02:21 +02:00
versionJsonTag := utilfn.GetJsonTag(versionField)
if versionJsonTag != VersionKeyName {
2024-05-27 08:05:11 +02:00
panic(fmt.Sprintf("Version field json tag must be %q for %v", VersionKeyName, rtype))
2024-05-26 20:59:14 +02:00
}
metaField, found := rtype.Elem().FieldByName(MetaGoFieldName)
if !found {
panic(fmt.Sprintf("missing Meta field for %v", rtype))
}
if metaField.Type != metaMapRType {
panic(fmt.Sprintf("Meta field must be MetaMapType for %v", rtype))
}
2024-05-26 20:59:14 +02:00
_, found = waveObjMap.Load(otype)
if found {
panic(fmt.Sprintf("otype %q already registered", otype))
}
waveObjMap.Store(otype, &waveObjDesc{
RType: rtype,
OIDField: oidField,
VersionField: versionField,
MetaField: metaField,
2024-05-26 20:59:14 +02:00
})
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
func getWaveObjDesc(otype string) *waveObjDesc {
desc, _ := waveObjMap.Load(otype)
if desc == nil {
return nil
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
return desc.(*waveObjDesc)
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
func GetOID(waveObj WaveObj) string {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return ""
}
return reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.OIDField.Index).String()
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
func SetOID(waveObj WaveObj, oid string) {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return
}
reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.OIDField.Index).SetString(oid)
}
func GetVersion(waveObj WaveObj) int {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return 0
}
return int(reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.VersionField.Index).Int())
}
func SetVersion(waveObj WaveObj, version int) {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return
}
reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.VersionField.Index).SetInt(int64(version))
2024-05-26 02:37:05 +02:00
}
func GetMeta(waveObj WaveObj) MetaMapType {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return nil
}
mval := reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.MetaField.Index).Interface()
if mval == nil {
return nil
}
return mval.(MetaMapType)
}
func SetMeta(waveObj WaveObj, meta map[string]any) {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return
}
reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.MetaField.Index).Set(reflect.ValueOf(meta))
}
func ToJsonMap(w WaveObj) (map[string]any, error) {
2024-06-03 22:43:50 +02:00
if w == nil {
return nil, nil
}
2024-05-26 02:37:05 +02:00
m := make(map[string]any)
2024-05-26 20:59:14 +02:00
dconfig := &mapstructure.DecoderConfig{
Result: &m,
TagName: "json",
}
decoder, err := mapstructure.NewDecoder(dconfig)
2024-05-26 02:37:05 +02:00
if err != nil {
return nil, err
}
2024-05-26 20:59:14 +02:00
err = decoder.Decode(w)
if err != nil {
return nil, err
2024-05-26 02:37:05 +02:00
}
m[OTypeKeyName] = w.GetOType()
m[OIDKeyName] = GetOID(w)
m[VersionKeyName] = GetVersion(w)
return m, nil
}
func ToJson(w WaveObj) ([]byte, error) {
m, err := ToJsonMap(w)
if err != nil {
return nil, err
}
2024-05-26 02:37:05 +02:00
return json.Marshal(m)
}
func FromJson(data []byte) (WaveObj, error) {
var m map[string]any
err := json.Unmarshal(data, &m)
if err != nil {
return nil, err
}
return FromJsonMap(m)
}
func FromJsonMap(m map[string]any) (WaveObj, error) {
2024-05-26 02:37:05 +02:00
otype, ok := m[OTypeKeyName].(string)
if !ok {
return nil, fmt.Errorf("missing otype")
}
2024-05-26 20:59:14 +02:00
desc := getWaveObjDesc(otype)
2024-05-26 02:37:05 +02:00
if desc == nil {
2024-05-26 20:59:14 +02:00
return nil, fmt.Errorf("unknown otype: %s", otype)
}
wobj := reflect.Zero(desc.RType).Interface().(WaveObj)
dconfig := &mapstructure.DecoderConfig{
Result: &wobj,
TagName: "json",
2024-05-26 02:37:05 +02:00
}
2024-05-26 20:59:14 +02:00
decoder, err := mapstructure.NewDecoder(dconfig)
2024-05-26 02:37:05 +02:00
if err != nil {
return nil, err
}
2024-05-26 20:59:14 +02:00
err = decoder.Decode(m)
if err != nil {
return nil, err
}
return wobj, nil
2024-05-26 02:37:05 +02:00
}
func ORefFromMap(m map[string]any) (*ORef, error) {
oref := ORef{}
err := mapstructure.Decode(m, &oref)
if err != nil {
return nil, err
}
return &oref, nil
2024-06-12 02:42:10 +02:00
}
2024-06-12 02:42:10 +02:00
func ORefFromWaveObj(w WaveObj) *ORef {
return &ORef{
OType: w.GetOType(),
OID: GetOID(w),
}
}
2024-05-26 02:37:05 +02:00
func FromJsonGen[T WaveObj](data []byte) (T, error) {
obj, err := FromJson(data)
if err != nil {
var zero T
return zero, err
}
rtn, ok := obj.(T)
if !ok {
var zero T
return zero, fmt.Errorf("type mismatch got %T, expected %T", obj, zero)
}
return rtn, nil
}