api.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. // Package api represents API abstractions for rendering service generated files.
  2. package api
  3. import (
  4. "bytes"
  5. "fmt"
  6. "regexp"
  7. "sort"
  8. "strings"
  9. "text/template"
  10. )
  11. // An API defines a service API's definition. and logic to serialize the definition.
  12. type API struct {
  13. Metadata Metadata
  14. Operations map[string]*Operation
  15. Shapes map[string]*Shape
  16. Documentation string
  17. // Disables inflection checks. Only use this when generating tests
  18. NoInflections bool
  19. // Set to true to avoid removing unused shapes
  20. NoRemoveUnusedShapes bool
  21. // Set to true to ignore service/request init methods (for testing)
  22. NoInitMethods bool
  23. // Set to true to ignore String() and GoString methods (for generated tests)
  24. NoStringerMethods bool
  25. initialized bool
  26. imports map[string]bool
  27. name string
  28. unrecognizedNames map[string]string
  29. path string
  30. }
  31. // A Metadata is the metadata about an API's definition.
  32. type Metadata struct {
  33. APIVersion string
  34. EndpointPrefix string
  35. SigningName string
  36. ServiceAbbreviation string
  37. ServiceFullName string
  38. SignatureVersion string
  39. JSONVersion string
  40. TargetPrefix string
  41. Protocol string
  42. }
  43. // PackageName name of the API package
  44. func (a *API) PackageName() string {
  45. return strings.ToLower(a.StructName())
  46. }
  47. // InterfacePackageName returns the package name for the interface.
  48. func (a *API) InterfacePackageName() string {
  49. return a.PackageName() + "iface"
  50. }
  51. var nameRegex = regexp.MustCompile(`^Amazon|AWS\s*|\(.*|\s+|\W+`)
  52. // StructName returns the struct name for a given API.
  53. func (a *API) StructName() string {
  54. if a.name == "" {
  55. name := a.Metadata.ServiceAbbreviation
  56. if name == "" {
  57. name = a.Metadata.ServiceFullName
  58. }
  59. name = nameRegex.ReplaceAllString(name, "")
  60. switch name {
  61. case "ElasticLoadBalancing":
  62. a.name = "ELB"
  63. case "Config":
  64. a.name = "ConfigService"
  65. default:
  66. a.name = name
  67. }
  68. }
  69. return a.name
  70. }
  71. // UseInitMethods returns if the service's init method should be rendered.
  72. func (a *API) UseInitMethods() bool {
  73. return !a.NoInitMethods
  74. }
  75. // NiceName returns the human friendly API name.
  76. func (a *API) NiceName() string {
  77. if a.Metadata.ServiceAbbreviation != "" {
  78. return a.Metadata.ServiceAbbreviation
  79. }
  80. return a.Metadata.ServiceFullName
  81. }
  82. // ProtocolPackage returns the package name of the protocol this API uses.
  83. func (a *API) ProtocolPackage() string {
  84. switch a.Metadata.Protocol {
  85. case "json":
  86. return "jsonrpc"
  87. case "ec2":
  88. return "ec2query"
  89. default:
  90. return strings.Replace(a.Metadata.Protocol, "-", "", -1)
  91. }
  92. }
  93. // OperationNames returns a slice of API operations supported.
  94. func (a *API) OperationNames() []string {
  95. i, names := 0, make([]string, len(a.Operations))
  96. for n := range a.Operations {
  97. names[i] = n
  98. i++
  99. }
  100. sort.Strings(names)
  101. return names
  102. }
  103. // OperationList returns a slice of API operation pointers
  104. func (a *API) OperationList() []*Operation {
  105. list := make([]*Operation, len(a.Operations))
  106. for i, n := range a.OperationNames() {
  107. list[i] = a.Operations[n]
  108. }
  109. return list
  110. }
  111. // ShapeNames returns a slice of names for each shape used by the API.
  112. func (a *API) ShapeNames() []string {
  113. i, names := 0, make([]string, len(a.Shapes))
  114. for n := range a.Shapes {
  115. names[i] = n
  116. i++
  117. }
  118. sort.Strings(names)
  119. return names
  120. }
  121. // ShapeList returns a slice of shape pointers used by the API.
  122. func (a *API) ShapeList() []*Shape {
  123. list := make([]*Shape, len(a.Shapes))
  124. for i, n := range a.ShapeNames() {
  125. list[i] = a.Shapes[n]
  126. }
  127. return list
  128. }
  129. // resetImports resets the import map to default values.
  130. func (a *API) resetImports() {
  131. a.imports = map[string]bool{
  132. "github.com/aws/aws-sdk-go/aws": true,
  133. }
  134. }
  135. // importsGoCode returns the generated Go import code.
  136. func (a *API) importsGoCode() string {
  137. if len(a.imports) == 0 {
  138. return ""
  139. }
  140. corePkgs, extPkgs := []string{}, []string{}
  141. for i := range a.imports {
  142. if strings.Contains(i, ".") {
  143. extPkgs = append(extPkgs, i)
  144. } else {
  145. corePkgs = append(corePkgs, i)
  146. }
  147. }
  148. sort.Strings(corePkgs)
  149. sort.Strings(extPkgs)
  150. code := "import (\n"
  151. for _, i := range corePkgs {
  152. code += fmt.Sprintf("\t%q\n", i)
  153. }
  154. if len(corePkgs) > 0 {
  155. code += "\n"
  156. }
  157. for _, i := range extPkgs {
  158. code += fmt.Sprintf("\t%q\n", i)
  159. }
  160. code += ")\n\n"
  161. return code
  162. }
  163. // A tplAPI is the top level template for the API
  164. var tplAPI = template.Must(template.New("api").Parse(`
  165. {{ range $_, $o := .OperationList }}
  166. {{ $o.GoCode }}
  167. {{ end }}
  168. {{ range $_, $s := .ShapeList }}
  169. {{ if and $s.IsInternal (eq $s.Type "structure") }}{{ $s.GoCode }}{{ end }}
  170. {{ end }}
  171. {{ range $_, $s := .ShapeList }}
  172. {{ if $s.IsEnum }}{{ $s.GoCode }}{{ end }}
  173. {{ end }}
  174. `))
  175. // APIGoCode renders the API in Go code. Returning it as a string
  176. func (a *API) APIGoCode() string {
  177. a.resetImports()
  178. delete(a.imports, "github.com/aws/aws-sdk-go/aws")
  179. a.imports["github.com/aws/aws-sdk-go/aws/awsutil"] = true
  180. a.imports["github.com/aws/aws-sdk-go/aws/request"] = true
  181. var buf bytes.Buffer
  182. err := tplAPI.Execute(&buf, a)
  183. if err != nil {
  184. panic(err)
  185. }
  186. code := a.importsGoCode() + strings.TrimSpace(buf.String())
  187. return code
  188. }
  189. // A tplService defines the template for the service generated code.
  190. var tplService = template.Must(template.New("service").Parse(`
  191. {{ .Documentation }}type {{ .StructName }} struct {
  192. *service.Service
  193. }
  194. {{ if .UseInitMethods }}// Used for custom service initialization logic
  195. var initService func(*service.Service)
  196. // Used for custom request initialization logic
  197. var initRequest func(*request.Request)
  198. {{ end }}
  199. // New returns a new {{ .StructName }} client.
  200. func New(config *aws.Config) *{{ .StructName }} {
  201. service := &service.Service{
  202. ServiceInfo: serviceinfo.ServiceInfo{
  203. Config: defaults.DefaultConfig.Merge(config),
  204. ServiceName: "{{ .Metadata.EndpointPrefix }}",{{ if ne .Metadata.SigningName "" }}
  205. SigningName: "{{ .Metadata.SigningName }}",{{ end }}
  206. APIVersion: "{{ .Metadata.APIVersion }}",
  207. {{ if eq .Metadata.Protocol "json" }}JSONVersion: "{{ .Metadata.JSONVersion }}",
  208. TargetPrefix: "{{ .Metadata.TargetPrefix }}",
  209. {{ end }}
  210. },
  211. }
  212. service.Initialize()
  213. // Handlers
  214. service.Handlers.Sign.PushBack(v4.Sign)
  215. service.Handlers.Build.PushBack({{ .ProtocolPackage }}.Build)
  216. service.Handlers.Unmarshal.PushBack({{ .ProtocolPackage }}.Unmarshal)
  217. service.Handlers.UnmarshalMeta.PushBack({{ .ProtocolPackage }}.UnmarshalMeta)
  218. service.Handlers.UnmarshalError.PushBack({{ .ProtocolPackage }}.UnmarshalError)
  219. {{ if .UseInitMethods }}// Run custom service initialization if present
  220. if initService != nil {
  221. initService(service)
  222. }
  223. {{ end }}
  224. return &{{ .StructName }}{service}
  225. }
  226. // newRequest creates a new request for a {{ .StructName }} operation and runs any
  227. // custom request initialization.
  228. func (c *{{ .StructName }}) newRequest(op *request.Operation, params, data interface{}) *request.Request {
  229. req := c.NewRequest(op, params, data)
  230. {{ if .UseInitMethods }}// Run custom request initialization if present
  231. if initRequest != nil {
  232. initRequest(req)
  233. }
  234. {{ end }}
  235. return req
  236. }
  237. `))
  238. // ServiceGoCode renders service go code. Returning it as a string.
  239. func (a *API) ServiceGoCode() string {
  240. a.resetImports()
  241. a.imports["github.com/aws/aws-sdk-go/aws"] = true
  242. a.imports["github.com/aws/aws-sdk-go/aws/defaults"] = true
  243. a.imports["github.com/aws/aws-sdk-go/aws/request"] = true
  244. a.imports["github.com/aws/aws-sdk-go/aws/service"] = true
  245. a.imports["github.com/aws/aws-sdk-go/aws/service/serviceinfo"] = true
  246. a.imports["github.com/aws/aws-sdk-go/internal/signer/v4"] = true
  247. a.imports["github.com/aws/aws-sdk-go/internal/protocol/"+a.ProtocolPackage()] = true
  248. var buf bytes.Buffer
  249. err := tplService.Execute(&buf, a)
  250. if err != nil {
  251. panic(err)
  252. }
  253. code := a.importsGoCode() + buf.String()
  254. return code
  255. }
  256. // ExampleGoCode renders service example code. Returning it as a string.
  257. func (a *API) ExampleGoCode() string {
  258. exs := []string{}
  259. for _, o := range a.OperationList() {
  260. exs = append(exs, o.Example())
  261. }
  262. code := fmt.Sprintf("import (\n%q\n%q\n%q\n\n%q\n%q\n)\n\n"+
  263. "var _ time.Duration\nvar _ bytes.Buffer\n\n%s",
  264. "bytes",
  265. "fmt",
  266. "time",
  267. "github.com/aws/aws-sdk-go/aws",
  268. "github.com/aws/aws-sdk-go/service/"+a.PackageName(),
  269. strings.Join(exs, "\n\n"),
  270. )
  271. return code
  272. }
  273. // A tplInterface defines the template for the service interface type.
  274. var tplInterface = template.Must(template.New("interface").Parse(`
  275. // {{ .StructName }}API is the interface type for {{ .PackageName }}.{{ .StructName }}.
  276. type {{ .StructName }}API interface {
  277. {{ range $_, $o := .OperationList }}
  278. {{ $o.InterfaceSignature }}
  279. {{ end }}
  280. }
  281. `))
  282. // InterfaceGoCode returns the go code for the service's API operations as an
  283. // interface{}. Assumes that the interface is being created in a different
  284. // package than the service API's package.
  285. func (a *API) InterfaceGoCode() string {
  286. a.resetImports()
  287. a.imports = map[string]bool{
  288. "github.com/aws/aws-sdk-go/aws/request": true,
  289. "github.com/aws/aws-sdk-go/service/" + a.PackageName(): true,
  290. }
  291. var buf bytes.Buffer
  292. err := tplInterface.Execute(&buf, a)
  293. if err != nil {
  294. panic(err)
  295. }
  296. code := a.importsGoCode() + strings.TrimSpace(buf.String())
  297. return code
  298. }
  299. var tplInterfaceTest = template.Must(template.New("interfacetest").Parse(`
  300. func TestInterface(t *testing.T) {
  301. assert.Implements(t, (*{{ .InterfacePackageName }}.{{ .StructName }}API)(nil), {{ .PackageName }}.New(nil))
  302. }
  303. `))
  304. // InterfaceTestGoCode returns the go code for the testing of a service interface.
  305. func (a *API) InterfaceTestGoCode() string {
  306. a.resetImports()
  307. a.imports = map[string]bool{
  308. "testing": true,
  309. "github.com/aws/aws-sdk-go/service/" + a.PackageName(): true,
  310. "github.com/aws/aws-sdk-go/service/" + a.PackageName() + "/" + a.InterfacePackageName(): true,
  311. "github.com/stretchr/testify/assert": true,
  312. }
  313. var buf bytes.Buffer
  314. err := tplInterfaceTest.Execute(&buf, a)
  315. if err != nil {
  316. panic(err)
  317. }
  318. code := a.importsGoCode() + strings.TrimSpace(buf.String())
  319. return code
  320. }
  321. // NewAPIGoCodeWithPkgName returns a string of instantiating the API prefixed
  322. // with its package name. Takes a string depicting the Config.
  323. func (a *API) NewAPIGoCodeWithPkgName(cfg string) string {
  324. return fmt.Sprintf("%s.New(%s)", a.PackageName(), cfg)
  325. }