Added GQL enum

master
noah metz 2024-03-17 14:25:34 -06:00
parent c0407b094c
commit 8f9a759b26
5 changed files with 172 additions and 101 deletions

@ -149,7 +149,6 @@ type Pair struct {
} }
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)
node_types := strings.SplitN(node_type, ":", 2) node_types := strings.SplitN(node_type, ":", 2)
if len(node_types) != 2 { if len(node_types) != 2 {
@ -173,7 +172,6 @@ func RegisterMap(ctx *Context, reflect_type reflect.Type, node_type string) erro
gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_") gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_")
gql_name = strings.ReplaceAll(gql_name, "[", "_") gql_name = strings.ReplaceAll(gql_name, "[", "_")
gql_name = strings.ReplaceAll(gql_name, "]", "_") gql_name = strings.ReplaceAll(gql_name, "]", "_")
ctx.Log.Logf("gql", "Registering %s with gql name %s", reflect_type, gql_name)
gql_pair := graphql.NewObject(graphql.ObjectConfig{ gql_pair := graphql.NewObject(graphql.ObjectConfig{
Name: gql_name, Name: gql_name,
@ -211,7 +209,6 @@ func RegisterMap(ctx *Context, reflect_type reflect.Type, node_type string) erro
}, },
}) })
ctx.Log.Logf("gql", "Registering new map with pair type %+v", gql_pair)
gql_map := graphql.NewList(gql_pair) gql_map := graphql.NewList(gql_pair)
serialized_type := SerializeType(reflect_type) serialized_type := SerializeType(reflect_type)
@ -248,14 +245,13 @@ func BuildSchema(ctx *Context, query, mutation *graphql.Object) (graphql.Schema,
ctx.Log.Logf("gql", "Building Schema") ctx.Log.Logf("gql", "Building Schema")
for _, info := range(ctx.TypeMap) { for _, info := range(ctx.TypeMap) {
ctx.Log.Logf("gql", "Adding type %+v", info.Type) if info.Type != nil {
types = append(types, info.Type) types = append(types, info.Type)
}
} }
for _, info := range(ctx.Nodes) { for _, info := range(ctx.Nodes) {
ctx.Log.Logf("gql", "Adding node type object %+v", info.Type)
types = append(types, info.Type) types = append(types, info.Type)
ctx.Log.Logf("gql", "Adding node type interface %+v", info.Interface)
types = append(types, info.Interface) types = append(types, info.Interface)
} }
@ -329,8 +325,6 @@ func RegisterExtension[E any, T interface { *E; Extension}](ctx *Context, data i
} }
} }
ctx.Log.Logf("serialize_types", "Registered ExtType: %+v - %+v", reflect_type, ext_type)
ctx.Extensions[ext_type] = &ExtensionInfo{ ctx.Extensions[ext_type] = &ExtensionInfo{
ExtType: ext_type, ExtType: ext_type,
Data: data, Data: data,
@ -342,7 +336,6 @@ func RegisterExtension[E any, T interface { *E; Extension}](ctx *Context, data i
} }
func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error { func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error {
ctx.Log.Logf("gql", "Registering NodeType %s with extensions %+v", name, extensions)
node_type := NodeTypeFor(extensions) node_type := NodeTypeFor(extensions)
_, exists := ctx.Nodes[node_type] _, exists := ctx.Nodes[node_type]
if exists == true { if exists == true {
@ -373,7 +366,7 @@ func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error {
fields[field_name] = extension fields[field_name] = extension
} }
} }
gql_interface := graphql.NewInterface(graphql.InterfaceConfig{ gql_interface := graphql.NewInterface(graphql.InterfaceConfig{
Name: name, Name: name,
Fields: graphql.Fields{ Fields: graphql.Fields{
@ -522,7 +515,6 @@ func RegisterObject[T any](ctx *Context) error {
} }
gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_") gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_")
ctx.Log.Logf("gql", "Registering %s with gql name %s", reflect_type, gql_name)
gql := graphql.NewObject(graphql.ObjectConfig{ gql := graphql.NewObject(graphql.ObjectConfig{
Name: gql_name, Name: gql_name,
IsTypeOf: func(p graphql.IsTypeOfParams) bool { IsTypeOf: func(p graphql.IsTypeOfParams) bool {
@ -549,14 +541,12 @@ func RegisterObject[T any](ctx *Context) error {
NodeTag: node_tag, NodeTag: node_tag,
Tag: gv_tag, Tag: gv_tag,
} }
gql_type, err := ctx.GQLType(field.Type, node_tag) gql_type, err := ctx.GQLType(field.Type, node_tag)
if err != nil { if err != nil {
return err return err
} }
gql_resolve := ctx.GQLResolve(field.Type, node_tag) gql_resolve := ctx.GQLResolve(field.Type, node_tag)
gql.AddFieldConfig(gv_tag, &graphql.Field{ gql.AddFieldConfig(gv_tag, &graphql.Field{
Type: gql_type, Type: gql_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { Resolve: func(p graphql.ResolveParams) (interface{}, error) {
@ -564,7 +554,7 @@ func RegisterObject[T any](ctx *Context) error {
if ok == false { if ok == false {
return nil, fmt.Errorf("%s is not %s", reflect.TypeOf(p.Source), reflect_type) return nil, fmt.Errorf("%s is not %s", reflect.TypeOf(p.Source), reflect_type)
} }
value, err := reflect.ValueOf(val).FieldByIndexErr(field.Index) value, err := reflect.ValueOf(val).FieldByIndexErr(field.Index)
if err != nil { if err != nil {
return nil, err return nil, err
@ -709,6 +699,40 @@ func astInt[T constraints.Integer](value ast.Value) interface{} {
} }
} }
func RegisterEnum[E comparable](ctx *Context, str_map map[E]string) error {
reflect_type := reflect.TypeFor[E]()
serialized_type := SerializedTypeFor[E]()
_, exists := ctx.TypeTypes[reflect_type]
if exists {
return fmt.Errorf("%+v already registered in TypeMap", reflect_type)
}
value_config := graphql.EnumValueConfigMap{}
for value, value_name := range(str_map) {
value_config[value_name] = &graphql.EnumValueConfig{
Value: value,
}
}
gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_")
gql := graphql.NewEnum(graphql.EnumConfig{
Name: gql_name,
Values: value_config,
})
ctx.TypeMap[serialized_type] = &TypeInfo{
Serialized: serialized_type,
Reflect: reflect_type,
Type: gql,
Resolve: nil,
}
ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type]
return nil
}
func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, from_json func(interface{})interface{}, from_ast func(ast.Value)interface{}, resolve func(interface{},graphql.ResolveParams)(interface{},error)) error { func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, from_json func(interface{})interface{}, from_ast func(ast.Value)interface{}, resolve func(interface{},graphql.ResolveParams)(interface{},error)) error {
reflect_type := reflect.TypeFor[S]() reflect_type := reflect.TypeFor[S]()
serialized_type := SerializedTypeFor[S]() serialized_type := SerializedTypeFor[S]()
@ -719,7 +743,6 @@ func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, f
} }
gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_") gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_")
ctx.Log.Logf("gql", "Registering %s with gql name %s", reflect_type, gql_name)
gql := graphql.NewScalar(graphql.ScalarConfig{ gql := graphql.NewScalar(graphql.ScalarConfig{
Name: gql_name, Name: gql_name,
Serialize: to_json, Serialize: to_json,
@ -966,7 +989,7 @@ func NewContext(db * badger.DB, log Logger) (*Context, error) {
return nil, err return nil, err
} }
err = RegisterScalar[ReqState](ctx, identity, coerce[ReqState], astInt[ReqState], nil) err = RegisterEnum[ReqState](ctx, ReqStateStrings)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1040,7 +1063,7 @@ 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["Lockable"].Interface, Type: ctx.NodeTypes["Base"].Interface,
Resolve: func(p graphql.ResolveParams) (interface{}, error) { Resolve: func(p graphql.ResolveParams) (interface{}, error) {
ctx, err := PrepResolve(p) ctx, err := PrepResolve(p)
if err != nil { if err != nil {

@ -544,7 +544,7 @@ type GQLExt struct {
State string `gv:"state"` State string `gv:"state"`
TLSKey []byte `gv:"tls_key"` TLSKey []byte `gv:"tls_key"`
TLSCert []byte `gv:"tls_cert"` TLSCert []byte `gv:"tls_cert"`
Listen string `gv:"listen"` Listen string `gv:"listen" gql:"GQLListen"`
} }
func (ext *GQLExt) Load(ctx *Context, node *Node) error { func (ext *GQLExt) Load(ctx *Context, node *Node) error {

@ -25,17 +25,36 @@ func ResolveNodeType(p graphql.ResolveParams) (interface{}, error) {
return uint64(node.NodeType), nil return uint64(node.NodeType), nil
} }
func GetFieldNames(ctx *Context, selection_set *ast.SelectionSet) []string { type FieldIndex struct {
names := []string{} Extension ExtType
Tag string
}
func GetFields(ctx *Context, node_type string, selection_set *ast.SelectionSet) []FieldIndex {
names := []FieldIndex{}
if selection_set == nil { if selection_set == nil {
return names return names
} }
node_info, mapped := ctx.NodeTypes[node_type]
if mapped == false {
return nil
}
for _, sel := range(selection_set.Selections) { for _, sel := range(selection_set.Selections) {
switch field := sel.(type) { switch field := sel.(type) {
case *ast.Field: case *ast.Field:
names = append(names, field.Name.Value) if field.Name.Value == "ID" || field.Name.Value == "Type" {
continue
}
extension, mapped := node_info.Fields[field.Name.Value]
if mapped == false {
continue
}
names = append(names, FieldIndex{extension, field.Name.Value})
case *ast.InlineFragment: case *ast.InlineFragment:
names = append(names, GetFields(ctx, field.TypeCondition.Name.Value, 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))
} }
@ -46,47 +65,33 @@ func GetFieldNames(ctx *Context, selection_set *ast.SelectionSet) []string {
// Returns the fields that need to be resolved // Returns the fields that need to be resolved
func GetResolveFields(id NodeID, ctx *ResolveContext, p graphql.ResolveParams) (map[ExtType][]string, error) { func GetResolveFields(id NodeID, ctx *ResolveContext, p graphql.ResolveParams) (map[ExtType][]string, error) {
node_info, mapped := ctx.Context.NodeTypes[p.Info.ReturnType.Name()] m := map[ExtType][]string{}
if mapped == false { fields := []FieldIndex{}
return nil, fmt.Errorf("No NodeType %s", p.Info.ReturnType.Name())
}
fields := map[ExtType][]string{}
names := []string{}
for _, field := range(p.Info.FieldASTs) { for _, field := range(p.Info.FieldASTs) {
names = append(names, GetFieldNames(ctx.Context, field.SelectionSet)...) fields = append(fields, GetFields(ctx.Context, p.Info.ReturnType.Name(), field.SelectionSet)...)
} }
cache, node_cached := ctx.NodeCache[id] cache, node_cached := ctx.NodeCache[id]
for _, name := range(names) { for _, field := range(fields) {
if name == "ID" || name == "Type" { ext_fields, exists := m[field.Extension]
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 { if exists == false {
ext_fields = []string{} ext_fields = []string{}
} }
if node_cached { if node_cached {
ext_cache, ext_cached := cache.Data[ext_type] ext_cache, ext_cached := cache.Data[field.Extension]
if ext_cached { if ext_cached {
_, field_cached := ext_cache[name] _, field_cached := ext_cache[field.Tag]
if field_cached { if field_cached {
continue continue
} }
} }
} }
fields[ext_type] = append(ext_fields, name) m[field.Extension] = append(ext_fields, field.Tag)
} }
return fields, nil return m, nil
} }
func ResolveNode(id NodeID, p graphql.ResolveParams) (NodeResult, error) { func ResolveNode(id NodeID, p graphql.ResolveParams) (NodeResult, error) {

@ -1,26 +1,31 @@
package graphvent package graphvent
import ( import (
"testing" "bytes"
"fmt" "crypto/tls"
"encoding/json" "encoding/json"
"io" "fmt"
"net/http" "io"
"net" "net"
"crypto/tls" "net/http"
"bytes" "reflect"
"golang.org/x/net/websocket" "testing"
"github.com/google/uuid" "time"
"github.com/google/uuid"
"golang.org/x/net/websocket"
) )
func TestGQLServer(t *testing.T) {
ctx := logTestContext(t, []string{"test", "gqlws", "gql", "gql_subscribe"})
gql_ext, err := NewGQLExt(ctx, ":0", nil, nil) func TestGQLSubscribe(t *testing.T) {
ctx := logTestContext(t, []string{"test"})
n1, err := NewNode(ctx, nil, "Lockable", 10, NewLockableExt(nil))
fatalErr(t, err) fatalErr(t, err)
listener_ext := NewListenerExt(10) listener_ext := NewListenerExt(10)
n1, err := NewNode(ctx, nil, "Lockable", 10, NewLockableExt(nil))
gql_ext, err := NewGQLExt(ctx, ":0", nil, nil)
fatalErr(t, err) fatalErr(t, err)
gql, err := NewNode(ctx, nil, "Lockable", 10, NewLockableExt([]NodeID{n1.ID}), gql_ext, listener_ext) gql, err := NewNode(ctx, nil, "Lockable", 10, NewLockableExt([]NodeID{n1.ID}), gql_ext, listener_ext)
@ -29,52 +34,14 @@ func TestGQLServer(t *testing.T) {
ctx.Log.Logf("test", "GQL: %s", gql.ID) ctx.Log.Logf("test", "GQL: %s", gql.ID)
ctx.Log.Logf("test", "NODE: %s", n1.ID) ctx.Log.Logf("test", "NODE: %s", n1.ID)
skipVerifyTransport := &http.Transport{ sub_1 := GQLPayload{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, Query: "subscription Self { Self { ID, Type } }",
} }
client := &http.Client{Transport: skipVerifyTransport}
port := gql_ext.tcp_listener.Addr().(*net.TCPAddr).Port port := gql_ext.tcp_listener.Addr().(*net.TCPAddr).Port
url := fmt.Sprintf("http://localhost:%d/gql", port) url := fmt.Sprintf("http://localhost:%d/gql", port)
ws_url := fmt.Sprintf("ws://127.0.0.1:%d/gqlws", port) ws_url := fmt.Sprintf("ws://127.0.0.1:%d/gqlws", port)
req_1 := GQLPayload{
Query: "query Node($id:graphvent_NodeID) { Node(id:$id) { ID, Type } }",
Variables: map[string]interface{}{
"id": n1.ID.String(),
},
}
req_2 := GQLPayload{
Query: "query Self { Self { ID, Type } }",
}
SendGQL := func(payload GQLPayload) []byte {
ser, err := json.MarshalIndent(&payload, "", " ")
fatalErr(t, err)
req_data := bytes.NewBuffer(ser)
req, err := http.NewRequest("GET", url, req_data)
fatalErr(t, err)
resp, err := client.Do(req)
fatalErr(t, err)
body, err := io.ReadAll(resp.Body)
fatalErr(t, err)
resp.Body.Close()
return body
}
resp_1 := SendGQL(req_1)
ctx.Log.Logf("test", "RESP_1: %s", resp_1)
resp_2 := SendGQL(req_2)
ctx.Log.Logf("test", "RESP_2: %s", resp_2)
sub_1 := GQLPayload{
Query: "subscription Self { Self { ID, Type } }",
}
SubGQL := func(payload GQLPayload) { SubGQL := func(payload GQLPayload) {
config, err := websocket.NewConfig(ws_url, url) config, err := websocket.NewConfig(ws_url, url)
fatalErr(t, err) fatalErr(t, err)
@ -147,6 +114,82 @@ func TestGQLServer(t *testing.T) {
SubGQL(sub_1) SubGQL(sub_1)
} }
func TestGQLQuery(t *testing.T) {
ctx := logTestContext(t, []string{"test", "lockable"})
n1_listener := NewListenerExt(10)
n1, err := NewNode(ctx, nil, "Lockable", 10, NewLockableExt(nil), n1_listener)
fatalErr(t, err)
gql_listener := NewListenerExt(10)
gql_ext, err := NewGQLExt(ctx, ":0", nil, nil)
fatalErr(t, err)
gql, err := NewNode(ctx, nil, "Lockable", 10, NewLockableExt([]NodeID{n1.ID}), gql_ext, gql_listener)
fatalErr(t, err)
ctx.Log.Logf("test", "GQL: %s", gql.ID)
ctx.Log.Logf("test", "NODE: %s", n1.ID)
skipVerifyTransport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: skipVerifyTransport}
port := gql_ext.tcp_listener.Addr().(*net.TCPAddr).Port
url := fmt.Sprintf("http://localhost:%d/gql", port)
req_1 := GQLPayload{
Query: "query Node($id:graphvent_NodeID) { Node(id:$id) { ID, Type, ... on Lockable { lockable_state } } }",
Variables: map[string]interface{}{
"id": n1.ID.String(),
},
}
req_2 := GQLPayload{
Query: "query Self { Self { ID, Type, ... on Lockable { lockable_state, requirements { Key { ID ... on Lockable { lockable_state } } } } } }",
}
SendGQL := func(payload GQLPayload) []byte {
ser, err := json.MarshalIndent(&payload, "", " ")
fatalErr(t, err)
req_data := bytes.NewBuffer(ser)
req, err := http.NewRequest("GET", url, req_data)
fatalErr(t, err)
resp, err := client.Do(req)
fatalErr(t, err)
body, err := io.ReadAll(resp.Body)
fatalErr(t, err)
resp.Body.Close()
return body
}
resp_1 := SendGQL(req_1)
ctx.Log.Logf("test", "RESP_1: %s", resp_1)
resp_2 := SendGQL(req_2)
ctx.Log.Logf("test", "RESP_2: %s", resp_2)
lock_id, err := LockLockable(ctx, n1)
fatalErr(t, err)
response, _, err := WaitForResponse(n1_listener.Chan, 100*time.Millisecond, lock_id)
fatalErr(t, err)
switch response := response.(type) {
case *SuccessSignal:
default:
t.Fatalf("Wrong response: %s", reflect.TypeOf(response))
}
resp_3 := SendGQL(req_1)
ctx.Log.Logf("test", "RESP_3: %s", resp_3)
resp_4 := SendGQL(req_2)
ctx.Log.Logf("test", "RESP_4: %s", resp_4)
}
func TestGQLDB(t *testing.T) { func TestGQLDB(t *testing.T) {
ctx := logTestContext(t, []string{"test", "db", "node"}) ctx := logTestContext(t, []string{"test", "db", "node"})

@ -23,7 +23,7 @@ var ReqStateStrings = map[ReqState]string {
} }
type LockableExt struct{ type LockableExt struct{
State ReqState `gv:"state"` State ReqState `gv:"lockable_state"`
ReqID *uuid.UUID `gv:"req_id"` ReqID *uuid.UUID `gv:"req_id"`
Owner *NodeID `gv:"owner" node:"Base"` Owner *NodeID `gv:"owner" node:"Base"`
PendingOwner *NodeID `gv:"pending_owner" node:"Base"` PendingOwner *NodeID `gv:"pending_owner" node:"Base"`