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