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