File: shape_value_builder.go

package info (click to toggle)
golang-github-aws-aws-sdk-go 1.49.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 312,636 kB
  • sloc: makefile: 120
file content (281 lines) | stat: -rw-r--r-- 8,020 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//go:build codegen
// +build codegen

package api

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/private/protocol"
)

// ShapeValueBuilder provides the logic to build the nested values for a shape.
// Base64BlobValues is true if the blob field in shapeRef.Shape.Type is base64
// encoded.
type ShapeValueBuilder struct {
	// Specifies if API shapes modeled as blob types, input values are base64
	// encoded or not, and strings values instead.
	Base64BlobValues bool

	// The helper that will provide the logic and formated code to convert a
	// timestamp input value into a Go time.Time.
	ParseTimeString func(ref *ShapeRef, memberName, v string) string
}

// NewShapeValueBuilder returns an initialized ShapeValueBuilder for generating
// API shape types initialized with values.
func NewShapeValueBuilder() ShapeValueBuilder {
	return ShapeValueBuilder{ParseTimeString: parseUnixTimeString}
}

// BuildShape will recursively build the referenced shape based on the json
// object provided.  isMap will dictate how the field name is specified. If
// isMap is true, we will expect the member name to be quotes like "Foo".
func (b ShapeValueBuilder) BuildShape(ref *ShapeRef, shapes map[string]interface{}, isMap bool) string {
	order := make([]string, len(shapes))
	for k := range shapes {
		order = append(order, k)
	}
	sort.Strings(order)

	ret := ""
	for _, name := range order {
		if name == "" {
			continue
		}
		shape := shapes[name]

		// If the shape isn't a map, we want to export the value, since every field
		// defined in our shapes are exported.
		if len(name) > 0 && !isMap && strings.ToLower(name[0:1]) == name[0:1] {
			name = strings.Title(name)
		}

		memName := name
		passRef := ref.Shape.MemberRefs[name]
		if isMap {
			memName = fmt.Sprintf("%q", memName)
			passRef = &ref.Shape.ValueRef
		}
		switch v := shape.(type) {
		case map[string]interface{}:
			ret += b.BuildComplex(name, memName, passRef, ref.Shape, v)
		case []interface{}:
			ret += b.BuildList(name, memName, passRef, v)
		default:

			ret += b.BuildScalar(name, memName, passRef, v, ref.Shape.Payload == name)
		}
	}
	return ret
}

// BuildList will construct a list shape based off the service's definition of
// that list.
func (b ShapeValueBuilder) BuildList(name, memName string, ref *ShapeRef, v []interface{}) string {
	ret := ""

	if len(v) == 0 || ref == nil {
		return ""
	}

	passRef := &ref.Shape.MemberRef
	ret += fmt.Sprintf("%s: %s {\n", memName, b.GoType(ref, false))
	ret += b.buildListElements(passRef, v)
	ret += "},\n"
	return ret
}

func (b ShapeValueBuilder) buildListElements(ref *ShapeRef, v []interface{}) string {
	if len(v) == 0 || ref == nil {
		return ""
	}

	ret := ""
	format := ""
	isComplex := false
	isList := false

	// get format for atomic type. If it is not an atomic type,
	// get the element.
	switch v[0].(type) {
	case string:
		format = "%s"
	case bool:
		format = "%t"
	case float64:
		switch ref.Shape.Type {
		case "integer", "int64", "long":
			format = "%d"
		default:
			format = "%f"
		}
	case []interface{}:
		isList = true
	case map[string]interface{}:
		isComplex = true
	}

	for _, elem := range v {
		if isComplex {
			ret += fmt.Sprintf("{\n%s\n},\n", b.BuildShape(ref, elem.(map[string]interface{}), ref.Shape.Type == "map"))
		} else if isList {
			ret += fmt.Sprintf("{\n%s\n},\n", b.buildListElements(&ref.Shape.MemberRef, elem.([]interface{})))
		} else {
			switch ref.Shape.Type {
			case "integer", "int64", "long":
				elem = int(elem.(float64))
			}
			ret += fmt.Sprintf("%s,\n", getValue(ref.Shape.Type, fmt.Sprintf(format, elem)))
		}
	}
	return ret
}

