example.go 7.9 KB

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