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