1// +build codegen
2
3package api
4
5import (
6	"bytes"
7	"encoding/json"
8	"fmt"
9	"os"
10	"sort"
11	"strings"
12	"text/template"
13
14	"github.com/aws/aws-sdk-go/private/util"
15)
16
17type Examples map[string][]Example
18
19// ExamplesDefinition is the structural representation of the examples-1.json file
20type ExamplesDefinition struct {
21	*API     `json:"-"`
22	Examples Examples `json:"examples"`
23}
24
25// Example is a single entry within the examples-1.json file.
26type Example struct {
27	API           *API                   `json:"-"`
28	Operation     *Operation             `json:"-"`
29	OperationName string                 `json:"-"`
30	Index         string                 `json:"-"`
31	Builder       examplesBuilder        `json:"-"`
32	VisitedErrors map[string]struct{}    `json:"-"`
33	Title         string                 `json:"title"`
34	Description   string                 `json:"description"`
35	ID            string                 `json:"id"`
36	Comments      Comments               `json:"comments"`
37	Input         map[string]interface{} `json:"input"`
38	Output        map[string]interface{} `json:"output"`
39}
40
41type Comments struct {
42	Input  map[string]interface{} `json:"input"`
43	Output map[string]interface{} `json:"output"`
44}
45
46var exampleFuncMap = template.FuncMap{
47	"commentify":           commentify,
48	"wrap":                 wrap,
49	"generateExampleInput": generateExampleInput,
50	"generateTypes":        generateTypes,
51}
52
53var exampleCustomizations = map[string]template.FuncMap{}
54
55var exampleTmpls = template.Must(template.New("example").Funcs(exampleFuncMap).Parse(`
56{{ generateTypes . }}
57{{ commentify (wrap .Title 80) }}
58//
59{{ commentify (wrap .Description 80) }}
60func Example{{ .API.StructName }}_{{ .MethodName }}() {
61	svc := {{ .API.PackageName }}.New(session.New())
62	input := {{ generateExampleInput . }}
63
64	result, err := svc.{{ .OperationName }}(input)
65	if err != nil {
66		if aerr, ok := err.(awserr.Error); ok {
67			switch aerr.Code() {
68				{{ range $_, $ref := .Operation.ErrorRefs -}}
69					{{ if not ($.HasVisitedError $ref) -}}
70			case {{ .API.PackageName }}.{{ $ref.Shape.ErrorCodeName }}:
71				fmt.Println({{ .API.PackageName }}.{{ $ref.Shape.ErrorCodeName }}, aerr.Error())
72					{{ end -}}
73				{{ end -}}
74			default:
75				fmt.Println(aerr.Error())
76			}
77		} else {
78			// Print the error, cast err to awserr.Error to get the Code and
79			// Message from an error.
80			fmt.Println(err.Error())
81		}
82		return
83	}
84
85	fmt.Println(result)
86}
87`))
88
89// Names will return the name of the example. This will also be the name of the operation
90// that is to be tested.
91func (exs Examples) Names() []string {
92	names := make([]string, 0, len(exs))
93	for k := range exs {
94		names = append(names, k)
95	}
96
97	sort.Strings(names)
98	return names
99}
100
101func (exs Examples) GoCode() string {
102	buf := bytes.NewBuffer(nil)
103	for _, opName := range exs.Names() {
104		examples := exs[opName]
105		for _, ex := range examples {
106			buf.WriteString(util.GoFmt(ex.GoCode()))
107			buf.WriteString("\n")
108		}
109	}
110	return buf.String()
111}
112
113// ExampleCode will generate the example code for the given Example shape.
114// TODO: Can delete
115func (ex Example) GoCode() string {
116	var buf bytes.Buffer
117	m := exampleFuncMap
118	if fMap, ok := exampleCustomizations[ex.API.PackageName()]; ok {
119		m = fMap
120	}
121	tmpl := exampleTmpls.Funcs(m)
122	if err := tmpl.ExecuteTemplate(&buf, "example", &ex); err != nil {
123		panic(err)
124	}
125
126	return strings.TrimSpace(buf.String())
127}
128
129func generateExampleInput(ex Example) string {
130	if ex.Operation.HasInput() {
131		return fmt.Sprintf("&%s{\n%s\n}",
132			ex.Builder.GoType(&ex.Operation.InputRef, true),
133			ex.Builder.BuildShape(&ex.Operation.InputRef, ex.Input, false),
134		)
135	}
136	return ""
137}
138
139// generateTypes will generate no types for default examples, but customizations may
140// require their own defined types.
141func generateTypes(ex Example) string {
142	return ""
143}
144
145// correctType will cast the value to the correct type when printing the string.
146// This is due to the json decoder choosing numbers to be floats, but the shape may
147// actually be an int. To counter this, we pass the shape's type and properly do the
148// casting here.
149func correctType(memName string, t string, value interface{}) string {
150	if value == nil {
151		return ""
152	}
153
154	v := ""
155	switch value.(type) {
156	case string:
157		v = value.(string)
158	case int:
159		v = fmt.Sprintf("%d", value.(int))
160	case float64:
161		if t == "integer" || t == "long" || t == "int64" {
162			v = fmt.Sprintf("%d", int(value.(float64)))
163		} else {
164			v = fmt.Sprintf("%f", value.(float64))
165		}
166	case bool:
167		v = fmt.Sprintf("%t", value.(bool))
168	}
169
170	return convertToCorrectType(memName, t, v)
171}
172
173func convertToCorrectType(memName, t, v string) string {
174	return fmt.Sprintf("%s: %s,\n", memName, getValue(t, v))
175}
176
177func getValue(t, v string) string {
178	if t[0] == '*' {
179		t = t[1:]
180	}
181	switch t {
182	case "string":
183		return fmt.Sprintf("aws.String(%q)", v)
184	case "integer", "long", "int64":
185		return fmt.Sprintf("aws.Int64(%s)", v)
186	case "float", "float64", "double":
187		return fmt.Sprintf("aws.Float64(%s)", v)
188	case "boolean":
189		return fmt.Sprintf("aws.Bool(%s)", v)
190	default:
191		panic("Unsupported type: " + t)
192	}
193}
194
195// AttachExamples will create a new ExamplesDefinition from the examples file
196// and reference the API object.
197func (a *API) AttachExamples(filename string) error {
198	p := ExamplesDefinition{API: a}
199
200	f, err := os.Open(filename)
201	defer f.Close()
202	if err != nil {
203		return err
204	}
205	err = json.NewDecoder(f).Decode(&p)
206	if err != nil {
207		return fmt.Errorf("failed to decode %s, err: %v", filename, err)
208	}
209
210	return p.setup()
211}
212
213var examplesBuilderCustomizations = map[string]examplesBuilder{
214	"wafregional": NewWAFregionalExamplesBuilder(),
215}
216
217func (p *ExamplesDefinition) setup() error {
218	var builder examplesBuilder
219	ok := false
220	if builder, ok = examplesBuilderCustomizations[p.API.PackageName()]; !ok {
221		builder = NewExamplesBuilder()
222	}
223
224	keys := p.Examples.Names()
225	for _, n := range keys {
226		examples := p.Examples[n]
227		for i, e := range examples {
228			n = p.ExportableName(n)
229			e.OperationName = n
230			e.API = p.API
231			e.Index = fmt.Sprintf("shared%02d", i)
232
233			e.Builder = builder
234
235			e.VisitedErrors = map[string]struct{}{}
236			op := p.API.Operations[e.OperationName]
237			e.OperationName = p.ExportableName(e.OperationName)
238			e.Operation = op
239			p.Examples[n][i] = e
240		}
241	}
242
243	p.API.Examples = p.Examples
244
245	return nil
246}
247
248var exampleHeader = template.Must(template.New("exampleHeader").Parse(`
249import (
250	{{ .Builder.Imports .API }}
251)
252
253var _ time.Duration
254var _ strings.Reader
255var _ aws.Config
256
257func parseTime(layout, value string) *time.Time {
258	t, err := time.Parse(layout, value)
259	if err != nil {
260		panic(err)
261	}
262	return &t
263}
264
265`))
266
267type exHeader struct {
268	Builder examplesBuilder
269	API     *API
270}
271
272// ExamplesGoCode will return a code representation of the entry within the
273// examples.json file.
274func (a *API) ExamplesGoCode() string {
275	var buf bytes.Buffer
276	var builder examplesBuilder
277	ok := false
278	if builder, ok = examplesBuilderCustomizations[a.PackageName()]; !ok {
279		builder = NewExamplesBuilder()
280	}
281
282	if err := exampleHeader.ExecuteTemplate(&buf, "exampleHeader", &exHeader{builder, a}); err != nil {
283		panic(err)
284	}
285
286	code := a.Examples.GoCode()
287	if len(code) == 0 {
288		return ""
289	}
290
291	buf.WriteString(code)
292	return buf.String()
293}
294
295// TODO: In the operation docuentation where we list errors, this needs to be done
296// there as well.
297func (ex *Example) HasVisitedError(errRef *ShapeRef) bool {
298	errName := errRef.Shape.ErrorCodeName()
299	_, ok := ex.VisitedErrors[errName]
300	ex.VisitedErrors[errName] = struct{}{}
301	return ok
302}
303
304func (ex *Example) MethodName() string {
305	return fmt.Sprintf("%s_%s", ex.OperationName, ex.Index)
306}
307