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