-
Notifications
You must be signed in to change notification settings - Fork 46
/
method.go
345 lines (313 loc) · 10 KB
/
method.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package meta
import (
"fmt"
"reflect"
"strings"
)
func newArg(t reflect.Type) *arg {
ret := &arg{}
// Dereference the pointer types to get at the underlying concrete type.
Loop:
for {
switch t.Kind() {
case reflect.Pointer:
ret.numPtr++
t = t.Elem()
default:
ret.pkg = t.PkgPath()
ret.typeName += t.Name()
break Loop
}
}
return ret
}
type arg struct {
pkg, typeName string
numPtr int
}
func (a *arg) normalizedPkg() string {
if a.pkg == "" {
return ""
}
// Strip the repo.../vendor/ prefix from the package path if present.
parts := strings.Split(a.pkg, "/")
// Remove vendor prefix.
for i := 0; i < len(parts); i++ {
if parts[i] == "vendor" {
parts = parts[i+1:]
break
}
}
switch strings.Join(parts, "/") {
case "google.golang.org/api/compute/v1":
return "computega."
case "google.golang.org/api/compute/v0.alpha":
return "computealpha."
case "google.golang.org/api/compute/v0.beta":
return "computebeta."
case "google.golang.org/api/networkservices/v1":
return "networkservicesga."
case "google.golang.org/api/networkservices/v1beta1":
return "networkservicesbeta."
default:
panic(fmt.Errorf("unhandled package %q", a.pkg))
}
}
func (a *arg) String() string {
var ret string
for i := 0; i < a.numPtr; i++ {
ret += "*"
}
ret += a.normalizedPkg()
ret += a.typeName
return ret
}
// newMethod returns a newly initialized method.
func newMethod(s *ServiceInfo, m reflect.Method) *Method {
ret := &Method{
ServiceInfo: s,
m: m,
kind: MethodOperation,
ReturnType: "",
}
ret.init()
return ret
}
// MethodKind is the type of method that we are generated code for.
type MethodKind int
const (
// MethodOperation is a long running method that returns an operation.
MethodOperation MethodKind = iota
// MethodGet is a method that immediately returns some data.
MethodGet MethodKind = iota
// MethodPaged is a method that returns a paged set of data.
MethodPaged MethodKind = iota
)
// Method is used to generate the calling code for non-standard methods.
type Method struct {
*ServiceInfo
m reflect.Method
kind MethodKind
// ReturnType is the return type for the method.
ReturnType string
// ItemType is the type of the individual elements returns from a
// Pages() call. This is only applicable for MethodPaged kind.
ItemType string
}
// IsOperation is true if the method is an Operation.
func (m *Method) IsOperation() bool {
return m.kind == MethodOperation
}
// IsPaged is true if the method paged.
func (m *Method) IsPaged() bool {
return m.kind == MethodPaged
}
// IsGet is true if the method simple get.
func (m *Method) IsGet() bool {
return m.kind == MethodGet
}
// argsSkip is the number of arguments to skip when generating the
// synthesized method.
func (m *Method) argsSkip() int {
if m.ServiceInfo.APIGroup == APIGroupNetworkServices {
return 2
}
switch m.keyType {
case Zonal:
return 4
case Regional:
return 4
case Global:
return 3
}
panic(fmt.Errorf("invalid KeyType %v", m.keyType))
}
// args return a list of arguments to the method, skipping the first skip
// elements. If nameArgs is true, then the arguments will include a generated
// parameter name (arg<N>). prefix will be added to the parameters.
func (m *Method) args(skip int, nameArgs bool, prefix []string) []string {
var args []*arg
fType := m.m.Func.Type()
for i := 0; i < fType.NumIn(); i++ {
t := fType.In(i)
args = append(args, newArg(t))
}
var a []string
for i := skip; i < fType.NumIn(); i++ {
if nameArgs {
a = append(a, fmt.Sprintf("arg%d %s", i-skip, args[i]))
} else {
a = append(a, args[i].String())
}
}
return append(prefix, a...)
}
// init the method. This performs some rudimentary static checking as well as
// determines the kind of method by looking at the shape (method signature) of
// the object.
func (m *Method) init() {
fType := m.m.Func.Type()
if fType.NumIn() < m.argsSkip() {
err := fmt.Errorf("method %q.%q, arity = %d which is less than required (< %d)",
m.Service, m.Name(), fType.NumIn(), m.argsSkip())
panic(err)
}
// Skipped args should all be string (they will be projectID, zone, region etc).
for i := 1; i < m.argsSkip(); i++ {
if fType.In(i).Kind() != reflect.String {
panic(fmt.Errorf("method %q.%q: skipped args can only be strings", m.Service, m.Name()))
}
}
// Return of the method must return a single value of type *xxxCall.
if fType.NumOut() != 1 || fType.Out(0).Kind() != reflect.Pointer || !strings.HasSuffix(fType.Out(0).Elem().Name(), "Call") {
panic(fmt.Errorf("method %q.%q: generator only supports methods returning an *xxxCall object",
m.Service, m.Name()))
}
returnType := fType.Out(0)
returnTypeName := fType.Out(0).Elem().Name()
// xxxCall must have a Do() method.
doMethod, ok := returnType.MethodByName("Do")
if !ok {
panic(fmt.Errorf("method %q.%q: return type %q does not have a Do() method",
m.Service, m.Name(), returnTypeName))
}
_, hasPages := returnType.MethodByName("Pages")
// Do() method must return (*T, error).
switch doMethod.Func.Type().NumOut() {
case 2:
out0 := doMethod.Func.Type().Out(0)
if out0.Kind() != reflect.Pointer {
panic(fmt.Errorf("method %q.%q: return type %q of Do() = S, _; S must be pointer type (%v)",
m.Service, m.Name(), returnTypeName, out0))
}
m.ReturnType = out0.Elem().Name()
switch {
case out0.Elem().Name() == "Operation":
m.kind = MethodOperation
case hasPages:
m.kind = MethodPaged
// Pages() returns a xxxList that has the actual list
// of objects in the xxxList.Items field.
listType := out0.Elem()
itemsField, ok := listType.FieldByName("Items")
if !ok {
panic(fmt.Errorf("method %q.%q: paged return type %q does not have a .Items field", m.Service, m.Name(), listType.Name()))
}
// itemsField will be a []*ItemType. Dereference to
// extract the ItemType.
itemsType := itemsField.Type
if itemsType.Kind() != reflect.Slice || itemsType.Elem().Kind() != reflect.Pointer {
panic(fmt.Errorf("method %q.%q: paged return type %q.Items is not an array of pointers", m.Service, m.Name(), listType.Name()))
}
m.ItemType = itemsType.Elem().Elem().Name()
default:
m.kind = MethodGet
}
// Second argument must be "error".
if doMethod.Func.Type().Out(1).Name() != "error" {
panic(fmt.Errorf("method %q.%q: return type %q of Do() = S, T; T must be 'error'",
m.Service, m.Name(), returnTypeName))
}
default:
panic(fmt.Errorf("method %q.%q: %q Do() return type is not handled by the generator",
m.Service, m.Name(), returnTypeName))
}
}
// Name is the name of the method.
func (m *Method) Name() string {
return m.m.Name
}
// CallArgs is a list of comma separated "argN" used for calling the method.
// For example, if the method has two additional arguments, this will return
// "arg0, arg1".
func (m *Method) CallArgs() string {
var args []string
for i := m.argsSkip(); i < m.m.Func.Type().NumIn(); i++ {
args = append(args, fmt.Sprintf("arg%d", i-m.argsSkip()))
}
if len(args) == 0 {
return ""
}
return fmt.Sprintf(", %s", strings.Join(args, ", "))
}
// MockHookName is the name of the hook function in the mock.
func (m *Method) MockHookName() string {
return m.m.Name + "Hook"
}
// MockHook is the definition of the hook function.
func (m *Method) MockHook() string {
args := m.args(m.argsSkip(), false, []string{
"context.Context",
"*meta.Key",
})
if m.kind == MethodPaged {
args = append(args, "*filter.F")
}
args = append(args, fmt.Sprintf("*%s", m.MockWrapType()))
args = append(args, "...Option")
switch m.kind {
case MethodOperation:
return fmt.Sprintf("%v func(%v) error", m.MockHookName(), strings.Join(args, ", "))
case MethodGet:
return fmt.Sprintf("%v func(%v) (*%v%v.%v, error)", m.MockHookName(), strings.Join(args, ", "), m.APIGroup, m.Version(), m.ReturnType)
case MethodPaged:
return fmt.Sprintf("%v func(%v) ([]*%v%v.%v, error)", m.MockHookName(), strings.Join(args, ", "), m.APIGroup, m.Version(), m.ItemType)
default:
panic(fmt.Errorf("invalid method kind: %v", m.kind))
}
}
// FcnArgs is the function signature for the definition of the method.
func (m *Method) FcnArgs() string {
args := m.args(m.argsSkip(), true, []string{
"ctx context.Context",
"key *meta.Key",
})
if m.kind == MethodPaged {
args = append(args, "fl *filter.F")
}
args = append(args, "options ...Option")
switch m.kind {
case MethodOperation:
return fmt.Sprintf("%v(%v) error", m.m.Name, strings.Join(args, ", "))
case MethodGet:
return fmt.Sprintf("%v(%v) (*%v%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.APIGroup, m.Version(), m.ReturnType)
case MethodPaged:
return fmt.Sprintf("%v(%v) ([]*%v%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.APIGroup, m.Version(), m.ItemType)
default:
panic(fmt.Errorf("invalid method kind: %v", m.kind))
}
}
// InterfaceFunc is the function declaration of the method in the interface.
func (m *Method) InterfaceFunc() string {
args := []string{
"context.Context",
"*meta.Key",
}
args = m.args(m.argsSkip(), false, args)
if m.kind == MethodPaged {
args = append(args, "*filter.F")
}
args = append(args, "...Option")
switch m.kind {
case MethodOperation:
return fmt.Sprintf("%v(%v) error", m.m.Name, strings.Join(args, ", "))
case MethodGet:
return fmt.Sprintf("%v(%v) (*%v%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.APIGroup, m.Version(), m.ReturnType)
case MethodPaged:
return fmt.Sprintf("%v(%v) ([]*%v%v.%v, error)", m.m.Name, strings.Join(args, ", "), m.APIGroup, m.Version(), m.ItemType)
default:
panic(fmt.Errorf("invalid method kind: %v", m.kind))
}
}