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