1// +build codegen
2
3package api
4
5import (
6	"fmt"
7	"regexp"
8	"strings"
9)
10
11// updateTopLevelShapeReferences moves resultWrapper, locationName, and
12// xmlNamespace traits from toplevel shape references to the toplevel
13// shapes for easier code generation
14func (a *API) updateTopLevelShapeReferences() {
15	for _, o := range a.Operations {
16		// these are for REST-XML services
17		if o.InputRef.LocationName != "" {
18			o.InputRef.Shape.LocationName = o.InputRef.LocationName
19		}
20		if o.InputRef.Location != "" {
21			o.InputRef.Shape.Location = o.InputRef.Location
22		}
23		if o.InputRef.Payload != "" {
24			o.InputRef.Shape.Payload = o.InputRef.Payload
25		}
26		if o.InputRef.XMLNamespace.Prefix != "" {
27			o.InputRef.Shape.XMLNamespace.Prefix = o.InputRef.XMLNamespace.Prefix
28		}
29		if o.InputRef.XMLNamespace.URI != "" {
30			o.InputRef.Shape.XMLNamespace.URI = o.InputRef.XMLNamespace.URI
31		}
32	}
33
34}
35
36// writeShapeNames sets each shape's API and shape name values. Binding the
37// shape to its parent API.
38func (a *API) writeShapeNames() {
39	for n, s := range a.Shapes {
40		s.API = a
41		s.ShapeName = n
42	}
43}
44
45func (a *API) resolveReferences() {
46	resolver := referenceResolver{API: a, visited: map[*ShapeRef]bool{}}
47
48	for _, s := range a.Shapes {
49		resolver.resolveShape(s)
50	}
51
52	for _, o := range a.Operations {
53		o.API = a // resolve parent reference
54
55		resolver.resolveReference(&o.InputRef)
56		resolver.resolveReference(&o.OutputRef)
57
58		// Resolve references for errors also
59		for i := range o.ErrorRefs {
60			resolver.resolveReference(&o.ErrorRefs[i])
61			o.ErrorRefs[i].Shape.Exception = true
62			o.ErrorRefs[i].Shape.ErrorInfo.Type = o.ErrorRefs[i].Shape.ShapeName
63		}
64	}
65}
66
67// A referenceResolver provides a way to resolve shape references to
68// shape definitions.
69type referenceResolver struct {
70	*API
71	visited map[*ShapeRef]bool
72}
73
74// resolveReference updates a shape reference to reference the API and
75// its shape definition. All other nested references are also resolved.
76func (r *referenceResolver) resolveReference(ref *ShapeRef) {
77	if ref.ShapeName == "" {
78		return
79	}
80
81	shape, ok := r.API.Shapes[ref.ShapeName]
82	if !ok {
83		panic(fmt.Sprintf("unable resolve reference, %s", ref.ShapeName))
84	}
85
86	if ref.JSONValue {
87		ref.ShapeName = "JSONValue"
88		if _, ok := r.API.Shapes[ref.ShapeName]; !ok {
89			r.API.Shapes[ref.ShapeName] = &Shape{
90				API:       r.API,
91				ShapeName: "JSONValue",
92				Type:      "jsonvalue",
93				ValueRef: ShapeRef{
94					JSONValue: true,
95				},
96			}
97		}
98	}
99
100	ref.API = r.API   // resolve reference back to API
101	ref.Shape = shape // resolve shape reference
102
103	if r.visited[ref] {
104		return
105	}
106	r.visited[ref] = true
107
108	shape.refs = append(shape.refs, ref) // register the ref
109
110	// resolve shape's references, if it has any
111	r.resolveShape(shape)
112}
113
114// resolveShape resolves a shape's Member Key Value, and nested member
115// shape references.
116func (r *referenceResolver) resolveShape(shape *Shape) {
117	r.resolveReference(&shape.MemberRef)
118	r.resolveReference(&shape.KeyRef)
119	r.resolveReference(&shape.ValueRef)
120	for _, m := range shape.MemberRefs {
121		r.resolveReference(m)
122	}
123}
124
125// fixStutterNames fixes all name struttering based on Go naming conventions.
126// "Stuttering" is when the prefix of a structure or function matches the
127// package name (case insensitive).
128func (a *API) fixStutterNames() {
129	str, end := a.StructName(), ""
130	if len(str) > 1 {
131		l := len(str) - 1
132		str, end = str[0:l], str[l:]
133	}
134	re := regexp.MustCompile(fmt.Sprintf(`\A(?i:%s)%s`, str, end))
135
136	for name, op := range a.Operations {
137		newName := re.ReplaceAllString(name, "")
138		if newName != name && len(newName) > 0 {
139			delete(a.Operations, name)
140			a.Operations[newName] = op
141		}
142		op.ExportedName = newName
143	}
144
145	for k, s := range a.Shapes {
146		newName := re.ReplaceAllString(k, "")
147		if newName != s.ShapeName && len(newName) > 0 {
148			s.Rename(newName)
149		}
150	}
151}
152
153// renameExportable renames all operation names to be exportable names.
154// All nested Shape names are also updated to the exportable variant.
155func (a *API) renameExportable() {
156	for name, op := range a.Operations {
157		newName := a.ExportableName(name)
158		if newName != name {
159			delete(a.Operations, name)
160			a.Operations[newName] = op
161		}
162		op.ExportedName = newName
163	}
164
165	for k, s := range a.Shapes {
166		// FIXME SNS has lower and uppercased shape names with the same name,
167		// except the lowercased variant is used exclusively for string and
168		// other primitive types. Renaming both would cause a collision.
169		// We work around this by only renaming the structure shapes.
170		if s.Type == "string" {
171			continue
172		}
173
174		for mName, member := range s.MemberRefs {
175			ref := s.MemberRefs[mName]
176			ref.OrigShapeName = mName
177			s.MemberRefs[mName] = ref
178
179			newName := a.ExportableName(mName)
180			if newName != mName {
181				delete(s.MemberRefs, mName)
182				s.MemberRefs[newName] = member
183
184				// also apply locationName trait so we keep the old one
185				// but only if there's no locationName trait on ref or shape
186				if member.LocationName == "" && member.Shape.LocationName == "" {
187					member.LocationName = mName
188				}
189			}
190
191			if newName == "_" {
192				panic("Shape " + s.ShapeName + " uses reserved member name '_'")
193			}
194		}
195
196		newName := a.ExportableName(k)
197		if newName != s.ShapeName {
198			s.Rename(newName)
199		}
200
201		s.Payload = a.ExportableName(s.Payload)
202
203		// fix required trait names
204		for i, n := range s.Required {
205			s.Required[i] = a.ExportableName(n)
206		}
207	}
208
209	for _, s := range a.Shapes {
210		// fix enum names
211		if s.IsEnum() {
212			s.EnumConsts = make([]string, len(s.Enum))
213			for i := range s.Enum {
214				shape := s.ShapeName
215				shape = strings.ToUpper(shape[0:1]) + shape[1:]
216				s.EnumConsts[i] = shape + s.EnumName(i)
217			}
218		}
219	}
220}
221
222// renameCollidingFields will rename any fields that uses an SDK or Golang
223// specific name.
224func (a *API) renameCollidingFields() {
225	for _, v := range a.Shapes {
226		namesWithSet := map[string]struct{}{}
227		for k, field := range v.MemberRefs {
228			if _, ok := v.MemberRefs["Set"+k]; ok {
229				namesWithSet["Set"+k] = struct{}{}
230			}
231
232			if collides(k) || (v.Exception && exceptionCollides(k)) {
233				renameCollidingField(k, v, field)
234			}
235		}
236
237		// checks if any field names collide with setters.
238		for name := range namesWithSet {
239			field := v.MemberRefs[name]
240			renameCollidingField(name, v, field)
241		}
242	}
243}
244
245func renameCollidingField(name string, v *Shape, field *ShapeRef) {
246	newName := name + "_"
247	debugLogger.Logf("Shape %s's field %q renamed to %q", v.ShapeName, name, newName)
248	delete(v.MemberRefs, name)
249	v.MemberRefs[newName] = field
250}
251
252// collides will return true if it is a name used by the SDK or Golang.
253func collides(name string) bool {
254	switch name {
255	case "String",
256		"GoString",
257		"Validate":
258		return true
259	}
260	return false
261}
262
263func exceptionCollides(name string) bool {
264	switch name {
265	case "Code",
266		"Message",
267		"OrigErr":
268		return true
269	}
270	return false
271}
272
273func (a *API) applyShapeNameAliases() {
274	service, ok := shapeNameAliases[a.name]
275	if !ok {
276		return
277	}
278
279	// Generic Shape Aliases
280	for name, s := range a.Shapes {
281		if alias, ok := service[name]; ok {
282			s.Rename(alias)
283			s.AliasedShapeName = true
284		}
285	}
286}
287
288// createInputOutputShapes creates toplevel input/output shapes if they
289// have not been defined in the API. This normalizes all APIs to always
290// have an input and output structure in the signature.
291func (a *API) createInputOutputShapes() {
292	for _, op := range a.Operations {
293		createAPIParamShape(a, op.Name, &op.InputRef, op.ExportedName+"Input",
294			shamelist.Input,
295		)
296		createAPIParamShape(a, op.Name, &op.OutputRef, op.ExportedName+"Output",
297			shamelist.Output,
298		)
299	}
300}
301
302func (a *API) renameAPIPayloadShapes() {
303	for _, op := range a.Operations {
304		op.InputRef.Payload = a.ExportableName(op.InputRef.Payload)
305		op.OutputRef.Payload = a.ExportableName(op.OutputRef.Payload)
306	}
307}
308
309func createAPIParamShape(a *API, opName string, ref *ShapeRef, shapeName string, shamelistLookup func(string, string) bool) {
310	if len(ref.ShapeName) == 0 {
311		setAsPlacholderShape(ref, shapeName, a)
312		return
313	}
314
315	// nothing to do if already the correct name.
316	if s := ref.Shape; s.AliasedShapeName || s.ShapeName == shapeName || shamelistLookup(a.name, opName) {
317		return
318	}
319
320	if s, ok := a.Shapes[shapeName]; ok {
321		panic(fmt.Sprintf(
322			"attempting to create duplicate API parameter shape, %v, %v, %v, %v\n",
323			shapeName, opName, ref.ShapeName, s.OrigShapeName,
324		))
325	}
326
327	ref.Shape.removeRef(ref)
328	ref.OrigShapeName = shapeName
329	ref.ShapeName = shapeName
330	ref.Shape = ref.Shape.Clone(shapeName)
331	ref.Shape.refs = append(ref.Shape.refs, ref)
332}
333
334func setAsPlacholderShape(tgtShapeRef *ShapeRef, name string, a *API) {
335	shape := a.makeIOShape(name)
336	shape.Placeholder = true
337	*tgtShapeRef = ShapeRef{API: a, ShapeName: shape.ShapeName, Shape: shape}
338	shape.refs = append(shape.refs, tgtShapeRef)
339}
340
341// makeIOShape returns a pointer to a new Shape initialized by the name provided.
342func (a *API) makeIOShape(name string) *Shape {
343	shape := &Shape{
344		API: a, ShapeName: name, Type: "structure",
345		MemberRefs: map[string]*ShapeRef{},
346	}
347	a.Shapes[name] = shape
348	return shape
349}
350
351// removeUnusedShapes removes shapes from the API which are not referenced by any
352// other shape in the API.
353func (a *API) removeUnusedShapes() {
354	for _, s := range a.Shapes {
355		if len(s.refs) == 0 {
356			a.removeShape(s)
357		}
358	}
359}
360
361// Represents the service package name to EndpointsID mapping
362var custEndpointsKey = map[string]string{
363	"applicationautoscaling": "application-autoscaling",
364}
365
366// Sents the EndpointsID field of Metadata  with the value of the
367// EndpointPrefix if EndpointsID is not set. Also adds
368// customizations for services if EndpointPrefix is not a valid key.
369func (a *API) setMetadataEndpointsKey() {
370	if len(a.Metadata.EndpointsID) != 0 {
371		return
372	}
373
374	if v, ok := custEndpointsKey[a.PackageName()]; ok {
375		a.Metadata.EndpointsID = v
376	} else {
377		a.Metadata.EndpointsID = a.Metadata.EndpointPrefix
378	}
379}
380
381func (a *API) findEndpointDiscoveryOp() {
382	for _, op := range a.Operations {
383		if op.IsEndpointDiscoveryOp {
384			a.EndpointDiscoveryOp = op
385			return
386		}
387	}
388}
389func (a *API) injectUnboundedOutputStreaming() {
390	for _, op := range a.Operations {
391		if op.AuthType != V4UnsignedBodyAuthType {
392			continue
393		}
394		for _, ref := range op.InputRef.Shape.MemberRefs {
395			if ref.Streaming || ref.Shape.Streaming {
396				if len(ref.Documentation) != 0 {
397					ref.Documentation += `
398//`
399				}
400				ref.Documentation += `
401// To use an non-seekable io.Reader for this request wrap the io.Reader with
402// "aws.ReadSeekCloser". The SDK will not retry request errors for non-seekable
403// readers. This will allow the SDK to send the reader's payload as chunked
404// transfer encoding.`
405			}
406		}
407	}
408}
409