api.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  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. "unicode"
  13. )
  14. // SDKImportRoot is the root import path of the SDK.
  15. const SDKImportRoot = "github.com/aws/aws-sdk-go"
  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. Examples Examples
  24. SmokeTests SmokeTestSuite
  25. // Set to true to avoid removing unused shapes
  26. NoRemoveUnusedShapes bool
  27. // Set to true to avoid renaming to 'Input/Output' postfixed shapes
  28. NoRenameToplevelShapes bool
  29. // Set to true to ignore service/request init methods (for testing)
  30. NoInitMethods bool
  31. // Set to true to ignore String() and GoString methods (for generated tests)
  32. NoStringerMethods bool
  33. // Set to true to not generate API service name constants
  34. NoConstServiceNames bool
  35. // Set to true to not generate validation shapes
  36. NoValidataShapeMethods bool
  37. // Set to true to not generate struct field accessors
  38. NoGenStructFieldAccessors bool
  39. BaseImportPath string
  40. initialized bool
  41. imports map[string]bool
  42. name string
  43. path string
  44. BaseCrosslinkURL string
  45. HasEventStream bool `json:"-"`
  46. EndpointDiscoveryOp *Operation
  47. }
  48. // A Metadata is the metadata about an API's definition.
  49. type Metadata struct {
  50. APIVersion string
  51. EndpointPrefix string
  52. SigningName string
  53. ServiceAbbreviation string
  54. ServiceFullName string
  55. SignatureVersion string
  56. JSONVersion string
  57. TargetPrefix string
  58. Protocol string
  59. ProtocolSettings ProtocolSettings
  60. UID string
  61. EndpointsID string
  62. ServiceID string
  63. NoResolveEndpoint bool
  64. }
  65. // ProtocolSettings define how the SDK should handle requests in the context
  66. // of of a protocol.
  67. type ProtocolSettings struct {
  68. HTTP2 string `json:"h2,omitempty"`
  69. }
  70. // PackageName name of the API package
  71. func (a *API) PackageName() string {
  72. return strings.ToLower(a.StructName())
  73. }
  74. // ImportPath returns the client's full import path
  75. func (a *API) ImportPath() string {
  76. return path.Join(a.BaseImportPath, a.PackageName())
  77. }
  78. // InterfacePackageName returns the package name for the interface.
  79. func (a *API) InterfacePackageName() string {
  80. return a.PackageName() + "iface"
  81. }
  82. var stripServiceNamePrefixes = []string{
  83. "Amazon",
  84. "AWS",
  85. }
  86. // StructName returns the struct name for a given API.
  87. func (a *API) StructName() string {
  88. if len(a.name) != 0 {
  89. return a.name
  90. }
  91. name := a.Metadata.ServiceAbbreviation
  92. if len(name) == 0 {
  93. name = a.Metadata.ServiceFullName
  94. }
  95. name = strings.TrimSpace(name)
  96. // Strip out prefix names not reflected in service client symbol names.
  97. for _, prefix := range stripServiceNamePrefixes {
  98. if strings.HasPrefix(name, prefix) {
  99. name = name[len(prefix):]
  100. break
  101. }
  102. }
  103. // Replace all Non-letter/number values with space
  104. runes := []rune(name)
  105. for i := 0; i < len(runes); i++ {
  106. if r := runes[i]; !(unicode.IsNumber(r) || unicode.IsLetter(r)) {
  107. runes[i] = ' '
  108. }
  109. }
  110. name = string(runes)
  111. // Title case name so its readable as a symbol.
  112. name = strings.Title(name)
  113. // Strip out spaces.
  114. name = strings.Replace(name, " ", "", -1)
  115. a.name = name
  116. return a.name
  117. }
  118. // UseInitMethods returns if the service's init method should be rendered.
  119. func (a *API) UseInitMethods() bool {
  120. return !a.NoInitMethods
  121. }
  122. // NiceName returns the human friendly API name.
  123. func (a *API) NiceName() string {
  124. if a.Metadata.ServiceAbbreviation != "" {
  125. return a.Metadata.ServiceAbbreviation
  126. }
  127. return a.Metadata.ServiceFullName
  128. }
  129. // ProtocolPackage returns the package name of the protocol this API uses.
  130. func (a *API) ProtocolPackage() string {
  131. switch a.Metadata.Protocol {
  132. case "json":
  133. return "jsonrpc"
  134. case "ec2":
  135. return "ec2query"
  136. default:
  137. return strings.Replace(a.Metadata.Protocol, "-", "", -1)
  138. }
  139. }
  140. // OperationNames returns a slice of API operations supported.
  141. func (a *API) OperationNames() []string {
  142. i, names := 0, make([]string, len(a.Operations))
  143. for n := range a.Operations {
  144. names[i] = n
  145. i++
  146. }
  147. sort.Strings(names)
  148. return names
  149. }
  150. // OperationList returns a slice of API operation pointers
  151. func (a *API) OperationList() []*Operation {
  152. list := make([]*Operation, len(a.Operations))
  153. for i, n := range a.OperationNames() {
  154. list[i] = a.Operations[n]
  155. }
  156. return list
  157. }
  158. // OperationHasOutputPlaceholder returns if any of the API operation input
  159. // or output shapes are place holders.
  160. func (a *API) OperationHasOutputPlaceholder() bool {
  161. for _, op := range a.Operations {
  162. if op.OutputRef.Shape.Placeholder {
  163. return true
  164. }
  165. }
  166. return false
  167. }
  168. // ShapeNames returns a slice of names for each shape used by the API.
  169. func (a *API) ShapeNames() []string {
  170. i, names := 0, make([]string, len(a.Shapes))
  171. for n := range a.Shapes {
  172. names[i] = n
  173. i++
  174. }
  175. sort.Strings(names)
  176. return names
  177. }
  178. // ShapeList returns a slice of shape pointers used by the API.
  179. //
  180. // Will exclude error shapes from the list of shapes returned.
  181. func (a *API) ShapeList() []*Shape {
  182. list := make([]*Shape, 0, len(a.Shapes))
  183. for _, n := range a.ShapeNames() {
  184. // Ignore error shapes in list
  185. if s := a.Shapes[n]; !s.IsError {
  186. list = append(list, s)
  187. }
  188. }
  189. return list
  190. }
  191. // ShapeListErrors returns a list of the errors defined by the API model
  192. func (a *API) ShapeListErrors() []*Shape {
  193. list := []*Shape{}
  194. for _, n := range a.ShapeNames() {
  195. // Ignore error shapes in list
  196. if s := a.Shapes[n]; s.IsError {
  197. list = append(list, s)
  198. }
  199. }
  200. return list
  201. }
  202. // resetImports resets the import map to default values.
  203. func (a *API) resetImports() {
  204. a.imports = map[string]bool{}
  205. }
  206. // importsGoCode returns the generated Go import code.
  207. func (a *API) importsGoCode() string {
  208. if len(a.imports) == 0 {
  209. return ""
  210. }
  211. corePkgs, extPkgs := []string{}, []string{}
  212. for i := range a.imports {
  213. if strings.Contains(i, ".") {
  214. extPkgs = append(extPkgs, i)
  215. } else {
  216. corePkgs = append(corePkgs, i)
  217. }
  218. }
  219. sort.Strings(corePkgs)
  220. sort.Strings(extPkgs)
  221. code := "import (\n"
  222. for _, i := range corePkgs {
  223. code += fmt.Sprintf("\t%q\n", i)
  224. }
  225. if len(corePkgs) > 0 {
  226. code += "\n"
  227. }
  228. for _, i := range extPkgs {
  229. code += fmt.Sprintf("\t%q\n", i)
  230. }
  231. code += ")\n\n"
  232. return code
  233. }
  234. // A tplAPI is the top level template for the API
  235. var tplAPI = template.Must(template.New("api").Parse(`
  236. {{ range $_, $o := .OperationList }}
  237. {{ $o.GoCode }}
  238. {{ end }}
  239. {{ range $_, $s := .ShapeList }}
  240. {{ if and $s.IsInternal (eq $s.Type "structure") }}{{ $s.GoCode }}{{ end }}
  241. {{ end }}
  242. {{ range $_, $s := .ShapeList }}
  243. {{ if $s.IsEnum }}{{ $s.GoCode }}{{ end }}
  244. {{ end }}
  245. `))
  246. // AddImport adds the import path to the generated file's import.
  247. func (a *API) AddImport(v string) error {
  248. a.imports[v] = true
  249. return nil
  250. }
  251. // AddSDKImport adds a SDK package import to the generated file's import.
  252. func (a *API) AddSDKImport(v ...string) error {
  253. e := make([]string, 0, 5)
  254. e = append(e, SDKImportRoot)
  255. e = append(e, v...)
  256. a.imports[path.Join(e...)] = true
  257. return nil
  258. }
  259. // APIGoCode renders the API in Go code. Returning it as a string
  260. func (a *API) APIGoCode() string {
  261. a.resetImports()
  262. a.AddSDKImport("aws")
  263. a.AddSDKImport("aws/awsutil")
  264. a.AddSDKImport("aws/request")
  265. var buf bytes.Buffer
  266. err := tplAPI.Execute(&buf, a)
  267. if err != nil {
  268. panic(err)
  269. }
  270. code := a.importsGoCode() + strings.TrimSpace(buf.String())
  271. return code
  272. }
  273. var noCrossLinkServices = map[string]struct{}{
  274. "apigateway": {},
  275. "budgets": {},
  276. "cloudsearch": {},
  277. "cloudsearchdomain": {},
  278. "elastictranscoder": {},
  279. "es": {},
  280. "glacier": {},
  281. "importexport": {},
  282. "iot": {},
  283. "iot-data": {},
  284. "machinelearning": {},
  285. "rekognition": {},
  286. "sdb": {},
  287. "swf": {},
  288. }
  289. // HasCrosslinks will return whether or not a service has crosslinking .
  290. func HasCrosslinks(service string) bool {
  291. _, ok := noCrossLinkServices[service]
  292. return !ok
  293. }
  294. // GetCrosslinkURL returns the crosslinking URL for the shape based on the name and
  295. // uid provided. Empty string is returned if no crosslink link could be determined.
  296. func GetCrosslinkURL(baseURL, uid string, params ...string) string {
  297. if uid == "" || baseURL == "" {
  298. return ""
  299. }
  300. if !HasCrosslinks(strings.ToLower(ServiceIDFromUID(uid))) {
  301. return ""
  302. }
  303. return strings.Join(append([]string{baseURL, "goto", "WebAPI", uid}, params...), "/")
  304. }
  305. // ServiceIDFromUID will parse the service id from the uid and return
  306. // the service id that was found.
  307. func ServiceIDFromUID(uid string) string {
  308. found := 0
  309. i := len(uid) - 1
  310. for ; i >= 0; i-- {
  311. if uid[i] == '-' {
  312. found++
  313. }
  314. // Terminate after the date component is found, e.g. es-2017-11-11
  315. if found == 3 {
  316. break
  317. }
  318. }
  319. return uid[0:i]
  320. }
  321. // APIName returns the API's service name.
  322. func (a *API) APIName() string {
  323. return a.name
  324. }
  325. var tplServiceDoc = template.Must(template.New("service docs").Funcs(template.FuncMap{
  326. "GetCrosslinkURL": GetCrosslinkURL,
  327. }).
  328. Parse(`
  329. // Package {{ .PackageName }} provides the client and types for making API
  330. // requests to {{ .Metadata.ServiceFullName }}.
  331. {{ if .Documentation -}}
  332. //
  333. {{ .Documentation }}
  334. {{ end -}}
  335. {{ $crosslinkURL := GetCrosslinkURL $.BaseCrosslinkURL $.Metadata.UID -}}
  336. {{ if $crosslinkURL -}}
  337. //
  338. // See {{ $crosslinkURL }} for more information on this service.
  339. {{ end -}}
  340. //
  341. // See {{ .PackageName }} package documentation for more information.
  342. // https://docs.aws.amazon.com/sdk-for-go/api/service/{{ .PackageName }}/
  343. //
  344. // Using the Client
  345. //
  346. // To contact {{ .Metadata.ServiceFullName }} with the SDK use the New function to create
  347. // a new service client. With that client you can make API requests to the service.
  348. // These clients are safe to use concurrently.
  349. //
  350. // See the SDK's documentation for more information on how to use the SDK.
  351. // https://docs.aws.amazon.com/sdk-for-go/api/
  352. //
  353. // See aws.Config documentation for more information on configuring SDK clients.
  354. // https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
  355. //
  356. // See the {{ .Metadata.ServiceFullName }} client {{ .StructName }} for more
  357. // information on creating client for this service.
  358. // https://docs.aws.amazon.com/sdk-for-go/api/service/{{ .PackageName }}/#New
  359. `))
  360. var serviceIDRegex = regexp.MustCompile("[^a-zA-Z0-9 ]+")
  361. var prefixDigitRegex = regexp.MustCompile("^[0-9]+")
  362. // ServiceID will return a unique identifier specific to a service.
  363. func ServiceID(a *API) string {
  364. if len(a.Metadata.ServiceID) > 0 {
  365. return a.Metadata.ServiceID
  366. }
  367. name := a.Metadata.ServiceAbbreviation
  368. if len(name) == 0 {
  369. name = a.Metadata.ServiceFullName
  370. }
  371. name = strings.Replace(name, "Amazon", "", -1)
  372. name = strings.Replace(name, "AWS", "", -1)
  373. name = serviceIDRegex.ReplaceAllString(name, "")
  374. name = prefixDigitRegex.ReplaceAllString(name, "")
  375. name = strings.TrimSpace(name)
  376. return name
  377. }
  378. // A tplService defines the template for the service generated code.
  379. var tplService = template.Must(template.New("service").Funcs(template.FuncMap{
  380. "ServiceNameConstValue": ServiceName,
  381. "ServiceNameValue": func(a *API) string {
  382. if !a.NoConstServiceNames {
  383. return "ServiceName"
  384. }
  385. return fmt.Sprintf("%q", ServiceName(a))
  386. },
  387. "EndpointsIDConstValue": func(a *API) string {
  388. if a.NoConstServiceNames {
  389. return fmt.Sprintf("%q", a.Metadata.EndpointsID)
  390. }
  391. if a.Metadata.EndpointsID == ServiceName(a) {
  392. return "ServiceName"
  393. }
  394. return fmt.Sprintf("%q", a.Metadata.EndpointsID)
  395. },
  396. "EndpointsIDValue": func(a *API) string {
  397. if a.NoConstServiceNames {
  398. return fmt.Sprintf("%q", a.Metadata.EndpointsID)
  399. }
  400. return "EndpointsID"
  401. },
  402. "ServiceIDVar": func(a *API) string {
  403. if a.NoConstServiceNames {
  404. return fmt.Sprintf("%q", ServiceID(a))
  405. }
  406. return "ServiceID"
  407. },
  408. "ServiceID": ServiceID,
  409. }).Parse(`
  410. // {{ .StructName }} provides the API operation methods for making requests to
  411. // {{ .Metadata.ServiceFullName }}. See this package's package overview docs
  412. // for details on the service.
  413. //
  414. // {{ .StructName }} methods are safe to use concurrently. It is not safe to
  415. // modify mutate any of the struct's properties though.
  416. type {{ .StructName }} struct {
  417. *client.Client
  418. {{- if .EndpointDiscoveryOp }}
  419. endpointCache *crr.EndpointCache
  420. {{ end -}}
  421. }
  422. {{ if .UseInitMethods }}// Used for custom client initialization logic
  423. var initClient func(*client.Client)
  424. // Used for custom request initialization logic
  425. var initRequest func(*request.Request)
  426. {{ end }}
  427. {{ if not .NoConstServiceNames -}}
  428. // Service information constants
  429. const (
  430. ServiceName = "{{ ServiceNameConstValue . }}" // Name of service.
  431. EndpointsID = {{ EndpointsIDConstValue . }} // ID to lookup a service endpoint with.
  432. ServiceID = "{{ ServiceID . }}" // ServiceID is a unique identifer of a specific service.
  433. )
  434. {{- end }}
  435. // New creates a new instance of the {{ .StructName }} client with a session.
  436. // If additional configuration is needed for the client instance use the optional
  437. // aws.Config parameter to add your extra config.
  438. //
  439. // Example:
  440. // // Create a {{ .StructName }} client from just a session.
  441. // svc := {{ .PackageName }}.New(mySession)
  442. //
  443. // // Create a {{ .StructName }} client with additional configuration
  444. // svc := {{ .PackageName }}.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
  445. func New(p client.ConfigProvider, cfgs ...*aws.Config) *{{ .StructName }} {
  446. {{ if .Metadata.NoResolveEndpoint -}}
  447. var c client.Config
  448. if v, ok := p.(client.ConfigNoResolveEndpointProvider); ok {
  449. c = v.ClientConfigNoResolveEndpoint(cfgs...)
  450. } else {
  451. c = p.ClientConfig({{ EndpointsIDValue . }}, cfgs...)
  452. }
  453. {{- else -}}
  454. c := p.ClientConfig({{ EndpointsIDValue . }}, cfgs...)
  455. {{- end }}
  456. {{- if .Metadata.SigningName }}
  457. if c.SigningNameDerived || len(c.SigningName) == 0{
  458. c.SigningName = "{{ .Metadata.SigningName }}"
  459. }
  460. {{- end }}
  461. return newClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion, c.SigningName)
  462. }
  463. // newClient creates, initializes and returns a new service client instance.
  464. func newClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion, signingName string) *{{ .StructName }} {
  465. svc := &{{ .StructName }}{
  466. Client: client.New(
  467. cfg,
  468. metadata.ClientInfo{
  469. ServiceName: {{ ServiceNameValue . }},
  470. ServiceID : {{ ServiceIDVar . }},
  471. SigningName: signingName,
  472. SigningRegion: signingRegion,
  473. Endpoint: endpoint,
  474. APIVersion: "{{ .Metadata.APIVersion }}",
  475. {{ if .Metadata.JSONVersion -}}
  476. JSONVersion: "{{ .Metadata.JSONVersion }}",
  477. {{- end }}
  478. {{ if .Metadata.TargetPrefix -}}
  479. TargetPrefix: "{{ .Metadata.TargetPrefix }}",
  480. {{- end }}
  481. },
  482. handlers,
  483. ),
  484. }
  485. {{- if .EndpointDiscoveryOp }}
  486. svc.endpointCache = crr.NewEndpointCache(10)
  487. {{- end }}
  488. // Handlers
  489. svc.Handlers.Sign.PushBackNamed(
  490. {{- if eq .Metadata.SignatureVersion "v2" -}}
  491. v2.SignRequestHandler
  492. {{- else if or (eq .Metadata.SignatureVersion "s3") (eq .Metadata.SignatureVersion "s3v4") -}}
  493. v4.BuildNamedHandler(v4.SignRequestHandler.Name, func(s *v4.Signer) {
  494. s.DisableURIPathEscaping = true
  495. })
  496. {{- else -}}
  497. v4.SignRequestHandler
  498. {{- end -}}
  499. )
  500. {{- if eq .Metadata.SignatureVersion "v2" }}
  501. svc.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
  502. {{- end }}
  503. svc.Handlers.Build.PushBackNamed({{ .ProtocolPackage }}.BuildHandler)
  504. svc.Handlers.Unmarshal.PushBackNamed({{ .ProtocolPackage }}.UnmarshalHandler)
  505. svc.Handlers.UnmarshalMeta.PushBackNamed({{ .ProtocolPackage }}.UnmarshalMetaHandler)
  506. svc.Handlers.UnmarshalError.PushBackNamed({{ .ProtocolPackage }}.UnmarshalErrorHandler)
  507. {{ if .HasEventStream }}
  508. svc.Handlers.UnmarshalStream.PushBackNamed({{ .ProtocolPackage }}.UnmarshalHandler)
  509. {{ end }}
  510. {{ if .UseInitMethods }}// Run custom client initialization if present
  511. if initClient != nil {
  512. initClient(svc.Client)
  513. }
  514. {{ end }}
  515. return svc
  516. }
  517. // newRequest creates a new request for a {{ .StructName }} operation and runs any
  518. // custom request initialization.
  519. func (c *{{ .StructName }}) newRequest(op *request.Operation, params, data interface{}) *request.Request {
  520. req := c.NewRequest(op, params, data)
  521. {{ if .UseInitMethods }}// Run custom request initialization if present
  522. if initRequest != nil {
  523. initRequest(req)
  524. }
  525. {{ end }}
  526. return req
  527. }
  528. `))
  529. // ServicePackageDoc generates the contents of the doc file for the service.
  530. //
  531. // Will also read in the custom doc templates for the service if found.
  532. func (a *API) ServicePackageDoc() string {
  533. a.imports = map[string]bool{}
  534. var buf bytes.Buffer
  535. if err := tplServiceDoc.Execute(&buf, a); err != nil {
  536. panic(err)
  537. }
  538. return buf.String()
  539. }
  540. // ServiceGoCode renders service go code. Returning it as a string.
  541. func (a *API) ServiceGoCode() string {
  542. a.resetImports()
  543. a.AddSDKImport("aws")
  544. a.AddSDKImport("aws/client")
  545. a.AddSDKImport("aws/client/metadata")
  546. a.AddSDKImport("aws/request")
  547. if a.Metadata.SignatureVersion == "v2" {
  548. a.AddSDKImport("private/signer/v2")
  549. a.AddSDKImport("aws/corehandlers")
  550. } else {
  551. a.AddSDKImport("aws/signer/v4")
  552. }
  553. a.AddSDKImport("private/protocol", a.ProtocolPackage())
  554. if a.EndpointDiscoveryOp != nil {
  555. a.AddSDKImport("aws/crr")
  556. }
  557. var buf bytes.Buffer
  558. err := tplService.Execute(&buf, a)
  559. if err != nil {
  560. panic(err)
  561. }
  562. code := a.importsGoCode() + buf.String()
  563. return code
  564. }
  565. // ExampleGoCode renders service example code. Returning it as a string.
  566. func (a *API) ExampleGoCode() string {
  567. exs := []string{}
  568. imports := map[string]bool{}
  569. for _, o := range a.OperationList() {
  570. o.imports = map[string]bool{}
  571. exs = append(exs, o.Example())
  572. for k, v := range o.imports {
  573. imports[k] = v
  574. }
  575. }
  576. code := fmt.Sprintf("import (\n%q\n%q\n%q\n\n%q\n%q\n%q\n",
  577. "bytes",
  578. "fmt",
  579. "time",
  580. SDKImportRoot+"/aws",
  581. SDKImportRoot+"/aws/session",
  582. a.ImportPath(),
  583. )
  584. for k := range imports {
  585. code += fmt.Sprintf("%q\n", k)
  586. }
  587. code += ")\n\n"
  588. code += "var _ time.Duration\nvar _ bytes.Buffer\n\n"
  589. code += strings.Join(exs, "\n\n")
  590. return code
  591. }
  592. // A tplInterface defines the template for the service interface type.
  593. var tplInterface = template.Must(template.New("interface").Parse(`
  594. // {{ .StructName }}API provides an interface to enable mocking the
  595. // {{ .PackageName }}.{{ .StructName }} service client's API operation,
  596. // paginators, and waiters. This make unit testing your code that calls out
  597. // to the SDK's service client's calls easier.
  598. //
  599. // The best way to use this interface is so the SDK's service client's calls
  600. // can be stubbed out for unit testing your code with the SDK without needing
  601. // to inject custom request handlers into the SDK's request pipeline.
  602. //
  603. // // myFunc uses an SDK service client to make a request to
  604. // // {{.Metadata.ServiceFullName}}. {{ $opts := .OperationList }}{{ $opt := index $opts 0 }}
  605. // func myFunc(svc {{ .InterfacePackageName }}.{{ .StructName }}API) bool {
  606. // // Make svc.{{ $opt.ExportedName }} request
  607. // }
  608. //
  609. // func main() {
  610. // sess := session.New()
  611. // svc := {{ .PackageName }}.New(sess)
  612. //
  613. // myFunc(svc)
  614. // }
  615. //
  616. // In your _test.go file:
  617. //
  618. // // Define a mock struct to be used in your unit tests of myFunc.
  619. // type mock{{ .StructName }}Client struct {
  620. // {{ .InterfacePackageName }}.{{ .StructName }}API
  621. // }
  622. // func (m *mock{{ .StructName }}Client) {{ $opt.ExportedName }}(input {{ $opt.InputRef.GoTypeWithPkgName }}) ({{ $opt.OutputRef.GoTypeWithPkgName }}, error) {
  623. // // mock response/functionality
  624. // }
  625. //
  626. // func TestMyFunc(t *testing.T) {
  627. // // Setup Test
  628. // mockSvc := &mock{{ .StructName }}Client{}
  629. //
  630. // myfunc(mockSvc)
  631. //
  632. // // Verify myFunc's functionality
  633. // }
  634. //
  635. // It is important to note that this interface will have breaking changes
  636. // when the service model is updated and adds new API operations, paginators,
  637. // and waiters. Its suggested to use the pattern above for testing, or using
  638. // tooling to generate mocks to satisfy the interfaces.
  639. type {{ .StructName }}API interface {
  640. {{ range $_, $o := .OperationList }}
  641. {{ $o.InterfaceSignature }}
  642. {{ end }}
  643. {{ range $_, $w := .Waiters }}
  644. {{ $w.InterfaceSignature }}
  645. {{ end }}
  646. }
  647. var _ {{ .StructName }}API = (*{{ .PackageName }}.{{ .StructName }})(nil)
  648. `))
  649. // InterfaceGoCode returns the go code for the service's API operations as an
  650. // interface{}. Assumes that the interface is being created in a different
  651. // package than the service API's package.
  652. func (a *API) InterfaceGoCode() string {
  653. a.resetImports()
  654. a.AddSDKImport("aws")
  655. a.AddSDKImport("aws/request")
  656. a.AddImport(a.ImportPath())
  657. var buf bytes.Buffer
  658. err := tplInterface.Execute(&buf, a)
  659. if err != nil {
  660. panic(err)
  661. }
  662. code := a.importsGoCode() + strings.TrimSpace(buf.String())
  663. return code
  664. }
  665. // NewAPIGoCodeWithPkgName returns a string of instantiating the API prefixed
  666. // with its package name. Takes a string depicting the Config.
  667. func (a *API) NewAPIGoCodeWithPkgName(cfg string) string {
  668. return fmt.Sprintf("%s.New(%s)", a.PackageName(), cfg)
  669. }
  670. // computes the validation chain for all input shapes
  671. func (a *API) addShapeValidations() {
  672. for _, o := range a.Operations {
  673. resolveShapeValidations(o.InputRef.Shape)
  674. }
  675. }
  676. // Updates the source shape and all nested shapes with the validations that
  677. // could possibly be needed.
  678. func resolveShapeValidations(s *Shape, ancestry ...*Shape) {
  679. for _, a := range ancestry {
  680. if a == s {
  681. return
  682. }
  683. }
  684. children := []string{}
  685. for _, name := range s.MemberNames() {
  686. ref := s.MemberRefs[name]
  687. if s.IsRequired(name) && !s.Validations.Has(ref, ShapeValidationRequired) {
  688. s.Validations = append(s.Validations, ShapeValidation{
  689. Name: name, Ref: ref, Type: ShapeValidationRequired,
  690. })
  691. }
  692. if ref.Shape.Min != 0 && !s.Validations.Has(ref, ShapeValidationMinVal) {
  693. s.Validations = append(s.Validations, ShapeValidation{
  694. Name: name, Ref: ref, Type: ShapeValidationMinVal,
  695. })
  696. }
  697. if !ref.CanBeEmpty() && !s.Validations.Has(ref, ShapeValidationMinVal) {
  698. s.Validations = append(s.Validations, ShapeValidation{
  699. Name: name, Ref: ref, Type: ShapeValidationMinVal,
  700. })
  701. }
  702. switch ref.Shape.Type {
  703. case "map", "list", "structure":
  704. children = append(children, name)
  705. }
  706. }
  707. ancestry = append(ancestry, s)
  708. for _, name := range children {
  709. ref := s.MemberRefs[name]
  710. // Since this is a grab bag we will just continue since
  711. // we can't validate because we don't know the valued shape.
  712. if ref.JSONValue {
  713. continue
  714. }
  715. nestedShape := ref.Shape.NestedShape()
  716. var v *ShapeValidation
  717. if len(nestedShape.Validations) > 0 {
  718. v = &ShapeValidation{
  719. Name: name, Ref: ref, Type: ShapeValidationNested,
  720. }
  721. } else {
  722. resolveShapeValidations(nestedShape, ancestry...)
  723. if len(nestedShape.Validations) > 0 {
  724. v = &ShapeValidation{
  725. Name: name, Ref: ref, Type: ShapeValidationNested,
  726. }
  727. }
  728. }
  729. if v != nil && !s.Validations.Has(v.Ref, v.Type) {
  730. s.Validations = append(s.Validations, *v)
  731. }
  732. }
  733. ancestry = ancestry[:len(ancestry)-1]
  734. }
  735. // A tplAPIErrors is the top level template for the API
  736. var tplAPIErrors = template.Must(template.New("api").Parse(`
  737. const (
  738. {{ range $_, $s := $.ShapeListErrors }}
  739. // {{ $s.ErrorCodeName }} for service response error code
  740. // {{ printf "%q" $s.ErrorName }}.
  741. {{ if $s.Docstring -}}
  742. //
  743. {{ $s.Docstring }}
  744. {{ end -}}
  745. {{ $s.ErrorCodeName }} = {{ printf "%q" $s.ErrorName }}
  746. {{ end }}
  747. )
  748. `))
  749. func (a *API) APIErrorsGoCode() string {
  750. var buf bytes.Buffer
  751. err := tplAPIErrors.Execute(&buf, a)
  752. if err != nil {
  753. panic(err)
  754. }
  755. return strings.TrimSpace(buf.String())
  756. }
  757. // removeOperation removes an operation, its input/output shapes, as well as
  758. // any references/shapes that are unique to this operation.
  759. func (a *API) removeOperation(name string) {
  760. debugLogger.Logln("removing operation,", name)
  761. op := a.Operations[name]
  762. delete(a.Operations, name)
  763. delete(a.Examples, name)
  764. a.removeShape(op.InputRef.Shape)
  765. a.removeShape(op.OutputRef.Shape)
  766. }
  767. // removeShape removes the given shape, and all form member's reference target
  768. // shapes. Will also remove member reference targeted shapes if those shapes do
  769. // not have any additional references.
  770. func (a *API) removeShape(s *Shape) {
  771. debugLogger.Logln("removing shape,", s.ShapeName)
  772. delete(a.Shapes, s.ShapeName)
  773. for name, ref := range s.MemberRefs {
  774. a.removeShapeRef(ref)
  775. delete(s.MemberRefs, name)
  776. }
  777. for _, ref := range []*ShapeRef{&s.MemberRef, &s.KeyRef, &s.ValueRef} {
  778. if ref.Shape == nil {
  779. continue
  780. }
  781. a.removeShapeRef(ref)
  782. *ref = ShapeRef{}
  783. }
  784. }
  785. // removeShapeRef removes the shape reference from its target shape. If the
  786. // reference was the last reference to the target shape, the shape will also be
  787. // removed.
  788. func (a *API) removeShapeRef(ref *ShapeRef) {
  789. if ref.Shape == nil {
  790. return
  791. }
  792. ref.Shape.removeRef(ref)
  793. if len(ref.Shape.refs) == 0 {
  794. a.removeShape(ref.Shape)
  795. }
  796. }
  797. func getDeprecatedMessage(msg string, name string) string {
  798. if len(msg) == 0 {
  799. return name + " has been deprecated"
  800. }
  801. return msg
  802. }