diff --git a/cmd/graphiql/main.go b/cmd/graphiql/main.go index 554e49c..9447f7f 100644 --- a/cmd/graphiql/main.go +++ b/cmd/graphiql/main.go @@ -25,11 +25,13 @@ func main() { listener_ext := gv.NewListenerExt(1000) - _, err = gv.NewNode(ctx, nil, "Base", 1000, gql_ext, listener_ext) + _, err = gv.NewNode(ctx, nil, "Lockable", 1000, gql_ext, listener_ext, gv.NewLockableExt(nil)) check(err) - select { - case message := <- listener_ext.Chan: - fmt.Printf("Listener Message: %+v\n", message) + for true { + select { + case message := <- listener_ext.Chan: + fmt.Printf("Listener Message: %+v\n", message) + } } } diff --git a/context.go b/context.go index b625da8..09cf5a6 100644 --- a/context.go +++ b/context.go @@ -47,6 +47,8 @@ type TypeInfo struct { Serialize SerializeFn Deserialize DeserializeFn + + Resolve func(interface{})(interface{}, error) } type ExtensionInfo struct { @@ -55,16 +57,12 @@ type ExtensionInfo struct { Data interface{} } -type FieldIndex struct { - FieldTag FieldTag - Extension ExtType -} - type NodeInfo struct { NodeType Type *graphql.Object Interface *graphql.Interface Extensions []ExtType + Fields map[string]ExtType } // A Context stores all the data to run a graphvent process @@ -137,6 +135,11 @@ func (ctx *Context) GQLType(t reflect.Type, node_type string) (graphql.Type, err } } +type Pair struct { + Key any + Val any +} + func RegisterMap(ctx *Context, reflect_type reflect.Type, node_type string) error { ctx.Log.Logf("gql", "Registering map %s with node_type %s", reflect_type, node_type) node_types := strings.Split(node_type, ":") @@ -166,13 +169,23 @@ func RegisterMap(ctx *Context, reflect_type reflect.Type, node_type string) erro "Key": &graphql.Field{ Type: key_type, Resolve: func(p graphql.ResolveParams) (interface{}, error) { - return nil, fmt.Errorf("NOT_IMPLEMENTED") + source, ok := p.Source.(Pair) + if ok == false { + return nil, fmt.Errorf("%+v is not Pair", source) + } + + return source.Key, nil }, }, "Value": &graphql.Field{ Type: val_type, Resolve: func(p graphql.ResolveParams) (interface{}, error) { - return nil, fmt.Errorf("NOT_IMPLEMENTED") + source, ok := p.Source.(Pair) + if ok == false { + return nil, fmt.Errorf("%+v is not Pair", source) + } + + return source.Val, nil }, }, }, @@ -186,6 +199,24 @@ func RegisterMap(ctx *Context, reflect_type reflect.Type, node_type string) erro Serialized: serialized_type, Reflect: reflect_type, Type: gql_map, + Resolve: func(v interface{}) (interface{}, error) { + val := reflect.ValueOf(v) + if val.Type() != (reflect_type) { + return nil, fmt.Errorf("%s is not %s", val.Type(), reflect_type) + } else { + pairs := make([]Pair, val.Len()) + iter := val.MapRange() + i := 0 + for iter.Next() { + pairs[i] = Pair{ + Key: iter.Key().Interface(), + Val: iter.Value().Interface(), + } + i += 1 + } + return pairs, nil + } + }, } ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type] @@ -270,9 +301,11 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { return fmt.Errorf("Cannot register node type %+v, type already exists in context", node_type) } + fields := map[string]ExtType{} + ext_found := map[ExtType]bool{} for _, extension := range(extensions) { - _, in_ctx := ctx.Extensions[extension] + ext_info, in_ctx := ctx.Extensions[extension] if in_ctx == false { return fmt.Errorf("Cannot register node type %+v, required extension %+v not in context", name, extension) } @@ -283,6 +316,14 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { } ext_found[extension] = true + + for field_name := range(ext_info.Fields) { + _, exists := fields[field_name] + if exists { + return fmt.Errorf("Cannot register NodeType %s with duplicate field name %s", name, field_name) + } + fields[field_name] = extension + } } gql_interface := graphql.NewInterface(graphql.InterfaceConfig{ @@ -304,17 +345,19 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { val, ok := p.Value.(NodeResult) if ok == false { + ctx.Context.Log.Logf("gql", "Interface ResolveType got bad Value %+v", p.Value) return nil } node_info, exists := ctx.Context.Nodes[val.NodeType] if exists == false { + ctx.Context.Log.Logf("gql", "Interface ResolveType got bad NodeType", val.NodeType) return nil } for _, ext_type := range(extensions) { if slices.Contains(node_info.Extensions, ext_type) == false { - // node_info does not contain required extension, so this cannot be a type of this interface + ctx.Context.Log.Logf("gql", "Interface ResolveType for %s missing extensions %s: %+v", name, ext_type, val) return nil } } @@ -325,26 +368,15 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { gql := graphql.NewObject(graphql.ObjectConfig{ Name: name + "Node", + Interfaces: ctx.GQLInterfaces(node_type, extensions), Fields: graphql.Fields{ "ID": &graphql.Field{ Type: ctx.TypeTypes[reflect.TypeFor[NodeID]()].Type, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - source, ok := p.Source.(NodeResult) - if ok == false { - return nil, fmt.Errorf("GQL Node value is not NodeResult") - } - return source.NodeID, nil - }, + Resolve: ResolveNodeID, }, "Type": &graphql.Field{ Type: ctx.TypeTypes[reflect.TypeFor[NodeType]()].Type, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - source, ok := p.Source.(NodeResult) - if ok == false { - return nil, fmt.Errorf("GQL Node value is not NodeResult") - } - return source.NodeType, nil - }, + Resolve: ResolveNodeType, }, }, IsTypeOf: func(p graphql.IsTypeOfParams) bool { @@ -361,6 +393,7 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { Interface: gql_interface, Type: gql, Extensions: extensions, + Fields: fields, } ctx.NodeTypes[name] = ctx.Nodes[node_type] @@ -375,6 +408,11 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { if err != nil { return err } + + gql_resolve, err := ctx.GQLResolve(field_info.Type, field_info.NodeTag) + if err != nil { + return err + } ctx.Log.Logf("gql", "Adding field %s[%+v] to %s with gql type %+v", field_name, field_info, name, gql_type) gql_interface.AddFieldConfig(field_name, &graphql.Field{ @@ -384,7 +422,21 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { gql.AddFieldConfig(field_name, &graphql.Field{ Type: gql_type, Resolve: func(p graphql.ResolveParams) (interface{}, error) { - return nil, fmt.Errorf("NOT_IMPLEMENTED: TODO") + node, ok := p.Source.(NodeResult) + if ok == false { + return nil, fmt.Errorf("Can't resolve Node field on non-Node %s", reflect.TypeOf(p.Source)) + } + + node_info, mapped := ctx.Nodes[node.NodeType] + if mapped == false { + return nil, fmt.Errorf("Can't resolve unknown NodeType %s", node.NodeType) + } + + if gql_resolve != nil { + return gql_resolve(node.Data[node_info.Fields[field_name]][field_name]) + } else { + return node.Data[node_info.Fields[field_name]][field_name], nil + } }, }) } @@ -394,6 +446,28 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { return nil } +func (ctx *Context) GQLInterfaces(known_type NodeType, extensions []ExtType) graphql.InterfacesThunk { + return func() []*graphql.Interface { + interfaces := []*graphql.Interface{} + for node_type, node_info := range(ctx.Nodes) { + if node_type != known_type { + has_ext := true + for _, ext := range(node_info.Extensions) { + if slices.Contains(extensions, ext) == false { + has_ext = false + break + } + } + if has_ext == false { + continue + } + } + interfaces = append(interfaces, node_info.Interface) + } + return interfaces + } +} + func RegisterObject[T any](ctx *Context) error { reflect_type := reflect.TypeFor[T]() serialized_type := SerializedTypeFor[T]() @@ -462,6 +536,7 @@ func RegisterObject[T any](ctx *Context) error { Reflect: reflect_type, Fields: field_infos, Type: gql, + Resolve: nil, } ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type] @@ -490,7 +565,7 @@ func unstringify[T any, E interface { *T; encoding.TextUnmarshaler }](value inte return nil } - var tmp E + var tmp E = new(T) err := tmp.UnmarshalText([]byte(str)) if err != nil { return nil @@ -606,6 +681,7 @@ func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, f Serialized: serialized_type, Reflect: reflect_type, Type: gql, + Resolve: nil, } ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type] @@ -698,6 +774,21 @@ func (ctx *Context) Send(node *Node, messages []SendMsg) error { return nil } +func (ctx *Context)GQLResolve(t reflect.Type, node_type string) (func(interface{})(interface{}, error), error) { + info, mapped := ctx.TypeTypes[t] + if mapped { + return info.Resolve, nil + } else { + switch t.Kind() { + //case reflect.Array: + //case reflect.Slice: + case reflect.Pointer: + return ctx.GQLResolve(t.Elem(), node_type) + } + } + return nil, fmt.Errorf("Cannot get resolver for %s", t) +} + // Create a new Context with the base library content added func NewContext(db * badger.DB, log Logger) (*Context, error) { ctx := &Context{ @@ -842,13 +933,29 @@ func NewContext(db * badger.DB, log Logger) (*Context, error) { Name: "Query", Fields: graphql.Fields{ "Self": &graphql.Field{ - Type: ctx.NodeTypes["Base"].Type, + Type: ctx.NodeTypes["Lockable"].Interface, Resolve: func(p graphql.ResolveParams) (interface{}, error) { - // TODO: Send read request and get response instead of hard-coding - return NodeResult{ - NodeID: RandID(), - NodeType: NodeTypeFor([]ExtType{}), - }, nil + ctx, err := PrepResolve(p) + if err != nil { + return nil, err + } + return ResolveNode(ctx.Server.ID, p) + }, + }, + "Node": &graphql.Field{ + Type: ctx.NodeTypes["Base"].Interface, + Args: graphql.FieldConfigArgument{ + "id": &graphql.ArgumentConfig{ + Type: ctx.TypeTypes[reflect.TypeFor[NodeID]()].Type, + }, + }, + Resolve: func(p graphql.ResolveParams) (interface{}, error) { + id, err := ExtractParam[NodeID](p, "id") + if err != nil { + return nil, err + } + + return ResolveNode(id, p) }, }, }, diff --git a/db.go b/db.go index 09c21b9..ae59c21 100644 --- a/db.go +++ b/db.go @@ -2,6 +2,7 @@ package graphvent import ( badger "github.com/dgraph-io/badger/v3" + "fmt" ) func WriteNodeInit(ctx *Context, node *Node) error { @@ -35,7 +36,7 @@ func WriteNodeChanges(ctx *Context, node *Node, changes map[ExtType]Changes) err } func LoadNode(ctx *Context, id NodeID) (*Node, error) { - err := ctx.DB.Update(func(tx *badger.Txn) error { + err := ctx.DB.View(func(tx *badger.Txn) error { return nil }) @@ -43,5 +44,5 @@ func LoadNode(ctx *Context, id NodeID) (*Node, error) { return nil, err } - return nil, nil + return nil, fmt.Errorf("NOT_IMPLEMENTED") } diff --git a/gql.go b/gql.go index 616338c..82f3f13 100644 --- a/gql.go +++ b/gql.go @@ -76,20 +76,6 @@ func ExtractList[K interface{}](p graphql.ResolveParams, name string) ([]K, erro return ret, nil } -func ExtractID(p graphql.ResolveParams, name string) (NodeID, error) { - id_str, err := ExtractParam[string](p, name) - if err != nil { - return ZeroID, err - } - - id, err := ParseID(id_str) - if err != nil { - return ZeroID, err - } - - return id, nil -} - func GraphiQLHandler() func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r * http.Request) { graphiql_string := fmt.Sprintf(` diff --git a/gql_node.go b/gql_node.go index 58a9045..0f4c3de 100644 --- a/gql_node.go +++ b/gql_node.go @@ -2,6 +2,7 @@ package graphvent import ( "reflect" "fmt" + "time" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" ) @@ -15,7 +16,7 @@ func ResolveNodeID(p graphql.ResolveParams) (interface{}, error) { return node.NodeID, nil } -func ResolveNodeTypeHash(p graphql.ResolveParams) (interface{}, error) { +func ResolveNodeType(p graphql.ResolveParams) (interface{}, error) { node, ok := p.Source.(NodeResult) if ok == false { return nil, fmt.Errorf("Can't get TypeHash from %+v", reflect.TypeOf(p.Source)) @@ -35,7 +36,6 @@ func GetFieldNames(ctx *Context, selection_set *ast.SelectionSet) []string { case *ast.Field: names = append(names, field.Name.Value) case *ast.InlineFragment: - names = append(names, GetFieldNames(ctx, field.SelectionSet)...) default: ctx.Log.Logf("gql", "Unknown selection type: %s", reflect.TypeOf(field)) } @@ -44,11 +44,109 @@ func GetFieldNames(ctx *Context, selection_set *ast.SelectionSet) []string { return names } -func GetResolveFields(ctx *Context, p graphql.ResolveParams) []string { +// Returns the fields that need to be resolved +func GetResolveFields(id NodeID, ctx *ResolveContext, p graphql.ResolveParams) (map[ExtType][]string, error) { + node_info, mapped := ctx.Context.NodeTypes[p.Info.ReturnType.Name()] + if mapped == false { + return nil, fmt.Errorf("No NodeType %s", p.Info.ReturnType.Name()) + } + + fields := map[ExtType][]string{} names := []string{} for _, field := range(p.Info.FieldASTs) { - names = append(names, GetFieldNames(ctx, field.SelectionSet)...) + names = append(names, GetFieldNames(ctx.Context, field.SelectionSet)...) } - return names + cache, node_cached := ctx.NodeCache[id] + for _, name := range(names) { + if name == "ID" || name == "Type" { + continue + } + + ext_type, field_mapped := node_info.Fields[name] + if field_mapped == false { + return nil, fmt.Errorf("NodeType %s does not have field %s", p.Info.ReturnType.Name(), name) + } + + ext_fields, exists := fields[ext_type] + if exists == false { + ext_fields = []string{} + } + + if node_cached { + ext_cache, ext_cached := cache.Data[ext_type] + if ext_cached { + _, field_cached := ext_cache[name] + if field_cached { + continue + } + } + } + + fields[ext_type] = append(ext_fields, name) + } + + return fields, nil +} + +func ResolveNode(id NodeID, p graphql.ResolveParams) (interface{}, error) { + ctx, err := PrepResolve(p) + if err != nil { + return nil, err + } + + fields, err := GetResolveFields(id, ctx, p) + if err != nil { + return nil, err + } + + ctx.Context.Log.Logf("gql", "Resolving fields %+v on node %s", fields, id) + + signal := NewReadSignal(fields) + response_chan := ctx.Ext.GetResponseChannel(signal.ID()) + // TODO: TIMEOUT DURATION + err = ctx.Context.Send(ctx.Server, []SendMsg{{ + Dest: id, + Signal: signal, + }}) + if err != nil { + ctx.Ext.FreeResponseChannel(signal.ID()) + return nil, err + } + + response, _, err := WaitForResponse(response_chan, 100*time.Millisecond, signal.ID()) + ctx.Ext.FreeResponseChannel(signal.ID()) + if err != nil { + return nil, err + } + + switch response := response.(type) { + case *ReadResultSignal: + cache, node_cached := ctx.NodeCache[id] + if node_cached == false { + cache = NodeResult{ + NodeID: id, + NodeType: response.NodeType, + Data: response.Extensions, + } + } else { + for ext_type, ext_data := range(response.Extensions) { + cached_ext, ext_cached := cache.Data[ext_type] + if ext_cached { + for field_name, field := range(ext_data) { + cache.Data[ext_type][field_name] = field + } + } else { + cache.Data[ext_type] = ext_data + } + + cache.Data[ext_type] = cached_ext + } + } + + ctx.NodeCache[id] = cache + return ctx.NodeCache[id], nil + default: + return nil, fmt.Errorf("Bad read response: %+v", response) + } }