...

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

Documentation: github.com/shurcooL/graphql

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

View as plain text