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