api.go 18 KB

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