example.go 7.9 KB

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