2023-07-09 14:30:30 -06:00
|
|
|
package graphvent
|
|
|
|
|
|
|
|
import (
|
2024-03-04 17:30:42 -07:00
|
|
|
"crypto/ecdh"
|
2024-03-04 22:11:40 -07:00
|
|
|
"encoding"
|
2024-03-04 17:30:42 -07:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"runtime"
|
2024-03-04 21:30:11 -07:00
|
|
|
"strconv"
|
2024-03-04 22:11:40 -07:00
|
|
|
"strings"
|
2024-03-04 17:30:42 -07:00
|
|
|
"sync"
|
2024-03-04 22:11:40 -07:00
|
|
|
"time"
|
2024-03-04 17:30:42 -07:00
|
|
|
|
2024-03-04 21:30:11 -07:00
|
|
|
"golang.org/x/exp/constraints"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
2024-03-04 17:30:42 -07:00
|
|
|
"github.com/graphql-go/graphql"
|
|
|
|
"github.com/graphql-go/graphql/language/ast"
|
|
|
|
|
|
|
|
badger "github.com/dgraph-io/badger/v3"
|
2023-07-27 23:15:58 -06:00
|
|
|
)
|
|
|
|
|
2023-07-28 00:04:18 -06:00
|
|
|
var (
|
|
|
|
NodeNotFoundError = errors.New("Node not found in DB")
|
2023-08-07 20:26:02 -06:00
|
|
|
ECDH = ecdh.X25519()
|
2023-07-28 00:04:18 -06:00
|
|
|
)
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
type TypeInfo struct {
|
|
|
|
Type graphql.Type
|
|
|
|
}
|
2024-03-03 15:45:45 -07:00
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
type ExtensionInfo struct {
|
|
|
|
Interface *graphql.Interface
|
|
|
|
Fields map[string][]int
|
2023-08-31 19:50:32 -06:00
|
|
|
Data interface{}
|
|
|
|
}
|
2023-08-01 20:55:15 -06:00
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
type SignalInfo struct {
|
|
|
|
Type graphql.Type
|
|
|
|
}
|
|
|
|
|
2024-03-03 15:45:45 -07:00
|
|
|
type FieldIndex struct {
|
|
|
|
Extension ExtType
|
|
|
|
Field string
|
|
|
|
}
|
|
|
|
|
2023-08-31 19:50:32 -06:00
|
|
|
type NodeInfo struct {
|
2024-03-04 17:30:42 -07:00
|
|
|
GQL *graphql.Object
|
2023-08-31 19:50:32 -06:00
|
|
|
Extensions []ExtType
|
2024-03-03 15:45:45 -07:00
|
|
|
Fields map[string]FieldIndex
|
2023-08-01 20:55:15 -06:00
|
|
|
}
|
2023-07-29 00:28:44 -06:00
|
|
|
|
2023-07-27 16:06:56 -06:00
|
|
|
// A Context stores all the data to run a graphvent process
|
2023-07-09 14:30:30 -06:00
|
|
|
type Context struct {
|
2024-03-04 17:30:42 -07:00
|
|
|
|
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-27 16:06:56 -06:00
|
|
|
// Logging interface
|
2023-07-09 14:30:30 -06:00
|
|
|
Log Logger
|
2024-03-04 17:30:42 -07:00
|
|
|
|
|
|
|
// Mapped types
|
|
|
|
TypeMap map[SerializedType]TypeInfo
|
|
|
|
TypeTypes map[reflect.Type]SerializedType
|
|
|
|
|
2023-07-27 16:06:56 -06:00
|
|
|
// Map between database extension hashes and the registered info
|
2023-08-31 19:50:32 -06:00
|
|
|
Extensions map[ExtType]ExtensionInfo
|
|
|
|
ExtensionTypes map[reflect.Type]ExtType
|
2024-03-04 17:30:42 -07:00
|
|
|
|
2023-07-27 16:06:56 -06:00
|
|
|
// Map between database type hashes and the registered info
|
2023-08-31 19:50:32 -06:00
|
|
|
Nodes map[NodeType]NodeInfo
|
2024-03-03 15:45:45 -07:00
|
|
|
NodeTypes map[string]NodeType
|
|
|
|
|
2023-07-27 16:06:56 -06:00
|
|
|
// Routing map to all the nodes local to this context
|
2023-08-31 19:50:32 -06:00
|
|
|
nodeMapLock sync.RWMutex
|
|
|
|
nodeMap map[NodeID]*Node
|
2023-07-10 21:15:01 -06:00
|
|
|
}
|
|
|
|
|
2024-03-04 21:30:11 -07:00
|
|
|
func (ctx *Context) GQLType(t reflect.Type) graphql.Type {
|
|
|
|
ser, mapped := ctx.TypeTypes[t]
|
|
|
|
if mapped {
|
|
|
|
return ctx.TypeMap[ser].Type
|
|
|
|
} else {
|
|
|
|
switch t.Kind() {
|
|
|
|
case reflect.Array:
|
|
|
|
ser, mapped := ctx.TypeTypes[t.Elem()]
|
|
|
|
if mapped {
|
|
|
|
return graphql.NewList(ctx.TypeMap[ser].Type)
|
|
|
|
}
|
|
|
|
case reflect.Slice:
|
|
|
|
ser, mapped := ctx.TypeTypes[t.Elem()]
|
|
|
|
if mapped {
|
|
|
|
return graphql.NewList(ctx.TypeMap[ser].Type)
|
|
|
|
}
|
|
|
|
case reflect.Map:
|
|
|
|
ser, exists := ctx.TypeTypes[t]
|
|
|
|
if exists {
|
|
|
|
return ctx.TypeMap[ser].Type
|
|
|
|
} else {
|
|
|
|
err := RegisterMap(ctx, t)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ctx.TypeMap[ctx.TypeTypes[t]].Type
|
|
|
|
}
|
|
|
|
case reflect.Pointer:
|
|
|
|
ser, mapped := ctx.TypeTypes[t.Elem()]
|
|
|
|
if mapped {
|
|
|
|
return ctx.TypeMap[ser].Type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-04 22:11:40 -07:00
|
|
|
func RegisterMap(ctx *Context, reflect_type reflect.Type) error {
|
|
|
|
key_type := ctx.GQLType(reflect_type.Key())
|
2024-03-04 21:30:11 -07:00
|
|
|
if key_type == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-04 22:11:40 -07:00
|
|
|
val_type := ctx.GQLType(reflect_type.Elem())
|
2024-03-04 21:30:11 -07:00
|
|
|
if val_type == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
gql_pair := graphql.NewObject(graphql.ObjectConfig{
|
2024-03-04 22:11:40 -07:00
|
|
|
Name: strings.ReplaceAll(reflect_type.String(), ".", "_"),
|
2024-03-04 21:30:11 -07:00
|
|
|
Fields: graphql.Fields{
|
|
|
|
"Key": &graphql.Field{
|
|
|
|
Type: key_type,
|
|
|
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
|
|
|
return nil, fmt.Errorf("NOT_IMPLEMENTED")
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"Value": &graphql.Field{
|
|
|
|
Type: val_type,
|
|
|
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
|
|
|
return nil, fmt.Errorf("NOT_IMPLEMENTED")
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
gql_map := graphql.NewList(gql_pair)
|
|
|
|
|
2024-03-04 22:11:40 -07:00
|
|
|
ctx.TypeTypes[reflect_type] = SerializeType(reflect_type)
|
|
|
|
ctx.TypeMap[SerializeType(reflect_type)] = TypeInfo{
|
2024-03-04 21:30:11 -07:00
|
|
|
Type: gql_map,
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
func BuildSchema(ctx *Context, query, mutation *graphql.Object) (graphql.Schema, error) {
|
|
|
|
types := []graphql.Type{}
|
2024-03-03 15:45:45 -07:00
|
|
|
|
2024-03-04 22:11:40 -07:00
|
|
|
for _, info := range(ctx.TypeMap) {
|
|
|
|
types = append(types, info.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, info := range(ctx.Extensions) {
|
|
|
|
types = append(types, info.Interface)
|
|
|
|
}
|
2023-07-29 00:28:44 -06:00
|
|
|
|
2024-03-04 22:11:40 -07:00
|
|
|
for _, info := range(ctx.Nodes) {
|
|
|
|
types = append(types, info.GQL)
|
|
|
|
}
|
|
|
|
|
|
|
|
subscription := graphql.NewObject(graphql.ObjectConfig{
|
|
|
|
Name: "Subscription",
|
|
|
|
Fields: graphql.Fields{
|
|
|
|
"Test": &graphql.Field{
|
|
|
|
Type: graphql.String,
|
|
|
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
|
|
|
return "TEST", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2024-03-04 17:30:42 -07:00
|
|
|
})
|
2023-10-29 18:26:14 -06:00
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
return graphql.NewSchema(graphql.SchemaConfig{
|
|
|
|
Types: types,
|
|
|
|
Query: query,
|
|
|
|
Subscription: subscription,
|
|
|
|
Mutation: mutation,
|
|
|
|
})
|
2023-08-31 19:50:32 -06:00
|
|
|
}
|
|
|
|
|
2024-03-03 15:45:45 -07:00
|
|
|
func RegisterSignal[S Signal](ctx *Context) error {
|
|
|
|
reflect_type := reflect.TypeFor[S]()
|
|
|
|
signal_type := SignalTypeFor[S]()
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
err := RegisterObject[S](ctx)
|
2023-10-29 18:26:14 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Log.Logf("serialize_types", "Registered SignalType: %+v - %+v", reflect_type, signal_type)
|
2023-07-29 00:28:44 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-03 15:45:45 -07:00
|
|
|
func RegisterExtension[E any, T interface { *E; Extension}](ctx *Context, data interface{}) error {
|
2024-03-04 22:11:40 -07:00
|
|
|
reflect_type := reflect.TypeFor[E]()
|
2024-03-03 15:45:45 -07:00
|
|
|
ext_type := ExtType(SerializedTypeFor[E]())
|
2023-08-31 19:50:32 -06:00
|
|
|
_, exists := ctx.Extensions[ext_type]
|
2023-07-09 14:30:30 -06:00
|
|
|
if exists == true {
|
2024-03-03 15:45:45 -07:00
|
|
|
return fmt.Errorf("Cannot register extension %+v of type %+v, type already exists in context", reflect_type, ext_type)
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
gql_interface := graphql.NewInterface(graphql.InterfaceConfig{
|
2024-03-04 22:11:40 -07:00
|
|
|
Name: "interface_" + strings.ReplaceAll(reflect_type.String(), ".", "_"),
|
2024-03-04 17:30:42 -07:00
|
|
|
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
|
|
|
|
ctx, ok := p.Context.Value("resolve").(*ResolveContext)
|
|
|
|
if ok == false {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
node, ok := p.Value.(NodeResult)
|
|
|
|
if ok == false {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type_info, type_exists := ctx.Context.Nodes[node.NodeType]
|
|
|
|
if type_exists == false {
|
|
|
|
return ctx.Context.Nodes[ctx.Context.NodeTypes["Base"]].GQL
|
|
|
|
}
|
|
|
|
|
|
|
|
return type_info.GQL
|
|
|
|
},
|
|
|
|
Fields: graphql.Fields{
|
|
|
|
"ID": &graphql.Field{
|
|
|
|
Type: graphql.String,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
fields := map[string][]int{}
|
|
|
|
for _, field := range reflect.VisibleFields(reflect.TypeFor[E]()) {
|
|
|
|
gv_tag, tagged_gv := field.Tag.Lookup("gv")
|
|
|
|
if tagged_gv {
|
|
|
|
fields[gv_tag] = field.Index
|
|
|
|
|
2024-03-04 21:30:11 -07:00
|
|
|
gql_type := ctx.GQLType(field.Type)
|
|
|
|
if gql_type == nil {
|
2024-03-04 17:30:42 -07:00
|
|
|
return fmt.Errorf("Extension %s has field %s of unregistered type %s", reflect_type, gv_tag, field.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
gql_interface.AddFieldConfig(gv_tag, &graphql.Field{
|
2024-03-04 21:30:11 -07:00
|
|
|
Type: gql_type,
|
2024-03-04 17:30:42 -07:00
|
|
|
})
|
|
|
|
}
|
2023-10-29 18:26:14 -06:00
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
err := RegisterObject[E](ctx)
|
2023-09-12 20:30:18 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-10-29 18:26:14 -06:00
|
|
|
ctx.Log.Logf("serialize_types", "Registered ExtType: %+v - %+v", reflect_type, ext_type)
|
2023-09-12 19:00:48 -06:00
|
|
|
|
2023-08-31 19:50:32 -06:00
|
|
|
ctx.Extensions[ext_type] = ExtensionInfo{
|
2024-03-04 17:30:42 -07:00
|
|
|
Interface: gql_interface,
|
2023-07-26 00:18:11 -06:00
|
|
|
Data: data,
|
2024-03-04 17:30:42 -07:00
|
|
|
Fields: fields,
|
2023-07-10 21:15:01 -06:00
|
|
|
}
|
2023-08-31 19:50:32 -06:00
|
|
|
ctx.ExtensionTypes[reflect_type] = ext_type
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
func RegisterNodeType(ctx *Context, name string, extensions []ExtType, mappings map[string]FieldIndex) error {
|
|
|
|
node_type := NodeTypeFor(name, extensions, mappings)
|
|
|
|
_, exists := ctx.Nodes[node_type]
|
2023-09-02 17:30:52 -06:00
|
|
|
if exists == true {
|
2024-03-04 17:30:42 -07:00
|
|
|
return fmt.Errorf("Cannot register node type %+v, type already exists in context", node_type)
|
2023-09-02 17:30:52 -06:00
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
ext_found := map[ExtType]bool{}
|
|
|
|
for _, extension := range(extensions) {
|
|
|
|
_, in_ctx := ctx.Extensions[extension]
|
|
|
|
if in_ctx == false {
|
|
|
|
return fmt.Errorf("Cannot register node type %+v, required extension %+v not in context", node_type, extension)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, duplicate := ext_found[extension]
|
|
|
|
if duplicate == true {
|
|
|
|
return fmt.Errorf("Duplicate extension %+v found in extension list", extension)
|
|
|
|
}
|
|
|
|
|
|
|
|
ext_found[extension] = true
|
2023-09-02 17:30:52 -06:00
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
ctx.Nodes[node_type] = NodeInfo{
|
|
|
|
Extensions: extensions,
|
|
|
|
Fields: mappings,
|
|
|
|
}
|
|
|
|
ctx.NodeTypes[name] = node_type
|
2023-10-29 18:26:14 -06:00
|
|
|
|
2023-09-02 17:30:52 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
func RegisterObject[T any](ctx *Context) error {
|
2024-03-03 15:45:45 -07:00
|
|
|
reflect_type := reflect.TypeFor[T]()
|
2024-03-04 22:11:40 -07:00
|
|
|
ctx.Log.Logf("test", "registering %+v", reflect_type)
|
2024-03-04 17:30:42 -07:00
|
|
|
serialized_type := SerializedTypeFor[T]()
|
|
|
|
|
|
|
|
_, exists := ctx.TypeTypes[reflect_type]
|
|
|
|
if exists {
|
|
|
|
return fmt.Errorf("%+v already registered in TypeMap", reflect_type)
|
|
|
|
}
|
|
|
|
|
|
|
|
gql := graphql.NewObject(graphql.ObjectConfig{
|
2024-03-04 22:11:40 -07:00
|
|
|
Name: strings.ReplaceAll(reflect_type.String(), ".", "_"),
|
2024-03-04 17:30:42 -07:00
|
|
|
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
|
|
|
|
return reflect_type == reflect.TypeOf(p.Value)
|
|
|
|
},
|
|
|
|
Fields: graphql.Fields{},
|
|
|
|
})
|
|
|
|
|
|
|
|
for _, field := range(reflect.VisibleFields(reflect_type)) {
|
|
|
|
gv_tag, tagged_gv := field.Tag.Lookup("gv")
|
|
|
|
if tagged_gv {
|
2024-03-04 21:30:11 -07:00
|
|
|
gql_type := ctx.GQLType(field.Type)
|
|
|
|
if gql_type == nil {
|
|
|
|
return fmt.Errorf("Object %+v has field %s of unknown type %+v", reflect_type, gv_tag, field.Type)
|
2023-10-29 18:26:14 -06:00
|
|
|
}
|
2024-03-04 17:30:42 -07:00
|
|
|
gql.AddFieldConfig(gv_tag, &graphql.Field{
|
2024-03-04 21:30:11 -07:00
|
|
|
Type: gql_type,
|
2024-03-04 17:30:42 -07:00
|
|
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
|
|
|
val, ok := p.Source.(T)
|
|
|
|
if ok == false {
|
|
|
|
return nil, fmt.Errorf("%s is not %s", reflect.TypeOf(p.Source), reflect_type)
|
|
|
|
}
|
|
|
|
|
|
|
|
value, err := reflect.ValueOf(val).FieldByIndexErr(field.Index)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return value.Interface(), nil
|
|
|
|
},
|
|
|
|
})
|
2023-10-29 18:26:14 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
ctx.TypeTypes[reflect_type] = serialized_type
|
|
|
|
ctx.TypeMap[serialized_type] = TypeInfo{
|
|
|
|
Type: gql,
|
2023-09-11 21:47:53 -06:00
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-04 21:30:11 -07:00
|
|
|
func identity(value interface{}) interface{} {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringify(value interface{}) interface{} {
|
|
|
|
v, ok := value.(encoding.TextMarshaler)
|
|
|
|
if ok {
|
|
|
|
b, err := v.MarshalText()
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func unstringify[T any, E interface { *T; encoding.TextUnmarshaler }](value interface{}) interface{} {
|
|
|
|
str, ok := value.(string)
|
|
|
|
if ok == false {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var tmp E
|
|
|
|
err := tmp.UnmarshalText([]byte(str))
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return *tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
func unstringifyAST[T any, E interface { *T; encoding.TextUnmarshaler}](value ast.Value)interface{} {
|
|
|
|
str, ok := value.(*ast.StringValue)
|
|
|
|
if ok == false {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var tmp E
|
|
|
|
err := tmp.UnmarshalText([]byte(str.Value))
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return *tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
func coerce[T any](value interface{}) interface{} {
|
|
|
|
t := reflect.TypeFor[T]()
|
|
|
|
if reflect.TypeOf(value).ConvertibleTo(t) {
|
|
|
|
return value.(T)
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func astString[T ~string](value ast.Value) interface{} {
|
|
|
|
str, ok := value.(*ast.StringValue)
|
|
|
|
if ok == false {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return T(str.Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func astInt[T constraints.Integer](value ast.Value) interface{} {
|
|
|
|
switch value := value.(type) {
|
|
|
|
case *ast.BooleanValue:
|
|
|
|
if value.Value {
|
|
|
|
return T(1)
|
|
|
|
} else {
|
|
|
|
return T(0)
|
|
|
|
}
|
|
|
|
case *ast.StringValue:
|
|
|
|
i, err := strconv.Atoi(value.Value)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return T(i)
|
|
|
|
}
|
|
|
|
case *ast.IntValue:
|
|
|
|
i, err := strconv.Atoi(value.Value)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return T(i)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func RegisterScalar[S any](ctx *Context, to_json func(interface{})interface{}, from_json func(interface{})interface{}, from_ast func(ast.Value)interface{}) error {
|
|
|
|
reflect_type := reflect.TypeFor[S]()
|
|
|
|
serialized_type := SerializedTypeFor[S]()
|
2024-03-04 17:30:42 -07:00
|
|
|
|
|
|
|
_, exists := ctx.TypeTypes[reflect_type]
|
|
|
|
if exists {
|
|
|
|
return fmt.Errorf("%+v already registered in TypeMap", reflect_type)
|
2023-08-31 19:50:32 -06:00
|
|
|
}
|
2023-10-29 18:26:14 -06:00
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
gql := graphql.NewScalar(graphql.ScalarConfig{
|
2024-03-04 22:11:40 -07:00
|
|
|
Name: strings.ReplaceAll(reflect_type.String(), ".", "_"),
|
2024-03-04 17:30:42 -07:00
|
|
|
Serialize: to_json,
|
|
|
|
ParseValue: from_json,
|
|
|
|
ParseLiteral: from_ast,
|
|
|
|
})
|
2023-08-31 19:50:32 -06:00
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
ctx.TypeTypes[reflect_type] = serialized_type
|
|
|
|
ctx.TypeMap[serialized_type] = TypeInfo{
|
|
|
|
Type: gql,
|
|
|
|
}
|
2023-09-11 21:47:53 -06:00
|
|
|
|
2023-07-09 14:30:30 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-03-03 15:45:45 -07:00
|
|
|
|
2023-07-28 12:46:06 -06:00
|
|
|
func (ctx *Context) AddNode(id NodeID, node *Node) {
|
2023-08-31 19:50:32 -06:00
|
|
|
ctx.nodeMapLock.Lock()
|
|
|
|
ctx.nodeMap[id] = node
|
|
|
|
ctx.nodeMapLock.Unlock()
|
2023-07-28 12:46:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *Context) Node(id NodeID) (*Node, bool) {
|
2023-08-31 19:50:32 -06:00
|
|
|
ctx.nodeMapLock.RLock()
|
|
|
|
node, exists := ctx.nodeMap[id]
|
|
|
|
ctx.nodeMapLock.RUnlock()
|
2023-07-28 12:46:06 -06:00
|
|
|
return node, exists
|
|
|
|
}
|
|
|
|
|
2024-03-03 16:37:03 -07:00
|
|
|
func (ctx *Context) Delete(id NodeID) error {
|
|
|
|
err := ctx.Unload(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// TODO: also delete any associated data
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *Context) Unload(id NodeID) error {
|
2023-10-06 20:04:53 -06:00
|
|
|
ctx.nodeMapLock.Lock()
|
|
|
|
defer ctx.nodeMapLock.Unlock()
|
|
|
|
node, exists := ctx.nodeMap[id]
|
|
|
|
if exists == false {
|
|
|
|
return fmt.Errorf("%s is not a node in ctx", id)
|
|
|
|
}
|
|
|
|
|
2024-03-03 16:37:03 -07:00
|
|
|
err := node.Unload(ctx)
|
2023-10-06 20:04:53 -06:00
|
|
|
delete(ctx.nodeMap, id)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-03-03 16:37:03 -07:00
|
|
|
func (ctx *Context) Stop() {
|
2023-10-06 20:04:53 -06:00
|
|
|
ctx.nodeMapLock.Lock()
|
|
|
|
for id, node := range(ctx.nodeMap) {
|
2024-03-03 16:37:03 -07:00
|
|
|
node.Unload(ctx)
|
2023-10-06 20:04:53 -06:00
|
|
|
delete(ctx.nodeMap, id)
|
|
|
|
}
|
|
|
|
ctx.nodeMapLock.Unlock()
|
|
|
|
}
|
|
|
|
|
2023-07-28 00:04:18 -06:00
|
|
|
// Get a node from the context, or load from the database if not loaded
|
2023-08-31 19:50:32 -06:00
|
|
|
func (ctx *Context) getNode(id NodeID) (*Node, error) {
|
2023-07-28 12:46:06 -06:00
|
|
|
target, exists := ctx.Node(id)
|
|
|
|
|
2023-07-27 15:49:21 -06:00
|
|
|
if exists == false {
|
2023-07-27 16:48:39 -06:00
|
|
|
var err error
|
|
|
|
target, err = LoadNode(ctx, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-07-27 15:49:21 -06:00
|
|
|
}
|
2023-07-27 16:48:39 -06:00
|
|
|
return target, nil
|
|
|
|
}
|
|
|
|
|
2023-11-03 21:41:06 -06:00
|
|
|
// Route Messages to dest. Currently only local context routing is supported
|
2024-03-04 17:30:42 -07:00
|
|
|
func (ctx *Context) Send(node *Node, messages []SendMsg) error {
|
2023-08-07 20:26:02 -06:00
|
|
|
for _, msg := range(messages) {
|
2023-11-05 21:18:14 -07:00
|
|
|
ctx.Log.Logf("signal", "Sending %s -> %+v", msg.Dest, msg)
|
2023-08-15 18:23:06 -06:00
|
|
|
if msg.Dest == ZeroID {
|
|
|
|
panic("Can't send to null ID")
|
|
|
|
}
|
2023-08-31 19:50:32 -06:00
|
|
|
target, err := ctx.getNode(msg.Dest)
|
2023-08-07 20:26:02 -06:00
|
|
|
if err == nil {
|
|
|
|
select {
|
2024-03-04 17:30:42 -07:00
|
|
|
case target.MsgChan <- RecvMsg{node.ID, msg.Signal}:
|
2023-08-08 14:00:17 -06:00
|
|
|
ctx.Log.Logf("signal", "Sent %s -> %+v", target.ID, msg)
|
2023-08-07 20:26:02 -06:00
|
|
|
default:
|
|
|
|
buf := make([]byte, 4096)
|
|
|
|
n := runtime.Stack(buf, false)
|
|
|
|
stack_str := string(buf[:n])
|
2023-08-08 14:00:17 -06:00
|
|
|
return fmt.Errorf("SIGNAL_OVERFLOW: %s - %s", msg.Dest, stack_str)
|
2023-08-07 20:26:02 -06:00
|
|
|
}
|
|
|
|
} else if errors.Is(err, NodeNotFoundError) {
|
|
|
|
// TODO: Handle finding nodes in other contexts
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
return err
|
2023-07-27 16:48:39 -06:00
|
|
|
}
|
2023-07-27 15:49:21 -06:00
|
|
|
}
|
2023-08-07 20:26:02 -06:00
|
|
|
return nil
|
2023-07-27 15:49:21 -06:00
|
|
|
}
|
|
|
|
|
2023-09-02 17:30:52 -06:00
|
|
|
// Create a new Context with the base library content added
|
|
|
|
func NewContext(db * badger.DB, log Logger) (*Context, error) {
|
|
|
|
ctx := &Context{
|
|
|
|
DB: db,
|
|
|
|
Log: log,
|
2024-03-04 17:30:42 -07:00
|
|
|
|
|
|
|
TypeMap: map[SerializedType]TypeInfo{},
|
|
|
|
TypeTypes: map[reflect.Type]SerializedType{},
|
|
|
|
|
2023-09-02 17:30:52 -06:00
|
|
|
Extensions: map[ExtType]ExtensionInfo{},
|
|
|
|
ExtensionTypes: map[reflect.Type]ExtType{},
|
2024-03-04 17:30:42 -07:00
|
|
|
|
2023-09-02 17:30:52 -06:00
|
|
|
Nodes: map[NodeType]NodeInfo{},
|
2024-03-03 15:45:45 -07:00
|
|
|
NodeTypes: map[string]NodeType{},
|
|
|
|
|
|
|
|
nodeMap: map[NodeID]*Node{},
|
2023-09-02 17:30:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
2023-09-12 16:56:01 -06:00
|
|
|
|
2024-03-04 21:30:11 -07:00
|
|
|
err = RegisterScalar[int](ctx, identity, coerce[int], astInt[int])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[uint8](ctx, identity, coerce[uint8], astInt[uint8])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[time.Time](ctx, stringify, unstringify[time.Time], unstringifyAST[time.Time])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[string](ctx, identity, coerce[string], astString[string])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[EventState](ctx, identity, coerce[EventState], astString[EventState])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[ReqState](ctx, identity, coerce[ReqState], astInt[ReqState])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[uuid.UUID](ctx, stringify, unstringify[uuid.UUID], unstringifyAST[uuid.UUID])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[NodeID](ctx, stringify, unstringify[NodeID], unstringifyAST[NodeID])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterScalar[WaitReason](ctx, identity, coerce[WaitReason], astString[WaitReason])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterObject[WaitInfo](ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = RegisterMap(ctx, reflect.TypeFor[WaitMap]())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-03 15:45:45 -07:00
|
|
|
err = RegisterExtension[ListenerExt](ctx, nil)
|
2023-09-12 19:48:16 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
err = RegisterExtension[LockableExt](ctx, nil)
|
2023-11-11 13:53:41 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-03 15:45:45 -07:00
|
|
|
err = RegisterExtension[EventExt](ctx, nil)
|
2023-11-05 21:18:14 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-04 17:30:42 -07:00
|
|
|
err = RegisterExtension[GQLExt](ctx, nil)
|
2023-10-13 13:45:30 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-04 22:11:40 -07:00
|
|
|
_, err = BuildSchema(ctx, graphql.NewObject(graphql.ObjectConfig{
|
|
|
|
Name: "Query",
|
|
|
|
Fields: graphql.Fields{
|
|
|
|
"Test": &graphql.Field{
|
|
|
|
Type: graphql.String,
|
|
|
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
|
|
|
return "TEST", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}), graphql.NewObject(graphql.ObjectConfig{
|
|
|
|
Name: "Mutation",
|
|
|
|
Fields: graphql.Fields{
|
|
|
|
"Test": &graphql.Field{
|
|
|
|
Type: graphql.String,
|
|
|
|
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
|
|
|
return "TEST", nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-25 21:43:15 -06:00
|
|
|
return ctx, nil
|
2023-07-09 14:30:30 -06:00
|
|
|
}
|