operation.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. // +build codegen
  2. package api
  3. import (
  4. "bytes"
  5. "fmt"
  6. "regexp"
  7. "sort"
  8. "strings"
  9. "text/template"
  10. )
  11. // An Operation defines a specific API Operation.
  12. type Operation struct {
  13. API *API `json:"-"`
  14. ExportedName string
  15. Name string
  16. Documentation string
  17. HTTP HTTPInfo
  18. InputRef ShapeRef `json:"input"`
  19. OutputRef ShapeRef `json:"output"`
  20. ErrorRefs []ShapeRef `json:"errors"`
  21. Paginator *Paginator
  22. Deprecated bool `json:"deprecated"`
  23. AuthType string `json:"authtype"`
  24. imports map[string]bool
  25. }
  26. // A HTTPInfo defines the method of HTTP request for the Operation.
  27. type HTTPInfo struct {
  28. Method string
  29. RequestURI string
  30. ResponseCode uint
  31. }
  32. // HasInput returns if the Operation accepts an input paramater
  33. func (o *Operation) HasInput() bool {
  34. return o.InputRef.ShapeName != ""
  35. }
  36. // HasOutput returns if the Operation accepts an output parameter
  37. func (o *Operation) HasOutput() bool {
  38. return o.OutputRef.ShapeName != ""
  39. }
  40. func (o *Operation) GetSigner() string {
  41. if o.AuthType == "v4-unsigned-body" {
  42. o.API.imports["github.com/aws/aws-sdk-go/aws/signer/v4"] = true
  43. }
  44. buf := bytes.NewBuffer(nil)
  45. switch o.AuthType {
  46. case "none":
  47. buf.WriteString("req.Config.Credentials = credentials.AnonymousCredentials")
  48. case "v4-unsigned-body":
  49. buf.WriteString("req.Handlers.Sign.Remove(v4.SignRequestHandler)\n")
  50. buf.WriteString("handler := v4.BuildNamedHandler(\"v4.CustomSignerHandler\", v4.WithUnsignedPayload)\n")
  51. buf.WriteString("req.Handlers.Sign.PushFrontNamed(handler)")
  52. }
  53. buf.WriteString("\n")
  54. return buf.String()
  55. }
  56. // tplOperation defines a template for rendering an API Operation
  57. var tplOperation = template.Must(template.New("operation").Funcs(template.FuncMap{
  58. "GetCrosslinkURL": GetCrosslinkURL,
  59. }).Parse(`
  60. const op{{ .ExportedName }} = "{{ .Name }}"
  61. // {{ .ExportedName }}Request generates a "aws/request.Request" representing the
  62. // client's request for the {{ .ExportedName }} operation. The "output" return
  63. // value can be used to capture response data after the request's "Send" method
  64. // is called.
  65. //
  66. // See {{ .ExportedName }} for usage and error information.
  67. //
  68. // Creating a request object using this method should be used when you want to inject
  69. // custom logic into the request's lifecycle using a custom handler, or if you want to
  70. // access properties on the request object before or after sending the request. If
  71. // you just want the service response, call the {{ .ExportedName }} method directly
  72. // instead.
  73. //
  74. // Note: You must call the "Send" method on the returned request object in order
  75. // to execute the request.
  76. //
  77. // // Example sending a request using the {{ .ExportedName }}Request method.
  78. // req, resp := client.{{ .ExportedName }}Request(params)
  79. //
  80. // err := req.Send()
  81. // if err == nil { // resp is now filled
  82. // fmt.Println(resp)
  83. // }
  84. {{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.APIName $.API.Metadata.UID $.ExportedName -}}
  85. {{ if ne $crosslinkURL "" -}}
  86. //
  87. // Please also see {{ $crosslinkURL }}
  88. {{ end -}}
  89. func (c *{{ .API.StructName }}) {{ .ExportedName }}Request(` +
  90. `input {{ .InputRef.GoType }}) (req *request.Request, output {{ .OutputRef.GoType }}) {
  91. {{ if (or .Deprecated (or .InputRef.Deprecated .OutputRef.Deprecated)) }}if c.Client.Config.Logger != nil {
  92. c.Client.Config.Logger.Log("This operation, {{ .ExportedName }}, has been deprecated")
  93. }
  94. op := &request.Operation{ {{ else }} op := &request.Operation{ {{ end }}
  95. Name: op{{ .ExportedName }},
  96. {{ if ne .HTTP.Method "" }}HTTPMethod: "{{ .HTTP.Method }}",
  97. {{ end }}HTTPPath: {{ if ne .HTTP.RequestURI "" }}"{{ .HTTP.RequestURI }}"{{ else }}"/"{{ end }},
  98. {{ if .Paginator }}Paginator: &request.Paginator{
  99. InputTokens: {{ .Paginator.InputTokensString }},
  100. OutputTokens: {{ .Paginator.OutputTokensString }},
  101. LimitToken: "{{ .Paginator.LimitKey }}",
  102. TruncationToken: "{{ .Paginator.MoreResults }}",
  103. },
  104. {{ end }}
  105. }
  106. if input == nil {
  107. input = &{{ .InputRef.GoTypeElem }}{}
  108. }
  109. output = &{{ .OutputRef.GoTypeElem }}{}
  110. req = c.newRequest(op, input, output){{ if eq .OutputRef.Shape.Placeholder true }}
  111. req.Handlers.Unmarshal.Remove({{ .API.ProtocolPackage }}.UnmarshalHandler)
  112. req.Handlers.Unmarshal.PushBackNamed(protocol.UnmarshalDiscardBodyHandler){{ end }}
  113. {{ if ne .AuthType "" }}{{ .GetSigner }}{{ end -}}
  114. return
  115. }
  116. // {{ .ExportedName }} API operation for {{ .API.Metadata.ServiceFullName }}.
  117. {{ if .Documentation -}}
  118. //
  119. {{ .Documentation }}
  120. {{ end -}}
  121. //
  122. // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
  123. // with awserr.Error's Code and Message methods to get detailed information about
  124. // the error.
  125. //
  126. // See the AWS API reference guide for {{ .API.Metadata.ServiceFullName }}'s
  127. // API operation {{ .ExportedName }} for usage and error information.
  128. {{ if .ErrorRefs -}}
  129. //
  130. // Returned Error Codes:
  131. {{ range $_, $err := .ErrorRefs -}}
  132. // * {{ $err.Shape.ErrorCodeName }} "{{ $err.Shape.ErrorName}}"
  133. {{ if $err.Docstring -}}
  134. {{ $err.IndentedDocstring }}
  135. {{ end -}}
  136. //
  137. {{ end -}}
  138. {{ end -}}
  139. {{ $crosslinkURL := GetCrosslinkURL $.API.BaseCrosslinkURL $.API.APIName $.API.Metadata.UID $.ExportedName -}}
  140. {{ if ne $crosslinkURL "" -}}
  141. // Please also see {{ $crosslinkURL }}
  142. {{ end -}}
  143. func (c *{{ .API.StructName }}) {{ .ExportedName }}(` +
  144. `input {{ .InputRef.GoType }}) ({{ .OutputRef.GoType }}, error) {
  145. req, out := c.{{ .ExportedName }}Request(input)
  146. return out, req.Send()
  147. }
  148. // {{ .ExportedName }}WithContext is the same as {{ .ExportedName }} with the addition of
  149. // the ability to pass a context and additional request options.
  150. //
  151. // See {{ .ExportedName }} for details on how to use this API operation.
  152. //
  153. // The context must be non-nil and will be used for request cancellation. If
  154. // the context is nil a panic will occur. In the future the SDK may create
  155. // sub-contexts for http.Requests. See https://golang.org/pkg/context/
  156. // for more information on using Contexts.
  157. func (c *{{ .API.StructName }}) {{ .ExportedName }}WithContext(` +
  158. `ctx aws.Context, input {{ .InputRef.GoType }}, opts ...request.Option) ` +
  159. `({{ .OutputRef.GoType }}, error) {
  160. req, out := c.{{ .ExportedName }}Request(input)
  161. req.SetContext(ctx)
  162. req.ApplyOptions(opts...)
  163. return out, req.Send()
  164. }
  165. {{ if .Paginator }}
  166. // {{ .ExportedName }}Pages iterates over the pages of a {{ .ExportedName }} operation,
  167. // calling the "fn" function with the response data for each page. To stop
  168. // iterating, return false from the fn function.
  169. //
  170. // See {{ .ExportedName }} method for more information on how to use this operation.
  171. //
  172. // Note: This operation can generate multiple requests to a service.
  173. //
  174. // // Example iterating over at most 3 pages of a {{ .ExportedName }} operation.
  175. // pageNum := 0
  176. // err := client.{{ .ExportedName }}Pages(params,
  177. // func(page {{ .OutputRef.GoType }}, lastPage bool) bool {
  178. // pageNum++
  179. // fmt.Println(page)
  180. // return pageNum <= 3
  181. // })
  182. //
  183. func (c *{{ .API.StructName }}) {{ .ExportedName }}Pages(` +
  184. `input {{ .InputRef.GoType }}, fn func({{ .OutputRef.GoType }}, bool) bool) error {
  185. return c.{{ .ExportedName }}PagesWithContext(aws.BackgroundContext(), input, fn)
  186. }
  187. // {{ .ExportedName }}PagesWithContext same as {{ .ExportedName }}Pages except
  188. // it takes a Context and allows setting request options on the pages.
  189. //
  190. // The context must be non-nil and will be used for request cancellation. If
  191. // the context is nil a panic will occur. In the future the SDK may create
  192. // sub-contexts for http.Requests. See https://golang.org/pkg/context/
  193. // for more information on using Contexts.
  194. func (c *{{ .API.StructName }}) {{ .ExportedName }}PagesWithContext(` +
  195. `ctx aws.Context, ` +
  196. `input {{ .InputRef.GoType }}, ` +
  197. `fn func({{ .OutputRef.GoType }}, bool) bool, ` +
  198. `opts ...request.Option) error {
  199. p := request.Pagination {
  200. NewRequest: func() (*request.Request, error) {
  201. var inCpy {{ .InputRef.GoType }}
  202. if input != nil {
  203. tmp := *input
  204. inCpy = &tmp
  205. }
  206. req, _ := c.{{ .ExportedName }}Request(inCpy)
  207. req.SetContext(ctx)
  208. req.ApplyOptions(opts...)
  209. return req, nil
  210. },
  211. }
  212. cont := true
  213. for p.Next() && cont {
  214. cont = fn(p.Page().({{ .OutputRef.GoType }}), !p.HasNextPage())
  215. }
  216. return p.Err()
  217. }
  218. {{ end }}
  219. `))
  220. // GoCode returns a string of rendered GoCode for this Operation
  221. func (o *Operation) GoCode() string {
  222. var buf bytes.Buffer
  223. err := tplOperation.Execute(&buf, o)
  224. if err != nil {
  225. panic(err)
  226. }
  227. return strings.TrimSpace(buf.String())
  228. }
  229. // tplInfSig defines the template for rendering an Operation's signature within an Interface definition.
  230. var tplInfSig = template.Must(template.New("opsig").Parse(`
  231. {{ .ExportedName }}({{ .InputRef.GoTypeWithPkgName }}) ({{ .OutputRef.GoTypeWithPkgName }}, error)
  232. {{ .ExportedName }}WithContext(aws.Context, {{ .InputRef.GoTypeWithPkgName }}, ...request.Option) ({{ .OutputRef.GoTypeWithPkgName }}, error)
  233. {{ .ExportedName }}Request({{ .InputRef.GoTypeWithPkgName }}) (*request.Request, {{ .OutputRef.GoTypeWithPkgName }})
  234. {{ if .Paginator -}}
  235. {{ .ExportedName }}Pages({{ .InputRef.GoTypeWithPkgName }}, func({{ .OutputRef.GoTypeWithPkgName }}, bool) bool) error
  236. {{ .ExportedName }}PagesWithContext(aws.Context, {{ .InputRef.GoTypeWithPkgName }}, func({{ .OutputRef.GoTypeWithPkgName }}, bool) bool, ...request.Option) error
  237. {{- end }}
  238. `))
  239. // InterfaceSignature returns a string representing the Operation's interface{}
  240. // functional signature.
  241. func (o *Operation) InterfaceSignature() string {
  242. var buf bytes.Buffer
  243. err := tplInfSig.Execute(&buf, o)
  244. if err != nil {
  245. panic(err)
  246. }
  247. return strings.TrimSpace(buf.String())
  248. }
  249. // tplExample defines the template for rendering an Operation example
  250. var tplExample = template.Must(template.New("operationExample").Parse(`
  251. func Example{{ .API.StructName }}_{{ .ExportedName }}() {
  252. sess := session.Must(session.NewSession())
  253. svc := {{ .API.PackageName }}.New(sess)
  254. {{ .ExampleInput }}
  255. resp, err := svc.{{ .ExportedName }}(params)
  256. if err != nil {
  257. // Print the error, cast err to awserr.Error to get the Code and
  258. // Message from an error.
  259. fmt.Println(err.Error())
  260. return
  261. }
  262. // Pretty-print the response data.
  263. fmt.Println(resp)
  264. }
  265. `))
  266. // Example returns a string of the rendered Go code for the Operation
  267. func (o *Operation) Example() string {
  268. var buf bytes.Buffer
  269. err := tplExample.Execute(&buf, o)
  270. if err != nil {
  271. panic(err)
  272. }
  273. return strings.TrimSpace(buf.String())
  274. }
  275. // ExampleInput return a string of the rendered Go code for an example's input parameters
  276. func (o *Operation) ExampleInput() string {
  277. if len(o.InputRef.Shape.MemberRefs) == 0 {
  278. if strings.Contains(o.InputRef.GoTypeElem(), ".") {
  279. o.imports["github.com/aws/aws-sdk-go/service/"+strings.Split(o.InputRef.GoTypeElem(), ".")[0]] = true
  280. return fmt.Sprintf("var params *%s", o.InputRef.GoTypeElem())
  281. }
  282. return fmt.Sprintf("var params *%s.%s",
  283. o.API.PackageName(), o.InputRef.GoTypeElem())
  284. }
  285. e := example{o, map[string]int{}}
  286. return "params := " + e.traverseAny(o.InputRef.Shape, false, false)
  287. }
  288. // A example provides
  289. type example struct {
  290. *Operation
  291. visited map[string]int
  292. }
  293. // traverseAny returns rendered Go code for the shape.
  294. func (e *example) traverseAny(s *Shape, required, payload bool) string {
  295. str := ""
  296. e.visited[s.ShapeName]++
  297. switch s.Type {
  298. case "structure":
  299. str = e.traverseStruct(s, required, payload)
  300. case "list":
  301. str = e.traverseList(s, required, payload)
  302. case "map":
  303. str = e.traverseMap(s, required, payload)
  304. case "jsonvalue":
  305. str = "aws.JSONValue{\"key\": \"value\"}"
  306. if required {
  307. str += " // Required"
  308. }
  309. default:
  310. str = e.traverseScalar(s, required, payload)
  311. }
  312. e.visited[s.ShapeName]--
  313. return str
  314. }
  315. var reType = regexp.MustCompile(`\b([A-Z])`)
  316. // traverseStruct returns rendered Go code for a structure type shape.
  317. func (e *example) traverseStruct(s *Shape, required, payload bool) string {
  318. var buf bytes.Buffer
  319. if s.resolvePkg != "" {
  320. e.imports[s.resolvePkg] = true
  321. buf.WriteString("&" + s.GoTypeElem() + "{")
  322. } else {
  323. buf.WriteString("&" + s.API.PackageName() + "." + s.GoTypeElem() + "{")
  324. }
  325. if required {
  326. buf.WriteString(" // Required")
  327. }
  328. buf.WriteString("\n")
  329. req := make([]string, len(s.Required))
  330. copy(req, s.Required)
  331. sort.Strings(req)
  332. if e.visited[s.ShapeName] < 2 {
  333. for _, n := range req {
  334. m := s.MemberRefs[n].Shape
  335. p := n == s.Payload && (s.MemberRefs[n].Streaming || m.Streaming)
  336. buf.WriteString(n + ": " + e.traverseAny(m, true, p) + ",")
  337. if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
  338. buf.WriteString(" // Required")
  339. }
  340. buf.WriteString("\n")
  341. }
  342. for _, n := range s.MemberNames() {
  343. if s.IsRequired(n) {
  344. continue
  345. }
  346. m := s.MemberRefs[n].Shape
  347. p := n == s.Payload && (s.MemberRefs[n].Streaming || m.Streaming)
  348. buf.WriteString(n + ": " + e.traverseAny(m, false, p) + ",\n")
  349. }
  350. } else {
  351. buf.WriteString("// Recursive values...\n")
  352. }
  353. buf.WriteString("}")
  354. return buf.String()
  355. }
  356. // traverseMap returns rendered Go code for a map type shape.
  357. func (e *example) traverseMap(s *Shape, required, payload bool) string {
  358. var buf bytes.Buffer
  359. t := ""
  360. if s.resolvePkg != "" {
  361. e.imports[s.resolvePkg] = true
  362. t = s.GoTypeElem()
  363. } else {
  364. t = reType.ReplaceAllString(s.GoTypeElem(), s.API.PackageName()+".$1")
  365. }
  366. buf.WriteString(t + "{")
  367. if required {
  368. buf.WriteString(" // Required")
  369. }
  370. buf.WriteString("\n")
  371. if e.visited[s.ShapeName] < 2 {
  372. m := s.ValueRef.Shape
  373. buf.WriteString("\"Key\": " + e.traverseAny(m, true, false) + ",")
  374. if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
  375. buf.WriteString(" // Required")
  376. }
  377. buf.WriteString("\n// More values...\n")
  378. } else {
  379. buf.WriteString("// Recursive values...\n")
  380. }
  381. buf.WriteString("}")
  382. return buf.String()
  383. }
  384. // traverseList returns rendered Go code for a list type shape.
  385. func (e *example) traverseList(s *Shape, required, payload bool) string {
  386. var buf bytes.Buffer
  387. t := ""
  388. if s.resolvePkg != "" {
  389. e.imports[s.resolvePkg] = true
  390. t = s.GoTypeElem()
  391. } else {
  392. t = reType.ReplaceAllString(s.GoTypeElem(), s.API.PackageName()+".$1")
  393. }
  394. buf.WriteString(t + "{")
  395. if required {
  396. buf.WriteString(" // Required")
  397. }
  398. buf.WriteString("\n")
  399. if e.visited[s.ShapeName] < 2 {
  400. m := s.MemberRef.Shape
  401. buf.WriteString(e.traverseAny(m, true, false) + ",")
  402. if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
  403. buf.WriteString(" // Required")
  404. }
  405. buf.WriteString("\n// More values...\n")
  406. } else {
  407. buf.WriteString("// Recursive values...\n")
  408. }
  409. buf.WriteString("}")
  410. return buf.String()
  411. }
  412. // traverseScalar returns an AWS Type string representation initialized to a value.
  413. // Will panic if s is an unsupported shape type.
  414. func (e *example) traverseScalar(s *Shape, required, payload bool) string {
  415. str := ""
  416. switch s.Type {
  417. case "integer", "long":
  418. str = `aws.Int64(1)`
  419. case "float", "double":
  420. str = `aws.Float64(1.0)`
  421. case "string", "character":
  422. str = `aws.String("` + s.ShapeName + `")`
  423. case "blob":
  424. if payload {
  425. str = `bytes.NewReader([]byte("PAYLOAD"))`
  426. } else {
  427. str = `[]byte("PAYLOAD")`
  428. }
  429. case "boolean":
  430. str = `aws.Bool(true)`
  431. case "timestamp":
  432. str = `aws.Time(time.Now())`
  433. default:
  434. panic("unsupported shape " + s.Type)
  435. }
  436. return str
  437. }