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