github.com/nathanstitt/genqlient@v0.3.1-0.20211028004951-a2bda3c41ab8/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 }