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