|
Packit Service |
509fd4 |
package runtime
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
import (
|
|
Packit Service |
509fd4 |
"encoding/json"
|
|
Packit Service |
509fd4 |
"fmt"
|
|
Packit Service |
509fd4 |
"net/url"
|
|
Packit Service |
509fd4 |
"reflect"
|
|
Packit Service |
509fd4 |
"sort"
|
|
Packit Service |
509fd4 |
"strconv"
|
|
Packit Service |
509fd4 |
"strings"
|
|
Packit Service |
509fd4 |
"time"
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
"github.com/pkg/errors"
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
"github.com/deepmap/oapi-codegen/pkg/types"
|
|
Packit Service |
509fd4 |
)
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func marshalDeepObject(in interface{}, path []string) ([]string, error) {
|
|
Packit Service |
509fd4 |
var result []string
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
switch t := in.(type) {
|
|
Packit Service |
509fd4 |
case []interface{}:
|
|
Packit Service |
509fd4 |
// For the array, we will use numerical subscripts of the form [x],
|
|
Packit Service |
509fd4 |
// in the same order as the array.
|
|
Packit Service |
509fd4 |
for i, iface := range t {
|
|
Packit Service |
509fd4 |
newPath := append(path, strconv.Itoa(i))
|
|
Packit Service |
509fd4 |
fields, err := marshalDeepObject(iface, newPath)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return nil, errors.Wrap(err, "error traversing array")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
result = append(result, fields...)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
case map[string]interface{}:
|
|
Packit Service |
509fd4 |
// For a map, each key (field name) becomes a member of the path, and
|
|
Packit Service |
509fd4 |
// we recurse. First, sort the keys.
|
|
Packit Service |
509fd4 |
keys := make([]string, len(t))
|
|
Packit Service |
509fd4 |
i := 0
|
|
Packit Service |
509fd4 |
for k := range t {
|
|
Packit Service |
509fd4 |
keys[i] = k
|
|
Packit Service |
509fd4 |
i++
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
sort.Strings(keys)
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Now, for each key, we recursively marshal it.
|
|
Packit Service |
509fd4 |
for _, k := range keys {
|
|
Packit Service |
509fd4 |
newPath := append(path, k)
|
|
Packit Service |
509fd4 |
fields, err := marshalDeepObject(t[k], newPath)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return nil, errors.Wrap(err, "error traversing map")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
result = append(result, fields...)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
default:
|
|
Packit Service |
509fd4 |
// Now, for a concrete value, we will turn the path elements
|
|
Packit Service |
509fd4 |
// into a deepObject style set of subscripts. [a, b, c] turns into
|
|
Packit Service |
509fd4 |
// [a][b][c]
|
|
Packit Service |
509fd4 |
prefix := "[" + strings.Join(path, "][") + "]"
|
|
Packit Service |
509fd4 |
result = []string{
|
|
Packit Service |
509fd4 |
prefix + fmt.Sprintf("=%v", t),
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
return result, nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func MarshalDeepObject(i interface{}, paramName string) (string, error) {
|
|
Packit Service |
509fd4 |
// We're going to marshal to JSON and unmarshal into an interface{},
|
|
Packit Service |
509fd4 |
// which will use the json pkg to deal with all the field annotations. We
|
|
Packit Service |
509fd4 |
// can then walk the generic object structure to produce a deepObject. This
|
|
Packit Service |
509fd4 |
// isn't efficient and it would be more efficient to reflect on our own,
|
|
Packit Service |
509fd4 |
// but it's complicated, error-prone code.
|
|
Packit Service |
509fd4 |
buf, err := json.Marshal(i)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return "", errors.Wrap(err, "failed to marshal input to JSON")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
var i2 interface{}
|
|
Packit Service |
509fd4 |
err = json.Unmarshal(buf, &i2)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return "", errors.Wrap(err, "failed to unmarshal JSON")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
fields, err := marshalDeepObject(i2, nil)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return "", errors.Wrap(err, "error traversing JSON structure")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Prefix the param name to each subscripted field.
|
|
Packit Service |
509fd4 |
for i := range fields {
|
|
Packit Service |
509fd4 |
fields[i] = paramName + fields[i]
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
return strings.Join(fields, "&"), nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
type fieldOrValue struct {
|
|
Packit Service |
509fd4 |
fields map[string]fieldOrValue
|
|
Packit Service |
509fd4 |
value string
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func (f *fieldOrValue) appendPathValue(path []string, value string) {
|
|
Packit Service |
509fd4 |
fieldName := path[0]
|
|
Packit Service |
509fd4 |
if len(path) == 1 {
|
|
Packit Service |
509fd4 |
f.fields[fieldName] = fieldOrValue{value: value}
|
|
Packit Service |
509fd4 |
return
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
pv, found := f.fields[fieldName]
|
|
Packit Service |
509fd4 |
if !found {
|
|
Packit Service |
509fd4 |
pv = fieldOrValue{
|
|
Packit Service |
509fd4 |
fields: make(map[string]fieldOrValue),
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
f.fields[fieldName] = pv
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
pv.appendPathValue(path[1:], value)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func makeFieldOrValue(paths [][]string, values []string) fieldOrValue {
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
f := fieldOrValue{
|
|
Packit Service |
509fd4 |
fields: make(map[string]fieldOrValue),
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
for i := range paths {
|
|
Packit Service |
509fd4 |
path := paths[i]
|
|
Packit Service |
509fd4 |
value := values[i]
|
|
Packit Service |
509fd4 |
f.appendPathValue(path, value)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
return f
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) error {
|
|
Packit Service |
509fd4 |
// Params are all the query args, so we need those that look like
|
|
Packit Service |
509fd4 |
// "paramName["...
|
|
Packit Service |
509fd4 |
var fieldNames []string
|
|
Packit Service |
509fd4 |
var fieldValues []string
|
|
Packit Service |
509fd4 |
searchStr := paramName + "["
|
|
Packit Service |
509fd4 |
for pName, pValues := range params {
|
|
Packit Service |
509fd4 |
if strings.HasPrefix(pName, searchStr) {
|
|
Packit Service |
509fd4 |
// trim the parameter name from the full name.
|
|
Packit Service |
509fd4 |
pName = pName[len(paramName):]
|
|
Packit Service |
509fd4 |
fieldNames = append(fieldNames, pName)
|
|
Packit Service |
509fd4 |
if len(pValues) != 1 {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("%s has multiple values", pName)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
fieldValues = append(fieldValues, pValues[0])
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Now, for each field, reconstruct its subscript path and value
|
|
Packit Service |
509fd4 |
paths := make([][]string, len(fieldNames))
|
|
Packit Service |
509fd4 |
for i, path := range fieldNames {
|
|
Packit Service |
509fd4 |
path = strings.TrimLeft(path, "[")
|
|
Packit Service |
509fd4 |
path = strings.TrimRight(path, "]")
|
|
Packit Service |
509fd4 |
paths[i] = strings.Split(path, "][")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
fieldPaths := makeFieldOrValue(paths, fieldValues)
|
|
Packit Service |
509fd4 |
err := assignPathValues(dst, fieldPaths)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return errors.Wrap(err, "error assigning value to destination")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// This returns a field name, either using the variable name, or the json
|
|
Packit Service |
509fd4 |
// annotation if that exists.
|
|
Packit Service |
509fd4 |
func getFieldName(f reflect.StructField) string {
|
|
Packit Service |
509fd4 |
n := f.Name
|
|
Packit Service |
509fd4 |
tag, found := f.Tag.Lookup("json")
|
|
Packit Service |
509fd4 |
if found {
|
|
Packit Service |
509fd4 |
// If we have a json field, and the first part of it before the
|
|
Packit Service |
509fd4 |
// first comma is non-empty, that's our field name.
|
|
Packit Service |
509fd4 |
parts := strings.Split(tag, ",")
|
|
Packit Service |
509fd4 |
if parts[0] != "" {
|
|
Packit Service |
509fd4 |
n = parts[0]
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
return n
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// Create a map of field names that we'll see in the deepObject to reflect
|
|
Packit Service |
509fd4 |
// field indices on the given type.
|
|
Packit Service |
509fd4 |
func fieldIndicesByJsonTag(i interface{}) (map[string]int, error) {
|
|
Packit Service |
509fd4 |
t := reflect.TypeOf(i)
|
|
Packit Service |
509fd4 |
if t.Kind() != reflect.Struct {
|
|
Packit Service |
509fd4 |
return nil, errors.New("expected a struct as input")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
n := t.NumField()
|
|
Packit Service |
509fd4 |
fieldMap := make(map[string]int)
|
|
Packit Service |
509fd4 |
for i := 0; i < n; i++ {
|
|
Packit Service |
509fd4 |
field := t.Field(i)
|
|
Packit Service |
509fd4 |
fieldName := getFieldName(field)
|
|
Packit Service |
509fd4 |
fieldMap[fieldName] = i
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
return fieldMap, nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func assignPathValues(dst interface{}, pathValues fieldOrValue) error {
|
|
Packit Service |
509fd4 |
//t := reflect.TypeOf(dst)
|
|
Packit Service |
509fd4 |
v := reflect.ValueOf(dst)
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
iv := reflect.Indirect(v)
|
|
Packit Service |
509fd4 |
it := iv.Type()
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
switch it.Kind() {
|
|
Packit Service |
509fd4 |
case reflect.Slice:
|
|
Packit Service |
509fd4 |
sliceLength := len(pathValues.fields)
|
|
Packit Service |
509fd4 |
dstSlice := reflect.MakeSlice(it, sliceLength, sliceLength)
|
|
Packit Service |
509fd4 |
err := assignSlice(dstSlice, pathValues)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return errors.Wrap(err, "error assigning slice")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
iv.Set(dstSlice)
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
case reflect.Struct:
|
|
Packit Service |
509fd4 |
// Some special types we care about are structs. Handle them
|
|
Packit Service |
509fd4 |
// here.
|
|
Packit Service |
509fd4 |
if _, isDate := iv.Interface().(types.Date); isDate {
|
|
Packit Service |
509fd4 |
var date types.Date
|
|
Packit Service |
509fd4 |
var err error
|
|
Packit Service |
509fd4 |
date.Time, err = time.Parse(types.DateFormat, pathValues.value)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return errors.Wrap(err, "invalid date format")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
iv.Set(reflect.ValueOf(date))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
if _, isTime := iv.Interface().(time.Time); isTime {
|
|
Packit Service |
509fd4 |
var tm time.Time
|
|
Packit Service |
509fd4 |
var err error
|
|
Packit Service |
509fd4 |
tm, err = time.Parse(types.DateFormat, pathValues.value)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return errors.Wrap(err, "invalid date format")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
iv.Set(reflect.ValueOf(tm))
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
fieldMap, err := fieldIndicesByJsonTag(iv.Interface())
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return errors.Wrap(err, "failed enumerating fields")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
for _, fieldName := range sortedFieldOrValueKeys(pathValues.fields) {
|
|
Packit Service |
509fd4 |
fieldValue := pathValues.fields[fieldName]
|
|
Packit Service |
509fd4 |
fieldIndex, found := fieldMap[fieldName]
|
|
Packit Service |
509fd4 |
if !found {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("field [%s] is not present in destination object", fieldName)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
field := iv.Field(fieldIndex)
|
|
Packit Service |
509fd4 |
err = assignPathValues(field.Addr().Interface(), fieldValue)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return errors.Wrapf(err, "error assigning field [%s]", fieldName)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
case reflect.Ptr:
|
|
Packit Service |
509fd4 |
// If we have a pointer after redirecting, it means we're dealing with
|
|
Packit Service |
509fd4 |
// an optional field, such as *string, which was passed in as &foo. We
|
|
Packit Service |
509fd4 |
// will allocate it if necessary, and call ourselves with a different
|
|
Packit Service |
509fd4 |
// interface.
|
|
Packit Service |
509fd4 |
dstVal := reflect.New(it.Elem())
|
|
Packit Service |
509fd4 |
dstPtr := dstVal.Interface()
|
|
Packit Service |
509fd4 |
err := assignPathValues(dstPtr, pathValues)
|
|
Packit Service |
509fd4 |
iv.Set(dstVal)
|
|
Packit Service |
509fd4 |
return err
|
|
Packit Service |
509fd4 |
case reflect.Bool:
|
|
Packit Service |
509fd4 |
val, err := strconv.ParseBool(pathValues.value)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("expected a valid bool, got %s", pathValues.value)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
iv.SetBool(val)
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
case reflect.Float32:
|
|
Packit Service |
509fd4 |
val, err := strconv.ParseFloat(pathValues.value, 32)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
iv.SetFloat(val)
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
case reflect.Float64:
|
|
Packit Service |
509fd4 |
val, err := strconv.ParseFloat(pathValues.value, 64)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
iv.SetFloat(val)
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
Packit Service |
509fd4 |
val, err := strconv.ParseInt(pathValues.value, 10, 64)
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return fmt.Errorf("expected a valid int, got %s", pathValues.value)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
iv.SetInt(val)
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
case reflect.String:
|
|
Packit Service |
509fd4 |
iv.SetString(pathValues.value)
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
default:
|
|
Packit Service |
509fd4 |
return errors.New("unhandled type: " + it.String())
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func assignSlice(dst reflect.Value, pathValues fieldOrValue) error {
|
|
Packit Service |
509fd4 |
// Gather up the values
|
|
Packit Service |
509fd4 |
nValues := len(pathValues.fields)
|
|
Packit Service |
509fd4 |
values := make([]string, nValues)
|
|
Packit Service |
509fd4 |
// We expect to have consecutive array indices in the map
|
|
Packit Service |
509fd4 |
for i := 0; i < nValues; i++ {
|
|
Packit Service |
509fd4 |
indexStr := strconv.Itoa(i)
|
|
Packit Service |
509fd4 |
fv, found := pathValues.fields[indexStr]
|
|
Packit Service |
509fd4 |
if !found {
|
|
Packit Service |
509fd4 |
return errors.New("array deepObjects must have consecutive indices")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
values[i] = fv.value
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
// This could be cleaner, but we can call into assignPathValues to
|
|
Packit Service |
509fd4 |
// avoid recreating this logic.
|
|
Packit Service |
509fd4 |
for i:=0; i < nValues; i++ {
|
|
Packit Service |
509fd4 |
dstElem := dst.Index(i).Addr()
|
|
Packit Service |
509fd4 |
err := assignPathValues(dstElem.Interface(), fieldOrValue{value:values[i]})
|
|
Packit Service |
509fd4 |
if err != nil {
|
|
Packit Service |
509fd4 |
return errors.Wrap(err, "error binding array")
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
return nil
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
|
|
Packit Service |
509fd4 |
func sortedFieldOrValueKeys(m map[string]fieldOrValue) []string {
|
|
Packit Service |
509fd4 |
keys := make([]string, 0, len(m))
|
|
Packit Service |
509fd4 |
for k := range m {
|
|
Packit Service |
509fd4 |
keys = append(keys, k)
|
|
Packit Service |
509fd4 |
}
|
|
Packit Service |
509fd4 |
sort.Strings(keys)
|
|
Packit Service |
509fd4 |
return keys
|
|
Packit Service |
509fd4 |
}
|