github.com/Desuuuu/genqlient@v0.5.3/generate/names.go (about)

     1  package generate
     2  
     3  // This file generates the names for genqlient's generated types.  This is
     4  // somewhat tricky because the names need to be unique, stable, and, to the
     5  // extent possible, human-readable and -writable.  See docs/DESIGN.md for an
     6  // overview of the considerations; in short, we need long names.
     7  //
     8  // Specifically, the names we generate are of the form:
     9  //	MyOperationMyFieldMyTypeMySubFieldMySubType
    10  // We need the "MyOperation" prefix because different operations may have
    11  // different fields selected in the same "location" within the query.  We need
    12  // to include the field-path, because even within the same query, the same
    13  // GraphQL type may have different selections in different locations.
    14  // Including the types along the path is only strictly necessary in the case of
    15  // interfaces, where, in a query like
    16  //	query Q {
    17  //		f {  # type: I
    18  //			... on T { g { h } }
    19  //			... on U { g { h } }
    20  //		}
    21  //	}
    22  // the type of <response>.f.g may be different depending on whether the
    23  // concrete type is a T or a U; if we simply called the types QFG they'd
    24  // collide.  We could in principle omit the types where there are no interfaces
    25  // in sight, but having the last thing in the name be the actual GraphQL type
    26  // name (MySubType in the first example) makes things more readable, and the
    27  // value of consistency seems greater than the value of brevity, given the
    28  // types are quite verbose either way.  Note that in all cases the "MyField" is
    29  // the alias of the field -- the name it has in this query -- since you could
    30  // have `query Q { a: f { b }, c: f { d } }` and Q.A and Q.C must have
    31  // different types.
    32  //
    33  // One subtlety in the above description is: is the "MyType" the interface or
    34  // the implementation?  When it's a suffix, the answer is both: we generate
    35  // both MyFieldMyInterface and MyFieldMyImplementation, and the latter, in Go,
    36  // implements the former.  (See docs/DESIGN.md for more.)  But as an infix, we
    37  // use the type on which the field is requested.  Concretely, the following
    38  // schema and query:
    39  //	type Query { f: I }
    40  //	interface I { g: G }
    41  //	type T implements I { g: G, h: H }
    42  //	type U implements I { g: G, h: H }
    43  //	type G { g1: String, g2: String }
    44  //	type H { h1: String, h2: String, h3: String, h4: String }
    45  //
    46  //	query Q {
    47  //		f {
    48  //			g { g1 g2 }
    49  //			... on T { h { h1 h2 } }
    50  //			... on U { h { h3 h4 } }
    51  //		}
    52  //	}
    53  // The field g must have type QFIG (not QFTG and QFHG), so that QFI's method
    54  // GetG() can return a consistent type.  But the fields h must have types QFTH
    55  // and QFUH (not QFIH), because the two are different: the former has fields h1
    56  // and h2, whereas the latter has fields h3 and h4.  So, in summary, since `g`
    57  // is selected in a context of type I, it uses that (interface) type in its
    58  // type-name, and `h` is selected in contexts of types T and U, they use those
    59  // (implementation) types in their type-names.
    60  //
    61  // We do shorten the names in one case: if the name of a field ends with
    62  // the name of its type, we omit the type name, avoiding types like
    63  // MyQueryUserUser when querying a field of type user and value user.  Note we
    64  // do not do this for field names, both because it's less common, and because
    65  // in `query Q { user { user { id } } }` we do need both QUser and QUserUser --
    66  // they have different fields.
    67  //
    68  // Note that there are a few potential collisions from this algorithm:
    69  // - When generating Go types for GraphQL interface types, we generate both
    70  //   ...MyFieldMyInterfaceType and ...MyFieldMyImplType.  If an interface's
    71  //   name is a suffix of its implementation's name, and both are suffixes of a
    72  //   field of that type, we'll shorten both, resulting in a collision.
    73  // - Names of different casing (e.g. fields `myField` and `MyField`) can
    74  //   collide (the first is standard usage but both are legal).
    75  // - We don't put a special character between parts, so fields like
    76  //		query Q {
    77  //			ab { ... }  # type: C
    78  //			abc { ... } # type: C
    79  //			a { ... }   # type: BC
    80  //		}
    81  //   can collide.
    82  // All cases seem fairly rare in practice; eventually we'll likely allow users
    83  // the ability to specify their own names, which they could use to avoid this
    84  // (see https://github.com/Khan/genqlient/issues/12).
    85  // TODO(benkraft): We should probably at least try to detect it and bail.
    86  //
    87  // To implement all of the above, as we traverse the operation (and schema) in
    88  // convert.go, we keep track of a list of parts to prefix to our type-names.
    89  // The list always ends with a field, not a type; and we extend it when
    90  // traversing fields, to allow for correct handling of the interface case
    91  // discussed above.  This file implements the actual maintenance of that
    92  // prefix, and the code to compute the actual type-name from it.
    93  //
    94  // Note that input objects and enums are handled separately (inline in
    95  // convertDefinition) since the same considerations don't apply and their names
    96  // are thus quite simple.  We also specially-handle the type of the toplevel
    97  // response object (inline in convertOperation).
    98  
    99  import (
   100  	"strings"
   101  
   102  	"github.com/vektah/gqlparser/v2/ast"
   103  )
   104  
   105  // Yes, a linked list!  Of name-prefixes in *reverse* order, i.e. from end to
   106  // start.
   107  //
   108  // We could use a stack -- it would probably be marginally
   109  // more efficient -- but then the caller would have to know more about how to
   110  // manage it safely.  Using a list, and treating it as immutable, makes it
   111  // easy.
   112  type prefixList struct {
   113  	head string // the list goes back-to-front, so this is the *last* prefix
   114  	tail *prefixList
   115  }
   116  
   117  // creates a new one-element list
   118  func newPrefixList(item string) *prefixList {
   119  	return &prefixList{head: item}
   120  }
   121  
   122  func joinPrefixList(prefix *prefixList) string {
   123  	var reversed []string
   124  	for ; prefix != nil; prefix = prefix.tail {
   125  		reversed = append(reversed, prefix.head)
   126  	}
   127  	l := len(reversed)
   128  	for i := 0; i < l/2; i++ {
   129  		reversed[i], reversed[l-1-i] = reversed[l-1-i], reversed[i]
   130  	}
   131  	return strings.Join(reversed, "")
   132  }
   133  
   134  // Given a prefix-list, and the next type-name, compute the prefix-list with
   135  // that type-name added (if applicable).  The returned value is not a valid
   136  // prefix-list, since it ends with a type, not a field (see top-of-file
   137  // comment), but it's used to construct both the type-names from the input and
   138  // the next prefix-list.
   139  func typeNameParts(prefix *prefixList, typeName string) *prefixList {
   140  	// GraphQL types are conventionally UpperCamelCase, but it's not required;
   141  	// our names will look best if they are.
   142  	typeName = upperFirst(typeName)
   143  	// If the prefix has just one part, that's the operation-name.  There's no
   144  	// need to add "Query" or "Mutation".  (Zero should never happen.)
   145  	if prefix == nil || prefix.tail == nil ||
   146  		// If the name-so-far ends with this type's name, omit the
   147  		// type-name (see the "shortened" case in the top-of-file comment).
   148  		strings.HasSuffix(joinPrefixList(prefix), typeName) {
   149  		return prefix
   150  	}
   151  	return &prefixList{typeName, prefix}
   152  }
   153  
   154  // Given a prefix-list, and a field, compute the next prefix-list, which will
   155  // be used for that field's selections.
   156  func nextPrefix(prefix *prefixList, field *ast.Field) *prefixList {
   157  	// Add the type.
   158  	prefix = typeNameParts(prefix, field.ObjectDefinition.Name)
   159  	// Add the field (there's no shortening here, see top-of-file comment).
   160  	prefix = &prefixList{upperFirst(field.Alias), prefix}
   161  	return prefix
   162  }
   163  
   164  // Given a prefix-list, and the GraphQL of the current type, compute the name
   165  // we should give it in Go.
   166  func makeTypeName(prefix *prefixList, typeName string) string {
   167  	return joinPrefixList(typeNameParts(prefix, typeName))
   168  }
   169  
   170  // Like makeTypeName, but append typeName unconditionally.
   171  //
   172  // This is used for when you specify a type-name for a field of interface
   173  // type; we use YourName for the interface, but need to do YourNameImplName for
   174  // the implementations.
   175  func makeLongTypeName(prefix *prefixList, typeName string) string {
   176  	typeName = upperFirst(typeName)
   177  	return joinPrefixList(&prefixList{typeName, prefix})
   178  }