diff --git a/gql.go b/gql.go index eb3858e..30dee29 100644 --- a/gql.go +++ b/gql.go @@ -645,61 +645,150 @@ func GQLWSHandler(ctx * Context, server *Node, gql_ext *GQLExt) func(http.Respon } } -// Map of go types to graphql types -type ObjTypeMap map[reflect.Type]*graphql.Object +type GQLInterface struct { + Interface *graphql.Interface + Default *graphql.Object + List *graphql.List + Extensions []ExtType +} + +func NewGQLInterface(if_name string, default_name string, interfaces []*graphql.Interface, extensions []ExtType, init_1 func(*GQLInterface), init_2 func(*GQLInterface)) *GQLInterface { + var gql GQLInterface + gql.Extensions = extensions + gql.Interface = graphql.NewInterface(graphql.InterfaceConfig{ + Name: if_name, + ResolveType: NodeResolver([]ExtType{}, &gql.Default), + Fields: graphql.Fields{}, + }) + gql.List = graphql.NewList(gql.Interface) + + init_1(&gql) + + gql.Default = graphql.NewObject(graphql.ObjectConfig{ + Name: default_name, + Interfaces: append(interfaces, gql.Interface), + IsTypeOf: GQLNodeHasExtensions([]ExtType{}), + Fields: graphql.Fields{}, + }) + + init_2(&gql) + + return &gql +} // GQL Specific Context information type GQLExtContext struct { // Generated GQL schema Schema graphql.Schema - // List of GQL types - TypeList []graphql.Type - - // Interface type maps to map go types of specific interfaces to gql types - ValidNodes ObjTypeMap - ValidLockables ObjTypeMap - ValidThreads ObjTypeMap - - BaseNodeType *graphql.Object - BaseLockableType *graphql.Object - BaseThreadType *graphql.Object + // Custom graphql types, mapped to NodeTypes + NodeTypes map[NodeType]*graphql.Object + Interfaces []*GQLInterface + // Schema parameters + Types []graphql.Type Query *graphql.Object Mutation *graphql.Object Subscription *graphql.Object } +func BuildSchema(ctx *GQLExtContext) (graphql.Schema, error) { + schemaConfig := graphql.SchemaConfig{ + Types: ctx.Types, + Query: ctx.Query, + Mutation: ctx.Mutation, + Subscription: ctx.Subscription, + } + + return graphql.NewSchema(schemaConfig) +} + +func (ctx *GQLExtContext) AddInterface(i *GQLInterface) error { + if i == nil { + return fmt.Errorf("interface is nil") + } + + if i.Interface == nil || i.Extensions == nil || i.Default == nil || i.List == nil { + return fmt.Errorf("invalid interface, contains nil") + } + + ctx.Interfaces = append(ctx.Interfaces, i) + ctx.Types = append(ctx.Types, i.Default) + + return nil +} + +func (ctx *GQLExtContext) RegisterNodeType(node_type NodeType, gql_type *graphql.Object) error { + if gql_type == nil { + return fmt.Errorf("gql_type is nil") + } + _, exists := ctx.NodeTypes[node_type] + if exists == true { + return fmt.Errorf("%s already in GQLExtContext.NodeTypes", node_type) + } + + ctx.NodeTypes[node_type] = gql_type + ctx.Types = append(ctx.Types, gql_type) + + return nil +} + func NewGQLExtContext() *GQLExtContext { query := graphql.NewObject(graphql.ObjectConfig{ Name: "Query", Fields: graphql.Fields{}, }) + query.AddFieldConfig("Self", GQLQuerySelf) + query.AddFieldConfig("User", GQLQueryUser) + mutation := graphql.NewObject(graphql.ObjectConfig{ Name: "Mutation", Fields: graphql.Fields{}, }) + mutation.AddFieldConfig("abort", GQLMutationAbort) + mutation.AddFieldConfig("startChild", GQLMutationStartChild) + subscription := graphql.NewObject(graphql.ObjectConfig{ Name: "Subscription", Fields: graphql.Fields{}, }) + subscription.AddFieldConfig("Self", GQLSubscriptionSelf) + subscription.AddFieldConfig("Update", GQLSubscriptionUpdate) + context := GQLExtContext{ Schema: graphql.Schema{}, - TypeList: []graphql.Type{}, - ValidNodes: ObjTypeMap{}, - ValidThreads: ObjTypeMap{}, - ValidLockables: ObjTypeMap{}, + Types: []graphql.Type{}, Query: query, Mutation: mutation, Subscription: subscription, - BaseNodeType: GQLTypeBaseNode.Type, - BaseLockableType: GQLTypeBaseLockable.Type, - BaseThreadType: GQLTypeBaseThread.Type, + NodeTypes: map[NodeType]*graphql.Object{}, + Interfaces: []*GQLInterface{}, } + var err error + err = context.AddInterface(GQLInterfaceNode) + if err != nil { + panic(err) + } + err = context.AddInterface(GQLInterfaceLockable) + if err != nil { + panic(err) + } + err = context.AddInterface(GQLInterfaceThread) + if err != nil { + panic(err) + } + + schema, err := BuildSchema(&context) + if err != nil { + panic(err) + } + + context.Schema = schema + return &context } diff --git a/gql_interfaces.go b/gql_interfaces.go index 473df7c..54d28c3 100644 --- a/gql_interfaces.go +++ b/gql_interfaces.go @@ -2,8 +2,6 @@ package graphvent import ( "github.com/graphql-go/graphql" - "reflect" - "fmt" ) func NewField(init func()*graphql.Field) *graphql.Field { @@ -27,146 +25,165 @@ func NewSingleton[K graphql.Type](init func() K, post_init func(K, *graphql.List } } -func AddNodeInterfaceFields(i *graphql.Interface) { - i.AddFieldConfig("ID", &graphql.Field{ +func AddNodeInterfaceFields(gql *GQLInterface) { + gql.Interface.AddFieldConfig("ID", &graphql.Field{ Type: graphql.String, }) - i.AddFieldConfig("TypeHash", &graphql.Field{ + gql.Interface.AddFieldConfig("TypeHash", &graphql.Field{ Type: graphql.String, }) } -func PrepTypeResolve(p graphql.ResolveTypeParams) (*ResolveContext, error) { - resolve_context, ok := p.Context.Value("resolve").(*ResolveContext) - if ok == false { - return nil, fmt.Errorf("Bad resolve in params context") - } - return resolve_context, nil +func AddNodeFields(gql *GQLInterface) { + gql.Default.AddFieldConfig("ID", &graphql.Field{ + Type: graphql.String, + Resolve: GQLNodeID, + }) + + gql.Default.AddFieldConfig("TypeHash", &graphql.Field{ + Type: graphql.String, + Resolve: GQLNodeTypeHash, + }) } -var GQLInterfaceNode = NewSingleton(func() *graphql.Interface { - i := graphql.NewInterface(graphql.InterfaceConfig{ - Name: "Node", - ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { - ctx, err := PrepTypeResolve(p) - if err != nil { - return nil - } +func LockableInterfaceFields(gql *GQLInterface) { + AddLockableInterfaceFields(gql, gql) +} - valid_nodes := ctx.GQLContext.ValidNodes - p_type := reflect.TypeOf(p.Value) +func AddLockableInterfaceFields(gql *GQLInterface, gql_lockable *GQLInterface) { + AddNodeInterfaceFields(gql) - for key, value := range(valid_nodes) { - if p_type == key { - return value - } - } + gql.Interface.AddFieldConfig("Requirements", &graphql.Field{ + Type: gql_lockable.List, + }) - _, ok := p.Value.(Node) - if ok == true { - return ctx.GQLContext.BaseNodeType - } + gql.Interface.AddFieldConfig("Dependencies", &graphql.Field{ + Type: gql_lockable.List, + }) - return nil - }, - Fields: graphql.Fields{}, + gql.Interface.AddFieldConfig("Owner", &graphql.Field{ + Type: gql_lockable.Interface, }) +} - AddNodeInterfaceFields(i) +func LockableFields(gql *GQLInterface) { + AddLockableFields(gql, gql) +} - return i -}, nil) +func AddLockableFields(gql *GQLInterface, gql_lockable *GQLInterface) { + AddNodeFields(gql) -var GQLInterfaceLockable = NewSingleton(func() *graphql.Interface { - gql_interface_lockable := graphql.NewInterface(graphql.InterfaceConfig{ - Name: "Lockable", - ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { - ctx, err := PrepTypeResolve(p) - if err != nil { - return nil - } + gql.Default.AddFieldConfig("Requirements", &graphql.Field{ + Type: gql_lockable.List, + Resolve: GQLLockableRequirements, + }) - valid_lockables := ctx.GQLContext.ValidLockables - p_type := reflect.TypeOf(p.Value) + gql.Default.AddFieldConfig("Owner", &graphql.Field{ + Type: gql_lockable.Interface, + Resolve: GQLLockableOwner, + }) - for key, value := range(valid_lockables) { - if p_type == key { - return value - } - } + gql.Default.AddFieldConfig("Dependencies", &graphql.Field{ + Type: gql_lockable.List, + Resolve: GQLLockableDependencies, + }) +} - _, ok := p.Value.(*Node) - if ok == false { - return ctx.GQLContext.BaseLockableType - } - return nil - }, - Fields: graphql.Fields{}, +func ThreadInterfaceFields(gql *GQLInterface) { + AddThreadInterfaceFields(gql, GQLInterfaceLockable, gql) +} + +func AddThreadInterfaceFields(gql *GQLInterface, gql_lockable *GQLInterface, gql_thread *GQLInterface) { + AddLockableInterfaceFields(gql, gql_lockable) + + gql.Interface.AddFieldConfig("Children", &graphql.Field{ + Type: gql_thread.List, + }) + + gql.Interface.AddFieldConfig("Parent", &graphql.Field{ + Type: gql_thread.Interface, }) +} + +func ThreadFields(gql *GQLInterface) { + AddThreadFields(gql, GQLInterfaceLockable, gql) +} - return gql_interface_lockable -}, func(lockable *graphql.Interface, lockable_list *graphql.List) { - lockable.AddFieldConfig("Requirements", &graphql.Field{ - Type: lockable_list, +func AddThreadFields(gql *GQLInterface, gql_lockable *GQLInterface, gql_thread *GQLInterface) { + AddLockableFields(gql, gql_lockable) + + gql.Default.AddFieldConfig("State", &graphql.Field{ + Type: graphql.String, + Resolve: GQLThreadState, }) - lockable.AddFieldConfig("Dependencies", &graphql.Field{ - Type: lockable_list, + gql.Default.AddFieldConfig("Children", &graphql.Field{ + Type: gql_thread.List, + Resolve: GQLThreadChildren, }) - lockable.AddFieldConfig("Owner", &graphql.Field{ - Type: lockable, + gql.Default.AddFieldConfig("Parent", &graphql.Field{ + Type: gql_thread.Interface, + Resolve: GQLThreadParent, }) - AddNodeInterfaceFields(lockable) -}) - -var GQLInterfaceThread = NewSingleton(func() *graphql.Interface { - gql_interface_thread := graphql.NewInterface(graphql.InterfaceConfig{ - Name: "Thread", - ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object { - ctx, err := PrepTypeResolve(p) - if err != nil { - return nil - } +} - valid_threads := ctx.GQLContext.ValidThreads - p_type := reflect.TypeOf(p.Value) +func NodeHasExtensions(node *Node, extensions []ExtType) bool { + if node == nil { + return false + } - for key, value := range(valid_threads) { - if p_type == key { - return value - } - } + for _, ext := range(extensions) { + _, has := node.Extensions[ext] + if has == false { + return false + } + } - node, ok := p.Value.(*Node) - if ok == false { - return nil - } + return true +} - _, err = GetExt[*ThreadExt](node) - if err == nil { - return ctx.GQLContext.BaseThreadType - } +func GQLNodeHasExtensions(extensions []ExtType) func(graphql.IsTypeOfParams) bool { + return func(p graphql.IsTypeOfParams) bool { + node, ok := p.Value.(*Node) + if ok == false { + return false + } + return NodeHasExtensions(node, extensions) + } +} + +func NodeResolver(required_extensions []ExtType, default_type **graphql.Object)func(graphql.ResolveTypeParams) *graphql.Object { + return func(p graphql.ResolveTypeParams) *graphql.Object { + ctx, ok := p.Context.Value("resolve").(*ResolveContext) + if ok == false { return nil - }, - Fields: graphql.Fields{}, - }) + } - return gql_interface_thread -}, func(thread *graphql.Interface, thread_list *graphql.List) { - thread.AddFieldConfig("Children", &graphql.Field{ - Type: thread_list, - }) + node, ok := p.Value.(*Node) + if ok == false { + return nil + } + + gql_type, exists := ctx.GQLContext.NodeTypes[node.Type] + if exists == false { + for _, ext := range(required_extensions) { + _, exists := node.Extensions[ext] + if exists == false { + return nil + } + } + return *default_type + } - thread.AddFieldConfig("Parent", &graphql.Field{ - Type: thread, - }) + return gql_type + } +} - thread.AddFieldConfig("State", &graphql.Field{ - Type: graphql.String, - }) +var GQLInterfaceNode = NewGQLInterface("Node", "DefaultNode", []*graphql.Interface{}, []ExtType{}, AddNodeInterfaceFields, AddNodeFields) + +var GQLInterfaceLockable = NewGQLInterface("Lockable", "DefaultLockable", []*graphql.Interface{GQLInterfaceNode.Interface}, []ExtType{LockableExtType}, LockableInterfaceFields, LockableFields) - AddNodeInterfaceFields(thread) -}) +var GQLInterfaceThread = NewGQLInterface("Thread", "DefaultThread", []*graphql.Interface{GQLInterfaceNode.Interface, }, []ExtType{ThreadExtType, LockableExtType}, ThreadInterfaceFields, ThreadFields) diff --git a/gql_query.go b/gql_query.go index 0ef86e5..0531e6b 100644 --- a/gql_query.go +++ b/gql_query.go @@ -4,7 +4,7 @@ import ( ) var GQLQuerySelf = &graphql.Field{ - Type: GQLTypeBaseThread.Type, + Type: GQLInterfaceThread.Default, Resolve: func(p graphql.ResolveParams) (interface{}, error) { _, ctx, err := PrepResolve(p) if err != nil { @@ -16,7 +16,7 @@ var GQLQuerySelf = &graphql.Field{ } var GQLQueryUser = &graphql.Field{ - Type: GQLTypeBaseNode.Type, + Type: GQLInterfaceNode.Default, Resolve: func(p graphql.ResolveParams) (interface{}, error) { _, ctx, err := PrepResolve(p) if err != nil { diff --git a/gql_subscribe.go b/gql_subscribe.go index e332af4..a211df5 100644 --- a/gql_subscribe.go +++ b/gql_subscribe.go @@ -46,7 +46,7 @@ func GQLSubscribeFn(p graphql.ResolveParams, send_nil bool, fn func(*Context, *N var GQLSubscriptionSelf = NewField(func()*graphql.Field{ gql_subscription_self := &graphql.Field{ - Type: GQLTypeBaseThread.Type, + Type: GQLInterfaceThread.Default, Resolve: func(p graphql.ResolveParams) (interface{}, error) { return p.Source, nil }, diff --git a/gql_types.go b/gql_types.go index 579c54d..b1caeba 100644 --- a/gql_types.go +++ b/gql_types.go @@ -4,123 +4,6 @@ import ( "github.com/graphql-go/graphql" ) -func AddNodeFields(obj *graphql.Object) { - obj.AddFieldConfig("ID", &graphql.Field{ - Type: graphql.String, - Resolve: GQLNodeID, - }) - - obj.AddFieldConfig("TypeHash", &graphql.Field{ - Type: graphql.String, - Resolve: GQLNodeTypeHash, - }) -} - -func AddLockableFields(obj *graphql.Object) { - AddNodeFields(obj) - - obj.AddFieldConfig("Requirements", &graphql.Field{ - Type: GQLInterfaceLockable.List, - Resolve: GQLLockableRequirements, - }) - - obj.AddFieldConfig("Owner", &graphql.Field{ - Type: GQLInterfaceLockable.Type, - Resolve: GQLLockableOwner, - }) - - obj.AddFieldConfig("Dependencies", &graphql.Field{ - Type: GQLInterfaceLockable.List, - Resolve: GQLLockableDependencies, - }) -} - -func AddThreadFields(obj *graphql.Object) { - AddNodeFields(obj) - - obj.AddFieldConfig("State", &graphql.Field{ - Type: graphql.String, - Resolve: GQLThreadState, - }) - - obj.AddFieldConfig("Children", &graphql.Field{ - Type: GQLInterfaceThread.List, - Resolve: GQLThreadChildren, - }) - - obj.AddFieldConfig("Parent", &graphql.Field{ - Type: GQLInterfaceThread.Type, - Resolve: GQLThreadParent, - }) -} - -var GQLTypeBaseThread = NewSingleton(func() *graphql.Object { - gql_type_simple_thread := graphql.NewObject(graphql.ObjectConfig{ - Name: "SimpleThread", - Interfaces: []*graphql.Interface{ - GQLInterfaceNode.Type, - GQLInterfaceThread.Type, - GQLInterfaceLockable.Type, - }, - IsTypeOf: func(p graphql.IsTypeOfParams) bool { - node, ok := p.Value.(*Node) - if ok == false { - return false - } - - _, err := GetExt[*ThreadExt](node) - return err == nil - }, - Fields: graphql.Fields{}, - }) - - AddThreadFields(gql_type_simple_thread) - - return gql_type_simple_thread -}, nil) - -var GQLTypeBaseLockable = NewSingleton(func() *graphql.Object { - gql_type_simple_lockable := graphql.NewObject(graphql.ObjectConfig{ - Name: "SimpleLockable", - Interfaces: []*graphql.Interface{ - GQLInterfaceNode.Type, - GQLInterfaceLockable.Type, - }, - IsTypeOf: func(p graphql.IsTypeOfParams) bool { - node, ok := p.Value.(*Node) - if ok == false { - return false - } - - _, err := GetExt[*LockableExt](node) - return err == nil - }, - Fields: graphql.Fields{}, - }) - - AddLockableFields(gql_type_simple_lockable) - - return gql_type_simple_lockable -}, nil) - -var GQLTypeBaseNode = NewSingleton(func() *graphql.Object { - object := graphql.NewObject(graphql.ObjectConfig{ - Name: "SimpleNode", - Interfaces: []*graphql.Interface{ - GQLInterfaceNode.Type, - }, - IsTypeOf: func(p graphql.IsTypeOfParams) bool { - _, ok := p.Value.(*Node) - return ok - }, - Fields: graphql.Fields{}, - }) - - AddNodeFields(object) - - return object -}, nil) - var GQLTypeSignal = NewSingleton(func() *graphql.Object { gql_type_signal := graphql.NewObject(graphql.ObjectConfig{ Name: "Signal",