1/*
2Copyright 2018 Google LLC
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8https://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package meta
18
19import (
20	"errors"
21	"fmt"
22	"reflect"
23	"sort"
24)
25
26// ServiceInfo defines the entry for a Service that code will be generated for.
27type ServiceInfo struct {
28	// Object is the Go name of the object type that the service deals
29	// with. Example: "ForwardingRule".
30	Object string
31	// Service is the Go name of the service struct i.e. where the methods
32	// are defined. Examples: "GlobalForwardingRules".
33	Service string
34	// Resource is the plural noun of the resource in the compute API URL (e.g.
35	// "forwardingRules").
36	Resource string
37	// version if unspecified will be assumed to be VersionGA.
38	version     Version
39	keyType     KeyType
40	serviceType reflect.Type
41
42	additionalMethods   []string
43	options             int
44	aggregatedListField string
45}
46
47// Version returns the version of the Service, defaulting to GA if APIVersion
48// is empty.
49func (i *ServiceInfo) Version() Version {
50	if i.version == "" {
51		return VersionGA
52	}
53	return i.version
54}
55
56// VersionTitle returns the capitalized golang CamelCase name for the version.
57func (i *ServiceInfo) VersionTitle() string {
58	switch i.Version() {
59	case VersionGA:
60		return "GA"
61	case VersionAlpha:
62		return "Alpha"
63	case VersionBeta:
64		return "Beta"
65	}
66	panic(fmt.Errorf("invalid version %q", i.Version()))
67}
68
69// WrapType is the name of the wrapper service type.
70func (i *ServiceInfo) WrapType() string {
71	switch i.Version() {
72	case VersionGA:
73		return i.Service
74	case VersionAlpha:
75		return "Alpha" + i.Service
76	case VersionBeta:
77		return "Beta" + i.Service
78	}
79	return "Invalid"
80}
81
82// WrapTypeOps is the name of the additional operations type.
83func (i *ServiceInfo) WrapTypeOps() string {
84	return i.WrapType() + "Ops"
85}
86
87// FQObjectType is fully qualified name of the object (e.g. compute.Instance).
88func (i *ServiceInfo) FQObjectType() string {
89	return fmt.Sprintf("%v.%v", i.Version(), i.Object)
90}
91
92// ObjectListType is the compute List type for the object (contains Items field).
93func (i *ServiceInfo) ObjectListType() string {
94	return fmt.Sprintf("%v.%vList", i.Version(), i.Object)
95}
96
97// ObjectAggregatedListType is the compute List type for the object (contains Items field).
98func (i *ServiceInfo) ObjectAggregatedListType() string {
99	return fmt.Sprintf("%v.%vAggregatedList", i.Version(), i.Object)
100}
101
102// MockWrapType is the name of the concrete mock for this type.
103func (i *ServiceInfo) MockWrapType() string {
104	return "Mock" + i.WrapType()
105}
106
107// MockField is the name of the field in the mock struct.
108func (i *ServiceInfo) MockField() string {
109	return "Mock" + i.WrapType()
110}
111
112// GCEWrapType is the name of the GCE wrapper type.
113func (i *ServiceInfo) GCEWrapType() string {
114	return "GCE" + i.WrapType()
115}
116
117// Field is the name of the GCE struct.
118func (i *ServiceInfo) Field() string {
119	return "gce" + i.WrapType()
120}
121
122// Methods returns a list of additional methods to generate code for.
123func (i *ServiceInfo) Methods() []*Method {
124	methods := map[string]bool{}
125	for _, m := range i.additionalMethods {
126		methods[m] = true
127	}
128
129	var ret []*Method
130	for j := 0; j < i.serviceType.NumMethod(); j++ {
131		m := i.serviceType.Method(j)
132		if _, ok := methods[m.Name]; !ok {
133			continue
134		}
135		ret = append(ret, newMethod(i, m))
136		methods[m.Name] = false
137	}
138
139	for k, b := range methods {
140		if b {
141			panic(fmt.Errorf("method %q was not found in service %q", k, i.Service))
142		}
143	}
144
145	return ret
146}
147
148// KeyIsGlobal is true if the key is global.
149func (i *ServiceInfo) KeyIsGlobal() bool {
150	return i.keyType == Global
151}
152
153// KeyIsRegional is true if the key is regional.
154func (i *ServiceInfo) KeyIsRegional() bool {
155	return i.keyType == Regional
156}
157
158// KeyIsZonal is true if the key is zonal.
159func (i *ServiceInfo) KeyIsZonal() bool {
160	return i.keyType == Zonal
161}
162
163// KeyIsProject is true if the key represents the project resource.
164func (i *ServiceInfo) KeyIsProject() bool {
165	// Projects are a special resource for ResourceId because there is no 'key' value. This func
166	// is used by the generator to not accept a key parameter.
167	return i.Service == "Projects"
168}
169
170// MakeKey returns the call used to create the appropriate key type.
171func (i *ServiceInfo) MakeKey(name, location string) string {
172	switch i.keyType {
173	case Global:
174		return fmt.Sprintf("GlobalKey(%q)", name)
175	case Regional:
176		return fmt.Sprintf("RegionalKey(%q, %q)", name, location)
177	case Zonal:
178		return fmt.Sprintf("ZonalKey(%q, %q)", name, location)
179	}
180	return "Invalid"
181}
182
183// GenerateGet is true if the method is to be generated.
184func (i *ServiceInfo) GenerateGet() bool {
185	return i.options&NoGet == 0
186}
187
188// GenerateList is true if the method is to be generated.
189func (i *ServiceInfo) GenerateList() bool {
190	return i.options&NoList == 0
191}
192
193// GenerateDelete is true if the method is to be generated.
194func (i *ServiceInfo) GenerateDelete() bool {
195	return i.options&NoDelete == 0
196}
197
198// GenerateInsert is true if the method is to be generated.
199func (i *ServiceInfo) GenerateInsert() bool {
200	return i.options&NoInsert == 0
201}
202
203// GenerateCustomOps is true if we should generated a xxxOps interface for
204// adding additional methods to the generated interface.
205func (i *ServiceInfo) GenerateCustomOps() bool {
206	return i.options&CustomOps != 0
207}
208
209// AggregatedList is true if the method is to be generated.
210func (i *ServiceInfo) AggregatedList() bool {
211	return i.options&AggregatedList != 0
212}
213
214// AggregatedListField is the name of the field used for the aggregated list
215// call. This is typically the same as the name of the service, but can be
216// customized by setting the aggregatedListField field.
217func (i *ServiceInfo) AggregatedListField() string {
218	if i.aggregatedListField == "" {
219		return i.Service
220	}
221	return i.aggregatedListField
222}
223
224// ServiceGroup is a grouping of the same service but at different API versions.
225type ServiceGroup struct {
226	Alpha *ServiceInfo
227	Beta  *ServiceInfo
228	GA    *ServiceInfo
229}
230
231// Service returns any ServiceInfo string belonging to the ServiceGroup.
232func (sg *ServiceGroup) Service() string {
233	return sg.ServiceInfo().Service
234}
235
236// ServiceInfo returns any ServiceInfo object belonging to the ServiceGroup.
237func (sg *ServiceGroup) ServiceInfo() *ServiceInfo {
238	switch {
239	case sg.GA != nil:
240		return sg.GA
241	case sg.Alpha != nil:
242		return sg.Alpha
243	case sg.Beta != nil:
244		return sg.Beta
245	default:
246		panic(errors.New("service group is empty"))
247	}
248}
249
250// HasGA returns true if this object has a GA representation.
251func (sg *ServiceGroup) HasGA() bool {
252	return sg.GA != nil
253}
254
255// HasAlpha returns true if this object has a Alpha representation.
256func (sg *ServiceGroup) HasAlpha() bool {
257	return sg.Alpha != nil
258}
259
260// HasBeta returns true if this object has a Beta representation.
261func (sg *ServiceGroup) HasBeta() bool {
262	return sg.Beta != nil
263}
264
265// groupServices together by version.
266func groupServices(services []*ServiceInfo) map[string]*ServiceGroup {
267	ret := map[string]*ServiceGroup{}
268	for _, si := range services {
269		if _, ok := ret[si.Service]; !ok {
270			ret[si.Service] = &ServiceGroup{}
271		}
272		group := ret[si.Service]
273		switch si.Version() {
274		case VersionAlpha:
275			group.Alpha = si
276		case VersionBeta:
277			group.Beta = si
278		case VersionGA:
279			group.GA = si
280		}
281	}
282	return ret
283}
284
285// AllServicesByGroup is a map of service name to ServicesGroup.
286var AllServicesByGroup map[string]*ServiceGroup
287
288// SortedServicesGroups is a slice of Servicegroup sorted by Service name.
289var SortedServicesGroups []*ServiceGroup
290
291func init() {
292	AllServicesByGroup = groupServices(AllServices)
293
294	for _, sg := range AllServicesByGroup {
295		SortedServicesGroups = append(SortedServicesGroups, sg)
296	}
297	sort.Slice(SortedServicesGroups, func(i, j int) bool {
298		return SortedServicesGroups[i].Service() < SortedServicesGroups[j].Service()
299	})
300}
301