1// +build codegen
2
3package api
4
5import (
6	"encoding/base64"
7	"encoding/json"
8	"fmt"
9	"reflect"
10	"sort"
11	"strings"
12
13	"github.com/aws/aws-sdk-go/aws"
14	"github.com/aws/aws-sdk-go/private/protocol"
15)
16
17// ShapeValueBuilder provides the logic to build the nested values for a shape.
18// Base64BlobValues is true if the blob field in shapeRef.Shape.Type is base64
19// encoded.
20type ShapeValueBuilder struct {
21	// Specifies if API shapes modeled as blob types, input values are base64
22	// encoded or not, and strings values instead.
23	Base64BlobValues bool
24
25	// The helper that will provide the logic and formated code to convert a
26	// timestamp input value into a Go time.Time.
27	ParseTimeString func(ref *ShapeRef, memberName, v string) string
28}
29
30// NewShapeValueBuilder returns an initialized ShapeValueBuilder for generating
31// API shape types initialized with values.
32func NewShapeValueBuilder() ShapeValueBuilder {
33	return ShapeValueBuilder{ParseTimeString: parseUnixTimeString}
34}
35
36// BuildShape will recursively build the referenced shape based on the json
37// object provided.  isMap will dictate how the field name is specified. If
38// isMap is true, we will expect the member name to be quotes like "Foo".
39func (b ShapeValueBuilder) BuildShape(ref *ShapeRef, shapes map[string]interface{}, isMap bool) string {
40	order := make([]string, len(shapes))
41	for k := range shapes {
42		order = append(order, k)
43	}
44	sort.Strings(order)
45
46	ret := ""
47	for _, name := range order {
48		if name == "" {
49			continue
50		}
51		shape := shapes[name]
52
53		// If the shape isn't a map, we want to export the value, since every field
54		// defined in our shapes are exported.
55		if len(name) > 0 && !isMap && strings.ToLower(name[0:1]) == name[0:1] {
56			name = strings.Title(name)
57		}
58
59		memName := name
60		passRef := ref.Shape.MemberRefs[name]
61		if isMap {
62			memName = fmt.Sprintf("%q", memName)
63			passRef = &ref.Shape.ValueRef
64		}
65		switch v := shape.(type) {
66		case map[string]interface{}:
67			ret += b.BuildComplex(name, memName, passRef, ref.Shape, v)
68		case []interface{}:
69			ret += b.BuildList(name, memName, passRef, v)
70		default:
71
72			ret += b.BuildScalar(name, memName, passRef, v, ref.Shape.Payload == name)
73		}
74	}
75	return ret
76}
77
78// BuildList will construct a list shape based off the service's definition of
79// that list.
80func (b ShapeValueBuilder) BuildList(name, memName string, ref *ShapeRef, v []interface{}) string {
81	ret := ""
82
83	if len(v) == 0 || ref == nil {
84		return ""
85	}
86
87	passRef := &ref.Shape.MemberRef
88	ret += fmt.Sprintf("%s: %s {\n", memName, b.GoType(ref, false))
89	ret += b.buildListElements(passRef, v)
90	ret += "},\n"
91	return ret
92}
93
94func (b ShapeValueBuilder) buildListElements(ref *ShapeRef, v []interface{}) string {
95	if len(v) == 0 || ref == nil {
96		return ""
97	}
98
99	ret := ""
100	format := ""
101	isComplex := false
102	isList := false
103
104	// get format for atomic type. If it is not an atomic type,
105	// get the element.
106	switch v[0].(type) {
107	case string:
108		format = "%s"
109	case bool:
110		format = "%t"
111	case float64:
112		switch ref.Shape.Type {
113		case "integer", "int64", "long":
114			format = "%d"
115		default:
116			format = "%f"
117		}
118	case []interface{}:
119		isList = true
120	case map[string]interface{}:
121		isComplex = true
122	}
123
124	for _, elem := range v {
125		if isComplex {
126			ret += fmt.Sprintf("{\n%s\n},\n", b.BuildShape(ref, elem.(map[string]interface{}), ref.Shape.Type == "map"))
127		} else if isList {
128			ret += fmt.Sprintf("{\n%s\n},\n", b.buildListElements(&ref.Shape.MemberRef, elem.([]interface{})))
129		} else {
130			switch ref.Shape.Type {
131			case "integer", "int64", "long":
132				elem = int(elem.(float64))
133			}
134			ret += fmt.Sprintf("%s,\n", getValue(ref.Shape.Type, fmt.Sprintf(format, elem)))
135		}
136	}
137	return ret
138}
139
140// BuildScalar will build atomic Go types.
141func (b ShapeValueBuilder) BuildScalar(name, memName string, ref *ShapeRef, shape interface{}, isPayload bool) string {
142	if ref == nil || ref.Shape == nil {
143		return ""
144	}
145
146	switch v := shape.(type) {
147	case bool:
148		return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%t", v))
149	case int:
150		if ref.Shape.Type == "timestamp" {
151			return b.ParseTimeString(ref, memName, fmt.Sprintf("%d", v))
152		}
153		return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%d", v))
154	case float64:
155
156		dataType := ref.Shape.Type
157
158		if dataType == "timestamp" {
159			return b.ParseTimeString(ref, memName, fmt.Sprintf("%f", v))
160		}
161		if dataType == "integer" || dataType == "int64" || dataType == "long" {
162			return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%d", int(shape.(float64))))
163		}
164		return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%f", v))
165	case string:
166		t := ref.Shape.Type
167		switch t {
168		case "timestamp":
169			return b.ParseTimeString(ref, memName, fmt.Sprintf("%s", v))
170
171		case "jsonvalue":
172			return fmt.Sprintf("%s: %#v,\n", memName, parseJSONString(v))
173
174		case "blob":
175			if (ref.Streaming || ref.Shape.Streaming) && isPayload {
176				return fmt.Sprintf("%s: aws.ReadSeekCloser(strings.NewReader(%q)),\n", memName, v)
177			}
178			if b.Base64BlobValues {
179				decodedBlob, err := base64.StdEncoding.DecodeString(v)
180				if err != nil {
181					panic(fmt.Errorf("Failed to decode string: %v", err))
182				}
183				return fmt.Sprintf("%s: []byte(%q),\n", memName, decodedBlob)
184			}
185			return fmt.Sprintf("%s: []byte(%q),\n", memName, v)
186		default:
187			return convertToCorrectType(memName, t, v)
188		}
189	default:
190		panic(fmt.Errorf("Unsupported scalar type: %v", reflect.TypeOf(v)))
191	}
192}
193
194// BuildComplex will build the shape's value for complex types such as structs,
195// and maps.
196func (b ShapeValueBuilder) BuildComplex(name, memName string, ref *ShapeRef, parent *Shape, v map[string]interface{}) string {
197	switch parent.Type {
198	case "structure":
199		if ref.Shape.Type == "map" {
200			return fmt.Sprintf(`%s: %s{
201				%s
202			},
203			`, memName, b.GoType(ref, true), b.BuildShape(ref, v, true))
204		} else {
205			return fmt.Sprintf(`%s: &%s{
206				%s
207			},
208			`, memName, b.GoType(ref, true), b.BuildShape(ref, v, false))
209		}
210	case "map":
211		if ref.Shape.Type == "map" {
212			return fmt.Sprintf(`%q: %s{
213				%s
214			},
215			`, name, b.GoType(ref, false), b.BuildShape(ref, v, true))
216		} else {
217			return fmt.Sprintf(`%s: &%s{
218				%s
219			},
220			`, memName, b.GoType(ref, true), b.BuildShape(ref, v, false))
221		}
222	default:
223		panic(fmt.Sprintf("Expected complex type but received %q", ref.Shape.Type))
224	}
225}
226
227// GoType returns the string of the shape's Go type identifier.
228func (b ShapeValueBuilder) GoType(ref *ShapeRef, elem bool) string {
229
230	if ref.Shape.Type != "structure" && ref.Shape.Type != "list" && ref.Shape.Type != "map" {
231		// Scalars are always pointers.
232		return ref.GoTypeWithPkgName()
233	}
234
235	prefix := ""
236	if ref.Shape.Type == "list" {
237		ref = &ref.Shape.MemberRef
238		prefix = "[]"
239	}
240
241	if elem {
242		return prefix + ref.Shape.GoTypeWithPkgNameElem()
243	}
244	return prefix + ref.GoTypeWithPkgName()
245}
246
247// parseJSONString a json string and returns aws.JSONValue.
248func parseJSONString(input string) aws.JSONValue {
249	var v aws.JSONValue
250	if err := json.Unmarshal([]byte(input), &v); err != nil {
251		panic(fmt.Sprintf("unable to unmarshal JSONValue, %v", err))
252	}
253	return v
254}
255
256// InlineParseModeledTime returns the string of an inline function which
257// returns time.
258func inlineParseModeledTime(format, v string) string {
259	const formatTimeTmpl = `func() *time.Time{
260        v, err := protocol.ParseTime("%s", "%s")
261        if err != nil {
262            panic(err)
263        }
264        return &v
265    }()`
266
267	return fmt.Sprintf(formatTimeTmpl, format, v)
268}
269
270// parseUnixTimeString returns a string which assigns the value of a time
271// member using an inline function Defined inline function parses time in
272// UnixTimeFormat.
273func parseUnixTimeString(ref *ShapeRef, memName, v string) string {
274	ref.API.AddSDKImport("private/protocol")
275	return fmt.Sprintf("%s: %s,\n", memName, inlineParseModeledTime(protocol.UnixTimeFormatName, v))
276}
277