operation.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. package api
  2. import (
  3. "bytes"
  4. "fmt"
  5. "regexp"
  6. "sort"
  7. "strings"
  8. "text/template"
  9. )
  10. // An Operation defines a specific API Operation.
  11. type Operation struct {
  12. API *API `json:"-"`
  13. ExportedName string
  14. Name string
  15. Documentation string
  16. HTTP HTTPInfo
  17. InputRef ShapeRef `json:"input"`
  18. OutputRef ShapeRef `json:"output"`
  19. Paginator *Paginator
  20. Deprecated bool `json:"deprecated"`
  21. AuthType string `json:"authtype"`
  22. }
  23. // A HTTPInfo defines the method of HTTP request for the Operation.
  24. type HTTPInfo struct {
  25. Method string
  26. RequestURI string
  27. ResponseCode uint
  28. }
  29. // HasInput returns if the Operation accepts an input paramater
  30. func (o *Operation) HasInput() bool {
  31. return o.InputRef.ShapeName != ""
  32. }
  33. // HasOutput returns if the Operation accepts an output parameter
  34. func (o *Operation) HasOutput() bool {
  35. return o.OutputRef.ShapeName != ""
  36. }
  37. // tplOperation defines a template for rendering an API Operation
  38. var tplOperation = template.Must(template.New("operation").Parse(`
  39. const op{{ .ExportedName }} = "{{ .Name }}"
  40. // {{ .ExportedName }}Request generates a "aws/request.Request" representing the
  41. // client's request for the {{ .ExportedName }} operation. The "output" return
  42. // value can be used to capture response data after the request's "Send" method
  43. // is called.
  44. //
  45. // Creating a request object using this method should be used when you want to inject
  46. // custom logic into the request's lifecycle using a custom handler, or if you want to
  47. // access properties on the request object before or after sending the request. If
  48. // you just want the service response, call the {{ .ExportedName }} method directly
  49. // instead.
  50. //
  51. // Note: You must call the "Send" method on the returned request object in order
  52. // to execute the request.
  53. //
  54. // // Example sending a request using the {{ .ExportedName }}Request method.
  55. // req, resp := client.{{ .ExportedName }}Request(params)
  56. //
  57. // err := req.Send()
  58. // if err == nil { // resp is now filled
  59. // fmt.Println(resp)
  60. // }
  61. //
  62. func (c *{{ .API.StructName }}) {{ .ExportedName }}Request(` +
  63. `input {{ .InputRef.GoType }}) (req *request.Request, output {{ .OutputRef.GoType }}) {
  64. {{ if (or .Deprecated (or .InputRef.Deprecated .OutputRef.Deprecated)) }}if c.Client.Config.Logger != nil {
  65. c.Client.Config.Logger.Log("This operation, {{ .ExportedName }}, has been deprecated")
  66. }
  67. op := &request.Operation{ {{ else }} op := &request.Operation{ {{ end }}
  68. Name: op{{ .ExportedName }},
  69. {{ if ne .HTTP.Method "" }}HTTPMethod: "{{ .HTTP.Method }}",
  70. {{ end }}HTTPPath: {{ if ne .HTTP.RequestURI "" }}"{{ .HTTP.RequestURI }}"{{ else }}"/"{{ end }},
  71. {{ if .Paginator }}Paginator: &request.Paginator{
  72. InputTokens: {{ .Paginator.InputTokensString }},
  73. OutputTokens: {{ .Paginator.OutputTokensString }},
  74. LimitToken: "{{ .Paginator.LimitKey }}",
  75. TruncationToken: "{{ .Paginator.MoreResults }}",
  76. },
  77. {{ end }}
  78. }
  79. if input == nil {
  80. input = &{{ .InputRef.GoTypeElem }}{}
  81. }
  82. req = c.newRequest(op, input, output){{ if eq .OutputRef.Shape.Placeholder true }}
  83. req.Handlers.Unmarshal.Remove({{ .API.ProtocolPackage }}.UnmarshalHandler)
  84. req.Handlers.Unmarshal.PushBackNamed(protocol.UnmarshalDiscardBodyHandler){{ end }}
  85. {{ if eq .AuthType "none" }}req.Config.Credentials = credentials.AnonymousCredentials
  86. output = &{{ .OutputRef.GoTypeElem }}{} {{ else }} output = &{{ .OutputRef.GoTypeElem }}{} {{ end }}
  87. req.Data = output
  88. return
  89. }
  90. {{ .Documentation }}func (c *{{ .API.StructName }}) {{ .ExportedName }}(` +
  91. `input {{ .InputRef.GoType }}) ({{ .OutputRef.GoType }}, error) {
  92. req, out := c.{{ .ExportedName }}Request(input)
  93. err := req.Send()
  94. return out, err
  95. }
  96. {{ if .Paginator }}
  97. // {{ .ExportedName }}Pages iterates over the pages of a {{ .ExportedName }} operation,
  98. // calling the "fn" function with the response data for each page. To stop
  99. // iterating, return false from the fn function.
  100. //
  101. // See {{ .ExportedName }} method for more information on how to use this operation.
  102. //
  103. // Note: This operation can generate multiple requests to a service.
  104. //
  105. // // Example iterating over at most 3 pages of a {{ .ExportedName }} operation.
  106. // pageNum := 0
  107. // err := client.{{ .ExportedName }}Pages(params,
  108. // func(page {{ .OutputRef.GoType }}, lastPage bool) bool {
  109. // pageNum++
  110. // fmt.Println(page)
  111. // return pageNum <= 3
  112. // })
  113. //
  114. func (c *{{ .API.StructName }}) {{ .ExportedName }}Pages(` +
  115. `input {{ .InputRef.GoType }}, fn func(p {{ .OutputRef.GoType }}, lastPage bool) (shouldContinue bool)) error {
  116. page, _ := c.{{ .ExportedName }}Request(input)
  117. page.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler("Paginator"))
  118. return page.EachPage(func(p interface{}, lastPage bool) bool {
  119. return fn(p.({{ .OutputRef.GoType }}), lastPage)
  120. })
  121. }
  122. {{ end }}
  123. `))
  124. // GoCode returns a string of rendered GoCode for this Operation
  125. func (o *Operation) GoCode() string {
  126. var buf bytes.Buffer
  127. err := tplOperation.Execute(&buf, o)
  128. if err != nil {
  129. panic(err)
  130. }
  131. return strings.TrimSpace(buf.String())
  132. }
  133. // tplInfSig defines the template for rendering an Operation's signature within an Interface definition.
  134. var tplInfSig = template.Must(template.New("opsig").Parse(`
  135. {{ .ExportedName }}Request({{ .InputRef.GoTypeWithPkgName }}) (*request.Request, {{ .OutputRef.GoTypeWithPkgName }})
  136. {{ .ExportedName }}({{ .InputRef.GoTypeWithPkgName }}) ({{ .OutputRef.GoTypeWithPkgName }}, error)
  137. {{ if .Paginator }}
  138. {{ .ExportedName }}Pages({{ .InputRef.GoTypeWithPkgName }}, func({{ .OutputRef.GoTypeWithPkgName }}, bool) bool) error{{ end }}
  139. `))
  140. // InterfaceSignature returns a string representing the Operation's interface{}
  141. // functional signature.
  142. func (o *Operation) InterfaceSignature() string {
  143. var buf bytes.Buffer
  144. err := tplInfSig.Execute(&buf, o)
  145. if err != nil {
  146. panic(err)
  147. }
  148. return strings.TrimSpace(buf.String())
  149. }
  150. // tplExample defines the template for rendering an Operation example
  151. var tplExample = template.Must(template.New("operationExample").Parse(`
  152. func Example{{ .API.StructName }}_{{ .ExportedName }}() {
  153. sess, err := session.NewSession()
  154. if err != nil {
  155. fmt.Println("failed to create session,", err)
  156. return
  157. }
  158. svc := {{ .API.PackageName }}.New(sess)
  159. {{ .ExampleInput }}
  160. resp, err := svc.{{ .ExportedName }}(params)
  161. if err != nil {
  162. // Print the error, cast err to awserr.Error to get the Code and
  163. // Message from an error.
  164. fmt.Println(err.Error())
  165. return
  166. }
  167. // Pretty-print the response data.
  168. fmt.Println(resp)
  169. }
  170. `))
  171. // Example returns a string of the rendered Go code for the Operation
  172. func (o *Operation) Example() string {
  173. var buf bytes.Buffer
  174. err := tplExample.Execute(&buf, o)
  175. if err != nil {
  176. panic(err)
  177. }
  178. return strings.TrimSpace(buf.String())
  179. }
  180. // ExampleInput return a string of the rendered Go code for an example's input parameters
  181. func (o *Operation) ExampleInput() string {
  182. if len(o.InputRef.Shape.MemberRefs) == 0 {
  183. return fmt.Sprintf("var params *%s.%s",
  184. o.API.PackageName(), o.InputRef.GoTypeElem())
  185. }
  186. e := example{o, map[string]int{}}
  187. return "params := " + e.traverseAny(o.InputRef.Shape, false, false)
  188. }
  189. // A example provides
  190. type example struct {
  191. *Operation
  192. visited map[string]int
  193. }
  194. // traverseAny returns rendered Go code for the shape.
  195. func (e *example) traverseAny(s *Shape, required, payload bool) string {
  196. str := ""
  197. e.visited[s.ShapeName]++
  198. switch s.Type {
  199. case "structure":
  200. str = e.traverseStruct(s, required, payload)
  201. case "list":
  202. str = e.traverseList(s, required, payload)
  203. case "map":
  204. str = e.traverseMap(s, required, payload)
  205. default:
  206. str = e.traverseScalar(s, required, payload)
  207. }
  208. e.visited[s.ShapeName]--
  209. return str
  210. }
  211. var reType = regexp.MustCompile(`\b([A-Z])`)
  212. // traverseStruct returns rendered Go code for a structure type shape.
  213. func (e *example) traverseStruct(s *Shape, required, payload bool) string {
  214. var buf bytes.Buffer
  215. buf.WriteString("&" + s.API.PackageName() + "." + s.GoTypeElem() + "{")
  216. if required {
  217. buf.WriteString(" // Required")
  218. }
  219. buf.WriteString("\n")
  220. req := make([]string, len(s.Required))
  221. copy(req, s.Required)
  222. sort.Strings(req)
  223. if e.visited[s.ShapeName] < 2 {
  224. for _, n := range req {
  225. m := s.MemberRefs[n].Shape
  226. p := n == s.Payload && (s.MemberRefs[n].Streaming || m.Streaming)
  227. buf.WriteString(n + ": " + e.traverseAny(m, true, p) + ",")
  228. if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
  229. buf.WriteString(" // Required")
  230. }
  231. buf.WriteString("\n")
  232. }
  233. for _, n := range s.MemberNames() {
  234. if s.IsRequired(n) {
  235. continue
  236. }
  237. m := s.MemberRefs[n].Shape
  238. p := n == s.Payload && (s.MemberRefs[n].Streaming || m.Streaming)
  239. buf.WriteString(n + ": " + e.traverseAny(m, false, p) + ",\n")
  240. }
  241. } else {
  242. buf.WriteString("// Recursive values...\n")
  243. }
  244. buf.WriteString("}")
  245. return buf.String()
  246. }
  247. // traverseMap returns rendered Go code for a map type shape.
  248. func (e *example) traverseMap(s *Shape, required, payload bool) string {
  249. var buf bytes.Buffer
  250. t := reType.ReplaceAllString(s.GoTypeElem(), s.API.PackageName()+".$1")
  251. buf.WriteString(t + "{")
  252. if required {
  253. buf.WriteString(" // Required")
  254. }
  255. buf.WriteString("\n")
  256. if e.visited[s.ShapeName] < 2 {
  257. m := s.ValueRef.Shape
  258. buf.WriteString("\"Key\": " + e.traverseAny(m, true, false) + ",")
  259. if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
  260. buf.WriteString(" // Required")
  261. }
  262. buf.WriteString("\n// More values...\n")
  263. } else {
  264. buf.WriteString("// Recursive values...\n")
  265. }
  266. buf.WriteString("}")
  267. return buf.String()
  268. }
  269. // traverseList returns rendered Go code for a list type shape.
  270. func (e *example) traverseList(s *Shape, required, payload bool) string {
  271. var buf bytes.Buffer
  272. t := reType.ReplaceAllString(s.GoTypeElem(), s.API.PackageName()+".$1")
  273. buf.WriteString(t + "{")
  274. if required {
  275. buf.WriteString(" // Required")
  276. }
  277. buf.WriteString("\n")
  278. if e.visited[s.ShapeName] < 2 {
  279. m := s.MemberRef.Shape
  280. buf.WriteString(e.traverseAny(m, true, false) + ",")
  281. if m.Type != "list" && m.Type != "structure" && m.Type != "map" {
  282. buf.WriteString(" // Required")
  283. }
  284. buf.WriteString("\n// More values...\n")
  285. } else {
  286. buf.WriteString("// Recursive values...\n")
  287. }
  288. buf.WriteString("}")
  289. return buf.String()
  290. }
  291. // traverseScalar returns an AWS Type string representation initialized to a value.
  292. // Will panic if s is an unsupported shape type.
  293. func (e *example) traverseScalar(s *Shape, required, payload bool) string {
  294. str := ""
  295. switch s.Type {
  296. case "integer", "long":
  297. str = `aws.Int64(1)`
  298. case "float", "double":
  299. str = `aws.Float64(1.0)`
  300. case "string", "character":
  301. str = `aws.String("` + s.ShapeName + `")`
  302. case "blob":
  303. if payload {
  304. str = `bytes.NewReader([]byte("PAYLOAD"))`
  305. } else {
  306. str = `[]byte("PAYLOAD")`
  307. }
  308. case "boolean":
  309. str = `aws.Bool(true)`
  310. case "timestamp":
  311. str = `aws.Time(time.Now())`
  312. default:
  313. panic("unsupported shape " + s.Type)
  314. }
  315. return str
  316. }