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