1// +build codegen
2
3package api
4
5import (
6	"bytes"
7	"encoding/json"
8	"fmt"
9	"os"
10	"sort"
11	"strings"
12	"text/template"
13)
14
15// WaiterAcceptor is the acceptors defined in the model the SDK will use
16// to wait on resource states with.
17type WaiterAcceptor struct {
18	State    string
19	Matcher  string
20	Argument string
21	Expected interface{}
22}
23
24// ExpectedString returns the string that was expected by the WaiterAcceptor
25func (a *WaiterAcceptor) ExpectedString() string {
26	switch a.Expected.(type) {
27	case string:
28		return fmt.Sprintf("%q", a.Expected)
29	default:
30		return fmt.Sprintf("%v", a.Expected)
31	}
32}
33
34// A Waiter is an individual waiter definition.
35type Waiter struct {
36	Name          string
37	Delay         int
38	MaxAttempts   int
39	OperationName string `json:"operation"`
40	Operation     *Operation
41	Acceptors     []WaiterAcceptor
42}
43
44// WaitersGoCode generates and returns Go code for each of the waiters of
45// this API.
46func (a *API) WaitersGoCode() string {
47	var buf bytes.Buffer
48	fmt.Fprintf(&buf, "import (\n%q\n\n%q\n%q\n)",
49		"time",
50		SDKImportRoot+"/aws",
51		SDKImportRoot+"/aws/request",
52	)
53
54	for _, w := range a.Waiters {
55		buf.WriteString(w.GoCode())
56	}
57	return buf.String()
58}
59
60// used for unmarshaling from the waiter JSON file
61type waiterDefinitions struct {
62	*API
63	Waiters map[string]Waiter
64}
65
66// AttachWaiters reads a file of waiter definitions, and adds those to the API.
67// Will panic if an error occurs.
68func (a *API) AttachWaiters(filename string) error {
69	p := waiterDefinitions{API: a}
70
71	f, err := os.Open(filename)
72	defer f.Close()
73	if err != nil {
74		return err
75	}
76	err = json.NewDecoder(f).Decode(&p)
77	if err != nil {
78		return err
79	}
80
81	return p.setup()
82}
83
84func (p *waiterDefinitions) setup() error {
85	p.API.Waiters = []Waiter{}
86	i, keys := 0, make([]string, len(p.Waiters))
87	for k := range p.Waiters {
88		keys[i] = k
89		i++
90	}
91	sort.Strings(keys)
92
93	for _, n := range keys {
94		e := p.Waiters[n]
95		n = p.ExportableName(n)
96		e.Name = n
97		e.OperationName = p.ExportableName(e.OperationName)
98		e.Operation = p.API.Operations[e.OperationName]
99		if e.Operation == nil {
100			return fmt.Errorf("unknown operation %s for waiter %s",
101				e.OperationName, n)
102		}
103		p.API.Waiters = append(p.API.Waiters, e)
104	}
105
106	return nil
107}
108
109var waiterTmpls = template.Must(template.New("waiterTmpls").Funcs(
110	template.FuncMap{
111		"titleCase": func(v string) string {
112			return strings.Title(v)
113		},
114	},
115).Parse(`
116{{ define "waiter"}}
117// WaitUntil{{ .Name }} uses the {{ .Operation.API.NiceName }} API operation
118// {{ .OperationName }} to wait for a condition to be met before returning.
119// If the condition is not met within the max attempt window, an error will
120// be returned.
121func (c *{{ .Operation.API.StructName }}) WaitUntil{{ .Name }}(input {{ .Operation.InputRef.GoType }}) error {
122	return c.WaitUntil{{ .Name }}WithContext(aws.BackgroundContext(), input)
123}
124
125// WaitUntil{{ .Name }}WithContext is an extended version of WaitUntil{{ .Name }}.
126// With the support for passing in a context and options to configure the
127// Waiter and the underlying request options.
128//
129// The context must be non-nil and will be used for request cancellation. If
130// the context is nil a panic will occur. In the future the SDK may create
131// sub-contexts for http.Requests. See https://golang.org/pkg/context/
132// for more information on using Contexts.
133func (c *{{ .Operation.API.StructName }}) WaitUntil{{ .Name }}WithContext(` +
134	`ctx aws.Context, input {{ .Operation.InputRef.GoType }}, opts ...request.WaiterOption) error {
135	w := request.Waiter{
136		Name:    "WaitUntil{{ .Name }}",
137		MaxAttempts: {{ .MaxAttempts }},
138		Delay: request.ConstantWaiterDelay({{ .Delay }} * time.Second),
139		Acceptors: []request.WaiterAcceptor{
140			{{ range $_, $a := .Acceptors }}{
141				State:    request.{{ titleCase .State }}WaiterState,
142				Matcher:  request.{{ titleCase .Matcher }}WaiterMatch,
143				{{- if .Argument }}Argument: "{{ .Argument }}",{{ end }}
144				Expected: {{ .ExpectedString }},
145			},
146			{{ end }}
147		},
148		Logger: c.Config.Logger,
149		NewRequest: func(opts []request.Option) (*request.Request, error) {
150			var inCpy {{ .Operation.InputRef.GoType }}
151			if input != nil  {
152				tmp := *input
153				inCpy = &tmp
154			}
155			req, _ := c.{{ .OperationName }}Request(inCpy)
156			req.SetContext(ctx)
157			req.ApplyOptions(opts...)
158			return req, nil
159		},
160	}
161	w.ApplyOptions(opts...)
162
163	return w.WaitWithContext(ctx)
164}
165{{- end }}
166
167{{ define "waiter interface" }}
168WaitUntil{{ .Name }}({{ .Operation.InputRef.GoTypeWithPkgName }}) error
169WaitUntil{{ .Name }}WithContext(aws.Context, {{ .Operation.InputRef.GoTypeWithPkgName }}, ...request.WaiterOption) error
170{{- end }}
171`))
172
173// InterfaceSignature returns a string representing the Waiter's interface
174// function signature.
175func (w *Waiter) InterfaceSignature() string {
176	var buf bytes.Buffer
177	if err := waiterTmpls.ExecuteTemplate(&buf, "waiter interface", w); err != nil {
178		panic(err)
179	}
180
181	return strings.TrimSpace(buf.String())
182}
183
184// GoCode returns the generated Go code for an individual waiter.
185func (w *Waiter) GoCode() string {
186	var buf bytes.Buffer
187	if err := waiterTmpls.ExecuteTemplate(&buf, "waiter", w); err != nil {
188		panic(err)
189	}
190
191	return buf.String()
192}
193