|
|
|
@ -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
|
|
|
|
|