...

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

Documentation: github.com/cli/shurcooL-graphql/internal/jsonutil

     1  // Package jsonutil provides a function for decoding JSON
     2  // into a GraphQL query data structure.
     3  package jsonutil
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"reflect"
    12  	"strings"
    13  )
    14  
    15  // UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores
    16  // the result in the GraphQL query data structure pointed to by v.
    17  //
    18  // The implementation is created on top of the JSON tokenizer available
    19  // in "encoding/json".Decoder.
    20  func UnmarshalGraphQL(data []byte, v any) error {
    21  	dec := json.NewDecoder(bytes.NewReader(data))
    22  	dec.UseNumber()
    23  	err := (&decoder{tokenizer: dec}).Decode(v)
    24  	if err != nil {
    25  		return err
    26  	}
    27  	tok, err := dec.Token()
    28  	switch err {
    29  	case io.EOF:
    30  		// Expect to get io.EOF. There shouldn't be any more
    31  		// tokens left after we've decoded v successfully.
    32  		return nil
    33  	case nil:
    34  		return fmt.Errorf("invalid token '%v' after top-level value", tok)
    35  	default:
    36  		return err
    37  	}
    38  }
    39  
    40  // decoder is a JSON decoder that performs custom unmarshaling behavior
    41  // for GraphQL query data structures. It's implemented on top of a JSON tokenizer.
    42  type decoder struct {
    43  	tokenizer interface {
    44  		Token() (json.Token, error)
    45  	}
    46  
    47  	// Stack of what part of input JSON we're in the middle of - objects, arrays.
    48  	parseState []json.Delim
    49  
    50  	// Stacks of values where to unmarshal.
    51  	// The top of each stack is the reflect.Value where to unmarshal next JSON value.
    52  	//
    53  	// The reason there's more than one stack is because we might be unmarshaling
    54  	// a single JSON value into multiple GraphQL fragments or embedded structs, so
    55  	// we keep track of them all.
    56  	vs [][]reflect.Value
    57  }
    58  
    59  // Decode decodes a single JSON value from d.tokenizer into v.
    60  func (d *decoder) Decode(v any) error {
    61  	rv := reflect.ValueOf(v)
    62  	if rv.Kind() != reflect.Ptr {
    63  		return fmt.Errorf("cannot decode into non-pointer %T", v)
    64  	}
    65  	d.vs = [][]reflect.Value{{rv.Elem()}}
    66  	return d.decode()
    67  }
    68  
    69  // decode decodes a single JSON value from d.tokenizer into d.vs.
    70  func (d *decoder) decode() error {
    71  	// The loop invariant is that the top of each d.vs stack
    72  	// is where we try to unmarshal the next JSON value we see.
    73  	for len(d.vs) > 0 {
    74  		tok, err := d.tokenizer.Token()
    75  		if err == io.EOF {
    76  			return errors.New("unexpected end of JSON input")
    77  		} else if err != nil {
    78  			return err
    79  		}
    80  
    81  		switch {
    82  
    83  		// Are we inside an object and seeing next key (rather than end of object)?
    84  		case d.state() == '{' && tok != json.Delim('}'):
    85  			key, ok := tok.(string)
    86  			if !ok {
    87  				return errors.New("unexpected non-key in JSON input")
    88  			}
    89  			someFieldExist := false
    90  			for i := range d.vs {
    91  				v := d.vs[i][len(d.vs[i])-1]
    92  				if v.Kind() == reflect.Ptr {
    93  					v = v.Elem()
    94  				}
    95  				var f reflect.Value
    96  				if v.Kind() == reflect.Struct {
    97  					f = fieldByGraphQLName(v, key)
    98  					if f.IsValid() {
    99  						someFieldExist = true
   100  					}
   101  				}
   102  				d.vs[i] = append(d.vs[i], f)
   103  			}
   104  			if !someFieldExist {
   105  				return fmt.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs))
   106  			}
   107  
   108  			// We've just consumed the current token, which was the key.
   109  			// Read the next token, which should be the value, and let the rest of code process it.
   110  			tok, err = d.tokenizer.Token()
   111  			if err == io.EOF {
   112  				return errors.New("unexpected end of JSON input")
   113  			} else if err != nil {
   114  				return err
   115  			}
   116  
   117  		// Are we inside an array and seeing next value (rather than end of array)?
   118  		case d.state() == '[' && tok != json.Delim(']'):
   119  			someSliceExist := false
   120  			for i := range d.vs {
   121  				v := d.vs[i][len(d.vs[i])-1]
   122  				if v.Kind() == reflect.Ptr {
   123  					v = v.Elem()
   124  				}
   125  				var f reflect.Value
   126  				if v.Kind() == reflect.Slice {
   127  					v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) // v = append(v, T).
   128  					f = v.Index(v.Len() - 1)
   129  					someSliceExist = true
   130  				}
   131  				d.vs[i] = append(d.vs[i], f)
   132  			}
   133  			if !someSliceExist {
   134  				return fmt.Errorf("slice doesn't exist in any of %v places to unmarshal", len(d.vs))
   135  			}
   136  		}
   137  
   138  		switch tok := tok.(type) {
   139  		case string, json.Number, bool, nil:
   140  			// Value.
   141  
   142  			for i := range d.vs {
   143  				v := d.vs[i][len(d.vs[i])-1]
   144  				if !v.IsValid() {
   145  					continue
   146  				}
   147  				err := unmarshalValue(tok, v)
   148  				if err != nil {
   149  					return err
   150  				}
   151  			}
   152  			d.popAllVs()
   153  
   154  		case json.Delim:
   155  			switch tok {
   156  			case '{':
   157  				// Start of object.
   158  
   159  				d.pushState(tok)
   160  
   161  				frontier := make([]reflect.Value, len(d.vs)) // Places to look for GraphQL fragments/embedded structs.
   162  				for i := range d.vs {
   163  					v := d.vs[i][len(d.vs[i])-1]
   164  					frontier[i] = v
   165  					// TODO: Do this recursively or not? Add a test case if needed.
   166  					if v.Kind() == reflect.Ptr && v.IsNil() {
   167  						v.Set(reflect.New(v.Type().Elem())) // v = new(T).
   168  					}
   169  				}
   170  				// Find GraphQL fragments/embedded structs recursively, adding to frontier
   171  				// as new ones are discovered and exploring them further.
   172  				for len(frontier) > 0 {
   173  					v := frontier[0]
   174  					frontier = frontier[1:]
   175  					if v.Kind() == reflect.Ptr {
   176  						v = v.Elem()
   177  					}
   178  					if v.Kind() != reflect.Struct {
   179  						continue
   180  					}
   181  					for i := 0; i < v.NumField(); i++ {
   182  						if isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous {
   183  							// Add GraphQL fragment or embedded struct.
   184  							d.vs = append(d.vs, []reflect.Value{v.Field(i)})
   185  							frontier = append(frontier, v.Field(i))
   186  						}
   187  					}
   188  				}
   189  			case '[':
   190  				// Start of array.
   191  
   192  				d.pushState(tok)
   193  
   194  				for i := range d.vs {
   195  					v := d.vs[i][len(d.vs[i])-1]
   196  					// TODO: Confirm this is needed, write a test case.
   197  					//if v.Kind() == reflect.Ptr && v.IsNil() {
   198  					//	v.Set(reflect.New(v.Type().Elem())) // v = new(T).
   199  					//}
   200  
   201  					// Reset slice to empty (in case it had non-zero initial value).
   202  					if v.Kind() == reflect.Ptr {
   203  						v = v.Elem()
   204  					}
   205  					if v.Kind() != reflect.Slice {
   206  						continue
   207  					}
   208  					v.Set(reflect.MakeSlice(v.Type(), 0, 0)) // v = make(T, 0, 0).
   209  				}
   210  			case '}', ']':
   211  				// End of object or array.
   212  				d.popAllVs()
   213  				d.popState()
   214  			default:
   215  				return errors.New("unexpected delimiter in JSON input")
   216  			}
   217  		default:
   218  			return errors.New("unexpected token in JSON input")
   219  		}
   220  	}
   221  	return nil
   222  }
   223  
   224  // pushState pushes a new parse state s onto the stack.
   225  func (d *decoder) pushState(s json.Delim) {
   226  	d.parseState = append(d.parseState, s)
   227  }
   228  
   229  // popState pops a parse state (already obtained) off the stack.
   230  // The stack must be non-empty.
   231  func (d *decoder) popState() {
   232  	d.parseState = d.parseState[:len(d.parseState)-1]
   233  }
   234  
   235  // state reports the parse state on top of stack, or 0 if empty.
   236  func (d *decoder) state() json.Delim {
   237  	if len(d.parseState) == 0 {
   238  		return 0
   239  	}
   240  	return d.parseState[len(d.parseState)-1]
   241  }
   242  
   243  // popAllVs pops from all d.vs stacks, keeping only non-empty ones.
   244  func (d *decoder) popAllVs() {
   245  	var nonEmpty [][]reflect.Value
   246  	for i := range d.vs {
   247  		d.vs[i] = d.vs[i][:len(d.vs[i])-1]
   248  		if len(d.vs[i]) > 0 {
   249  			nonEmpty = append(nonEmpty, d.vs[i])
   250  		}
   251  	}
   252  	d.vs = nonEmpty
   253  }
   254  
   255  // fieldByGraphQLName returns an exported struct field of struct v
   256  // that matches GraphQL name, or invalid reflect.Value if none found.
   257  func fieldByGraphQLName(v reflect.Value, name string) reflect.Value {
   258  	for i := 0; i < v.NumField(); i++ {
   259  		if v.Type().Field(i).PkgPath != "" {
   260  			// Skip unexported field.
   261  			continue
   262  		}
   263  		if hasGraphQLName(v.Type().Field(i), name) {
   264  			return v.Field(i)
   265  		}
   266  	}
   267  	return reflect.Value{}
   268  }
   269  
   270  // hasGraphQLName reports whether struct field f has GraphQL name.
   271  func hasGraphQLName(f reflect.StructField, name string) bool {
   272  	value, ok := f.Tag.Lookup("graphql")
   273  	if !ok {
   274  		// TODO: caseconv package is relatively slow. Optimize it, then consider using it here.
   275  		//return caseconv.MixedCapsToLowerCamelCase(f.Name) == name
   276  		return strings.EqualFold(f.Name, name)
   277  	}
   278  	value = strings.TrimSpace(value) // TODO: Parse better.
   279  	if strings.HasPrefix(value, "...") {
   280  		// GraphQL fragment. It doesn't have a name.
   281  		return false
   282  	}
   283  	// Cut off anything that follows the field name,
   284  	// such as field arguments, aliases, directives.
   285  	if i := strings.IndexAny(value, "(:@"); i != -1 {
   286  		value = value[:i]
   287  	}
   288  	return strings.TrimSpace(value) == name
   289  }
   290  
   291  // isGraphQLFragment reports whether struct field f is a GraphQL fragment.
   292  func isGraphQLFragment(f reflect.StructField) bool {
   293  	value, ok := f.Tag.Lookup("graphql")
   294  	if !ok {
   295  		return false
   296  	}
   297  	value = strings.TrimSpace(value) // TODO: Parse better.
   298  	return strings.HasPrefix(value, "...")
   299  }
   300  
   301  // unmarshalValue unmarshals JSON value into v.
   302  // Argument v must be addressable and not obtained by the use of unexported
   303  // struct fields, otherwise unmarshalValue will panic.
   304  func unmarshalValue(value json.Token, v reflect.Value) error {
   305  	b, err := json.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it).
   306  	if err != nil {
   307  		return err
   308  	}
   309  	return json.Unmarshal(b, v.Addr().Interface())
   310  }
   311  

View as plain text