Browse Source

feat: Adding plain node support for GraphQL AST

Matthias Ladkau 1 year ago
parent
commit
3a5d35db2b

+ 4 - 0
lang/graphql/parser/const.go

@@ -94,6 +94,10 @@ const (
 	// characters so escape sequences must be used to represent these characters.
 
 	TokenStringValue
+
+	// General token used for plain ASTs
+
+	TokenGeneral
 )
 
 /*

+ 57 - 0
lang/graphql/parser/node.go

@@ -30,6 +30,63 @@ type ASTNode struct {
 	leftDenotation func(p *parser, self *ASTNode, left *ASTNode) (*ASTNode, error) // Configure token as left node
 }
 
+/*
+ASTFromPlain creates an AST from a plain AST.
+A plain AST is a nested map structure like this:
+
+	{
+		name     : <name of node>
+		value    : <value of node>
+		children : [ <child nodes> ]
+	}
+*/
+func ASTFromPlain(plainAST map[string]interface{}) (*ASTNode, error) {
+	var astChildren []*ASTNode
+
+	nameValue, ok := plainAST["name"]
+	if !ok {
+		return nil, fmt.Errorf("Found plain ast node without a name: %v", plainAST)
+	}
+	name := fmt.Sprint(nameValue)
+
+	valueValue, ok := plainAST["value"]
+	if stringutil.IndexOf(name, ValueNodes) != -1 && !ok {
+		return nil, fmt.Errorf("Found plain ast value node without a value: %v", name)
+	}
+	value := fmt.Sprint(valueValue)
+
+	// Create children
+
+	if children, ok := plainAST["children"]; ok {
+
+		if ic, ok := children.([]interface{}); ok {
+
+			// Do a list conversion if necessary - this is necessary when we parse
+			// JSON with map[string]interface{} this
+
+			childrenList := make([]map[string]interface{}, len(ic))
+			for i := range ic {
+				childrenList[i] = ic[i].(map[string]interface{})
+			}
+
+			children = childrenList
+		}
+
+		for _, child := range children.([]map[string]interface{}) {
+
+			astChild, err := ASTFromPlain(child)
+			if err != nil {
+				return nil, err
+			}
+
+			astChildren = append(astChildren, astChild)
+		}
+	}
+
+	return &ASTNode{fmt.Sprint(name), &LexToken{TokenGeneral, 0,
+		fmt.Sprint(value), 0, 0}, astChildren, nil, 0, nil, nil}, nil
+}
+
 /*
 newAstNode creates an instance of this ASTNode which is connected to a concrete lexer token.
 */

+ 38 - 0
lang/graphql/parser/prettyprinter_test.go

@@ -10,8 +10,10 @@
 package parser
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
+	"strings"
 	"testing"
 )
 
@@ -538,6 +540,24 @@ Document
 		t.Error("Unexpected result:", astres)
 		return
 	}
+
+	plainAST := astres.Plain()
+
+	plainAST["children"].([]map[string]interface{})[0]["name"] = "Value"
+	_, err = ASTFromPlain(plainAST)
+
+	if err == nil || err.Error() != "Found plain ast value node without a value: Value" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	delete(plainAST["children"].([]map[string]interface{})[0], "name")
+	_, err = ASTFromPlain(plainAST)
+
+	if err == nil || !strings.HasPrefix(err.Error(), "Found plain ast node without a name") {
+		t.Error("Unexpected result:", err)
+		return
+	}
 }
 
 func testPPOut(input string) (string, error) {
@@ -572,5 +592,23 @@ func testPrettyPrinting(input, astOutput, ppOutput string) error {
 		return fmt.Errorf("Unexpected parser output from pretty print string:\n%v expected was:\n%v Error: %v", astres2, astOutput, err)
 	}
 
+	mAST, _ := json.Marshal(astres2.Plain())
+	var plainAST interface{}
+	json.Unmarshal(mAST, &plainAST)
+
+	ast, err := ASTFromPlain(plainAST.(map[string]interface{}))
+	if err != nil {
+		return fmt.Errorf("Could not build AST from plain AST: %v", err)
+	}
+
+	ppres2, err := PrettyPrint(ast)
+	if err != nil {
+		return fmt.Errorf("Could not pretty print plain AST: %v", err)
+	}
+
+	if ppres != ppres2 {
+		return fmt.Errorf("Unexpected pretty print - normal:\n%v\nFrom plain:\n%v", ppres, ppres2)
+	}
+
 	return nil
 }