Got some basic node resolving working

gql_cataclysm
noah metz 2024-03-09 22:09:40 -07:00
parent eef8451566
commit f8dad12fdb
5 changed files with 250 additions and 56 deletions

@ -25,11 +25,13 @@ func main() {
listener_ext := gv.NewListenerExt(1000) 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) check(err)
select { for true {
case message := <- listener_ext.Chan: select {
fmt.Printf("Listener Message: %+v\n", message) case message := <- listener_ext.Chan:
fmt.Printf("Listener Message: %+v\n", message)
}
} }
} }

@ -47,6 +47,8 @@ type TypeInfo struct {
Serialize SerializeFn Serialize SerializeFn
Deserialize DeserializeFn Deserialize DeserializeFn
Resolve func(interface{})(interface{}, error)
} }
type ExtensionInfo struct { type ExtensionInfo struct {
@ -55,16 +57,12 @@ type ExtensionInfo struct {
Data interface{} Data interface{}
} }
type FieldIndex struct {
FieldTag FieldTag
Extension ExtType
}
type NodeInfo struct { type NodeInfo struct {
NodeType NodeType
Type *graphql.Object Type *graphql.Object
Interface *graphql.Interface Interface *graphql.Interface
Extensions []ExtType Extensions []ExtType
Fields map[string]ExtType
} }
// A Context stores all the data to run a graphvent process // 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 { 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) ctx.Log.Logf("gql", "Registering map %s with node_type %s", reflect_type, node_type)
node_types := strings.Split(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{ "Key": &graphql.Field{
Type: key_type, Type: key_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { 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{ "Value": &graphql.Field{
Type: val_type, Type: val_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { 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, Serialized: serialized_type,
Reflect: reflect_type, Reflect: reflect_type,
Type: gql_map, 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] 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) return fmt.Errorf("Cannot register node type %+v, type already exists in context", node_type)
} }
fields := map[string]ExtType{}
ext_found := map[ExtType]bool{} ext_found := map[ExtType]bool{}
for _, extension := range(extensions) { for _, extension := range(extensions) {
_, in_ctx := ctx.Extensions[extension] ext_info, in_ctx := ctx.Extensions[extension]
if in_ctx == false { if in_ctx == false {
return fmt.Errorf("Cannot register node type %+v, required extension %+v not in context", name, extension) 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 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{ gql_interface := graphql.NewInterface(graphql.InterfaceConfig{
@ -304,17 +345,19 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error {
val, ok := p.Value.(NodeResult) val, ok := p.Value.(NodeResult)
if ok == false { if ok == false {
ctx.Context.Log.Logf("gql", "Interface ResolveType got bad Value %+v", p.Value)
return nil return nil
} }
node_info, exists := ctx.Context.Nodes[val.NodeType] node_info, exists := ctx.Context.Nodes[val.NodeType]
if exists == false { if exists == false {
ctx.Context.Log.Logf("gql", "Interface ResolveType got bad NodeType", val.NodeType)
return nil return nil
} }
for _, ext_type := range(extensions) { for _, ext_type := range(extensions) {
if slices.Contains(node_info.Extensions, ext_type) == false { 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 return nil
} }
} }
@ -325,26 +368,15 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error {
gql := graphql.NewObject(graphql.ObjectConfig{ gql := graphql.NewObject(graphql.ObjectConfig{
Name: name + "Node", Name: name + "Node",
Interfaces: ctx.GQLInterfaces(node_type, extensions),
Fields: graphql.Fields{ Fields: graphql.Fields{
"ID": &graphql.Field{ "ID": &graphql.Field{
Type: ctx.TypeTypes[reflect.TypeFor[NodeID]()].Type, Type: ctx.TypeTypes[reflect.TypeFor[NodeID]()].Type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { Resolve: ResolveNodeID,
source, ok := p.Source.(NodeResult)
if ok == false {
return nil, fmt.Errorf("GQL Node value is not NodeResult")
}
return source.NodeID, nil
},
}, },
"Type": &graphql.Field{ "Type": &graphql.Field{
Type: ctx.TypeTypes[reflect.TypeFor[NodeType]()].Type, Type: ctx.TypeTypes[reflect.TypeFor[NodeType]()].Type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { Resolve: ResolveNodeType,
source, ok := p.Source.(NodeResult)
if ok == false {
return nil, fmt.Errorf("GQL Node value is not NodeResult")
}
return source.NodeType, nil
},
}, },
}, },
IsTypeOf: func(p graphql.IsTypeOfParams) bool { IsTypeOf: func(p graphql.IsTypeOfParams) bool {
@ -361,6 +393,7 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error {
Interface: gql_interface, Interface: gql_interface,
Type: gql, Type: gql,
Extensions: extensions, Extensions: extensions,
Fields: fields,
} }
ctx.NodeTypes[name] = ctx.Nodes[node_type] ctx.NodeTypes[name] = ctx.Nodes[node_type]
@ -375,6 +408,11 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error {
if err != nil { if err != nil {
return err 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) 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{ 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{ gql.AddFieldConfig(field_name, &graphql.Field{
Type: gql_type, Type: gql_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { 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 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 { func RegisterObject[T any](ctx *Context) error {
reflect_type := reflect.TypeFor[T]() reflect_type := reflect.TypeFor[T]()
serialized_type := SerializedTypeFor[T]() serialized_type := SerializedTypeFor[T]()
@ -462,6 +536,7 @@ func RegisterObject[T any](ctx *Context) error {
Reflect: reflect_type, Reflect: reflect_type,
Fields: field_infos, Fields: field_infos,
Type: gql, Type: gql,
Resolve: nil,
} }
ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type] 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 return nil
} }
var tmp E var tmp E = new(T)
err := tmp.UnmarshalText([]byte(str)) err := tmp.UnmarshalText([]byte(str))
if err != nil { if err != nil {
return nil return nil
@ -606,6 +681,7 @@ func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, f
Serialized: serialized_type, Serialized: serialized_type,
Reflect: reflect_type, Reflect: reflect_type,
Type: gql, Type: gql,
Resolve: nil,
} }
ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type] ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type]
@ -698,6 +774,21 @@ func (ctx *Context) Send(node *Node, messages []SendMsg) error {
return nil 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 // Create a new Context with the base library content added
func NewContext(db * badger.DB, log Logger) (*Context, error) { func NewContext(db * badger.DB, log Logger) (*Context, error) {
ctx := &Context{ ctx := &Context{
@ -842,13 +933,29 @@ func NewContext(db * badger.DB, log Logger) (*Context, error) {
Name: "Query", Name: "Query",
Fields: graphql.Fields{ Fields: graphql.Fields{
"Self": &graphql.Field{ "Self": &graphql.Field{
Type: ctx.NodeTypes["Base"].Type, Type: ctx.NodeTypes["Lockable"].Interface,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// TODO: Send read request and get response instead of hard-coding ctx, err := PrepResolve(p)
return NodeResult{ if err != nil {
NodeID: RandID(), return nil, err
NodeType: NodeTypeFor([]ExtType{}), }
}, nil 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)
}, },
}, },
}, },

@ -2,6 +2,7 @@ package graphvent
import ( import (
badger "github.com/dgraph-io/badger/v3" badger "github.com/dgraph-io/badger/v3"
"fmt"
) )
func WriteNodeInit(ctx *Context, node *Node) error { 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) { 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 return nil
}) })
@ -43,5 +44,5 @@ func LoadNode(ctx *Context, id NodeID) (*Node, error) {
return nil, err return nil, err
} }
return nil, nil return nil, fmt.Errorf("NOT_IMPLEMENTED")
} }

@ -76,20 +76,6 @@ func ExtractList[K interface{}](p graphql.ResolveParams, name string) ([]K, erro
return ret, nil 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) { func GraphiQLHandler() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r * http.Request) { return func(w http.ResponseWriter, r * http.Request) {
graphiql_string := fmt.Sprintf(` graphiql_string := fmt.Sprintf(`

@ -2,6 +2,7 @@ package graphvent
import ( import (
"reflect" "reflect"
"fmt" "fmt"
"time"
"github.com/graphql-go/graphql" "github.com/graphql-go/graphql"
"github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/ast"
) )
@ -15,7 +16,7 @@ func ResolveNodeID(p graphql.ResolveParams) (interface{}, error) {
return node.NodeID, nil return node.NodeID, nil
} }
func ResolveNodeTypeHash(p graphql.ResolveParams) (interface{}, error) { func ResolveNodeType(p graphql.ResolveParams) (interface{}, error) {
node, ok := p.Source.(NodeResult) node, ok := p.Source.(NodeResult)
if ok == false { if ok == false {
return nil, fmt.Errorf("Can't get TypeHash from %+v", reflect.TypeOf(p.Source)) 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: case *ast.Field:
names = append(names, field.Name.Value) names = append(names, field.Name.Value)
case *ast.InlineFragment: case *ast.InlineFragment:
names = append(names, GetFieldNames(ctx, field.SelectionSet)...)
default: default:
ctx.Log.Logf("gql", "Unknown selection type: %s", reflect.TypeOf(field)) 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 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{} names := []string{}
for _, field := range(p.Info.FieldASTs) { 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)
}
} }