2023-07-09 14:30:30 -06:00
|
|
|
package graphvent
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/graphql-go/graphql"
|
|
|
|
badger "github.com/dgraph-io/badger/v3"
|
|
|
|
"reflect"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// NodeLoadFunc is the footprint of the function used to create a new node in memory from persisted bytes
|
2023-07-09 14:30:30 -06:00
|
|
|
type NodeLoadFunc func(*Context, NodeID, []byte, NodeMap)(Node, error)
|
2023-07-10 22:31:43 -06:00
|
|
|
|
|
|
|
// A NodeDef is a description of a node that can be added to a Context
|
2023-07-09 14:30:30 -06:00
|
|
|
type NodeDef struct {
|
|
|
|
Load NodeLoadFunc
|
|
|
|
Type NodeType
|
2023-07-10 21:15:01 -06:00
|
|
|
GQLType *graphql.Object
|
|
|
|
Reflect reflect.Type
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Create a new Node def, extracting the Type and Reflect from example
|
2023-07-10 21:24:47 -06:00
|
|
|
func NewNodeDef(example Node, load_func NodeLoadFunc, gql_type *graphql.Object) NodeDef {
|
2023-07-09 14:30:30 -06:00
|
|
|
return NodeDef{
|
2023-07-10 21:24:47 -06:00
|
|
|
Type: example.Type(),
|
2023-07-09 14:30:30 -06:00
|
|
|
Load: load_func,
|
2023-07-10 21:15:01 -06:00
|
|
|
GQLType: gql_type,
|
2023-07-10 21:24:47 -06:00
|
|
|
Reflect: reflect.TypeOf(example),
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// A Context is all the data needed to run a graphvent
|
2023-07-09 14:30:30 -06:00
|
|
|
type Context struct {
|
2023-07-10 22:31:43 -06:00
|
|
|
// DB is the database connection used to load and write nodes
|
2023-07-09 14:30:30 -06:00
|
|
|
DB * badger.DB
|
2023-07-10 22:31:43 -06:00
|
|
|
// Log is an interface used to record events happening
|
2023-07-09 14:30:30 -06:00
|
|
|
Log Logger
|
2023-07-10 22:31:43 -06:00
|
|
|
// A mapping between type hashes and their corresponding node definitions
|
2023-07-09 14:30:30 -06:00
|
|
|
Types map[uint64]NodeDef
|
2023-07-10 22:31:43 -06:00
|
|
|
// GQL substructure
|
2023-07-10 21:15:01 -06:00
|
|
|
GQL GQLContext
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Recreate the GQL schema after making changes
|
2023-07-10 21:15:01 -06:00
|
|
|
func (ctx * Context) RebuildSchema() error {
|
|
|
|
schemaConfig := graphql.SchemaConfig{
|
|
|
|
Types: ctx.GQL.TypeList,
|
|
|
|
Query: ctx.GQL.Query,
|
|
|
|
Mutation: ctx.GQL.Mutation,
|
|
|
|
Subscription: ctx.GQL.Subscription,
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
2023-07-10 21:15:01 -06:00
|
|
|
schema, err := graphql.NewSchema(schemaConfig)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.GQL.Schema = schema
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Add a non-node type to the gql context
|
2023-07-10 21:15:01 -06:00
|
|
|
func (ctx * Context) AddGQLType(gql_type graphql.Type) {
|
|
|
|
ctx.GQL.TypeList = append(ctx.GQL.TypeList, gql_type)
|
|
|
|
}
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Add a node to a context, returns an error if the def is invalid or already exists in the context
|
2023-07-10 21:15:01 -06:00
|
|
|
func (ctx * Context) RegisterNodeType(def NodeDef) error {
|
|
|
|
if def.Load == nil {
|
|
|
|
return fmt.Errorf("Cannot register a node without a load function: %s", def.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
if def.Reflect == nil {
|
|
|
|
return fmt.Errorf("Cannot register a node without a reflect type: %s", def.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
if def.GQLType == nil {
|
|
|
|
return fmt.Errorf("Cannot register a node without a gql type: %s", def.Type)
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type_hash := def.Type.Hash()
|
|
|
|
_, exists := ctx.Types[type_hash]
|
|
|
|
if exists == true {
|
2023-07-10 21:15:01 -06:00
|
|
|
return fmt.Errorf("Cannot register node of type %s, type already exists in context", def.Type)
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Types[type_hash] = def
|
2023-07-10 21:15:01 -06:00
|
|
|
|
|
|
|
if def.Reflect.Implements(ctx.GQL.NodeType) {
|
|
|
|
ctx.GQL.ValidNodes[def.Reflect] = def.GQLType
|
|
|
|
}
|
|
|
|
if def.Reflect.Implements(ctx.GQL.LockableType) {
|
|
|
|
ctx.GQL.ValidLockables[def.Reflect] = def.GQLType
|
|
|
|
}
|
|
|
|
if def.Reflect.Implements(ctx.GQL.ThreadType) {
|
|
|
|
ctx.GQL.ValidThreads[def.Reflect] = def.GQLType
|
|
|
|
}
|
|
|
|
ctx.GQL.TypeList = append(ctx.GQL.TypeList, def.GQLType)
|
|
|
|
|
2023-07-09 14:30:30 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Map of go types to graphql types
|
2023-07-09 14:30:30 -06:00
|
|
|
type ObjTypeMap map[reflect.Type]*graphql.Object
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// GQL Specific Context information
|
2023-07-09 14:30:30 -06:00
|
|
|
type GQLContext struct {
|
2023-07-10 22:31:43 -06:00
|
|
|
// Generated GQL schema
|
2023-07-09 14:30:30 -06:00
|
|
|
Schema graphql.Schema
|
2023-07-10 21:15:01 -06:00
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Interface types to compare against
|
2023-07-09 14:30:30 -06:00
|
|
|
NodeType reflect.Type
|
|
|
|
LockableType reflect.Type
|
|
|
|
ThreadType reflect.Type
|
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// List of GQL types
|
|
|
|
TypeList []graphql.Type
|
2023-07-09 14:30:30 -06:00
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Interface type maps to map go types of specific interfaces to gql types
|
2023-07-10 21:15:01 -06:00
|
|
|
ValidNodes ObjTypeMap
|
|
|
|
ValidLockables ObjTypeMap
|
|
|
|
ValidThreads ObjTypeMap
|
2023-07-09 14:30:30 -06:00
|
|
|
|
2023-07-10 21:15:01 -06:00
|
|
|
Query *graphql.Object
|
|
|
|
Mutation *graphql.Object
|
|
|
|
Subscription *graphql.Object
|
|
|
|
}
|
2023-07-09 14:30:30 -06:00
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Create a new GQL context without any content
|
2023-07-10 21:15:01 -06:00
|
|
|
func NewGQLContext() GQLContext {
|
|
|
|
query := graphql.NewObject(graphql.ObjectConfig{
|
|
|
|
Name: "Query",
|
|
|
|
Fields: graphql.Fields{},
|
|
|
|
})
|
2023-07-09 14:30:30 -06:00
|
|
|
|
2023-07-10 21:15:01 -06:00
|
|
|
mutation := graphql.NewObject(graphql.ObjectConfig{
|
|
|
|
Name: "Mutation",
|
|
|
|
Fields: graphql.Fields{},
|
|
|
|
})
|
2023-07-09 14:30:30 -06:00
|
|
|
|
2023-07-10 21:15:01 -06:00
|
|
|
subscription := graphql.NewObject(graphql.ObjectConfig{
|
|
|
|
Name: "Subscription",
|
|
|
|
Fields: graphql.Fields{},
|
|
|
|
})
|
2023-07-09 14:30:30 -06:00
|
|
|
|
|
|
|
ctx := GQLContext{
|
2023-07-10 21:15:01 -06:00
|
|
|
Schema: graphql.Schema{},
|
2023-07-10 22:31:43 -06:00
|
|
|
TypeList: []graphql.Type{},
|
2023-07-10 21:15:01 -06:00
|
|
|
ValidNodes: ObjTypeMap{},
|
|
|
|
NodeType: reflect.TypeOf((*Node)(nil)).Elem(),
|
|
|
|
ValidThreads: ObjTypeMap{},
|
|
|
|
ThreadType: reflect.TypeOf((*Thread)(nil)).Elem(),
|
|
|
|
ValidLockables: ObjTypeMap{},
|
|
|
|
LockableType: reflect.TypeOf((*Lockable)(nil)).Elem(),
|
|
|
|
Query: query,
|
|
|
|
Mutation: mutation,
|
|
|
|
Subscription: subscription,
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
2023-07-10 21:15:01 -06:00
|
|
|
return ctx
|
2023-07-10 01:07:56 -06:00
|
|
|
}
|
2023-07-09 14:30:30 -06:00
|
|
|
|
2023-07-10 22:31:43 -06:00
|
|
|
// Create a new Context with all the library content added
|
2023-07-10 21:15:01 -06:00
|
|
|
func NewContext(db * badger.DB, log Logger) * Context {
|
2023-07-09 14:30:30 -06:00
|
|
|
ctx := &Context{
|
2023-07-10 21:15:01 -06:00
|
|
|
GQL: NewGQLContext(),
|
2023-07-09 14:30:30 -06:00
|
|
|
DB: db,
|
|
|
|
Log: log,
|
|
|
|
Types: map[uint64]NodeDef{},
|
|
|
|
}
|
|
|
|
|
2023-07-10 21:24:47 -06:00
|
|
|
err := ctx.RegisterNodeType(NewNodeDef((*GraphNode)(nil), LoadGraphNode, GQLTypeGraphNode()))
|
2023-07-09 14:30:30 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-07-10 21:24:47 -06:00
|
|
|
err = ctx.RegisterNodeType(NewNodeDef((*SimpleLockable)(nil), LoadSimpleLockable, GQLTypeSimpleLockable()))
|
2023-07-09 14:30:30 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-07-10 21:24:47 -06:00
|
|
|
err = ctx.RegisterNodeType(NewNodeDef((*SimpleThread)(nil), LoadSimpleThread, GQLTypeSimpleThread()))
|
2023-07-09 14:30:30 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-07-10 21:24:47 -06:00
|
|
|
err = ctx.RegisterNodeType(NewNodeDef((*GQLThread)(nil), LoadGQLThread, GQLTypeGQLThread()))
|
2023-07-09 14:30:30 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2023-07-09 20:30:19 -06:00
|
|
|
}
|
2023-07-20 22:03:25 -06:00
|
|
|
err = ctx.RegisterNodeType(NewNodeDef((*User)(nil), LoadUser, GQLTypeUser()))
|
2023-07-20 00:24:22 -06:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-07-20 23:19:10 -06:00
|
|
|
err = ctx.RegisterNodeType(NewNodeDef((*PerNodePolicy)(nil), LoadPerNodePolicy, GQLTypeGraphNode()))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
err = ctx.RegisterNodeType(NewNodeDef((*AllNodePolicy)(nil), LoadAllNodePolicy, GQLTypeGraphNode()))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-07-20 00:24:22 -06:00
|
|
|
|
2023-07-10 21:15:01 -06:00
|
|
|
ctx.AddGQLType(GQLTypeSignal())
|
|
|
|
|
|
|
|
ctx.GQL.Query.AddFieldConfig("Self", GQLQuerySelf())
|
|
|
|
|
|
|
|
ctx.GQL.Subscription.AddFieldConfig("Update", GQLSubscriptionUpdate())
|
|
|
|
ctx.GQL.Subscription.AddFieldConfig("Self", GQLSubscriptionSelf())
|
|
|
|
|
|
|
|
ctx.GQL.Mutation.AddFieldConfig("SendUpdate", GQLMutationSendUpdate())
|
|
|
|
|
|
|
|
err = ctx.RebuildSchema()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return ctx
|
|
|
|
}
|