1// +build codegen
2
3package api
4
5import (
6	"bytes"
7	"fmt"
8	"path"
9	"regexp"
10	"sort"
11	"strings"
12	"text/template"
13)
14
15// A ShapeRef defines the usage of a shape within the API.
16type ShapeRef struct {
17	API           *API   `json:"-"`
18	Shape         *Shape `json:"-"`
19	Documentation string
20	ShapeName     string `json:"shape"`
21	Location      string
22	LocationName  string
23	QueryName     string
24	Flattened     bool
25	Streaming     bool
26	XMLAttribute  bool
27	// Ignore, if set, will not be sent over the wire
28	Ignore           bool
29	XMLNamespace     XMLInfo
30	Payload          string
31	IdempotencyToken bool `json:"idempotencyToken"`
32	JSONValue        bool `json:"jsonvalue"`
33	Deprecated       bool `json:"deprecated"`
34
35	OrigShapeName string `json:"-"`
36
37	GenerateGetter bool
38}
39
40// ErrorInfo represents the error block of a shape's structure
41type ErrorInfo struct {
42	Code           string
43	HTTPStatusCode int
44}
45
46// A XMLInfo defines URL and prefix for Shapes when rendered as XML
47type XMLInfo struct {
48	Prefix string
49	URI    string
50}
51
52// A Shape defines the definition of a shape type
53type Shape struct {
54	API              *API `json:"-"`
55	ShapeName        string
56	Documentation    string
57	MemberRefs       map[string]*ShapeRef `json:"members"`
58	MemberRef        ShapeRef             `json:"member"`
59	KeyRef           ShapeRef             `json:"key"`
60	ValueRef         ShapeRef             `json:"value"`
61	Required         []string
62	Payload          string
63	Type             string
64	Exception        bool
65	Enum             []string
66	EnumConsts       []string
67	Flattened        bool
68	Streaming        bool
69	Location         string
70	LocationName     string
71	IdempotencyToken bool `json:"idempotencyToken"`
72	XMLNamespace     XMLInfo
73	Min              float64 // optional Minimum length (string, list) or value (number)
74	Max              float64 // optional Maximum length (string, list) or value (number)
75
76	refs       []*ShapeRef // References to this shape
77	resolvePkg string      // use this package in the goType() if present
78
79	OrigShapeName string `json:"-"`
80
81	// Defines if the shape is a placeholder and should not be used directly
82	Placeholder bool
83
84	Deprecated bool `json:"deprecated"`
85
86	Validations ShapeValidations
87
88	// Error information that is set if the shape is an error shape.
89	IsError   bool
90	ErrorInfo ErrorInfo `json:"error"`
91}
92
93// ErrorCodeName will return the error shape's name formated for
94// error code const.
95func (s *Shape) ErrorCodeName() string {
96	return "ErrCode" + s.ShapeName
97}
98
99// ErrorName will return the shape's name or error code if available based
100// on the API's protocol. This is the error code string returned by the service.
101func (s *Shape) ErrorName() string {
102	name := s.ShapeName
103	switch s.API.Metadata.Protocol {
104	case "query", "ec2query", "rest-xml":
105		if len(s.ErrorInfo.Code) > 0 {
106			name = s.ErrorInfo.Code
107		}
108	}
109
110	return name
111}
112
113// GoTags returns the struct tags for a shape.
114func (s *Shape) GoTags(root, required bool) string {
115	ref := &ShapeRef{ShapeName: s.ShapeName, API: s.API, Shape: s}
116	return ref.GoTags(root, required)
117}
118
119// Rename changes the name of the Shape to newName. Also updates
120// the associated API's reference to use newName.
121func (s *Shape) Rename(newName string) {
122	for _, r := range s.refs {
123		r.OrigShapeName = r.ShapeName
124		r.ShapeName = newName
125	}
126
127	delete(s.API.Shapes, s.ShapeName)
128	s.OrigShapeName = s.ShapeName
129	s.API.Shapes[newName] = s
130	s.ShapeName = newName
131}
132
133// MemberNames returns a slice of struct member names.
134func (s *Shape) MemberNames() []string {
135	i, names := 0, make([]string, len(s.MemberRefs))
136	for n := range s.MemberRefs {
137		names[i] = n
138		i++
139	}
140	sort.Strings(names)
141	return names
142}
143
144// GoTypeWithPkgName returns a shape's type as a string with the package name in
145// <packageName>.<type> format. Package naming only applies to structures.
146func (s *Shape) GoTypeWithPkgName() string {
147	return goType(s, true)
148}
149
150func (s *Shape) GoTypeWithPkgNameElem() string {
151	t := goType(s, true)
152	if strings.HasPrefix(t, "*") {
153		return t[1:]
154	}
155	return t
156}
157
158// GenAccessors returns if the shape's reference should have setters generated.
159func (s *ShapeRef) UseIndirection() bool {
160	switch s.Shape.Type {
161	case "map", "list", "blob", "structure", "jsonvalue":
162		return false
163	}
164
165	if s.Streaming || s.Shape.Streaming {
166		return false
167	}
168
169	if s.JSONValue {
170		return false
171	}
172
173	return true
174}
175
176// GoStructValueType returns the Shape's Go type value instead of a pointer
177// for the type.
178func (s *Shape) GoStructValueType(name string, ref *ShapeRef) string {
179	v := s.GoStructType(name, ref)
180
181	if ref.UseIndirection() && v[0] == '*' {
182		return v[1:]
183	}
184
185	return v
186}
187
188// GoStructType returns the type of a struct field based on the API
189// model definition.
190func (s *Shape) GoStructType(name string, ref *ShapeRef) string {
191	if (ref.Streaming || ref.Shape.Streaming) && s.Payload == name {
192		rtype := "io.ReadSeeker"
193		if strings.HasSuffix(s.ShapeName, "Output") {
194			rtype = "io.ReadCloser"
195		}
196
197		s.API.imports["io"] = true
198		return rtype
199	}
200
201	if ref.JSONValue {
202		s.API.imports["github.com/aws/aws-sdk-go/aws"] = true
203		return "aws.JSONValue"
204	}
205
206	for _, v := range s.Validations {
207		// TODO move this to shape validation resolution
208		if (v.Ref.Shape.Type == "map" || v.Ref.Shape.Type == "list") && v.Type == ShapeValidationNested {
209			s.API.imports["fmt"] = true
210		}
211	}
212
213	return ref.GoType()
214}
215
216// GoType returns a shape's Go type
217func (s *Shape) GoType() string {
218	return goType(s, false)
219}
220
221// GoType returns a shape ref's Go type.
222func (ref *ShapeRef) GoType() string {
223	if ref.Shape == nil {
224		panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
225	}
226
227	return ref.Shape.GoType()
228}
229
230// GoTypeWithPkgName returns a shape's type as a string with the package name in
231// <packageName>.<type> format. Package naming only applies to structures.
232func (ref *ShapeRef) GoTypeWithPkgName() string {
233	if ref.Shape == nil {
234		panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
235	}
236
237	return ref.Shape.GoTypeWithPkgName()
238}
239
240// Returns a string version of the Shape's type.
241// If withPkgName is true, the package name will be added as a prefix
242func goType(s *Shape, withPkgName bool) string {
243	switch s.Type {
244	case "structure":
245		if withPkgName || s.resolvePkg != "" {
246			pkg := s.resolvePkg
247			if pkg != "" {
248				s.API.imports[pkg] = true
249				pkg = path.Base(pkg)
250			} else {
251				pkg = s.API.PackageName()
252			}
253			return fmt.Sprintf("*%s.%s", pkg, s.ShapeName)
254		}
255		return "*" + s.ShapeName
256	case "map":
257		return "map[string]" + goType(s.ValueRef.Shape, withPkgName)
258	case "jsonvalue":
259		return "aws.JSONValue"
260	case "list":
261		return "[]" + goType(s.MemberRef.Shape, withPkgName)
262	case "boolean":
263		return "*bool"
264	case "string", "character":
265		return "*string"
266	case "blob":
267		return "[]byte"
268	case "integer", "long":
269		return "*int64"
270	case "float", "double":
271		return "*float64"
272	case "timestamp":
273		s.API.imports["time"] = true
274		return "*time.Time"
275	default:
276		panic("Unsupported shape type: " + s.Type)
277	}
278}
279
280// GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just
281// the type will be returned minus the pointer *.
282func (s *Shape) GoTypeElem() string {
283	t := s.GoType()
284	if strings.HasPrefix(t, "*") {
285		return t[1:]
286	}
287	return t
288}
289
290// GoTypeElem returns the Go type for the Shape. If the shape type is a pointer just
291// the type will be returned minus the pointer *.
292func (ref *ShapeRef) GoTypeElem() string {
293	if ref.Shape == nil {
294		panic(fmt.Errorf("missing shape definition on reference for %#v", ref))
295	}
296
297	return ref.Shape.GoTypeElem()
298}
299
300// ShapeTag is a struct tag that will be applied to a shape's generated code
301type ShapeTag struct {
302	Key, Val string
303}
304
305// String returns the string representation of the shape tag
306func (s ShapeTag) String() string {
307	return fmt.Sprintf(`%s:"%s"`, s.Key, s.Val)
308}
309
310// ShapeTags is a collection of shape tags and provides serialization of the
311// tags in an ordered list.
312type ShapeTags []ShapeTag
313
314// Join returns an ordered serialization of the shape tags with the provided
315// separator.
316func (s ShapeTags) Join(sep string) string {
317	o := &bytes.Buffer{}
318	for i, t := range s {
319		o.WriteString(t.String())
320		if i < len(s)-1 {
321			o.WriteString(sep)
322		}
323	}
324
325	return o.String()
326}
327
328// String is an alias for Join with the empty space separator.
329func (s ShapeTags) String() string {
330	return s.Join(" ")
331}
332
333// GoTags returns the rendered tags string for the ShapeRef
334func (ref *ShapeRef) GoTags(toplevel bool, isRequired bool) string {
335	tags := ShapeTags{}
336
337	if ref.Location != "" {
338		tags = append(tags, ShapeTag{"location", ref.Location})
339	} else if ref.Shape.Location != "" {
340		tags = append(tags, ShapeTag{"location", ref.Shape.Location})
341	}
342
343	if ref.LocationName != "" {
344		tags = append(tags, ShapeTag{"locationName", ref.LocationName})
345	} else if ref.Shape.LocationName != "" {
346		tags = append(tags, ShapeTag{"locationName", ref.Shape.LocationName})
347	}
348
349	if ref.QueryName != "" {
350		tags = append(tags, ShapeTag{"queryName", ref.QueryName})
351	}
352	if ref.Shape.MemberRef.LocationName != "" {
353		tags = append(tags, ShapeTag{"locationNameList", ref.Shape.MemberRef.LocationName})
354	}
355	if ref.Shape.KeyRef.LocationName != "" {
356		tags = append(tags, ShapeTag{"locationNameKey", ref.Shape.KeyRef.LocationName})
357	}
358	if ref.Shape.ValueRef.LocationName != "" {
359		tags = append(tags, ShapeTag{"locationNameValue", ref.Shape.ValueRef.LocationName})
360	}
361	if ref.Shape.Min > 0 {
362		tags = append(tags, ShapeTag{"min", fmt.Sprintf("%v", ref.Shape.Min)})
363	}
364
365	if ref.Deprecated || ref.Shape.Deprecated {
366		tags = append(tags, ShapeTag{"deprecated", "true"})
367	}
368
369	// All shapes have a type
370	tags = append(tags, ShapeTag{"type", ref.Shape.Type})
371
372	// embed the timestamp type for easier lookups
373	if ref.Shape.Type == "timestamp" {
374		t := ShapeTag{Key: "timestampFormat"}
375		if ref.Location == "header" {
376			t.Val = "rfc822"
377		} else {
378			switch ref.API.Metadata.Protocol {
379			case "json", "rest-json":
380				t.Val = "unix"
381			case "rest-xml", "ec2", "query":
382				t.Val = "iso8601"
383			}
384		}
385		tags = append(tags, t)
386	}
387
388	if ref.Shape.Flattened || ref.Flattened {
389		tags = append(tags, ShapeTag{"flattened", "true"})
390	}
391	if ref.XMLAttribute {
392		tags = append(tags, ShapeTag{"xmlAttribute", "true"})
393	}
394	if isRequired {
395		tags = append(tags, ShapeTag{"required", "true"})
396	}
397	if ref.Shape.IsEnum() {
398		tags = append(tags, ShapeTag{"enum", ref.ShapeName})
399	}
400
401	if toplevel {
402		if ref.Shape.Payload != "" {
403			tags = append(tags, ShapeTag{"payload", ref.Shape.Payload})
404		}
405	}
406
407	if ref.XMLNamespace.Prefix != "" {
408		tags = append(tags, ShapeTag{"xmlPrefix", ref.XMLNamespace.Prefix})
409	} else if ref.Shape.XMLNamespace.Prefix != "" {
410		tags = append(tags, ShapeTag{"xmlPrefix", ref.Shape.XMLNamespace.Prefix})
411	}
412
413	if ref.XMLNamespace.URI != "" {
414		tags = append(tags, ShapeTag{"xmlURI", ref.XMLNamespace.URI})
415	} else if ref.Shape.XMLNamespace.URI != "" {
416		tags = append(tags, ShapeTag{"xmlURI", ref.Shape.XMLNamespace.URI})
417	}
418
419	if ref.IdempotencyToken || ref.Shape.IdempotencyToken {
420		tags = append(tags, ShapeTag{"idempotencyToken", "true"})
421	}
422
423	if ref.Ignore {
424		tags = append(tags, ShapeTag{"ignore", "true"})
425	}
426
427	return fmt.Sprintf("`%s`", tags)
428}
429
430// Docstring returns the godocs formated documentation
431func (ref *ShapeRef) Docstring() string {
432	if ref.Documentation != "" {
433		return strings.Trim(ref.Documentation, "\n ")
434	}
435	return ref.Shape.Docstring()
436}
437
438// Docstring returns the godocs formated documentation
439func (s *Shape) Docstring() string {
440	return strings.Trim(s.Documentation, "\n ")
441}
442
443// IndentedDocstring is the indented form of the doc string.
444func (ref *ShapeRef) IndentedDocstring() string {
445	doc := ref.Docstring()
446	return strings.Replace(doc, "// ", "//   ", -1)
447}
448
449var goCodeStringerTmpl = template.Must(template.New("goCodeStringerTmpl").Parse(`
450// String returns the string representation
451func (s {{ .ShapeName }}) String() string {
452	return awsutil.Prettify(s)
453}
454// GoString returns the string representation
455func (s {{ .ShapeName }}) GoString() string {
456	return s.String()
457}
458`))
459
460// GoCodeStringers renders the Stringers for API input/output shapes
461func (s *Shape) GoCodeStringers() string {
462	w := bytes.Buffer{}
463	if err := goCodeStringerTmpl.Execute(&w, s); err != nil {
464		panic(fmt.Sprintln("Unexpected error executing GoCodeStringers template", err))
465	}
466
467	return w.String()
468}
469
470var enumStrip = regexp.MustCompile(`[^a-zA-Z0-9_:\./-]`)
471var enumDelims = regexp.MustCompile(`[-_:\./]+`)
472var enumCamelCase = regexp.MustCompile(`([a-z])([A-Z])`)
473
474// EnumName returns the Nth enum in the shapes Enum list
475func (s *Shape) EnumName(n int) string {
476	enum := s.Enum[n]
477	enum = enumStrip.ReplaceAllLiteralString(enum, "")
478	enum = enumCamelCase.ReplaceAllString(enum, "$1-$2")
479	parts := enumDelims.Split(enum, -1)
480	for i, v := range parts {
481		v = strings.ToLower(v)
482		parts[i] = ""
483		if len(v) > 0 {
484			parts[i] = strings.ToUpper(v[0:1])
485		}
486		if len(v) > 1 {
487			parts[i] += v[1:]
488		}
489	}
490	enum = strings.Join(parts, "")
491	enum = strings.ToUpper(enum[0:1]) + enum[1:]
492	return enum
493}
494
495// NestedShape returns the shape pointer value for the shape which is nested
496// under the current shape. If the shape is not nested nil will be returned.
497//
498// strucutures, the current shape is returned
499// map: the value shape of the map is returned
500// list: the element shape of the list is returned
501func (s *Shape) NestedShape() *Shape {
502	var nestedShape *Shape
503	switch s.Type {
504	case "structure":
505		nestedShape = s
506	case "map":
507		nestedShape = s.ValueRef.Shape
508	case "list":
509		nestedShape = s.MemberRef.Shape
510	}
511
512	return nestedShape
513}
514
515var structShapeTmpl = template.Must(template.New("StructShape").Funcs(template.FuncMap{
516	"GetCrosslinkURL": GetCrosslinkURL,
517}).Parse(`
518{{ .Docstring }}
519{{ if ne $.OrigShapeName "" -}}
520{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.Metadata.UID $.OrigShapeName -}}
521{{ if ne $crosslinkURL "" -}}
522// Please also see {{ $crosslinkURL }}
523{{ end -}}
524{{ else -}}
525{{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.Metadata.UID $.ShapeName -}}
526{{ if ne $crosslinkURL "" -}}
527// Please also see {{ $crosslinkURL }}
528{{ end -}}
529{{ end -}}
530{{ $context := . -}}
531type {{ .ShapeName }} struct {
532	_ struct{} {{ .GoTags true false }}
533
534	{{ range $_, $name := $context.MemberNames -}}
535		{{ $elem := index $context.MemberRefs $name -}}
536		{{ $isBlob := $context.WillRefBeBase64Encoded $name -}}
537		{{ $isRequired := $context.IsRequired $name -}}
538		{{ $doc := $elem.Docstring -}}
539
540		{{ if $doc -}}
541			{{ $doc }}
542		{{ end -}}
543		{{ if $isBlob -}}
544			{{ if $doc -}}
545				//
546			{{ end -}}
547			// {{ $name }} is automatically base64 encoded/decoded by the SDK.
548		{{ end -}}
549		{{ if $isRequired -}}
550			{{ if or $doc $isBlob -}}
551				//
552			{{ end -}}
553			// {{ $name }} is a required field
554		{{ end -}}
555		{{ $name }} {{ $context.GoStructType $name $elem }} {{ $elem.GoTags false $isRequired }}
556
557	{{ end }}
558}
559{{ if not .API.NoStringerMethods }}
560	{{ .GoCodeStringers }}
561{{ end }}
562{{ if not .API.NoValidataShapeMethods }}
563	{{ if .Validations -}}
564		{{ .Validations.GoCode . }}
565	{{ end }}
566{{ end }}
567
568{{ if not .API.NoGenStructFieldAccessors }}
569
570{{ $builderShapeName := print .ShapeName -}}
571
572{{ range $_, $name := $context.MemberNames -}}
573	{{ $elem := index $context.MemberRefs $name -}}
574
575// Set{{ $name }} sets the {{ $name }} field's value.
576func (s *{{ $builderShapeName }}) Set{{ $name }}(v {{ $context.GoStructValueType $name $elem }}) *{{ $builderShapeName }} {
577	{{ if $elem.UseIndirection -}}
578	s.{{ $name }} = &v
579	{{ else -}}
580	s.{{ $name }} = v
581	{{ end -}}
582	return s
583}
584
585{{ if $elem.GenerateGetter -}}
586func (s *{{ $builderShapeName }}) get{{ $name }}() (v {{ $context.GoStructValueType $name $elem }}) {
587	{{ if $elem.UseIndirection -}}
588		if s.{{ $name }} == nil {
589			return v
590		}
591		return *s.{{ $name }}
592	{{ else -}}
593		return s.{{ $name }}
594	{{ end -}}
595}
596{{- end }}
597
598{{ end }}
599{{ end }}
600`))
601
602var enumShapeTmpl = template.Must(template.New("EnumShape").Parse(`
603{{ .Docstring }}
604const (
605	{{ $context := . -}}
606	{{ range $index, $elem := .Enum -}}
607		{{ $name := index $context.EnumConsts $index -}}
608		// {{ $name }} is a {{ $context.ShapeName }} enum value
609		{{ $name }} = "{{ $elem }}"
610
611	{{ end }}
612)
613`))
614
615// GoCode returns the rendered Go code for the Shape.
616func (s *Shape) GoCode() string {
617	b := &bytes.Buffer{}
618
619	switch {
620	case s.Type == "structure":
621		if err := structShapeTmpl.Execute(b, s); err != nil {
622			panic(fmt.Sprintf("Failed to generate struct shape %s, %v\n", s.ShapeName, err))
623		}
624	case s.IsEnum():
625		if err := enumShapeTmpl.Execute(b, s); err != nil {
626			panic(fmt.Sprintf("Failed to generate enum shape %s, %v\n", s.ShapeName, err))
627		}
628	default:
629		panic(fmt.Sprintln("Cannot generate toplevel shape for", s.Type))
630	}
631
632	return b.String()
633}
634
635// IsEnum returns whether this shape is an enum list
636func (s *Shape) IsEnum() bool {
637	return s.Type == "string" && len(s.Enum) > 0
638}
639
640// IsRequired returns if member is a required field.
641func (s *Shape) IsRequired(member string) bool {
642	for _, n := range s.Required {
643		if n == member {
644			return true
645		}
646	}
647	return false
648}
649
650// IsInternal returns whether the shape was defined in this package
651func (s *Shape) IsInternal() bool {
652	return s.resolvePkg == ""
653}
654
655// removeRef removes a shape reference from the list of references this
656// shape is used in.
657func (s *Shape) removeRef(ref *ShapeRef) {
658	r := s.refs
659	for i := 0; i < len(r); i++ {
660		if r[i] == ref {
661			j := i + 1
662			copy(r[i:], r[j:])
663			for k, n := len(r)-j+i, len(r); k < n; k++ {
664				r[k] = nil // free up the end of the list
665			} // for k
666			s.refs = r[:len(r)-j+i]
667			break
668		}
669	}
670}
671
672func (s *Shape) WillRefBeBase64Encoded(refName string) bool {
673	payloadRefName := s.Payload
674	if payloadRefName == refName {
675		return false
676	}
677
678	ref, ok := s.MemberRefs[refName]
679	if !ok {
680		panic(fmt.Sprintf("shape %s does not contain %q refName", s.ShapeName, refName))
681	}
682
683	return ref.Shape.Type == "blob"
684}
685