...

Source file src/github.com/cli/shurcooL-graphql/query.go

Documentation: github.com/cli/shurcooL-graphql

     1  package graphql
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"reflect"
     8  	"sort"
     9  
    10  	"github.com/cli/shurcooL-graphql/ident"
    11  )
    12  
    13  func constructQuery(v any, variables map[string]any, queryName string) string {
    14  	query := query(v)
    15  	if len(variables) > 0 {
    16  		return "query" + queryNameFormat(queryName) + "(" + queryArguments(variables) + ")" + query
    17  	} else if queryName != "" {
    18  		return "query" + queryNameFormat(queryName) + query
    19  	}
    20  	return query
    21  }
    22  
    23  func constructMutation(v any, variables map[string]any, queryName string) string {
    24  	query := query(v)
    25  	if len(variables) > 0 {
    26  		return "mutation" + queryNameFormat(queryName) + "(" + queryArguments(variables) + ")" + query
    27  	}
    28  	return "mutation" + queryNameFormat(queryName) + query
    29  }
    30  
    31  func queryNameFormat(n string) string {
    32  	if n != "" {
    33  		return " " + n
    34  	}
    35  	return n
    36  }
    37  
    38  // queryArguments constructs a minified arguments string for variables.
    39  //
    40  // E.g., map[string]any{"a": Int(123), "b": NewBoolean(true)} -> "$a:Int!$b:Boolean".
    41  func queryArguments(variables map[string]any) string {
    42  	// Sort keys in order to produce deterministic output for testing purposes.
    43  	// TODO: If tests can be made to work with non-deterministic output, then no need to sort.
    44  	keys := make([]string, 0, len(variables))
    45  	for k := range variables {
    46  		keys = append(keys, k)
    47  	}
    48  	sort.Strings(keys)
    49  
    50  	var buf bytes.Buffer
    51  	for _, k := range keys {
    52  		_, _ = io.WriteString(&buf, "$")
    53  		_, _ = io.WriteString(&buf, k)
    54  		_, _ = io.WriteString(&buf, ":")
    55  		writeArgumentType(&buf, reflect.TypeOf(variables[k]), true)
    56  		// Don't insert a comma here.
    57  		// Commas in GraphQL are insignificant, and we want minified output.
    58  		// See https://spec.graphql.org/October2021/#sec-Insignificant-Commas.
    59  	}
    60  	return buf.String()
    61  }
    62  
    63  // writeArgumentType writes a minified GraphQL type for t to w.
    64  // Argument value indicates whether t is a value (required) type or pointer (optional) type.
    65  // If value is true, then "!" is written at the end of t.
    66  func writeArgumentType(w io.Writer, t reflect.Type, value bool) {
    67  	if t.Kind() == reflect.Ptr {
    68  		// Pointer is an optional type, so no "!" at the end of the pointer's underlying type.
    69  		writeArgumentType(w, t.Elem(), false)
    70  		return
    71  	}
    72  
    73  	switch t.Kind() {
    74  	case reflect.Slice, reflect.Array:
    75  		// List. E.g., "[Int]".
    76  		_, _ = io.WriteString(w, "[")
    77  		writeArgumentType(w, t.Elem(), true)
    78  		_, _ = io.WriteString(w, "]")
    79  	default:
    80  		// Named type. E.g., "Int".
    81  		name := t.Name()
    82  		if name == "string" { // HACK: Workaround for https://github.com/shurcooL/githubv4/issues/12.
    83  			name = "ID"
    84  		}
    85  		_, _ = io.WriteString(w, name)
    86  	}
    87  
    88  	if value {
    89  		// Value is a required type, so add "!" to the end.
    90  		_, _ = io.WriteString(w, "!")
    91  	}
    92  }
    93  
    94  // query uses writeQuery to recursively construct
    95  // a minified query string from the provided struct v.
    96  //
    97  // E.g., struct{Foo Int, BarBaz *Boolean} -> "{foo,barBaz}".
    98  func query(v any) string {
    99  	var buf bytes.Buffer
   100  	writeQuery(&buf, reflect.TypeOf(v), false)
   101  	return buf.String()
   102  }
   103  
   104  // writeQuery writes a minified query for t to w.
   105  // If inline is true, the struct fields of t are inlined into parent struct.
   106  func writeQuery(w io.Writer, t reflect.Type, inline bool) {
   107  	switch t.Kind() {
   108  	case reflect.Ptr, reflect.Slice:
   109  		writeQuery(w, t.Elem(), false)
   110  	case reflect.Struct:
   111  		// If the type implements json.Unmarshaler, it's a scalar. Don't expand it.
   112  		if reflect.PtrTo(t).Implements(jsonUnmarshaler) {
   113  			return
   114  		}
   115  		if !inline {
   116  			_, _ = io.WriteString(w, "{")
   117  		}
   118  		for i := 0; i < t.NumField(); i++ {
   119  			if i != 0 {
   120  				_, _ = io.WriteString(w, ",")
   121  			}
   122  			f := t.Field(i)
   123  			value, ok := f.Tag.Lookup("graphql")
   124  			inlineField := f.Anonymous && !ok
   125  			if !inlineField {
   126  				if ok {
   127  					_, _ = io.WriteString(w, value)
   128  				} else {
   129  					_, _ = io.WriteString(w, ident.ParseMixedCaps(f.Name).ToLowerCamelCase())
   130  				}
   131  			}
   132  			writeQuery(w, f.Type, inlineField)
   133  		}
   134  		if !inline {
   135  			_, _ = io.WriteString(w, "}")
   136  		}
   137  	}
   138  }
   139  
   140  var jsonUnmarshaler = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
   141  

View as plain text