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) 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 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 {{ if and (.Metadata.JSONVersion) (eq .Metadata.Protocol "json") -}} 587 JSONVersion: "{{ .Metadata.JSONVersion }}", 588 {{- end }} 589 {{ if and (.Metadata.TargetPrefix) (eq .Metadata.Protocol "json") -}} 590 TargetPrefix: "{{ .Metadata.TargetPrefix }}", 591 {{- end }} 592 }, 593 handlers, 594 ), 595 } 596 597 {{- if .EndpointDiscoveryOp }} 598 svc.endpointCache = crr.NewEndpointCache(10) 599 {{- end }} 600 601 // Handlers 602 svc.Handlers.Sign.PushBackNamed( 603 {{- if eq .Metadata.SignatureVersion "v2" -}} 604 v2.SignRequestHandler 605 {{- else if or (eq .Metadata.SignatureVersion "s3") (eq .Metadata.SignatureVersion "s3v4") -}} 606 v4.BuildNamedHandler(v4.SignRequestHandler.Name, func(s *v4.Signer) { 607 s.DisableURIPathEscaping = true 608 }) 609 {{- else -}} 610 v4.SignRequestHandler 611 {{- end -}} 612 ) 613 {{- if eq .Metadata.SignatureVersion "v2" }} 614 svc.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) 615 {{- end }} 616 svc.Handlers.Build.PushBackNamed({{ .ProtocolPackage }}.BuildHandler) 617 svc.Handlers.Unmarshal.PushBackNamed({{ .ProtocolPackage }}.UnmarshalHandler) 618 svc.Handlers.UnmarshalMeta.PushBackNamed({{ .ProtocolPackage }}.UnmarshalMetaHandler) 619 620 {{- if and $.WithGeneratedTypedErrors (gt (len $.ShapeListErrors) 0) }} 621 {{- $_ := $.AddSDKImport "private/protocol" }} 622 svc.Handlers.UnmarshalError.PushBackNamed( 623 protocol.NewUnmarshalErrorHandler({{ .ProtocolPackage }}.NewUnmarshalTypedError(exceptionFromCode)).NamedHandler(), 624 ) 625 {{- else }} 626 svc.Handlers.UnmarshalError.PushBackNamed({{ .ProtocolPackage }}.UnmarshalErrorHandler) 627 {{- end }} 628 629 {{- if .HasEventStream }} 630 631 svc.Handlers.BuildStream.PushBackNamed({{ .ProtocolPackage }}.BuildHandler) 632 svc.Handlers.UnmarshalStream.PushBackNamed({{ .ProtocolPackage }}.UnmarshalHandler) 633 {{- end }} 634 635 {{- if .UseInitMethods }} 636 637 // Run custom client initialization if present 638 if initClient != nil { 639 initClient(svc.Client) 640 } 641 {{- end }} 642 643 return svc 644} 645 646// newRequest creates a new request for a {{ .StructName }} operation and runs any 647// custom request initialization. 648func (c *{{ .StructName }}) newRequest(op *request.Operation, params, data interface{}) *request.Request { 649 req := c.NewRequest(op, params, data) 650 651 {{- if .UseInitMethods }} 652 653 // Run custom request initialization if present 654 if initRequest != nil { 655 initRequest(req) 656 } 657 {{- end }} 658 659 return req 660} 661`)) 662 663// ServicePackageDoc generates the contents of the doc file for the service. 664// 665// Will also read in the custom doc templates for the service if found. 666func (a *API) ServicePackageDoc() string { 667 a.imports = map[string]bool{} 668 669 var buf bytes.Buffer 670 if err := tplServiceDoc.Execute(&buf, a); err != nil { 671 panic(err) 672 } 673 674 return buf.String() 675} 676 677// ServiceGoCode renders service go code. Returning it as a string. 678func (a *API) ServiceGoCode() string { 679 a.resetImports() 680 a.AddSDKImport("aws") 681 a.AddSDKImport("aws/client") 682 a.AddSDKImport("aws/client/metadata") 683 a.AddSDKImport("aws/request") 684 if a.Metadata.SignatureVersion == "v2" { 685 a.AddSDKImport("private/signer/v2") 686 a.AddSDKImport("aws/corehandlers") 687 } else { 688 a.AddSDKImport("aws/signer/v4") 689 } 690 a.AddSDKImport("private/protocol", a.ProtocolPackage()) 691 if a.EndpointDiscoveryOp != nil { 692 a.AddSDKImport("aws/crr") 693 } 694 695 var buf bytes.Buffer 696 err := tplService.Execute(&buf, a) 697 if err != nil { 698 panic(err) 699 } 700 701 code := a.importsGoCode() + buf.String() 702 return code 703} 704 705// ExampleGoCode renders service example code. Returning it as a string. 706func (a *API) ExampleGoCode() string { 707 exs := []string{} 708 imports := map[string]bool{} 709 for _, o := range a.OperationList() { 710 o.imports = map[string]bool{} 711 exs = append(exs, o.Example()) 712 for k, v := range o.imports { 713 imports[k] = v 714 } 715 } 716 717 code := fmt.Sprintf("import (\n%q\n%q\n%q\n\n%q\n%q\n%q\n", 718 "bytes", 719 "fmt", 720 "time", 721 SDKImportRoot+"/aws", 722 SDKImportRoot+"/aws/session", 723 a.ImportPath(), 724 ) 725 for k := range imports { 726 code += fmt.Sprintf("%q\n", k) 727 } 728 code += ")\n\n" 729 code += "var _ time.Duration\nvar _ bytes.Buffer\n\n" 730 code += strings.Join(exs, "\n\n") 731 return code 732} 733 734// A tplInterface defines the template for the service interface type. 735var tplInterface = template.Must(template.New("interface").Parse(` 736// {{ .StructName }}API provides an interface to enable mocking the 737// {{ .PackageName }}.{{ .StructName }} service client's API operation, 738// paginators, and waiters. This make unit testing your code that calls out 739// to the SDK's service client's calls easier. 740// 741// The best way to use this interface is so the SDK's service client's calls 742// can be stubbed out for unit testing your code with the SDK without needing 743// to inject custom request handlers into the SDK's request pipeline. 744// 745// // myFunc uses an SDK service client to make a request to 746// // {{.Metadata.ServiceFullName}}. {{ $opts := .OperationList }}{{ $opt := index $opts 0 }} 747// func myFunc(svc {{ .InterfacePackageName }}.{{ .StructName }}API) bool { 748// // Make svc.{{ $opt.ExportedName }} request 749// } 750// 751// func main() { 752// sess := session.New() 753// svc := {{ .PackageName }}.New(sess) 754// 755// myFunc(svc) 756// } 757// 758// In your _test.go file: 759// 760// // Define a mock struct to be used in your unit tests of myFunc. 761// type mock{{ .StructName }}Client struct { 762// {{ .InterfacePackageName }}.{{ .StructName }}API 763// } 764// func (m *mock{{ .StructName }}Client) {{ $opt.ExportedName }}(input {{ $opt.InputRef.GoTypeWithPkgName }}) ({{ $opt.OutputRef.GoTypeWithPkgName }}, error) { 765// // mock response/functionality 766// } 767// 768// func TestMyFunc(t *testing.T) { 769// // Setup Test 770// mockSvc := &mock{{ .StructName }}Client{} 771// 772// myfunc(mockSvc) 773// 774// // Verify myFunc's functionality 775// } 776// 777// It is important to note that this interface will have breaking changes 778// when the service model is updated and adds new API operations, paginators, 779// and waiters. Its suggested to use the pattern above for testing, or using 780// tooling to generate mocks to satisfy the interfaces. 781type {{ .StructName }}API interface { 782 {{ range $_, $o := .OperationList }} 783 {{ $o.InterfaceSignature }} 784 {{ end }} 785 {{ range $_, $w := .Waiters }} 786 {{ $w.InterfaceSignature }} 787 {{ end }} 788} 789 790var _ {{ .StructName }}API = (*{{ .PackageName }}.{{ .StructName }})(nil) 791`)) 792 793// InterfaceGoCode returns the go code for the service's API operations as an 794// interface{}. Assumes that the interface is being created in a different 795// package than the service API's package. 796func (a *API) InterfaceGoCode() string { 797 a.resetImports() 798 a.AddSDKImport("aws") 799 a.AddSDKImport("aws/request") 800 a.AddImport(a.ImportPath()) 801 802 var buf bytes.Buffer 803 err := tplInterface.Execute(&buf, a) 804 805 if err != nil { 806 panic(err) 807 } 808 809 code := a.importsGoCode() + strings.TrimSpace(buf.String()) 810 return code 811} 812 813// NewAPIGoCodeWithPkgName returns a string of instantiating the API prefixed 814// with its package name. Takes a string depicting the Config. 815func (a *API) NewAPIGoCodeWithPkgName(cfg string) string { 816 return fmt.Sprintf("%s.New(%s)", a.PackageName(), cfg) 817} 818 819// computes the validation chain for all input shapes 820func (a *API) addShapeValidations() { 821 for _, o := range a.Operations { 822 resolveShapeValidations(o.InputRef.Shape) 823 } 824} 825 826// Updates the source shape and all nested shapes with the validations that 827// could possibly be needed. 828func resolveShapeValidations(s *Shape, ancestry ...*Shape) { 829 for _, a := range ancestry { 830 if a == s { 831 return 832 } 833 } 834 835 children := []string{} 836 for _, name := range s.MemberNames() { 837 ref := s.MemberRefs[name] 838 if s.IsRequired(name) && !s.Validations.Has(ref, ShapeValidationRequired) { 839 s.Validations = append(s.Validations, ShapeValidation{ 840 Name: name, Ref: ref, Type: ShapeValidationRequired, 841 }) 842 } 843 844 if ref.Shape.Min != 0 && !s.Validations.Has(ref, ShapeValidationMinVal) { 845 s.Validations = append(s.Validations, ShapeValidation{ 846 Name: name, Ref: ref, Type: ShapeValidationMinVal, 847 }) 848 } 849 850 if !ref.CanBeEmpty() && !s.Validations.Has(ref, ShapeValidationMinVal) { 851 s.Validations = append(s.Validations, ShapeValidation{ 852 Name: name, Ref: ref, Type: ShapeValidationMinVal, 853 }) 854 } 855 856 switch ref.Shape.Type { 857 case "map", "list", "structure": 858 children = append(children, name) 859 } 860 } 861 862 ancestry = append(ancestry, s) 863 for _, name := range children { 864 ref := s.MemberRefs[name] 865 // Since this is a grab bag we will just continue since 866 // we can't validate because we don't know the valued shape. 867 if ref.JSONValue || (s.UsedAsInput && ref.Shape.IsEventStream) { 868 continue 869 } 870 871 nestedShape := ref.Shape.NestedShape() 872 873 var v *ShapeValidation 874 if len(nestedShape.Validations) > 0 { 875 v = &ShapeValidation{ 876 Name: name, Ref: ref, Type: ShapeValidationNested, 877 } 878 } else { 879 resolveShapeValidations(nestedShape, ancestry...) 880 if len(nestedShape.Validations) > 0 { 881 v = &ShapeValidation{ 882 Name: name, Ref: ref, Type: ShapeValidationNested, 883 } 884 } 885 } 886 887 if v != nil && !s.Validations.Has(v.Ref, v.Type) { 888 s.Validations = append(s.Validations, *v) 889 } 890 } 891 ancestry = ancestry[:len(ancestry)-1] 892} 893 894// A tplAPIErrors is the top level template for the API 895var tplAPIErrors = template.Must(template.New("api").Parse(` 896const ( 897 {{- range $_, $s := $.ShapeListErrors }} 898 899 // {{ $s.ErrorCodeName }} for service response error code 900 // {{ printf "%q" $s.ErrorName }}. 901 {{ if $s.Docstring -}} 902 // 903 {{ $s.Docstring }} 904 {{ end -}} 905 {{ $s.ErrorCodeName }} = {{ printf "%q" $s.ErrorName }} 906 {{- end }} 907) 908 909{{- if $.WithGeneratedTypedErrors }} 910 {{- $_ := $.AddSDKImport "private/protocol" }} 911 912 var exceptionFromCode = map[string]func(protocol.ResponseMetadata)error { 913 {{- range $_, $s := $.ShapeListErrors }} 914 "{{ $s.ErrorName }}": newError{{ $s.ShapeName }}, 915 {{- end }} 916 } 917{{- end }} 918`)) 919 920// APIErrorsGoCode returns the Go code for the errors.go file. 921func (a *API) APIErrorsGoCode() string { 922 a.resetImports() 923 924 var buf bytes.Buffer 925 err := tplAPIErrors.Execute(&buf, a) 926 927 if err != nil { 928 panic(err) 929 } 930 931 return a.importsGoCode() + strings.TrimSpace(buf.String()) 932} 933 934// removeOperation removes an operation, its input/output shapes, as well as 935// any references/shapes that are unique to this operation. 936func (a *API) removeOperation(name string) { 937 debugLogger.Logln("removing operation,", name) 938 op := a.Operations[name] 939 940 delete(a.Operations, name) 941 delete(a.Examples, name) 942 943 a.removeShape(op.InputRef.Shape) 944 a.removeShape(op.OutputRef.Shape) 945} 946 947// removeShape removes the given shape, and all form member's reference target 948// shapes. Will also remove member reference targeted shapes if those shapes do 949// not have any additional references. 950func (a *API) removeShape(s *Shape) { 951 debugLogger.Logln("removing shape,", s.ShapeName) 952 953 delete(a.Shapes, s.ShapeName) 954 955 for name, ref := range s.MemberRefs { 956 a.removeShapeRef(ref) 957 delete(s.MemberRefs, name) 958 } 959 960 for _, ref := range []*ShapeRef{&s.MemberRef, &s.KeyRef, &s.ValueRef} { 961 if ref.Shape == nil { 962 continue 963 } 964 a.removeShapeRef(ref) 965 *ref = ShapeRef{} 966 } 967} 968 969// removeShapeRef removes the shape reference from its target shape. If the 970// reference was the last reference to the target shape, the shape will also be 971// removed. 972func (a *API) removeShapeRef(ref *ShapeRef) { 973 if ref.Shape == nil { 974 return 975 } 976 977 ref.Shape.removeRef(ref) 978 if len(ref.Shape.refs) == 0 { 979 a.removeShape(ref.Shape) 980 } 981} 982 983// writeInputOutputLocationName writes the ShapeName to the 984// shapes LocationName in the event that there is no LocationName 985// specified. 986func (a *API) writeInputOutputLocationName() { 987 for _, o := range a.Operations { 988 setInput := len(o.InputRef.LocationName) == 0 && a.Metadata.Protocol == "rest-xml" 989 setOutput := len(o.OutputRef.LocationName) == 0 && (a.Metadata.Protocol == "rest-xml" || a.Metadata.Protocol == "ec2") 990 991 if setInput { 992 o.InputRef.LocationName = o.InputRef.Shape.OrigShapeName 993 } 994 if setOutput { 995 o.OutputRef.LocationName = o.OutputRef.Shape.OrigShapeName 996 } 997 } 998} 999 1000func (a *API) addHeaderMapDocumentation() { 1001 for _, shape := range a.Shapes { 1002 if !shape.UsedAsOutput { 1003 continue 1004 } 1005 for _, shapeRef := range shape.MemberRefs { 1006 if shapeRef.Location == "headers" { 1007 if dLen := len(shapeRef.Documentation); dLen > 0 { 1008 if shapeRef.Documentation[dLen-1] != '\n' { 1009 shapeRef.Documentation += "\n" 1010 } 1011 shapeRef.Documentation += "//" 1012 } 1013 shapeRef.Documentation += ` 1014// By default unmarshaled keys are written as a map keys in following canonicalized format: 1015// the first letter and any letter following a hyphen will be capitalized, and the rest as lowercase. 1016// Set ` + "`aws.Config.LowerCaseHeaderMaps`" + ` to ` + "`true`" + ` to write unmarshaled keys to the map as lowercase. 1017` 1018 } 1019 } 1020 } 1021} 1022 1023func (a *API) validateNoDocumentShapes() error { 1024 var shapes []string 1025 for name, shape := range a.Shapes { 1026 if shape.Type != "structure" { 1027 continue 1028 } 1029 if shape.Document { 1030 shapes = append(shapes, name) 1031 } 1032 } 1033 1034 if len(shapes) == 0 { 1035 return nil 1036 } 1037 1038 return fmt.Errorf("model contains document shapes: %s", strings.Join(shapes, ", ")) 1039} 1040 1041func getDeprecatedMessage(msg string, name string) string { 1042 if len(msg) == 0 { 1043 return name + " has been deprecated" 1044 } 1045 1046 return msg 1047} 1048