123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- package interpreter
- import (
- "fmt"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/lang/graphql/parser"
- "devt.de/krotik/eliasdb/graph"
- )
- // Runtime definition
- // ==================
- /*
- Instance function for runtime components
- */
- type runtimeInst func(*GraphQLRuntimeProvider, *parser.ASTNode) parser.Runtime
- /*
- Runtime map for runtime components
- */
- var runtimeProviderMap = map[string]runtimeInst{
- parser.NodeEOF: invalidRuntimeInst,
- parser.NodeDocument: documentRuntimeInst,
- parser.NodeExecutableDefinition: executableDefinitionRuntimeInst,
- parser.NodeFragmentDefinition: fragmentDefinitionRuntimeInst,
- parser.NodeInlineFragment: inlineFragmentDefinitionRuntimeInst,
- parser.NodeOperationDefinition: operationDefinitionRuntimeInst,
- parser.NodeSelectionSet: selectionSetRuntimeInst,
- parser.NodeField: fieldRuntimeInst,
- parser.NodeDirective: argumentExpressionRuntimeInst,
- parser.NodeObjectValue: valueRuntimeInst,
- parser.NodeValue: valueRuntimeInst,
- parser.NodeDefaultValue: valueRuntimeInst,
- parser.NodeEnumValue: valueRuntimeInst,
- parser.NodeListValue: valueRuntimeInst,
- parser.NodeVariable: valueRuntimeInst,
- }
- // General runtime provider
- // ========================
- /*
- QueryType is a know GraphQL query type
- */
- type QueryType string
- /*
- All known query types
- */
- const (
- QueryTypeQuery QueryType = "query"
- QueryTypeMutation = "mutation"
- QueryTypeSubscription = "subscription"
- )
- /*
- GraphQLRuntimeProvider defines the main interpreter
- datastructure and all functions for general evaluation.
- */
- type GraphQLRuntimeProvider struct {
- Name string // Name to identify the input
- QueryType QueryType // Query type (query, mutation, subscription)
- OperationName string // Name of operation to execute
- VariableValues map[string]interface{} // Values of variables
- ErrorKeys []string // List of error hashes (used for deduplication)
- Errors []*RuntimeError // List of errors
- ErrorPaths [][]string // List of error paths
- part string // Graph partition to query
- gm *graph.Manager // GraphManager to operate on
- callbackHandler SubscriptionCallbackHandler // Subscription callback handler for updates
- subscriptionHandler *subscriptionHandler // Subscription handler forwarding event is the callback object
- readOnly bool // Flag if only read operations are allowed
- operation *parser.ASTNode // Operation to execute
- fragments map[string]*fragmentDefinitionRuntime // Fragment definitions
- }
- /*
- NewGraphQLRuntimeProvider creates a new GraphQLRuntimeProvider object.
- */
- func NewGraphQLRuntimeProvider(name string, part string, gm *graph.Manager,
- op string, vars map[string]interface{}, callbackHandler SubscriptionCallbackHandler,
- readOnly bool) *GraphQLRuntimeProvider {
- return &GraphQLRuntimeProvider{name, "", op, vars, []string{}, []*RuntimeError{},
- [][]string{}, part, gm, callbackHandler, nil, readOnly, nil,
- make(map[string]*fragmentDefinitionRuntime)}
- }
- /*
- CheckWritePermission checks if the current query is allowed to modify data.
- Returns true if data can be modified.
- */
- func (rtp *GraphQLRuntimeProvider) CheckWritePermission(path []string, node *parser.ASTNode) bool {
- if rtp.readOnly {
- rtp.handleRuntimeError(fmt.Errorf("Can only perform read operations"),
- path, node)
- return false
- }
- if rtp.QueryType != QueryTypeMutation {
- rtp.handleRuntimeError(fmt.Errorf("Operation must be a mutation to modify data"),
- path, node)
- return false
- }
- return true
- }
- /*
- Initialise data structures.
- */
- func (rtp *GraphQLRuntimeProvider) init() error {
- rtp.QueryType = ""
- rtp.operation = nil
- return nil
- }
- /*
- Runtime returns a runtime component for a given ASTNode.
- */
- func (rtp *GraphQLRuntimeProvider) Runtime(node *parser.ASTNode) parser.Runtime {
- if pinst, ok := runtimeProviderMap[node.Name]; ok {
- return pinst(rtp, node)
- }
- return invalidRuntimeInst(rtp, node)
- }
- /*
- TraverseAST traverses the AST starting with a given root and executes a given
- visitor function on each node. An accumulator is given to track state. A path
- is given to track selection sets.
- */
- func (rtp *GraphQLRuntimeProvider) TraverseAST(root *parser.ASTNode,
- visitor func(*parser.ASTNode)) {
- visitor(root)
- for _, child := range root.Children {
- rtp.TraverseAST(child, visitor)
- }
- }
- // Document Runtime
- // ================
- type documentRuntime struct {
- rtp *GraphQLRuntimeProvider
- node *parser.ASTNode
- }
- /*
- documentRuntimeInst creates a new document runtime instance.
- */
- func documentRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {
- return &documentRuntime{rtp, node}
- }
- /*
- Validate and reset this runtime component and all its child components.
- */
- func (rt *documentRuntime) Validate() error {
- err := rt.rtp.init()
- for _, c := range rt.node.Children {
- if err == nil {
- err = c.Runtime.Validate()
- }
- }
- if rt.rtp.operation == nil {
- // We didn't find an operation to execute
- if rt.rtp.OperationName == "" {
- err = rt.rtp.newFatalRuntimeError(ErrMissingOperation,
- "No executable expression found", rt.node)
- } else {
- err = rt.rtp.newFatalRuntimeError(ErrMissingOperation,
- fmt.Sprintf("Operation %s not found", rt.rtp.OperationName), rt.node)
- }
- }
- if err == nil && rt.rtp.QueryType == "" {
- rt.rtp.QueryType = QueryTypeQuery
- }
- if err == nil {
- // Check variables - types are not checked
- ort := rt.rtp.operation.Runtime.(*operationDefinitionRuntime)
- declared, defaultValues, _ := ort.DeclaredVariables()
- // Build up variable values
- vals := rt.rtp.VariableValues
- rt.rtp.VariableValues = make(map[string]interface{})
- for _, name := range declared {
- val, ok := vals[name]
- if ok {
- rt.rtp.VariableValues[name] = val
- } else {
- rt.rtp.VariableValues[name] = defaultValues[name]
- }
- }
- }
- if err == nil {
- // Collect fragment definitions
- rt.rtp.TraverseAST(rt.node, func(n *parser.ASTNode) {
- if err == nil && n.Name == parser.NodeFragmentDefinition {
- fr := n.Runtime.(*fragmentDefinitionRuntime)
- if _, ok := rt.rtp.fragments[fr.Name()]; ok {
- err = rt.rtp.newFatalRuntimeError(ErrAmbiguousDefinition,
- fmt.Sprintf("Fragment %s defined multiple times",
- fr.Name()), rt.node)
- }
- if err == nil {
- rt.rtp.fragments[fr.Name()] = fr
- }
- }
- })
- if err == nil {
- // Validate that all fragment spreads can be resolved
- rt.rtp.TraverseAST(rt.node, func(n *parser.ASTNode) {
- if err == nil && n.Name == parser.NodeFragmentSpread {
- name := n.Token.Val
- if _, ok := rt.rtp.fragments[name]; !ok {
- err = rt.rtp.newFatalRuntimeError(ErrInvalidConstruct,
- fmt.Sprintf("Fragment %s is not defined",
- name), rt.node)
- }
- }
- })
- }
- }
- return err
- }
- /*
- Eval evaluate this runtime component.
- */
- func (rt *documentRuntime) Eval() (map[string]interface{}, error) {
- var err error
- // First validate the query and reset the runtime provider datastructures
- if rt.rtp.QueryType == "" {
- if err = rt.Validate(); err != nil {
- return nil, err
- }
- }
- // Validate must have found the query type and the operation to execute
- errorutil.AssertTrue(rt.rtp.QueryType != "", "Unknown querytype")
- errorutil.AssertTrue(rt.rtp.operation != nil, "Unknown operation")
- if rt.rtp.QueryType == QueryTypeSubscription && rt.rtp.callbackHandler != nil {
- rt.rtp.InitSubscription(rt)
- }
- return rt.rtp.operation.Runtime.Eval()
- }
- // ExecutableDefinition Runtime
- // ============================
- type executableDefinitionRuntime struct {
- *invalidRuntime
- rtp *GraphQLRuntimeProvider
- node *parser.ASTNode
- }
- /*
- executableDefinitionRuntimeInst creates a new document runtime instance.
- */
- func executableDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {
- return &executableDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}
- }
- /*
- Validate and reset this runtime component and all its child components.
- */
- func (rt *executableDefinitionRuntime) Validate() error {
- if rt.rtp.operation == nil {
- // Choose an operation to execute
- if rt.node.Children[0].Name == parser.NodeOperationDefinition {
- if rt.rtp.OperationName == "" {
- // No operation name defined - take the first available operation
- rt.rtp.operation = rt.node.Children[0]
- // Check the operation type
- if rt.node.Children[0].Children[0].Name == parser.NodeOperationType {
- if rt.node.Children[0].Children[0].Token.Val == "mutation" {
- rt.rtp.QueryType = QueryTypeMutation
- } else if rt.node.Children[0].Children[0].Token.Val == "subscription" {
- rt.rtp.QueryType = QueryTypeSubscription
- }
- }
- } else {
- // If an operation name is defined we must not have a query shorthand
- if rt.node.Children[0].Children[0].Name == parser.NodeOperationType {
- name := rt.node.Children[0].Children[1].Token.Val
- if rt.rtp.OperationName == name {
- // We found the operation to execture
- if rt.node.Children[0].Children[0].Name == parser.NodeOperationType {
- // See what type it is
- if rt.node.Children[0].Children[0].Token.Val == "mutation" {
- rt.rtp.QueryType = QueryTypeMutation
- } else if rt.node.Children[0].Children[0].Token.Val == "subscription" {
- rt.rtp.QueryType = QueryTypeSubscription
- }
- }
- rt.rtp.operation = rt.node.Children[0]
- }
- }
- }
- }
- }
- return nil
- }
- // OperationDefinition Runtime
- // ============================
- type operationDefinitionRuntime struct {
- *invalidRuntime
- rtp *GraphQLRuntimeProvider
- node *parser.ASTNode
- }
- /*
- operationDefinitionRuntimeInst creates a new operation definition runtime instance.
- */
- func operationDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {
- return &operationDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}
- }
- /*
- Eval evaluate this runtime component.
- */
- func (rt *operationDefinitionRuntime) Eval() (map[string]interface{}, error) {
- res := make(map[string]interface{})
- // Execute the selection set
- data, err := rt.node.Children[len(rt.node.Children)-1].Runtime.Eval()
- res["data"] = data
- // Collect errors
- resErr := make([]map[string]interface{}, 0)
- for i, rterr := range rt.rtp.Errors {
- resErr = append(resErr, map[string]interface{}{
- "message": rterr.Detail,
- "locations": []map[string]interface{}{
- {
- "line": rterr.Line,
- "column": rterr.Pos,
- },
- },
- "path": rt.rtp.ErrorPaths[i],
- })
- }
- if len(resErr) > 0 {
- // Only add errors if there are any (@spec 7.1.2)
- res["errors"] = resErr
- }
- return res, err
- }
- /*
- DeclaredVariables returns all declared variables as list and their default
- values (if defined) and their type as maps.
- */
- func (rt *operationDefinitionRuntime) DeclaredVariables() ([]string, map[string]interface{}, map[string]string) {
- declared := make([]string, 0)
- defValues := make(map[string]interface{})
- types := make(map[string]string)
- for _, c := range rt.node.Children {
- if c.Name == parser.NodeVariableDefinitions {
- for _, vardef := range c.Children {
- name := vardef.Children[0].Token.Val
- declared = append(declared, name)
- if len(vardef.Children) > 2 {
- defValues[name] = vardef.Children[2].Runtime.(*valueRuntime).Value()
- }
- types[name] = vardef.Children[1].Token.Val
- }
- }
- }
- return declared, defValues, types
- }
- // FragmentDefinition Runtime
- // ==========================
- type fragmentDefinitionRuntime struct {
- *invalidRuntime
- rtp *GraphQLRuntimeProvider
- node *parser.ASTNode
- }
- /*
- fragmentDefinitionRuntimeInst creates a new fragment definition runtime instance.
- */
- func fragmentDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {
- return &fragmentDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}
- }
- /*
- Name returns the name of the fragment definition.
- */
- func (rt *fragmentDefinitionRuntime) Name() string {
- return rt.node.Children[0].Token.Val
- }
- /*
- TypeCondition returns the type condition of the fragment definition.
- */
- func (rt *fragmentDefinitionRuntime) TypeCondition() string {
- return rt.node.Children[1].Token.Val
- }
- /*
- SelectionSet returns the selection set of the fragment definition.
- */
- func (rt *fragmentDefinitionRuntime) SelectionSet() *parser.ASTNode {
- return rt.node.Children[len(rt.node.Children)-1]
- }
- // InlineFragmentDefinition Runtime
- // ================================
- type inlineFragmentDefinitionRuntime struct {
- *invalidRuntime
- rtp *GraphQLRuntimeProvider
- node *parser.ASTNode
- }
- /*
- fragmentDefinitionRuntimeInst creates a new inline fragment definition runtime instance.
- */
- func inlineFragmentDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {
- return &inlineFragmentDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}
- }
- /*
- TypeCondition returns the type condition of the inline fragment definition.
- */
- func (rt *inlineFragmentDefinitionRuntime) TypeCondition() string {
- return rt.node.Children[0].Token.Val
- }
- /*
- SelectionSet returns the selection set of the inline fragment definition.
- */
- func (rt *inlineFragmentDefinitionRuntime) SelectionSet() *parser.ASTNode {
- return rt.node.Children[len(rt.node.Children)-1]
- }
|