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