| package queryutil |
| |
| import ( |
| "encoding/base64" |
| "fmt" |
| "net/url" |
| "reflect" |
| "sort" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/aws/aws-sdk-go/private/protocol" |
| ) |
| |
| // Parse parses an object i and fills a url.Values object. The isEC2 flag |
| // indicates if this is the EC2 Query sub-protocol. |
| func Parse(body url.Values, i interface{}, isEC2 bool) error { |
| q := queryParser{isEC2: isEC2} |
| return q.parseValue(body, reflect.ValueOf(i), "", "") |
| } |
| |
| func elemOf(value reflect.Value) reflect.Value { |
| for value.Kind() == reflect.Ptr { |
| value = value.Elem() |
| } |
| return value |
| } |
| |
| type queryParser struct { |
| isEC2 bool |
| } |
| |
| func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { |
| value = elemOf(value) |
| |
| // no need to handle zero values |
| if !value.IsValid() { |
| return nil |
| } |
| |
| t := tag.Get("type") |
| if t == "" { |
| switch value.Kind() { |
| case reflect.Struct: |
| t = "structure" |
| case reflect.Slice: |
| t = "list" |
| case reflect.Map: |
| t = "map" |
| } |
| } |
| |
| switch t { |
| case "structure": |
| return q.parseStruct(v, value, prefix) |
| case "list": |
| return q.parseList(v, value, prefix, tag) |
| case "map": |
| return q.parseMap(v, value, prefix, tag) |
| default: |
| return q.parseScalar(v, value, prefix, tag) |
| } |
| } |
| |
| func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error { |
| if !value.IsValid() { |
| return nil |
| } |
| |
| t := value.Type() |
| for i := 0; i < value.NumField(); i++ { |
| elemValue := elemOf(value.Field(i)) |
| field := t.Field(i) |
| |
| if field.PkgPath != "" { |
| continue // ignore unexported fields |
| } |
| if field.Tag.Get("ignore") != "" { |
| continue |
| } |
| |
| if protocol.CanSetIdempotencyToken(value.Field(i), field) { |
| token := protocol.GetIdempotencyToken() |
| elemValue = reflect.ValueOf(token) |
| } |
| |
| var name string |
| if q.isEC2 { |
| name = field.Tag.Get("queryName") |
| } |
| if name == "" { |
| if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" { |
| name = field.Tag.Get("locationNameList") |
| } else if locName := field.Tag.Get("locationName"); locName != "" { |
| name = locName |
| } |
| if name != "" && q.isEC2 { |
| name = strings.ToUpper(name[0:1]) + name[1:] |
| } |
| } |
| if name == "" { |
| name = field.Name |
| } |
| |
| if prefix != "" { |
| name = prefix + "." + name |
| } |
| |
| if err := q.parseValue(v, elemValue, name, field.Tag); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { |
| // If it's empty, generate an empty value |
| if !value.IsNil() && value.Len() == 0 { |
| v.Set(prefix, "") |
| return nil |
| } |
| |
| if _, ok := value.Interface().([]byte); ok { |
| return q.parseScalar(v, value, prefix, tag) |
| } |
| |
| // check for unflattened list member |
| if !q.isEC2 && tag.Get("flattened") == "" { |
| if listName := tag.Get("locationNameList"); listName == "" { |
| prefix += ".member" |
| } else { |
| prefix += "." + listName |
| } |
| } |
| |
| for i := 0; i < value.Len(); i++ { |
| slicePrefix := prefix |
| if slicePrefix == "" { |
| slicePrefix = strconv.Itoa(i + 1) |
| } else { |
| slicePrefix = slicePrefix + "." + strconv.Itoa(i+1) |
| } |
| if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { |
| // If it's empty, generate an empty value |
| if !value.IsNil() && value.Len() == 0 { |
| v.Set(prefix, "") |
| return nil |
| } |
| |
| // check for unflattened list member |
| if !q.isEC2 && tag.Get("flattened") == "" { |
| prefix += ".entry" |
| } |
| |
| // sort keys for improved serialization consistency. |
| // this is not strictly necessary for protocol support. |
| mapKeyValues := value.MapKeys() |
| mapKeys := map[string]reflect.Value{} |
| mapKeyNames := make([]string, len(mapKeyValues)) |
| for i, mapKey := range mapKeyValues { |
| name := mapKey.String() |
| mapKeys[name] = mapKey |
| mapKeyNames[i] = name |
| } |
| sort.Strings(mapKeyNames) |
| |
| for i, mapKeyName := range mapKeyNames { |
| mapKey := mapKeys[mapKeyName] |
| mapValue := value.MapIndex(mapKey) |
| |
| kname := tag.Get("locationNameKey") |
| if kname == "" { |
| kname = "key" |
| } |
| vname := tag.Get("locationNameValue") |
| if vname == "" { |
| vname = "value" |
| } |
| |
| // serialize key |
| var keyName string |
| if prefix == "" { |
| keyName = strconv.Itoa(i+1) + "." + kname |
| } else { |
| keyName = prefix + "." + strconv.Itoa(i+1) + "." + kname |
| } |
| |
| if err := q.parseValue(v, mapKey, keyName, ""); err != nil { |
| return err |
| } |
| |
| // serialize value |
| var valueName string |
| if prefix == "" { |
| valueName = strconv.Itoa(i+1) + "." + vname |
| } else { |
| valueName = prefix + "." + strconv.Itoa(i+1) + "." + vname |
| } |
| |
| if err := q.parseValue(v, mapValue, valueName, ""); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error { |
| switch value := r.Interface().(type) { |
| case string: |
| v.Set(name, value) |
| case []byte: |
| if !r.IsNil() { |
| v.Set(name, base64.StdEncoding.EncodeToString(value)) |
| } |
| case bool: |
| v.Set(name, strconv.FormatBool(value)) |
| case int64: |
| v.Set(name, strconv.FormatInt(value, 10)) |
| case int: |
| v.Set(name, strconv.Itoa(value)) |
| case float64: |
| v.Set(name, strconv.FormatFloat(value, 'f', -1, 64)) |
| case float32: |
| v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32)) |
| case time.Time: |
| const ISO8601UTC = "2006-01-02T15:04:05Z" |
| format := tag.Get("timestampFormat") |
| if len(format) == 0 { |
| format = protocol.ISO8601TimeFormatName |
| } |
| |
| v.Set(name, protocol.FormatTime(format, value)) |
| default: |
| return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name()) |
| } |
| return nil |
| } |