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