Blame vendor/github.com/deepmap/oapi-codegen/pkg/runtime/deepobject.go

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
}