// BuildScalar will build atomic Go types.
func (b ShapeValueBuilder) BuildScalar(name, memName string, ref *ShapeRef, shape interface{}, isPayload bool) string {
	if ref == nil || ref.Shape == nil {
		return ""
	}

	switch v := shape.(type) {
	case bool:
		return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%t", v))
	case int:
		if ref.Shape.Type == "timestamp" {
			return b.ParseTimeString(ref, memName, fmt.Sprintf("%d", v))
		}
		return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%d", v))
	case float64:

		dataType := ref.Shape.Type

		if dataType == "timestamp" {
			return b.ParseTimeString(ref, memName, fmt.Sprintf("%f", v))
		}
		if dataType == "integer" || dataType == "int64" || dataType == "long" {
			return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%d", int(shape.(float64))))
		}
		return convertToCorrectType(memName, ref.Shape.Type, fmt.Sprintf("%f", v))
	case string:
		t := ref.Shape.Type
		switch t {
		case "timestamp":
			return b.ParseTimeString(ref, memName, fmt.Sprintf("%s", v))

		case "jsonvalue":
			return fmt.Sprintf("%s: %#v,\n", memName, parseJSONString(v))

		case "blob":
			if (ref.Streaming || ref.Shape.Streaming) && isPayload {
				return fmt.Sprintf("%s: aws.ReadSeekCloser(strings.NewReader(%q)),\n", memName, v)
			}
			if b.Base64BlobValues {
				decodedBlob, err := base64.StdEncoding.DecodeString(v)
				if err != nil {
					panic(fmt.Errorf("Failed to decode string: %v", err))
				}
				return fmt.Sprintf("%s: []byte(%q),\n", memName, decodedBlob)
			}
			return fmt.Sprintf("%s: []byte(%q),\n", memName, v)
		default:
			return convertToCorrectType(memName, t, v)
		}
	default:
		panic(fmt.Errorf("Unsupported scalar type: %v", reflect.TypeOf(v)))
	}
}

// BuildComplex will build the shape's value for complex types such as structs,
// and maps.
func (b ShapeValueBuilder) BuildComplex(name, memName string, ref *ShapeRef, parent *Shape, v map[string]interface{}) string {
	switch parent.Type {
	case "structure":
		if ref.Shape.Type == "map" {
			return fmt.Sprintf(`%s: %s{
				%s
			},
			`, memName, b.GoType(ref, true), b.BuildShape(ref, v, true))
		} else {
			// Don't try to generate for members not actually modeled in the API
			if _, ok := parent.MemberRefs[memName]; !ok {
				return ""
			}
			return fmt.Sprintf(`%s: &%s{
				%s
			},
			`, memName, b.GoType(ref, true), b.BuildShape(ref, v, false))
		}
	case "map":
		if ref.Shape.Type == "map" {
			return fmt.Sprintf(`%q: %s{
				%s
			},
			`, name, b.GoType(ref, false), b.BuildShape(ref, v, true))
		} else {
			return fmt.Sprintf(`%s: &%s{
				%s
			},
			`, memName, b.GoType(ref, true), b.BuildShape(ref, v, false))
		}
	default:
		panic(fmt.Sprintf("Expected complex type but received %q", ref.Shape.Type))
	}
}

// GoType returns the string of the shape's Go type identifier.
func (b ShapeValueBuilder) GoType(ref *ShapeRef, elem bool) string {

	if ref.Shape.Type != "structure" && ref.Shape.Type != "list" && ref.Shape.Type != "map" {
		// Scalars are always pointers.
		return ref.GoTypeWithPkgName()
	}

	prefix := ""
	if ref.Shape.Type == "list" {
		ref = &ref.Shape.MemberRef
		prefix = "[]"
	}

	if elem {
		return prefix + ref.Shape.GoTypeWithPkgNameElem()
	}
	return prefix + ref.GoTypeWithPkgName()
}

// parseJSONString a json string and returns aws.JSONValue.
func parseJSONString(input string) aws.JSONValue {
	var v aws.JSONValue
	if err := json.Unmarshal([]byte(input), &v); err != nil {
		panic(fmt.Sprintf("unable to unmarshal JSONValue, %v", err))
	}
	return v
}

// InlineParseModeledTime returns the string of an inline function which
// returns time.
func inlineParseModeledTime(format, v string) string {
	const formatTimeTmpl = `func() *time.Time{
        v, err := protocol.ParseTime("%s", "%s")
        if err != nil {
            panic(err)
        }
        return &v
    }()`

	return fmt.Sprintf(formatTimeTmpl, format, v)
}

// parseUnixTimeString returns a string which assigns the value of a time
// member using an inline function Defined inline function parses time in
// UnixTimeFormat.
func parseUnixTimeString(ref *ShapeRef, memName, v string) string {
	ref.API.AddSDKImport("private/protocol")
	return fmt.Sprintf("%s: %s,\n", memName, inlineParseModeledTime(protocol.UnixTimeFormatName, v))
}