package printer import ( "fmt" "strings" "github.com/graphql-go/graphql/language/ast" "github.com/graphql-go/graphql/language/visitor" "reflect" ) func getMapValue(m map[string]interface{}, key string) interface{} { tokens := strings.Split(key, ".") valMap := m for _, token := range tokens { v, ok := valMap[token] if !ok { return nil } switch v := v.(type) { case []interface{}: return v case map[string]interface{}: valMap = v continue default: return v } } return valMap } func getMapSliceValue(m map[string]interface{}, key string) []interface{} { tokens := strings.Split(key, ".") valMap := m for _, token := range tokens { v, ok := valMap[token] if !ok { return []interface{}{} } switch v := v.(type) { case []interface{}: return v default: return []interface{}{} } } return []interface{}{} } func getMapValueString(m map[string]interface{}, key string) string { tokens := strings.Split(key, ".") valMap := m for _, token := range tokens { v, ok := valMap[token] if !ok { return "" } if v == nil { return "" } switch v := v.(type) { case map[string]interface{}: valMap = v continue case string: return v default: return fmt.Sprintf("%v", v) } } return "" } func toSliceString(slice interface{}) []string { if slice == nil { return []string{} } res := []string{} switch reflect.TypeOf(slice).Kind() { case reflect.Slice: s := reflect.ValueOf(slice) for i := 0; i < s.Len(); i++ { elem := s.Index(i) elemInterface := elem.Interface() if elem, ok := elemInterface.(string); ok { res = append(res, elem) } } return res default: return res } } func join(str []string, sep string) string { ss := []string{} // filter out empty strings for _, s := range str { if s == "" { continue } ss = append(ss, s) } return strings.Join(ss, sep) } func wrap(start, maybeString, end string) string { if maybeString == "" { return maybeString } return start + maybeString + end } // Given array, print each item on its own line, wrapped in an indented "{ }" block. func block(maybeArray interface{}) string { s := toSliceString(maybeArray) if len(s) == 0 { return "{}" } return indent("{\n"+join(s, "\n")) + "\n}" } func indent(maybeString interface{}) string { if maybeString == nil { return "" } switch str := maybeString.(type) { case string: return strings.Replace(str, "\n", "\n ", -1) } return "" } var printDocASTReducer = map[string]visitor.VisitFunc{ "Name": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Name: return visitor.ActionUpdate, node.Value case map[string]interface{}: return visitor.ActionUpdate, getMapValue(node, "Value") } return visitor.ActionNoChange, nil }, "Variable": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Variable: return visitor.ActionUpdate, fmt.Sprintf("$%v", node.Name) case map[string]interface{}: return visitor.ActionUpdate, "$" + getMapValueString(node, "Name") } return visitor.ActionNoChange, nil }, // Document "Document": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Document: definitions := toSliceString(node.Definitions) return visitor.ActionUpdate, join(definitions, "\n\n") + "\n" case map[string]interface{}: definitions := toSliceString(getMapValue(node, "Definitions")) return visitor.ActionUpdate, join(definitions, "\n\n") + "\n" } return visitor.ActionNoChange, nil }, "OperationDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.OperationDefinition: op := string(node.Operation) name := fmt.Sprintf("%v", node.Name) varDefs := wrap("(", join(toSliceString(node.VariableDefinitions), ", "), ")") directives := join(toSliceString(node.Directives), " ") selectionSet := fmt.Sprintf("%v", node.SelectionSet) // Anonymous queries with no directives or variable definitions can use // the query short form. str := "" if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery { str = selectionSet } else { str = join([]string{ op, join([]string{name, varDefs}, ""), directives, selectionSet, }, " ") } return visitor.ActionUpdate, str case map[string]interface{}: op := getMapValueString(node, "Operation") name := getMapValueString(node, "Name") varDefs := wrap("(", join(toSliceString(getMapValue(node, "VariableDefinitions")), ", "), ")") directives := join(toSliceString(getMapValue(node, "Directives")), " ") selectionSet := getMapValueString(node, "SelectionSet") str := "" if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery { str = selectionSet } else { str = join([]string{ op, join([]string{name, varDefs}, ""), directives, selectionSet, }, " ") } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "VariableDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.VariableDefinition: variable := fmt.Sprintf("%v", node.Variable) ttype := fmt.Sprintf("%v", node.Type) defaultValue := fmt.Sprintf("%v", node.DefaultValue) return visitor.ActionUpdate, variable + ": " + ttype + wrap(" = ", defaultValue, "") case map[string]interface{}: variable := getMapValueString(node, "Variable") ttype := getMapValueString(node, "Type") defaultValue := getMapValueString(node, "DefaultValue") return visitor.ActionUpdate, variable + ": " + ttype + wrap(" = ", defaultValue, "") } return visitor.ActionNoChange, nil }, "SelectionSet": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.SelectionSet: str := block(node.Selections) return visitor.ActionUpdate, str case map[string]interface{}: selections := getMapValue(node, "Selections") str := block(selections) return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "Field": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Argument: name := fmt.Sprintf("%v", node.Name) value := fmt.Sprintf("%v", node.Value) return visitor.ActionUpdate, name + ": " + value case map[string]interface{}: alias := getMapValueString(node, "Alias") name := getMapValueString(node, "Name") args := toSliceString(getMapValue(node, "Arguments")) directives := toSliceString(getMapValue(node, "Directives")) selectionSet := getMapValueString(node, "SelectionSet") str := join( []string{ wrap("", alias, ": ") + name + wrap("(", join(args, ", "), ")"), join(directives, " "), selectionSet, }, " ", ) return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "Argument": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FragmentSpread: name := fmt.Sprintf("%v", node.Name) directives := toSliceString(node.Directives) return visitor.ActionUpdate, "..." + name + wrap(" ", join(directives, " "), "") case map[string]interface{}: name := getMapValueString(node, "Name") value := getMapValueString(node, "Value") return visitor.ActionUpdate, name + ": " + value } return visitor.ActionNoChange, nil }, // Fragments "FragmentSpread": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InlineFragment: typeCondition := fmt.Sprintf("%v", node.TypeCondition) directives := toSliceString(node.Directives) selectionSet := fmt.Sprintf("%v", node.SelectionSet) return visitor.ActionUpdate, "... on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet case map[string]interface{}: name := getMapValueString(node, "Name") directives := toSliceString(getMapValue(node, "Directives")) return visitor.ActionUpdate, "..." + name + wrap(" ", join(directives, " "), "") } return visitor.ActionNoChange, nil }, "InlineFragment": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case map[string]interface{}: typeCondition := getMapValueString(node, "TypeCondition") directives := toSliceString(getMapValue(node, "Directives")) selectionSet := getMapValueString(node, "SelectionSet") return visitor.ActionUpdate, join([]string{ "...", wrap("on ", typeCondition, ""), join(directives, " "), selectionSet, }, " ") } return visitor.ActionNoChange, nil }, "FragmentDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FragmentDefinition: name := fmt.Sprintf("%v", node.Name) typeCondition := fmt.Sprintf("%v", node.TypeCondition) directives := toSliceString(node.Directives) selectionSet := fmt.Sprintf("%v", node.SelectionSet) return visitor.ActionUpdate, "fragment " + name + " on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet case map[string]interface{}: name := getMapValueString(node, "Name") typeCondition := getMapValueString(node, "TypeCondition") directives := toSliceString(getMapValue(node, "Directives")) selectionSet := getMapValueString(node, "SelectionSet") return visitor.ActionUpdate, "fragment " + name + " on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet } return visitor.ActionNoChange, nil }, // Value "IntValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.IntValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "FloatValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FloatValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "StringValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.StringValue: return visitor.ActionUpdate, `"` + fmt.Sprintf("%v", node.Value) + `"` case map[string]interface{}: return visitor.ActionUpdate, `"` + getMapValueString(node, "Value") + `"` } return visitor.ActionNoChange, nil }, "BooleanValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.BooleanValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "EnumValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.EnumValue: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Value") } return visitor.ActionNoChange, nil }, "ListValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ListValue: return visitor.ActionUpdate, "[" + join(toSliceString(node.Values), ", ") + "]" case map[string]interface{}: return visitor.ActionUpdate, "[" + join(toSliceString(getMapValue(node, "Values")), ", ") + "]" } return visitor.ActionNoChange, nil }, "ObjectValue": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ObjectValue: return visitor.ActionUpdate, "{" + join(toSliceString(node.Fields), ", ") + "}" case map[string]interface{}: return visitor.ActionUpdate, "{" + join(toSliceString(getMapValue(node, "Fields")), ", ") + "}" } return visitor.ActionNoChange, nil }, "ObjectField": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ObjectField: name := fmt.Sprintf("%v", node.Name) value := fmt.Sprintf("%v", node.Value) return visitor.ActionUpdate, name + ": " + value case map[string]interface{}: name := getMapValueString(node, "Name") value := getMapValueString(node, "Value") return visitor.ActionUpdate, name + ": " + value } return visitor.ActionNoChange, nil }, // Directive "Directive": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Directive: name := fmt.Sprintf("%v", node.Name) args := toSliceString(node.Arguments) return visitor.ActionUpdate, "@" + name + wrap("(", join(args, ", "), ")") case map[string]interface{}: name := getMapValueString(node, "Name") args := toSliceString(getMapValue(node, "Arguments")) return visitor.ActionUpdate, "@" + name + wrap("(", join(args, ", "), ")") } return visitor.ActionNoChange, nil }, // Type "Named": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.Named: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Name) case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Name") } return visitor.ActionNoChange, nil }, "List": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.List: return visitor.ActionUpdate, "[" + fmt.Sprintf("%v", node.Type) + "]" case map[string]interface{}: return visitor.ActionUpdate, "[" + getMapValueString(node, "Type") + "]" } return visitor.ActionNoChange, nil }, "NonNull": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.NonNull: return visitor.ActionUpdate, fmt.Sprintf("%v", node.Type) + "!" case map[string]interface{}: return visitor.ActionUpdate, getMapValueString(node, "Type") + "!" } return visitor.ActionNoChange, nil }, // Type System Definitions "SchemaDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.SchemaDefinition: directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "schema", join(directives, " "), block(node.OperationTypes), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: operationTypes := toSliceString(getMapValue(node, "OperationTypes")) directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "schema", join(directives, " "), block(operationTypes), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "OperationTypeDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.OperationTypeDefinition: str := fmt.Sprintf("%v: %v", node.Operation, node.Type) return visitor.ActionUpdate, str case map[string]interface{}: operation := getMapValueString(node, "Operation") ttype := getMapValueString(node, "Type") str := fmt.Sprintf("%v: %v", operation, ttype) return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "ScalarDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ScalarDefinition: directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "scalar", fmt.Sprintf("%v", node.Name), join(directives, " "), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "scalar", name, join(directives, " "), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "ObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.ObjectDefinition: name := fmt.Sprintf("%v", node.Name) interfaces := toSliceString(node.Interfaces) fields := node.Fields directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "type", name, wrap("implements ", join(interfaces, ", "), ""), join(directives, " "), block(fields), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") interfaces := toSliceString(getMapValue(node, "Interfaces")) fields := getMapValue(node, "Fields") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "type", name, wrap("implements ", join(interfaces, ", "), ""), join(directives, " "), block(fields), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "FieldDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.FieldDefinition: name := fmt.Sprintf("%v", node.Name) ttype := fmt.Sprintf("%v", node.Type) args := toSliceString(node.Arguments) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := name + wrap("(", join(args, ", "), ")") + ": " + ttype + wrap(" ", join(directives, " "), "") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") ttype := getMapValueString(node, "Type") args := toSliceString(getMapValue(node, "Arguments")) directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := name + wrap("(", join(args, ", "), ")") + ": " + ttype + wrap(" ", join(directives, " "), "") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "InputValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InputValueDefinition: name := fmt.Sprintf("%v", node.Name) ttype := fmt.Sprintf("%v", node.Type) defaultValue := fmt.Sprintf("%v", node.DefaultValue) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ name + ": " + ttype, wrap("= ", defaultValue, ""), join(directives, " "), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") ttype := getMapValueString(node, "Type") defaultValue := getMapValueString(node, "DefaultValue") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ name + ": " + ttype, wrap("= ", defaultValue, ""), join(directives, " "), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "InterfaceDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InterfaceDefinition: name := fmt.Sprintf("%v", node.Name) fields := node.Fields directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "interface", name, join(directives, " "), block(fields), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") fields := getMapValue(node, "Fields") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "interface", name, join(directives, " "), block(fields), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "UnionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.UnionDefinition: name := fmt.Sprintf("%v", node.Name) types := toSliceString(node.Types) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "union", name, join(directives, " "), "= " + join(types, " | "), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") types := toSliceString(getMapValue(node, "Types")) directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "union", name, join(directives, " "), "= " + join(types, " | "), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "EnumDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.EnumDefinition: name := fmt.Sprintf("%v", node.Name) values := node.Values directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "enum", name, join(directives, " "), block(values), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") values := getMapValue(node, "Values") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "enum", name, join(directives, " "), block(values), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "EnumValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.EnumValueDefinition: name := fmt.Sprintf("%v", node.Name) directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ name, join(directives, " "), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ name, join(directives, " "), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "InputObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.InputObjectDefinition: name := fmt.Sprintf("%v", node.Name) fields := node.Fields directives := []string{} for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } str := join([]string{ "input", name, join(directives, " "), block(fields), }, " ") return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") fields := getMapValue(node, "Fields") directives := []string{} for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } str := join([]string{ "input", name, join(directives, " "), block(fields), }, " ") return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "TypeExtensionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.TypeExtensionDefinition: definition := fmt.Sprintf("%v", node.Definition) str := "extend " + definition return visitor.ActionUpdate, str case map[string]interface{}: definition := getMapValueString(node, "Definition") str := "extend " + definition return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, "DirectiveDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.DirectiveDefinition: args := wrap("(", join(toSliceString(node.Arguments), ", "), ")") str := fmt.Sprintf("directive @%v%v on %v", node.Name, args, join(toSliceString(node.Locations), " | ")) return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") locations := toSliceString(getMapValue(node, "Locations")) args := toSliceString(getMapValue(node, "Arguments")) argsStr := wrap("(", join(args, ", "), ")") str := fmt.Sprintf("directive @%v%v on %v", name, argsStr, join(locations, " | ")) return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil }, } func Print(astNode ast.Node) (printed interface{}) { defer func() interface{} { if r := recover(); r != nil { return fmt.Sprintf("%v", astNode) } return printed }() printed = visitor.Visit(astNode, &visitor.VisitorOptions{ LeaveKindMap: printDocASTReducer, }, nil) return printed }