Added gql_signal.go

gql_cataclysm
noah metz 2023-10-10 18:25:30 -06:00
parent 92d8dfd006
commit 0941c6c64e
2 changed files with 203 additions and 3 deletions

@ -181,7 +181,7 @@ func ExtractID(p graphql.ResolveParams, name string) (NodeID, error) {
return id, nil
}
func GraphiQLHandler() func(http.ResponseWriter, *http.Request) {
func GraphiQLHandler(auth_token string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r * http.Request) {
graphiql_string := fmt.Sprintf(`
<!--
@ -240,6 +240,9 @@ func GraphiQLHandler() func(http.ResponseWriter, *http.Request) {
React.createElement(GraphiQL, {
fetcher: GraphiQL.createFetcher({
url: '/gql',
headers: {
"Authorization": "Basic %s",
},
}),
defaultEditorToolsVisibility: true,
}),
@ -247,7 +250,7 @@ func GraphiQLHandler() func(http.ResponseWriter, *http.Request) {
</script>
</body>
</html>
`)
`, auth_token)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
@ -1436,7 +1439,11 @@ func (ext *GQLExt) StartGQLServer(ctx *Context, node *Node) error {
mux.HandleFunc("/gqlws", GQLWSHandler(ctx, node, ext))
// Server a graphiql interface(TODO make configurable whether to start this)
mux.HandleFunc("/graphiql", GraphiQLHandler())
auth_header, err := AuthorizationHeader(node)
if err != nil {
return err
}
mux.HandleFunc("/graphiql", GraphiQLHandler(auth_header))
// Server the ./site directory to /site (TODO make configurable with better defaults)
fs := http.FileServer(http.Dir("./site"))

@ -0,0 +1,193 @@
package graphvent
import (
graphql "github.com/graphql-go/graphql"
"reflect"
"fmt"
"time"
)
type GQLTypeInfo struct {
graphql.Type
FromGQL func(interface{})(reflect.Value, error)
}
var kind_gql_map = map[reflect.Kind]GQLTypeInfo{
reflect.String: {
graphql.String,
func(value interface{})(reflect.Value, error) {
return reflect.ValueOf(value), nil
},
},
reflect.Bool: {
graphql.Boolean,
func(value interface{})(reflect.Value, error) {
return reflect.ValueOf(value), nil
},
},
}
var type_gql_map = map[reflect.Type]GQLTypeInfo{
reflect.TypeOf(&NodeID{}): {
graphql.String,
func(value interface{}) (reflect.Value, error) {
str, ok := value.(string)
if ok == false {
return reflect.Value{}, fmt.Errorf("value is not string")
}
if str == "" {
return reflect.New(reflect.TypeOf(NodeID{})), nil
}
id_parsed, err := ParseID(str)
if err != nil {
return reflect.Value{}, err
}
return reflect.ValueOf(&id_parsed), nil
},
},
reflect.TypeOf(NodeID{}): {
graphql.String,
func(value interface{})(reflect.Value, error) {
str, ok := value.(string)
if ok == false {
return reflect.Value{}, fmt.Errorf("value is not string")
}
id_parsed, err := ParseID(str)
if err != nil {
return reflect.Value{}, err
}
return reflect.ValueOf(id_parsed), nil
},
},
}
type StructFieldInfo struct {
Name string
Type reflect.Type
GQL *GQLTypeInfo
Index []int
}
func SignalFromArgs(signal_type reflect.Type, fields []StructFieldInfo, args map[string]interface{}) (Signal, error) {
signal_value := reflect.New(signal_type)
for _, field := range(fields) {
arg, arg_exists := args[field.Name]
if arg_exists == false {
return nil, fmt.Errorf("No arg provided named %s", field.Name)
}
field_value := signal_value.Elem().FieldByIndex(field.Index)
if field_value.CanConvert(field.Type) == false {
return nil, fmt.Errorf("Arg %s wrong type %s/%s", field.Name, field_value.Type(), field.Type)
}
value, err := field.GQL.FromGQL(arg)
if err != nil {
return nil, err
}
field_value.Set(value)
}
return signal_value.Interface().(Signal), nil
}
func (ext *GQLExtContext) AddSignalMutation(name string, send_id_key string, signal_type reflect.Type) error {
args := graphql.FieldConfigArgument{}
arg_info := []StructFieldInfo{}
for _, field := range(reflect.VisibleFields(signal_type)) {
gv_tag, tagged_gv := field.Tag.Lookup("gv")
if tagged_gv {
if gv_tag == "id" {
continue
}
if gv_tag == "direction" {
continue
}
_, exists := args[gv_tag]
if exists == true {
return fmt.Errorf("Signal has repeated tag %s", gv_tag)
}
var gql_info GQLTypeInfo
var type_mapped bool
gql_info, type_mapped = type_gql_map[field.Type]
if type_mapped == false {
var kind_mapped bool
gql_info, kind_mapped = kind_gql_map[field.Type.Kind()]
if kind_mapped == false {
return fmt.Errorf("Signal has unsupported type/kind: %s/%s", field.Type, field.Type.Kind())
}
}
args[gv_tag] = &graphql.ArgumentConfig{
Type: gql_info.Type,
}
arg_info = append(arg_info, StructFieldInfo{
gv_tag,
field.Type,
&gql_info,
field.Index,
})
}
}
_, send_exists := args[send_id_key]
if send_exists == false {
args[send_id_key] = &graphql.ArgumentConfig{
Type: graphql.String,
}
}
resolve_signal := func(p graphql.ResolveParams) (interface{}, error) {
ctx, err := PrepResolve(p)
if err != nil {
return nil, err
}
send_id, err := ExtractID(p, send_id_key)
if err != nil {
return nil, err
}
signal, err := SignalFromArgs(signal_type, arg_info, p.Args)
if err != nil {
return nil, err
}
msgs := Messages{}
msgs = msgs.Add(ctx.Context, ctx.User, ctx.Key, signal, send_id)
response_chan := ctx.Ext.GetResponseChannel(signal.ID())
err = ctx.Context.Send(msgs)
if err != nil {
ctx.Ext.FreeResponseChannel(signal.ID())
return nil, err
}
response, err := WaitForResponse(response_chan, 100*time.Millisecond, signal.ID())
if err != nil {
ctx.Ext.FreeResponseChannel(signal.ID())
return nil, err
}
_, is_success := response.(*SuccessSignal)
if is_success == true {
return "success", nil
}
error_response, is_error := response.(*ErrorSignal)
if is_error == true {
return "error", fmt.Errorf(error_response.Error)
}
return nil, fmt.Errorf("response of unhandled type %s", reflect.TypeOf(response))
}
ext.Mutation.AddFieldConfig(name, &graphql.Field{
Type: graphql.String,
Args: args,
Resolve: resolve_signal,
})
return nil
}