// Ths contents of this file are taken from https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/query.go // because they are not exported by the module, and we would like to use them in building the githubv4mock test utility. // // The original license, copied from https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/LICENSE // // MIT License // Copyright (c) 2017 Dmitri Shuralyov // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. package githubv4mock import ( "bytes" "encoding/json" "io" "reflect" "sort" "github.com/shurcooL/graphql/ident" ) func constructQuery(v any, variables map[string]any) string { query := query(v) if len(variables) > 0 { return "query(" + queryArguments(variables) + ")" + query } return query } func constructMutation(v any, variables map[string]any) string { query := query(v) if len(variables) > 0 { return "mutation(" + queryArguments(variables) + ")" + query } return "mutation" + query } // queryArguments constructs a minified arguments string for variables. // // E.g., map[string]any{"a": Int(123), "b": NewBoolean(true)} -> "$a:Int!$b:Boolean". func queryArguments(variables map[string]any) string { // Sort keys in order to produce deterministic output for testing purposes. // TODO: If tests can be made to work with non-deterministic output, then no need to sort. keys := make([]string, 0, len(variables)) for k := range variables { keys = append(keys, k) } sort.Strings(keys) var buf bytes.Buffer for _, k := range keys { _, _ = io.WriteString(&buf, "$") _, _ = io.WriteString(&buf, k) _, _ = io.WriteString(&buf, ":") writeArgumentType(&buf, reflect.TypeOf(variables[k]), true) // Don't insert a comma here. // Commas in GraphQL are insignificant, and we want minified output. // See https://spec.graphql.org/October2021/#sec-Insignificant-Commas. } return buf.String() } // writeArgumentType writes a minified GraphQL type for t to w. // value indicates whether t is a value (required) type or pointer (optional) type. // If value is true, then "!" is written at the end of t. func writeArgumentType(w io.Writer, t reflect.Type, value bool) { if t.Kind() == reflect.Ptr { // Pointer is an optional type, so no "!" at the end of the pointer's underlying type. writeArgumentType(w, t.Elem(), false) return } switch t.Kind() { case reflect.Slice, reflect.Array: // List. E.g., "[Int]". _, _ = io.WriteString(w, "[") writeArgumentType(w, t.Elem(), true) _, _ = io.WriteString(w, "]") default: // Named type. E.g., "Int". name := t.Name() if name == "string" { // HACK: Workaround for https://github.com/shurcooL/githubv4/issues/12. name = "ID" } _, _ = io.WriteString(w, name) } if value { // Value is a required type, so add "!" to the end. _, _ = io.WriteString(w, "!") } } // query uses writeQuery to recursively construct // a minified query string from the provided struct v. // // E.g., struct{Foo Int, BarBaz *Boolean} -> "{foo,barBaz}". func query(v any) string { var buf bytes.Buffer writeQuery(&buf, reflect.TypeOf(v), false) return buf.String() } // writeQuery writes a minified query for t to w. // If inline is true, the struct fields of t are inlined into parent struct. func writeQuery(w io.Writer, t reflect.Type, inline bool) { switch t.Kind() { case reflect.Ptr, reflect.Slice: writeQuery(w, t.Elem(), false) case reflect.Struct: // If the type implements json.Unmarshaler, it's a scalar. Don't expand it. if reflect.PointerTo(t).Implements(jsonUnmarshaler) { return } if !inline { _, _ = io.WriteString(w, "{") } for i := 0; i < t.NumField(); i++ { if i != 0 { _, _ = io.WriteString(w, ",") } f := t.Field(i) value, ok := f.Tag.Lookup("graphql") inlineField := f.Anonymous && !ok if !inlineField { if ok { _, _ = io.WriteString(w, value) } else { _, _ = io.WriteString(w, ident.ParseMixedCaps(f.Name).ToLowerCamelCase()) } } writeQuery(w, f.Type, inlineField) } if !inline { _, _ = io.WriteString(w, "}") } } } var jsonUnmarshaler = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()