graphvent/context.go

1197 lines
30 KiB
Go

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"
"slices"
"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
"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"
)
var (
NodeNotFoundError = errors.New("Node not found in DB")
ECDH = ecdh.X25519()
)
2024-03-08 00:22:51 -07:00
type SerializeFn func(ctx *Context, value reflect.Value) ([]byte, error)
type DeserializeFn func(ctx *Context, data []byte) (reflect.Value, []byte, error)
type FieldInfo struct {
Index []int
Tag string
NodeTag string
2024-03-08 00:22:51 -07:00
Type reflect.Type
}
2024-03-04 17:30:42 -07:00
type TypeInfo struct {
2024-03-08 00:22:51 -07:00
Serialized SerializedType
Reflect reflect.Type
2024-03-04 17:30:42 -07:00
Type graphql.Type
2024-03-08 00:22:51 -07:00
Fields map[FieldTag]FieldInfo
PostDeserializeIndex int
Serialize SerializeFn
Deserialize DeserializeFn
2024-03-09 22:09:40 -07:00
Resolve func(interface{},graphql.ResolveParams)(interface{},error)
2024-03-04 17:30:42 -07:00
}
2024-03-03 15:45:45 -07:00
2024-03-04 17:30:42 -07:00
type ExtensionInfo struct {
2024-03-08 00:22:51 -07:00
ExtType
Fields map[string]FieldInfo
Data interface{}
}
2023-08-01 20:55:15 -06:00
type NodeInfo struct {
2024-03-08 00:22:51 -07:00
NodeType
Type *graphql.Object
Interface *graphql.Interface
Extensions []ExtType
2024-03-09 22:09:40 -07:00
Fields map[string]ExtType
2023-08-01 20:55:15 -06:00
}
2023-07-29 00:28:44 -06:00
type InterfaceInfo struct {
Serialized SerializedType
Reflect reflect.Type
}
// 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
// Logging interface
2023-07-09 14:30:30 -06:00
Log Logger
2024-03-04 17:30:42 -07:00
// Mapped types
2024-03-08 00:22:51 -07:00
TypeMap map[SerializedType]*TypeInfo
TypeTypes map[reflect.Type]*TypeInfo
2024-03-04 17:30:42 -07:00
// Map between database extension hashes and the registered info
2024-03-08 00:22:51 -07:00
Extensions map[ExtType]*ExtensionInfo
ExtensionTypes map[reflect.Type]*ExtensionInfo
2024-03-04 17:30:42 -07:00
Interfaces map[SerializedType]*InterfaceInfo
InterfaceTypes map[reflect.Type]*InterfaceInfo
// Map between database type hashes and the registered info
2024-03-08 00:22:51 -07:00
Nodes map[NodeType]*NodeInfo
NodeTypes map[string]*NodeInfo
2024-03-03 15:45:45 -07:00
// Routing map to all the nodes local to this context
nodeMapLock sync.RWMutex
nodeMap map[NodeID]*Node
2023-07-10 21:15:01 -06:00
}
func (ctx *Context) GQLType(t reflect.Type, node_type string) (graphql.Type, error) {
if t == reflect.TypeFor[NodeID]() {
2024-03-21 14:22:34 -06:00
if node_type == "" {
node_type = "Base"
}
node_info, mapped := ctx.NodeTypes[node_type]
if mapped == false {
return nil, fmt.Errorf("Cannot get GQL type for unregistered Node Type \"%s\"", node_type)
} else {
return node_info.Interface, nil
}
} else {
info, mapped := ctx.TypeTypes[t]
if mapped {
return info.Type, nil
} else {
switch t.Kind() {
case reflect.Array:
info, mapped := ctx.TypeTypes[t.Elem()]
if mapped {
return graphql.NewList(info.Type), nil
}
case reflect.Slice:
info, mapped := ctx.TypeTypes[t.Elem()]
if mapped {
return graphql.NewList(info.Type), nil
}
case reflect.Map:
info, exists := ctx.TypeTypes[t]
if exists {
return info.Type, nil
} else {
err := RegisterMap(ctx, t, node_type)
if err != nil {
return nil, err
}
map_type := ctx.TypeTypes[t].Type
ctx.Log.Logf("gql", "Getting type for %s: %s", t, map_type)
return map_type, nil
}
case reflect.Pointer:
return ctx.GQLType(t.Elem(), node_type)
}
return nil, fmt.Errorf("Can't convert %s to GQL type", t)
}
}
}
2024-03-09 22:09:40 -07:00
type Pair struct {
Key any
Val any
}
func RegisterMap(ctx *Context, reflect_type reflect.Type, node_type string) error {
2024-03-21 14:22:34 -06:00
var node_types []string
if node_type == "" {
node_types = []string{"", ""}
} else {
node_types = strings.SplitN(node_type, ":", 2)
2024-03-21 14:22:34 -06:00
if len(node_types) != 2 {
return fmt.Errorf("Invalid node tag for map type %s: \"%s\"", reflect_type, node_type)
}
}
key_type, err := ctx.GQLType(reflect_type.Key(), node_types[0])
if err != nil {
return err
}
key_resolve := ctx.GQLResolve(reflect_type.Key(), node_types[0])
val_type, err := ctx.GQLType(reflect_type.Elem(), node_types[1])
if err != nil {
return err
}
val_resolve := ctx.GQLResolve(reflect_type.Elem(), node_types[1])
gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_")
gql_name = strings.ReplaceAll(gql_name, "[", "_")
gql_name = strings.ReplaceAll(gql_name, "]", "_")
gql_pair := graphql.NewObject(graphql.ObjectConfig{
Name: gql_name,
Fields: graphql.Fields{
"Key": &graphql.Field{
Type: key_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
2024-03-09 22:09:40 -07:00
source, ok := p.Source.(Pair)
if ok == false {
return nil, fmt.Errorf("%+v is not Pair", source)
}
if key_resolve == nil {
return source.Key, nil
} else {
return key_resolve(source.Key, p)
}
},
},
"Value": &graphql.Field{
Type: val_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
2024-03-09 22:09:40 -07:00
source, ok := p.Source.(Pair)
if ok == false {
return nil, fmt.Errorf("%+v is not Pair", source)
}
if val_resolve == nil {
return source.Val, nil
} else {
return val_resolve(source.Val, p)
}
},
},
},
})
gql_map := graphql.NewList(gql_pair)
2024-03-08 00:22:51 -07:00
serialized_type := SerializeType(reflect_type)
ctx.TypeMap[serialized_type] = &TypeInfo{
Serialized: serialized_type,
Reflect: reflect_type,
Type: gql_map,
Resolve: func(v interface{},p graphql.ResolveParams) (interface{}, error) {
2024-03-09 22:09:40 -07:00
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
}
},
}
2024-03-08 00:22:51 -07:00
ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type]
return nil
}
2024-03-04 17:30:42 -07:00
func BuildSchema(ctx *Context, query, mutation *graphql.Object) (graphql.Schema, error) {
types := []graphql.Type{}
ctx.Log.Logf("gql", "Building Schema")
2024-03-03 15:45:45 -07:00
2024-03-04 22:11:40 -07:00
for _, info := range(ctx.TypeMap) {
2024-03-17 14:25:34 -06:00
if info.Type != nil {
types = append(types, info.Type)
}
2024-03-04 22:11:40 -07:00
}
for _, info := range(ctx.Nodes) {
2024-03-08 00:22:51 -07:00
types = append(types, info.Type)
types = append(types, info.Interface)
2024-03-04 22:11:40 -07:00
}
subscription := graphql.NewObject(graphql.ObjectConfig{
Name: "Subscription",
Fields: graphql.Fields{},
2024-03-04 17:30:42 -07:00
})
for query_name, query := range(query.Fields()) {
args := graphql.FieldConfigArgument{}
for _, arg := range(query.Args) {
args[arg.Name()] = &graphql.ArgumentConfig{
Type: arg.Type,
DefaultValue: arg.DefaultValue,
Description: arg.Description(),
}
}
subscription.AddFieldConfig(query_name, &graphql.Field{
Type: query.Type,
Args: args,
Subscribe: func(p graphql.ResolveParams) (interface{}, error) {
ctx, err := PrepResolve(p)
if err != nil {
return nil, err
}
c, err := ctx.Ext.AddSubscription(ctx.ID)
if err != nil {
return nil, err
}
first_result, err := query.Resolve(p)
if err != nil {
return nil, err
}
c <- first_result
return c, nil
},
Resolve: query.Resolve,
})
}
2024-03-04 17:30:42 -07:00
return graphql.NewSchema(graphql.SchemaConfig{
Types: types,
Query: query,
Subscription: subscription,
Mutation: mutation,
})
}
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]())
_, 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
}
fields := map[string]FieldInfo{}
2024-03-04 17:30:42 -07:00
for _, field := range(reflect.VisibleFields(reflect_type)) {
2024-03-04 17:30:42 -07:00
gv_tag, tagged_gv := field.Tag.Lookup("gv")
if tagged_gv {
fields[gv_tag] = FieldInfo{
Index: field.Index,
Tag: gv_tag,
NodeTag: field.Tag.Get("node"),
Type: field.Type,
2024-03-04 17:30:42 -07:00
}
}
}
2024-03-08 00:22:51 -07:00
ctx.Extensions[ext_type] = &ExtensionInfo{
ExtType: ext_type,
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
}
2024-03-08 00:22:51 -07:00
ctx.ExtensionTypes[reflect_type] = ctx.Extensions[ext_type]
return nil
}
func RegisterNodeType(ctx *Context, name string, extensions []ExtType) error {
node_type := NodeTypeFor(extensions)
2024-03-04 17:30:42 -07:00
_, exists := ctx.Nodes[node_type]
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)
}
2024-03-09 22:09:40 -07:00
fields := map[string]ExtType{}
2024-03-04 17:30:42 -07:00
ext_found := map[ExtType]bool{}
for _, extension := range(extensions) {
2024-03-09 22:09:40 -07:00
ext_info, in_ctx := ctx.Extensions[extension]
2024-03-04 17:30:42 -07:00
if in_ctx == false {
return fmt.Errorf("Cannot register node type %+v, required extension %+v not in context", name, extension)
2024-03-04 17:30:42 -07:00
}
_, duplicate := ext_found[extension]
if duplicate == true {
return fmt.Errorf("Duplicate extension %+v found in extension list", extension)
}
ext_found[extension] = true
2024-03-09 22:09:40 -07:00
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
}
}
2024-03-17 14:25:34 -06:00
gql_interface := graphql.NewInterface(graphql.InterfaceConfig{
2024-03-08 00:22:51 -07:00
Name: name,
Fields: graphql.Fields{
"ID": &graphql.Field{
Type: ctx.TypeTypes[reflect.TypeFor[NodeID]()].Type,
2024-03-08 00:22:51 -07:00
},
"Type": &graphql.Field{
Type: ctx.TypeTypes[reflect.TypeFor[NodeType]()].Type,
},
},
ResolveType: func(p graphql.ResolveTypeParams) *graphql.Object {
ctx_val := p.Context.Value("resolve")
ctx, ok := ctx_val.(*ResolveContext)
if ok == false {
return nil
}
val, ok := p.Value.(NodeResult)
if ok == false {
2024-03-09 22:09:40 -07:00
ctx.Context.Log.Logf("gql", "Interface ResolveType got bad Value %+v", p.Value)
return nil
}
node_info, exists := ctx.Context.Nodes[val.NodeType]
if exists == false {
2024-03-09 22:09:40 -07:00
ctx.Context.Log.Logf("gql", "Interface ResolveType got bad NodeType", val.NodeType)
return nil
}
for _, ext_type := range(extensions) {
if slices.Contains(node_info.Extensions, ext_type) == false {
2024-03-09 22:09:40 -07:00
ctx.Context.Log.Logf("gql", "Interface ResolveType for %s missing extensions %s: %+v", name, ext_type, val)
return nil
}
}
return node_info.Type
},
})
gql := graphql.NewObject(graphql.ObjectConfig{
Name: name + "Node",
2024-03-09 22:09:40 -07:00
Interfaces: ctx.GQLInterfaces(node_type, extensions),
Fields: graphql.Fields{
"ID": &graphql.Field{
Type: ctx.TypeTypes[reflect.TypeFor[NodeID]()].Type,
2024-03-09 22:09:40 -07:00
Resolve: ResolveNodeID,
},
"Type": &graphql.Field{
Type: ctx.TypeTypes[reflect.TypeFor[NodeType]()].Type,
2024-03-09 22:09:40 -07:00
Resolve: ResolveNodeType,
2024-03-08 00:22:51 -07:00
},
},
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
source, ok := p.Value.(NodeResult)
if ok == false {
2024-03-08 00:22:51 -07:00
return false
}
return source.NodeType == node_type
2024-03-08 00:22:51 -07:00
},
})
2024-03-08 00:22:51 -07:00
ctx.Nodes[node_type] = &NodeInfo{
NodeType: node_type,
Interface: gql_interface,
2024-03-08 00:22:51 -07:00
Type: gql,
2024-03-04 17:30:42 -07:00
Extensions: extensions,
2024-03-09 22:09:40 -07:00
Fields: fields,
2024-03-04 17:30:42 -07:00
}
2024-03-08 00:22:51 -07:00
ctx.NodeTypes[name] = ctx.Nodes[node_type]
for _, ext_type := range(extensions) {
ext_info, ext_found := ctx.Extensions[ext_type]
if ext_found == false {
return fmt.Errorf("Extension %s not found", ext_type)
}
for field_name, field_info := range(ext_info.Fields) {
gql_type, err := ctx.GQLType(field_info.Type, field_info.NodeTag)
if err != nil {
return err
}
2024-03-09 22:09:40 -07:00
gql_resolve := ctx.GQLResolve(field_info.Type, field_info.NodeTag)
gql_interface.AddFieldConfig(field_name, &graphql.Field{
Type: gql_type,
})
gql.AddFieldConfig(field_name, &graphql.Field{
Type: gql_type,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
2024-03-09 22:09:40 -07:00
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], p)
2024-03-09 22:09:40 -07:00
} else {
return node.Data[node_info.Fields[field_name]][field_name], nil
}
},
})
}
}
return nil
}
2024-03-09 22:09:40 -07:00
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
}
}
2024-03-21 14:22:34 -06:00
func RegisterSignal[S Signal](ctx *Context) error {
return RegisterObject[S](ctx)
}
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 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_name := strings.ReplaceAll(reflect_type.String(), ".", "_")
2024-03-04 17:30:42 -07:00
gql := graphql.NewObject(graphql.ObjectConfig{
Name: gql_name,
2024-03-04 17:30:42 -07:00
IsTypeOf: func(p graphql.IsTypeOfParams) bool {
return reflect_type == reflect.TypeOf(p.Value)
},
Fields: graphql.Fields{},
})
2024-03-08 00:22:51 -07:00
field_infos := map[FieldTag]FieldInfo{}
post_deserialize, post_deserialize_exists := reflect.PointerTo(reflect_type).MethodByName("PostDeserialize")
post_deserialize_index := -1
if post_deserialize_exists {
post_deserialize_index = post_deserialize.Index
}
2024-03-04 17:30:42 -07:00
for _, field := range(reflect.VisibleFields(reflect_type)) {
gv_tag, tagged_gv := field.Tag.Lookup("gv")
if tagged_gv {
node_tag := field.Tag.Get("node")
2024-03-08 00:22:51 -07:00
field_infos[GetFieldTag(gv_tag)] = FieldInfo{
Type: field.Type,
Index: field.Index,
NodeTag: node_tag,
Tag: gv_tag,
2024-03-08 00:22:51 -07:00
}
gql_type, err := ctx.GQLType(field.Type, node_tag)
if err != nil {
return err
}
gql_resolve := ctx.GQLResolve(field.Type, node_tag)
2024-03-04 17:30:42 -07:00
gql.AddFieldConfig(gv_tag, &graphql.Field{
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)
}
2024-03-17 14:25:34 -06:00
2024-03-04 17:30:42 -07:00
value, err := reflect.ValueOf(val).FieldByIndexErr(field.Index)
if err != nil {
return nil, err
}
if gql_resolve == nil {
return value.Interface(), nil
} else {
return gql_resolve(value.Interface(), p)
}
2024-03-04 17:30:42 -07:00
},
})
}
}
2024-03-08 00:22:51 -07:00
ctx.TypeMap[serialized_type] = &TypeInfo{
PostDeserializeIndex: post_deserialize_index,
Serialized: serialized_type,
Reflect: reflect_type,
Fields: field_infos,
2024-03-04 17:30:42 -07:00
Type: gql,
2024-03-09 22:09:40 -07:00
Resolve: nil,
}
2024-03-08 00:22:51 -07:00
ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type]
2024-03-04 17:30:42 -07:00
return nil
}
2024-03-21 14:13:54 -06:00
func RegisterObjectNoGQL[T any](ctx *Context) error {
reflect_type := reflect.TypeFor[T]()
serialized_type := SerializedTypeFor[T]()
_, exists := ctx.TypeTypes[reflect_type]
if exists {
return fmt.Errorf("%+v already registered in TypeMap", reflect_type)
}
field_infos := map[FieldTag]FieldInfo{}
post_deserialize, post_deserialize_exists := reflect.PointerTo(reflect_type).MethodByName("PostDeserialize")
post_deserialize_index := -1
if post_deserialize_exists {
post_deserialize_index = post_deserialize.Index
}
for _, field := range(reflect.VisibleFields(reflect_type)) {
gv_tag, tagged_gv := field.Tag.Lookup("gv")
if tagged_gv {
node_tag := field.Tag.Get("node")
field_infos[GetFieldTag(gv_tag)] = FieldInfo{
Type: field.Type,
Index: field.Index,
NodeTag: node_tag,
Tag: gv_tag,
}
}
}
ctx.TypeMap[serialized_type] = &TypeInfo{
PostDeserializeIndex: post_deserialize_index,
Serialized: serialized_type,
Reflect: reflect_type,
Fields: field_infos,
Type: nil,
Resolve: nil,
}
ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type]
return nil
}
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
}
2024-03-09 22:09:40 -07:00
var tmp E = new(T)
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)
}
2024-03-08 00:22:51 -07:00
func astBool[T ~bool](value ast.Value) interface{} {
switch value := value.(type) {
case *ast.BooleanValue:
if value.Value {
return T(true)
} else {
return T(false)
}
case *ast.IntValue:
i, err := strconv.Atoi(value.Value)
if err != nil {
return nil
}
return i != 0
case *ast.StringValue:
b, err := strconv.ParseBool(value.Value)
if err != nil {
return nil
}
return b
default:
return nil
}
}
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
}
}
2024-03-17 14:25:34 -06:00
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 {
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)
}
gql_name := strings.ReplaceAll(reflect_type.String(), ".", "_")
2024-03-04 17:30:42 -07:00
gql := graphql.NewScalar(graphql.ScalarConfig{
Name: gql_name,
2024-03-04 17:30:42 -07:00
Serialize: to_json,
ParseValue: from_json,
ParseLiteral: from_ast,
})
2024-03-08 00:22:51 -07:00
ctx.TypeMap[serialized_type] = &TypeInfo{
Serialized: serialized_type,
Reflect: reflect_type,
2024-03-04 17:30:42 -07:00
Type: gql,
Resolve: resolve,
2024-03-04 17:30:42 -07:00
}
2024-03-08 00:22:51 -07:00
ctx.TypeTypes[reflect_type] = ctx.TypeMap[serialized_type]
2023-07-09 14:30:30 -06:00
return nil
}
func (ctx *Context) AddNode(id NodeID, node *Node) {
ctx.nodeMapLock.Lock()
ctx.nodeMap[id] = node
ctx.nodeMapLock.Unlock()
}
func (ctx *Context) Node(id NodeID) (*Node, bool) {
ctx.nodeMapLock.RLock()
node, exists := ctx.nodeMap[id]
ctx.nodeMapLock.RUnlock()
return node, exists
}
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 {
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)
}
err := node.Unload(ctx)
delete(ctx.nodeMap, id)
return err
}
func (ctx *Context) Stop() {
ctx.nodeMapLock.Lock()
for id, node := range(ctx.nodeMap) {
node.Unload(ctx)
delete(ctx.nodeMap, id)
}
ctx.nodeMapLock.Unlock()
}
func (ctx *Context) Load(id NodeID) (*Node, error) {
node, err := LoadNode(ctx, id)
if err != nil {
return nil, err
}
ctx.AddNode(id, node)
started := make(chan error, 1)
go runNode(ctx, node, started)
err = <- started
if err != nil {
return nil, err
}
return node, nil
}
// Get a node from the context, or load from the database if not loaded
func (ctx *Context) getNode(id NodeID) (*Node, error) {
target, exists := ctx.Node(id)
2023-07-27 15:49:21 -06:00
if exists == false {
var err error
target, err = ctx.Load(id)
if err != nil {
return nil, err
}
2023-07-27 15:49:21 -06:00
}
return target, nil
}
// 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 {
for _, msg := range(messages) {
ctx.Log.Logf("signal", "Sending %s to %s", msg.Signal, msg.Dest)
2023-08-15 18:23:06 -06:00
if msg.Dest == ZeroID {
panic("Can't send to null ID")
}
target, err := ctx.getNode(msg.Dest)
if err == nil {
select {
2024-03-04 17:30:42 -07:00
case target.MsgChan <- RecvMsg{node.ID, msg.Signal}:
ctx.Log.Logf("signal", "Sent %s to %s", msg.Signal, msg.Dest)
default:
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
stack_str := string(buf[:n])
return fmt.Errorf("SIGNAL_OVERFLOW: %s - %s", msg.Dest, stack_str)
}
} else if errors.Is(err, NodeNotFoundError) {
// TODO: Handle finding nodes in other contexts
return err
} else {
return err
}
2023-07-27 15:49:21 -06:00
}
return nil
2023-07-27 15:49:21 -06:00
}
func (ctx *Context)GQLResolve(t reflect.Type, node_type string) (func(interface{},graphql.ResolveParams)(interface{},error)) {
2024-03-09 22:09:40 -07:00
info, mapped := ctx.TypeTypes[t]
if mapped {
return info.Resolve
2024-03-09 22:09:40 -07:00
} else {
switch t.Kind() {
//case reflect.Array:
//case reflect.Slice:
case reflect.Pointer:
return ctx.GQLResolve(t.Elem(), node_type)
default:
return nil
2024-03-09 22:09:40 -07:00
}
}
}
func RegisterInterface[T any](ctx *Context) error {
serialized_type := SerializeType(reflect.TypeFor[T]())
reflect_type := reflect.TypeFor[T]()
_, exists := ctx.Interfaces[serialized_type]
if exists == true {
return fmt.Errorf("Interface %+v already exists in context", reflect_type)
}
ctx.Interfaces[serialized_type] = &InterfaceInfo{
Serialized: serialized_type,
Reflect: reflect_type,
}
ctx.InterfaceTypes[reflect_type] = ctx.Interfaces[serialized_type]
return nil
2024-03-09 22:09:40 -07: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
2024-03-08 00:22:51 -07:00
TypeMap: map[SerializedType]*TypeInfo{},
TypeTypes: map[reflect.Type]*TypeInfo{},
2024-03-04 17:30:42 -07:00
2024-03-08 00:22:51 -07:00
Extensions: map[ExtType]*ExtensionInfo{},
ExtensionTypes: map[reflect.Type]*ExtensionInfo{},
2024-03-04 17:30:42 -07:00
Interfaces: map[SerializedType]*InterfaceInfo{},
InterfaceTypes: map[reflect.Type]*InterfaceInfo{},
2024-03-08 00:22:51 -07:00
Nodes: map[NodeType]*NodeInfo{},
NodeTypes: map[string]*NodeInfo{},
2024-03-03 15:45:45 -07:00
nodeMap: map[NodeID]*Node{},
}
var err error
err = RegisterScalar[NodeID](ctx, stringify, unstringify[NodeID], unstringifyAST[NodeID], func(v interface{}, p graphql.ResolveParams)(interface{}, error) {
id, ok := v.(NodeID)
if ok == false {
return nil, fmt.Errorf("%+v is not NodeID", v)
}
node, err := ResolveNode(id, p)
if err != nil {
return nil, err
}
return node, nil
})
if err != nil {
return nil, err
}
2024-03-21 14:13:54 -06:00
err = RegisterInterface[Extension](ctx)
if err != nil {
return nil, err
}
2024-03-21 14:13:54 -06:00
err = RegisterInterface[Signal](ctx)
if err != nil {
return nil, err
}
err = RegisterScalar[NodeType](ctx, identity, coerce[NodeType], astInt[NodeType], nil)
if err != nil {
return nil, err
}
2024-03-21 14:22:34 -06:00
err = RegisterScalar[ExtType](ctx, identity, coerce[ExtType], astInt[ExtType], nil)
if err != nil {
return nil, err
}
err = RegisterNodeType(ctx, "Base", []ExtType{})
if err != nil {
return nil, err
}
err = RegisterScalar[bool](ctx, identity, coerce[bool], astBool[bool], nil)
2024-03-08 00:22:51 -07:00
if err != nil {
return nil, err
}
err = RegisterScalar[int](ctx, identity, coerce[int], astInt[int], nil)
if err != nil {
return nil, err
}
err = RegisterScalar[uint32](ctx, identity, coerce[uint32], astInt[uint32], nil)
2024-03-08 00:22:51 -07:00
if err != nil {
return nil, err
}
err = RegisterScalar[uint8](ctx, identity, coerce[uint8], astInt[uint8], nil)
if err != nil {
return nil, err
}
err = RegisterScalar[time.Time](ctx, stringify, unstringify[time.Time], unstringifyAST[time.Time], nil)
if err != nil {
return nil, err
}
err = RegisterScalar[string](ctx, identity, coerce[string], astString[string], nil)
if err != nil {
return nil, err
}
err = RegisterScalar[EventState](ctx, identity, coerce[EventState], astString[EventState], nil)
if err != nil {
return nil, err
}
2024-03-17 14:25:34 -06:00
err = RegisterEnum[ReqState](ctx, ReqStateStrings)
if err != nil {
return nil, err
}
err = RegisterScalar[uuid.UUID](ctx, stringify, unstringify[uuid.UUID], unstringifyAST[uuid.UUID], nil)
if err != nil {
return nil, err
}
err = RegisterScalar[WaitReason](ctx, identity, coerce[WaitReason], astString[WaitReason], nil)
if err != nil {
return nil, err
}
2024-03-21 14:22:34 -06:00
err = RegisterScalar[Change](ctx, identity, coerce[Change], astString[Change], nil)
if err != nil {
return nil, err
}
// TODO: Register as a GQL type with Signal as an interface
2024-03-21 14:13:54 -06:00
err = RegisterObjectNoGQL[QueuedSignal](ctx)
if err != nil {
return nil, err
}
2024-03-21 14:22:34 -06:00
err = RegisterSignal[TimeoutSignal](ctx)
2024-03-21 14:13:54 -06:00
if err != nil {
return nil, err
}
2024-03-21 14:22:34 -06:00
err = RegisterSignal[StatusSignal](ctx)
2024-03-21 14:13:54 -06:00
if err != nil {
return nil, err
}
err = RegisterObject[Node](ctx)
if err != nil {
return nil, err
}
err = RegisterObject[WaitInfo](ctx)
2024-03-08 00:22:51 -07:00
if err != nil {
return nil, err
}
err = RegisterExtension[LockableExt](ctx, nil)
2024-03-08 00:22:51 -07:00
if err != nil {
return nil, err
}
err = RegisterExtension[EventExt](ctx, nil)
if err != nil {
return nil, err
}
err = RegisterExtension[ListenerExt](ctx, nil)
if err != nil {
return nil, err
}
err = RegisterExtension[GQLExt](ctx, nil)
2023-11-11 13:53:41 -07:00
if err != nil {
return nil, err
}
err = RegisterNodeType(ctx, "Lockable", []ExtType{ExtTypeFor[LockableExt]()})
if err != nil {
return nil, err
}
err = RegisterObject[LockableExt](ctx)
if err != nil {
return nil, err
}
err = RegisterObject[EventExt](ctx)
2023-10-13 13:45:30 -06:00
if err != nil {
return nil, err
}
err = RegisterObject[ListenerExt](ctx)
2024-03-08 00:22:51 -07:00
if err != nil {
return nil, err
}
err = RegisterObject[GQLExt](ctx)
if err != nil {
return nil, err
}
schema, err := BuildSchema(ctx, graphql.NewObject(graphql.ObjectConfig{
2024-03-04 22:11:40 -07:00
Name: "Query",
Fields: graphql.Fields{
"Self": &graphql.Field{
2024-03-17 14:25:34 -06:00
Type: ctx.NodeTypes["Base"].Interface,
2024-03-04 22:11:40 -07:00
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
2024-03-09 22:09:40 -07:00
ctx, err := PrepResolve(p)
if err != nil {
return nil, err
}
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)
2024-03-04 22:11:40 -07:00
},
},
},
}), 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
}
ctx.ExtensionTypes[reflect.TypeFor[GQLExt]()].Data = schema
2023-07-25 21:43:15 -06:00
return ctx, nil
2023-07-09 14:30:30 -06:00
}