api.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. // +build codegen
  2. // Package api represents API abstractions for rendering service generated files.
  3. package api
  4. import (
  5. "bytes"
  6. "fmt"
  7. "path"
  8. "regexp"
  9. "sort"
  10. "strings"
  11. "text/template"
  12. )
  13. // An API defines a service API's definition. and logic to serialize the definition.
  14. type API struct {
  15. Metadata Metadata
  16. Operations map[string]*Operation
  17. Shapes map[string]*Shape
  18. Waiters []Waiter
  19. Documentation string
  20. // Set to true to avoid removing unused shapes
  21. NoRemoveUnusedShapes bool
  22. // Set to true to avoid renaming to 'Input/Output' postfixed shapes
  23. NoRenameToplevelShapes bool
  24. // Set to true to ignore service/request init methods (for testing)
  25. NoInitMethods bool
  26. // Set to true to ignore String() and GoString methods (for generated tests)
  27. NoStringerMethods bool
  28. // Set to true to not generate API service name constants
  29. NoConstServiceNames bool
  30. // Set to true to not generate validation shapes
  31. NoValidataShapeMethods bool
  32. SvcClientImportPath string
  33. initialized bool
  34. imports map[string]bool
  35. name string
  36. path string
  37. }
  38. // A Metadata is the metadata about an API's definition.
  39. type Metadata struct {
  40. APIVersion string
  41. EndpointPrefix string
  42. SigningName string
  43. ServiceAbbreviation string
  44. ServiceFullName string
  45. SignatureVersion string
  46. JSONVersion string
  47. TargetPrefix string
  48. Protocol string
  49. }
  50. // PackageName name of the API package
  51. func (a *API) PackageName() string {
  52. return strings.ToLower(a.StructName())
  53. }
  54. // InterfacePackageName returns the package name for the interface.
  55. func (a *API) InterfacePackageName() string {
  56. return a.PackageName() + "iface"
  57. }
  58. var nameRegex = regexp.MustCompile(`^Amazon|AWS\s*|\(.*|\s+|\W+`)
  59. // StructName returns the struct name for a given API.
  60. func (a *API) StructName() string {
  61. if a.name == "" {
  62. name := a.Metadata.ServiceAbbreviation
  63. if name == "" {
  64. name = a.Metadata.ServiceFullName
  65. }
  66. name = nameRegex.ReplaceAllString(name, "")
  67. switch strings.ToLower(name) {
  68. case "elasticloadbalancing":
  69. a.name = "ELB"
  70. case "elasticloadbalancingv2":
  71. a.name = "ELBV2"
  72. case "config":
  73. a.name = "ConfigService"
  74. default:
  75. a.name = name
  76. }
  77. }
  78. return a.name
  79. }
  80. // UseInitMethods returns if the service's init method should be rendered.
  81. func (a *API) UseInitMethods() bool {
  82. return !a.NoInitMethods
  83. }
  84. // NiceName returns the human friendly API name.
  85. func (a *API) NiceName() string {
  86. if a.Metadata.ServiceAbbreviation != "" {
  87. return a.Metadata.ServiceAbbreviation
  88. }
  89. return a.Metadata.ServiceFullName
  90. }
  91. // ProtocolPackage returns the package name of the protocol this API uses.
  92. func (a *API) ProtocolPackage() string {
  93. switch a.Metadata.Protocol {
  94. case "json":
  95. return "jsonrpc"
  96. case "ec2":
  97. return "ec2query"
  98. default:
  99. return strings.Replace(a.Metadata.Protocol, "-", "", -1)
  100. }
  101. }
  102. // OperationNames returns a slice of API operations supported.
  103. func (a *API) OperationNames() []string {
  104. i, names := 0, make([]string, len(a.Operations))
  105. for n := range a.Operations {
  106. names[i] = n
  107. i++
  108. }
  109. sort.Strings(names)
  110. return names
  111. }
  112. // OperationList returns a slice of API operation pointers
  113. func (a *API) OperationList() []*Operation {
  114. list := make([]*Operation, len(a.Operations))
  115. for i, n := range a.OperationNames() {
  116. list[i] = a.Operations[n]
  117. }
  118. return list
  119. }
  120. // OperationHasOutputPlaceholder returns if any of the API operation input
  121. // or output shapes are place holders.
  122. func (a *API) OperationHasOutputPlaceholder() bool {
  123. for _, op := range a.Operations {
  124. if op.OutputRef.Shape.Placeholder {
  125. return true
  126. }
  127. }
  128. return false
  129. }
  130. // ShapeNames returns a slice of names for each shape used by the API.
  131. func (a *API) ShapeNames() []string {
  132. i, names := 0, make([]string, len(a.Shapes))
  133. for n := range a.Shapes {
  134. names[i] = n
  135. i++
  136. }
  137. sort.Strings(names)
  138. return names
  139. }
  140. // ShapeList returns a slice of shape pointers used by the API.
  141. //
  142. // Will exclude error shapes from the list of shapes returned.
  143. func (a *API) ShapeList() []*Shape {
  144. list := make([]*Shape, 0, len(a.Shapes))
  145. for _, n := range a.ShapeNames() {
  146. // Ignore error shapes in list
  147. if a.Shapes[n].IsError {
  148. continue
  149. }
  150. list = append(list, a.Shapes[n])
  151. }
  152. return list
  153. }
  154. // resetImports resets the import map to default values.
  155. func (a *API) resetImports() {
  156. a.imports = map[string]bool{
  157. "github.com/aws/aws-sdk-go/aws": true,
  158. }
  159. }
  160. // importsGoCode returns the generated Go import code.
  161. func (a *API) importsGoCode() string {
  162. if len(a.imports) == 0 {
  163. return ""
  164. }
  165. corePkgs, extPkgs := []string{}, []string{}
  166. for i := range a.imports {
  167. if strings.Contains(i, ".") {
  168. extPkgs = append(extPkgs, i)
  169. } else {
  170. corePkgs = append(corePkgs, i)
  171. }
  172. }
  173. sort.Strings(corePkgs)
  174. sort.Strings(extPkgs)
  175. code := "import (\n"
  176. for _, i := range corePkgs {
  177. code += fmt.Sprintf("\t%q\n", i)
  178. }
  179. if len(corePkgs) > 0 {
  180. code += "\n"
  181. }
  182. for _, i := range extPkgs {
  183. code += fmt.Sprintf("\t%q\n", i)
  184. }
  185. code += ")\n\n"
  186. return code
  187. }
  188. // A tplAPI is the top level template for the API
  189. var tplAPI = template.Must(template.New("api").Parse(`
  190. {{ range $_, $o := .OperationList }}
  191. {{ $o.GoCode }}
  192. {{ end }}
  193. {{ range $_, $s := .ShapeList }}
  194. {{ if and $s.IsInternal (eq $s.Type "structure") }}{{ $s.GoCode }}{{ end }}
  195. {{ end }}
  196. {{ range $_, $s := .ShapeList }}
  197. {{ if $s.IsEnum }}{{ $s.GoCode }}{{ end }}
  198. {{ end }}
  199. `))
  200. // APIGoCode renders the API in Go code. Returning it as a string
  201. func (a *API) APIGoCode() string {
  202. a.resetImports()
  203. delete(a.imports, "github.com/aws/aws-sdk-go/aws")
  204. a.imports["github.com/aws/aws-sdk-go/aws/awsutil"] = true
  205. a.imports["github.com/aws/aws-sdk-go/aws/request"] = true
  206. if a.OperationHasOutputPlaceholder() {
  207. a.imports["github.com/aws/aws-sdk-go/private/protocol/"+a.ProtocolPackage()] = true
  208. a.imports["github.com/aws/aws-sdk-go/private/protocol"] = true
  209. }
  210. for _, op := range a.Operations {
  211. if op.AuthType == "none" {
  212. a.imports["github.com/aws/aws-sdk-go/aws/credentials"] = true
  213. break
  214. }
  215. }
  216. var buf bytes.Buffer
  217. err := tplAPI.Execute(&buf, a)
  218. if err != nil {
  219. panic(err)
  220. }
  221. code := a.importsGoCode() + strings.TrimSpace(buf.String())
  222. return code
  223. }
  224. // A tplService defines the template for the service generated code.
  225. var tplService = template.Must(template.New("service").Parse(`
  226. {{ .Documentation }}//The service client's operations are safe to be used concurrently.
  227. // It is not safe to mutate any of the client's properties though.
  228. type {{ .StructName }} struct {
  229. *client.Client
  230. }
  231. {{ if .UseInitMethods }}// Used for custom client initialization logic
  232. var initClient func(*client.Client)
  233. // Used for custom request initialization logic
  234. var initRequest func(*request.Request)
  235. {{ end }}
  236. {{ if not .NoConstServiceNames }}
  237. // A ServiceName is the name of the service the client will make API calls to.
  238. const ServiceName = "{{ .Metadata.EndpointPrefix }}"
  239. {{ end }}
  240. // New creates a new instance of the {{ .StructName }} client with a session.
  241. // If additional configuration is needed for the client instance use the optional
  242. // aws.Config parameter to add your extra config.
  243. //
  244. // Example:
  245. // // Create a {{ .StructName }} client from just a session.
  246. // svc := {{ .PackageName }}.New(mySession)
  247. //
  248. // // Create a {{ .StructName }} client with additional configuration
  249. // svc := {{ .PackageName }}.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
  250. func New(p client.ConfigProvider, cfgs ...*aws.Config) *{{ .StructName }} {
  251. c := p.ClientConfig({{ if .NoConstServiceNames }}"{{ .Metadata.EndpointPrefix }}"{{ else }}ServiceName{{ end }}, cfgs...)
  252. return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
  253. }
  254. // newClient creates, initializes and returns a new service client instance.
  255. func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string) *{{ .StructName }} {
  256. svc := &{{ .StructName }}{
  257. Client: client.New(
  258. cfg,
  259. metadata.ClientInfo{
  260. ServiceName: {{ if .NoConstServiceNames }}"{{ .Metadata.EndpointPrefix }}"{{ else }}ServiceName{{ end }}, {{ if ne .Metadata.SigningName "" }}
  261. SigningName: "{{ .Metadata.SigningName }}",{{ end }}
  262. SigningRegion: signingRegion,
  263. Endpoint: endpoint,
  264. APIVersion: "{{ .Metadata.APIVersion }}",
  265. {{ if eq .Metadata.Protocol "json" }}JSONVersion: "{{ .Metadata.JSONVersion }}",
  266. TargetPrefix: "{{ .Metadata.TargetPrefix }}",
  267. {{ end }}
  268. },
  269. handlers,
  270. ),
  271. }
  272. // Handlers
  273. svc.Handlers.Sign.PushBackNamed({{if eq .Metadata.SignatureVersion "v2"}}v2{{else}}v4{{end}}.SignRequestHandler)
  274. {{if eq .Metadata.SignatureVersion "v2"}}svc.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
  275. {{end}}svc.Handlers.Build.PushBackNamed({{ .ProtocolPackage }}.BuildHandler)
  276. svc.Handlers.Unmarshal.PushBackNamed({{ .ProtocolPackage }}.UnmarshalHandler)
  277. svc.Handlers.UnmarshalMeta.PushBackNamed({{ .ProtocolPackage }}.UnmarshalMetaHandler)
  278. svc.Handlers.UnmarshalError.PushBackNamed({{ .ProtocolPackage }}.UnmarshalErrorHandler)
  279. {{ if .UseInitMethods }}// Run custom client initialization if present
  280. if initClient != nil {
  281. initClient(svc.Client)
  282. }
  283. {{ end }}
  284. return svc
  285. }
  286. // newRequest creates a new request for a {{ .StructName }} operation and runs any
  287. // custom request initialization.
  288. func (c *{{ .StructName }}) newRequest(op *request.Operation, params, data interface{}) *request.Request {
  289. req := c.NewRequest(op, params, data)
  290. {{ if .UseInitMethods }}// Run custom request initialization if present
  291. if initRequest != nil {
  292. initRequest(req)
  293. }
  294. {{ end }}
  295. return req
  296. }
  297. `))
  298. // ServiceGoCode renders service go code. Returning it as a string.
  299. func (a *API) ServiceGoCode() string {
  300. a.resetImports()
  301. a.imports["github.com/aws/aws-sdk-go/aws/client"] = true
  302. a.imports["github.com/aws/aws-sdk-go/aws/client/metadata"] = true
  303. a.imports["github.com/aws/aws-sdk-go/aws/request"] = true
  304. if a.Metadata.SignatureVersion == "v2" {
  305. a.imports["github.com/aws/aws-sdk-go/private/signer/v2"] = true
  306. a.imports["github.com/aws/aws-sdk-go/aws/corehandlers"] = true
  307. } else {
  308. a.imports["github.com/aws/aws-sdk-go/aws/signer/v4"] = true
  309. }
  310. a.imports["github.com/aws/aws-sdk-go/private/protocol/"+a.ProtocolPackage()] = true
  311. var buf bytes.Buffer
  312. err := tplService.Execute(&buf, a)
  313. if err != nil {
  314. panic(err)
  315. }
  316. code := a.importsGoCode() + buf.String()
  317. return code
  318. }
  319. // ExampleGoCode renders service example code. Returning it as a string.
  320. func (a *API) ExampleGoCode() string {
  321. exs := []string{}
  322. for _, o := range a.OperationList() {
  323. exs = append(exs, o.Example())
  324. }
  325. code := fmt.Sprintf("import (\n%q\n%q\n%q\n\n%q\n%q\n%q\n)\n\n"+
  326. "var _ time.Duration\nvar _ bytes.Buffer\n\n%s",
  327. "bytes",
  328. "fmt",
  329. "time",
  330. "github.com/aws/aws-sdk-go/aws",
  331. "github.com/aws/aws-sdk-go/aws/session",
  332. path.Join(a.SvcClientImportPath, a.PackageName()),
  333. strings.Join(exs, "\n\n"),
  334. )
  335. return code
  336. }
  337. // A tplInterface defines the template for the service interface type.
  338. var tplInterface = template.Must(template.New("interface").Parse(`
  339. // {{ .StructName }}API provides an interface to enable mocking the
  340. // {{ .PackageName }}.{{ .StructName }} service client's API operation,
  341. // paginators, and waiters. This make unit testing your code that calls out
  342. // to the SDK's service client's calls easier.
  343. //
  344. // The best way to use this interface is so the SDK's service client's calls
  345. // can be stubbed out for unit testing your code with the SDK without needing
  346. // to inject custom request handlers into the the SDK's request pipeline.
  347. //
  348. // // myFunc uses an SDK service client to make a request to
  349. // // {{.Metadata.ServiceFullName}}. {{ $opts := .OperationList }}{{ $opt := index $opts 0 }}
  350. // func myFunc(svc {{ .InterfacePackageName }}.{{ .StructName }}API) bool {
  351. // // Make svc.{{ $opt.ExportedName }} request
  352. // }
  353. //
  354. // func main() {
  355. // sess := session.New()
  356. // svc := {{ .PackageName }}.New(sess)
  357. //
  358. // myFunc(svc)
  359. // }
  360. //
  361. // In your _test.go file:
  362. //
  363. // // Define a mock struct to be used in your unit tests of myFunc.
  364. // type mock{{ .StructName }}Client struct {
  365. // {{ .InterfacePackageName }}.{{ .StructName }}API
  366. // }
  367. // func (m *mock{{ .StructName }}Client) {{ $opt.ExportedName }}(input {{ $opt.InputRef.GoTypeWithPkgName }}) ({{ $opt.OutputRef.GoTypeWithPkgName }}, error) {
  368. // // mock response/functionality
  369. // }
  370. //
  371. // TestMyFunc(t *testing.T) {
  372. // // Setup Test
  373. // mockSvc := &mock{{ .StructName }}Client{}
  374. //
  375. // myfunc(mockSvc)
  376. //
  377. // // Verify myFunc's functionality
  378. // }
  379. //
  380. // It is important to note that this interface will have breaking changes
  381. // when the service model is updated and adds new API operations, paginators,
  382. // and waiters. Its suggested to use the pattern above for testing, or using
  383. // tooling to generate mocks to satisfy the interfaces.
  384. type {{ .StructName }}API interface {
  385. {{ range $_, $o := .OperationList }}
  386. {{ $o.InterfaceSignature }}
  387. {{ end }}
  388. {{ range $_, $w := .Waiters }}
  389. {{ $w.InterfaceSignature }}
  390. {{ end }}
  391. }
  392. var _ {{ .StructName }}API = (*{{ .PackageName }}.{{ .StructName }})(nil)
  393. `))
  394. // InterfaceGoCode returns the go code for the service's API operations as an
  395. // interface{}. Assumes that the interface is being created in a different
  396. // package than the service API's package.
  397. func (a *API) InterfaceGoCode() string {
  398. a.resetImports()
  399. a.imports = map[string]bool{
  400. "github.com/aws/aws-sdk-go/aws/request": true,
  401. path.Join(a.SvcClientImportPath, a.PackageName()): true,
  402. }
  403. var buf bytes.Buffer
  404. err := tplInterface.Execute(&buf, a)
  405. if err != nil {
  406. panic(err)
  407. }
  408. code := a.importsGoCode() + strings.TrimSpace(buf.String())
  409. return code
  410. }
  411. // NewAPIGoCodeWithPkgName returns a string of instantiating the API prefixed
  412. // with its package name. Takes a string depicting the Config.
  413. func (a *API) NewAPIGoCodeWithPkgName(cfg string) string {
  414. return fmt.Sprintf("%s.New(%s)", a.PackageName(), cfg)
  415. }
  416. // computes the validation chain for all input shapes
  417. func (a *API) addShapeValidations() {
  418. for _, o := range a.Operations {
  419. resolveShapeValidations(o.InputRef.Shape)
  420. }
  421. }
  422. // Updates the source shape and all nested shapes with the validations that
  423. // could possibly be needed.
  424. func resolveShapeValidations(s *Shape, ancestry ...*Shape) {
  425. for _, a := range ancestry {
  426. if a == s {
  427. return
  428. }
  429. }
  430. children := []string{}
  431. for _, name := range s.MemberNames() {
  432. ref := s.MemberRefs[name]
  433. if s.IsRequired(name) && !s.Validations.Has(ref, ShapeValidationRequired) {
  434. s.Validations = append(s.Validations, ShapeValidation{
  435. Name: name, Ref: ref, Type: ShapeValidationRequired,
  436. })
  437. }
  438. if ref.Shape.Min != 0 && !s.Validations.Has(ref, ShapeValidationMinVal) {
  439. s.Validations = append(s.Validations, ShapeValidation{
  440. Name: name, Ref: ref, Type: ShapeValidationMinVal,
  441. })
  442. }
  443. switch ref.Shape.Type {
  444. case "map", "list", "structure":
  445. children = append(children, name)
  446. }
  447. }
  448. ancestry = append(ancestry, s)
  449. for _, name := range children {
  450. ref := s.MemberRefs[name]
  451. nestedShape := ref.Shape.NestedShape()
  452. var v *ShapeValidation
  453. if len(nestedShape.Validations) > 0 {
  454. v = &ShapeValidation{
  455. Name: name, Ref: ref, Type: ShapeValidationNested,
  456. }
  457. } else {
  458. resolveShapeValidations(nestedShape, ancestry...)
  459. if len(nestedShape.Validations) > 0 {
  460. v = &ShapeValidation{
  461. Name: name, Ref: ref, Type: ShapeValidationNested,
  462. }
  463. }
  464. }
  465. if v != nil && !s.Validations.Has(v.Ref, v.Type) {
  466. s.Validations = append(s.Validations, *v)
  467. }
  468. }
  469. ancestry = ancestry[:len(ancestry)-1]
  470. }