Started graphql and serialization come together

gql_cataclysm
noah metz 2024-03-04 21:30:11 -07:00
parent 6942dc02db
commit e16bec3997
5 changed files with 262 additions and 16 deletions

@ -6,8 +6,14 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"runtime" "runtime"
"strconv"
"sync" "sync"
"time"
"encoding"
"golang.org/x/exp/constraints"
"github.com/google/uuid"
"github.com/graphql-go/graphql" "github.com/graphql-go/graphql"
"github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/ast"
@ -69,6 +75,82 @@ type Context struct {
nodeMap map[NodeID]*Node 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) { func BuildSchema(ctx *Context, query, mutation *graphql.Object) (graphql.Schema, error) {
types := []graphql.Type{} types := []graphql.Type{}
@ -138,13 +220,13 @@ func RegisterExtension[E any, T interface { *E; Extension}](ctx *Context, data i
if tagged_gv { if tagged_gv {
fields[gv_tag] = field.Index fields[gv_tag] = field.Index
type_ser, type_mapped := ctx.TypeTypes[field.Type] gql_type := ctx.GQLType(field.Type)
if type_mapped == false { if gql_type == nil {
return fmt.Errorf("Extension %s has field %s of unregistered type %s", reflect_type, gv_tag, field.Type) 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{ 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)) { for _, field := range(reflect.VisibleFields(reflect_type)) {
gv_tag, tagged_gv := field.Tag.Lookup("gv") gv_tag, tagged_gv := field.Tag.Lookup("gv")
if tagged_gv { if tagged_gv {
field_type, mapped := ctx.TypeTypes[field.Type] gql_type := ctx.GQLType(field.Type)
if mapped == false { if gql_type == nil {
return fmt.Errorf("Object %+v has field %s of unknown type %+v", reflect_type, gv_tag, field_type) return fmt.Errorf("Object %+v has field %s of unknown type %+v", reflect_type, gv_tag, field.Type)
} }
gql.AddFieldConfig(gv_tag, &graphql.Field{ gql.AddFieldConfig(gv_tag, &graphql.Field{
Type: ctx.TypeMap[field_type].Type, Type: gql_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { Resolve: func(p graphql.ResolveParams) (interface{}, error) {
val, ok := p.Source.(T) val, ok := p.Source.(T)
if ok == false { if ok == false {
@ -248,9 +330,100 @@ func RegisterObject[T any](ctx *Context) error {
return nil 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 { func identity(value interface{}) interface{} {
reflect_type := reflect.TypeFor[T]() return value
serialized_type := SerializedTypeFor[T]() }
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] _, exists := ctx.TypeTypes[reflect_type]
if exists { if exists {
@ -379,6 +552,61 @@ func NewContext(db * badger.DB, log Logger) (*Context, error) {
var err 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) err = RegisterExtension[ListenerExt](ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, err

@ -31,6 +31,6 @@ require (
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.2 // indirect github.com/stretchr/testify v1.8.2 // indirect
go.opencensus.io v0.22.5 // 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 golang.org/x/sys v0.13.0 // indirect
) )

@ -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-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 h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=

@ -33,6 +33,16 @@ func IDFromBytes(bytes []byte) (NodeID, error) {
return NodeID(id), err 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 // Parse an ID from a string
func ParseID(str string) (NodeID, error) { func ParseID(str string) (NodeID, error) {
id_uuid, err := uuid.Parse(str) id_uuid, err := uuid.Parse(str)
@ -480,15 +490,18 @@ func ExtTypeSuffix(ext_type ExtType) []byte {
} }
func WriteNodeExtList(ctx *Context, node *Node) error { 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 { 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 { 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) { func LoadNode(ctx *Context, id NodeID) (*Node, error) {

@ -66,13 +66,16 @@ func NodeTypeFor(name string, extensions []ExtType, mappings map[string]FieldInd
return NodeType(binary.BigEndian.Uint64(hash[0:8])) return NodeType(binary.BigEndian.Uint64(hash[0:8]))
} }
func SerializedTypeFor[T any]() SerializedType { func SerializeType(t reflect.Type) SerializedType {
t := reflect.TypeFor[T]()
digest := []byte(t.String()) digest := []byte(t.String())
hash := sha512.Sum512(digest) hash := sha512.Sum512(digest)
return SerializedType(binary.BigEndian.Uint64(hash[0:8])) 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 { func ExtTypeFor[E any, T interface { *E; Extension}]() ExtType {
return ExtType(SerializedTypeFor[E]()) return ExtType(SerializedTypeFor[E]())
} }