api.go 15 KB

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