diff --git a/context.go b/context.go index f747369..cb569cb 100644 --- a/context.go +++ b/context.go @@ -6,8 +6,14 @@ import ( "fmt" "reflect" "runtime" + "strconv" "sync" + "time" + "encoding" + "golang.org/x/exp/constraints" + + "github.com/google/uuid" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" @@ -69,6 +75,82 @@ type Context struct { nodeMap map[NodeID]*Node } +func (ctx *Context) GQLType(t reflect.Type) graphql.Type { + ser, mapped := ctx.TypeTypes[t] + if mapped { + return ctx.TypeMap[ser].Type + } else { + switch t.Kind() { + case reflect.Array: + ser, mapped := ctx.TypeTypes[t.Elem()] + if mapped { + return graphql.NewList(ctx.TypeMap[ser].Type) + } + case reflect.Slice: + ser, mapped := ctx.TypeTypes[t.Elem()] + if mapped { + return graphql.NewList(ctx.TypeMap[ser].Type) + } + case reflect.Map: + ser, exists := ctx.TypeTypes[t] + if exists { + return ctx.TypeMap[ser].Type + } else { + err := RegisterMap(ctx, t) + if err != nil { + return nil + } + return ctx.TypeMap[ctx.TypeTypes[t]].Type + } + case reflect.Pointer: + ser, mapped := ctx.TypeTypes[t.Elem()] + if mapped { + return ctx.TypeMap[ser].Type + } + } + return nil + } +} + +func RegisterMap(ctx *Context, t reflect.Type) error { + key_type := ctx.GQLType(t.Key()) + if key_type == nil { + return nil + } + + val_type := ctx.GQLType(t.Elem()) + if val_type == nil { + return nil + } + + gql_pair := graphql.NewObject(graphql.ObjectConfig{ + Name: t.String(), + Fields: graphql.Fields{ + "Key": &graphql.Field{ + Type: key_type, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return nil, fmt.Errorf("NOT_IMPLEMENTED") + }, + }, + "Value": &graphql.Field{ + Type: val_type, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + return nil, fmt.Errorf("NOT_IMPLEMENTED") + }, + }, + }, + }) + + gql_map := graphql.NewList(gql_pair) + + ctx.TypeTypes[t] = SerializeType(t) + ctx.TypeMap[SerializeType(t)] = TypeInfo{ + Type: gql_map, + } + + return nil +} + func BuildSchema(ctx *Context, query, mutation *graphql.Object) (graphql.Schema, error) { types := []graphql.Type{} @@ -138,13 +220,13 @@ func RegisterExtension[E any, T interface { *E; Extension}](ctx *Context, data i if tagged_gv { fields[gv_tag] = field.Index - type_ser, type_mapped := ctx.TypeTypes[field.Type] - if type_mapped == false { + gql_type := ctx.GQLType(field.Type) + if gql_type == nil { return fmt.Errorf("Extension %s has field %s of unregistered type %s", reflect_type, gv_tag, field.Type) } gql_interface.AddFieldConfig(gv_tag, &graphql.Field{ - Type: ctx.TypeMap[type_ser].Type, + Type: gql_type, }) } } @@ -217,12 +299,12 @@ func RegisterObject[T any](ctx *Context) error { for _, field := range(reflect.VisibleFields(reflect_type)) { gv_tag, tagged_gv := field.Tag.Lookup("gv") if tagged_gv { - field_type, mapped := ctx.TypeTypes[field.Type] - if mapped == false { - return fmt.Errorf("Object %+v has field %s of unknown type %+v", reflect_type, gv_tag, field_type) + gql_type := ctx.GQLType(field.Type) + if gql_type == nil { + return fmt.Errorf("Object %+v has field %s of unknown type %+v", reflect_type, gv_tag, field.Type) } gql.AddFieldConfig(gv_tag, &graphql.Field{ - Type: ctx.TypeMap[field_type].Type, + Type: gql_type, Resolve: func(p graphql.ResolveParams) (interface{}, error) { val, ok := p.Source.(T) if ok == false { @@ -248,9 +330,100 @@ func RegisterObject[T any](ctx *Context) error { return nil } -func RegisterScalar[T any](ctx *Context, to_json func(interface{})interface{}, from_json func(interface{})interface{}, from_ast func(ast.Value)interface{}) error { - reflect_type := reflect.TypeFor[T]() - serialized_type := SerializedTypeFor[T]() +func identity(value interface{}) interface{} { + return value +} + +func stringify(value interface{}) interface{} { + v, ok := value.(encoding.TextMarshaler) + if ok { + b, err := v.MarshalText() + if err != nil { + return nil + } + return string(b) + } + return nil +} + +func unstringify[T any, E interface { *T; encoding.TextUnmarshaler }](value interface{}) interface{} { + str, ok := value.(string) + if ok == false { + return nil + } + + var tmp E + err := tmp.UnmarshalText([]byte(str)) + if err != nil { + return nil + } + + return *tmp +} + +func unstringifyAST[T any, E interface { *T; encoding.TextUnmarshaler}](value ast.Value)interface{} { + str, ok := value.(*ast.StringValue) + if ok == false { + return nil + } + + var tmp E + err := tmp.UnmarshalText([]byte(str.Value)) + if err != nil { + return nil + } + + return *tmp +} + +func coerce[T any](value interface{}) interface{} { + t := reflect.TypeFor[T]() + if reflect.TypeOf(value).ConvertibleTo(t) { + return value.(T) + } else { + return nil + } +} + +func astString[T ~string](value ast.Value) interface{} { + str, ok := value.(*ast.StringValue) + if ok == false { + return nil + } + + return T(str.Value) +} + +func astInt[T constraints.Integer](value ast.Value) interface{} { + switch value := value.(type) { + case *ast.BooleanValue: + if value.Value { + return T(1) + } else { + return T(0) + } + case *ast.StringValue: + i, err := strconv.Atoi(value.Value) + if err != nil { + return nil + } else { + return T(i) + } + case *ast.IntValue: + i, err := strconv.Atoi(value.Value) + if err != nil { + return nil + } else { + return T(i) + } + default: + return nil + } +} + +func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, from_json func(interface{})interface{}, from_ast func(ast.Value)interface{}) error { + reflect_type := reflect.TypeFor[S]() + serialized_type := SerializedTypeFor[S]() _, exists := ctx.TypeTypes[reflect_type] if exists { @@ -379,6 +552,61 @@ func NewContext(db * badger.DB, log Logger) (*Context, error) { var err error + err = RegisterScalar[int](ctx, identity, coerce[int], astInt[int]) + if err != nil { + return nil, err + } + + err = RegisterScalar[uint8](ctx, identity, coerce[uint8], astInt[uint8]) + if err != nil { + return nil, err + } + + err = RegisterScalar[time.Time](ctx, stringify, unstringify[time.Time], unstringifyAST[time.Time]) + if err != nil { + return nil, err + } + + err = RegisterScalar[string](ctx, identity, coerce[string], astString[string]) + if err != nil { + return nil, err + } + + err = RegisterScalar[EventState](ctx, identity, coerce[EventState], astString[EventState]) + if err != nil { + return nil, err + } + + err = RegisterScalar[ReqState](ctx, identity, coerce[ReqState], astInt[ReqState]) + if err != nil { + return nil, err + } + + err = RegisterScalar[uuid.UUID](ctx, stringify, unstringify[uuid.UUID], unstringifyAST[uuid.UUID]) + if err != nil { + return nil, err + } + + err = RegisterScalar[NodeID](ctx, stringify, unstringify[NodeID], unstringifyAST[NodeID]) + if err != nil { + return nil, err + } + + err = RegisterScalar[WaitReason](ctx, identity, coerce[WaitReason], astString[WaitReason]) + if err != nil { + return nil, err + } + + err = RegisterObject[WaitInfo](ctx) + if err != nil { + return nil, err + } + + err = RegisterMap(ctx, reflect.TypeFor[WaitMap]()) + if err != nil { + return nil, err + } + err = RegisterExtension[ListenerExt](ctx, nil) if err != nil { return nil, err diff --git a/go.mod b/go.mod index f28c45b..b347598 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,6 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.8.2 // indirect go.opencensus.io v0.22.5 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/sys v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 623b3f1..eb5730b 100644 --- a/go.sum +++ b/go.sum @@ -111,6 +111,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= diff --git a/node.go b/node.go index c9a764e..b0f3cfa 100644 --- a/node.go +++ b/node.go @@ -33,6 +33,16 @@ func IDFromBytes(bytes []byte) (NodeID, error) { return NodeID(id), err } +func (id NodeID) MarshalText() ([]byte, error) { + return []byte(id.String()), nil +} + +func (id *NodeID) UnmarshalText(text []byte) error { + parsed, err := ParseID(string(text)) + *id = parsed + return err +} + // Parse an ID from a string func ParseID(str string) (NodeID, error) { id_uuid, err := uuid.Parse(str) @@ -480,15 +490,18 @@ func ExtTypeSuffix(ext_type ExtType) []byte { } func WriteNodeExtList(ctx *Context, node *Node) error { - return fmt.Errorf("TODO: write node list") + ctx.Log.Logf("todo", "write node list") + return nil } func WriteNodeInit(ctx *Context, node *Node) error { - return fmt.Errorf("TODO: write initial node entry") + ctx.Log.Logf("todo", "write initial node entry") + return nil } func WriteNodeChanges(ctx *Context, node *Node, changes map[ExtType]Changes) error { - return fmt.Errorf("TODO: write changes to node(and any signal queue changes)") + ctx.Log.Logf("todo", "write node changes") + return nil } func LoadNode(ctx *Context, id NodeID) (*Node, error) { diff --git a/serialize.go b/serialize.go index 1f7fa3b..e5f16af 100644 --- a/serialize.go +++ b/serialize.go @@ -66,13 +66,16 @@ func NodeTypeFor(name string, extensions []ExtType, mappings map[string]FieldInd return NodeType(binary.BigEndian.Uint64(hash[0:8])) } -func SerializedTypeFor[T any]() SerializedType { - t := reflect.TypeFor[T]() +func SerializeType(t reflect.Type) SerializedType { digest := []byte(t.String()) hash := sha512.Sum512(digest) return SerializedType(binary.BigEndian.Uint64(hash[0:8])) } +func SerializedTypeFor[T any]() SerializedType { + return SerializeType(reflect.TypeFor[T]()) +} + func ExtTypeFor[E any, T interface { *E; Extension}]() ExtType { return ExtType(SerializedTypeFor[E]()) }