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