mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 22:32:25 -05:00 
			
		
		
		
	* Add go-playground/form pkg * [feature] Add support for profile fields * Add field attributes test * Validate profile fields form * Add profile field validation tests * Add Field Attributes definition to swagger --------- Co-authored-by: tobi <31960611+tsmethurst@users.noreply.github.com>
		
			
				
	
	
		
			261 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package form
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/url"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| type encoder struct {
 | |
| 	e         *Encoder
 | |
| 	errs      EncodeErrors
 | |
| 	values    url.Values
 | |
| 	namespace []byte
 | |
| }
 | |
| 
 | |
| func (e *encoder) setError(namespace []byte, err error) {
 | |
| 	if e.errs == nil {
 | |
| 		e.errs = make(EncodeErrors)
 | |
| 	}
 | |
| 
 | |
| 	e.errs[string(namespace)] = err
 | |
| }
 | |
| 
 | |
| func (e *encoder) setVal(namespace []byte, idx int, vals ...string) {
 | |
| 
 | |
| 	arr, ok := e.values[string(namespace)]
 | |
| 	if ok {
 | |
| 		arr = append(arr, vals...)
 | |
| 	} else {
 | |
| 		arr = vals
 | |
| 	}
 | |
| 
 | |
| 	e.values[string(namespace)] = arr
 | |
| }
 | |
| 
 | |
| func (e *encoder) traverseStruct(v reflect.Value, namespace []byte, idx int) {
 | |
| 
 | |
| 	typ := v.Type()
 | |
| 	l := len(namespace)
 | |
| 	first := l == 0
 | |
| 
 | |
| 	// anonymous structs will still work for caching as the whole definition is stored
 | |
| 	// including tags
 | |
| 	s, ok := e.e.structCache.Get(typ)
 | |
| 	if !ok {
 | |
| 		s = e.e.structCache.parseStruct(e.e.mode, v, typ, e.e.tagName)
 | |
| 	}
 | |
| 
 | |
| 	for _, f := range s.fields {
 | |
| 		namespace = namespace[:l]
 | |
| 
 | |
| 		if f.isAnonymous && e.e.embedAnonymous {
 | |
| 			e.setFieldByType(v.Field(f.idx), namespace, idx, f.isOmitEmpty)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if first {
 | |
| 			namespace = append(namespace, f.name...)
 | |
| 		} else {
 | |
| 			namespace = append(namespace, e.e.namespacePrefix...)
 | |
| 			namespace = append(namespace, f.name...)
 | |
| 			namespace = append(namespace, e.e.namespaceSuffix...)
 | |
| 		}
 | |
| 
 | |
| 		e.setFieldByType(v.Field(f.idx), namespace, idx, f.isOmitEmpty)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (e *encoder) setFieldByType(current reflect.Value, namespace []byte, idx int, isOmitEmpty bool) {
 | |
| 
 | |
| 	if idx > -1 && current.Kind() == reflect.Ptr {
 | |
| 		namespace = append(namespace, '[')
 | |
| 		namespace = strconv.AppendInt(namespace, int64(idx), 10)
 | |
| 		namespace = append(namespace, ']')
 | |
| 		idx = -2
 | |
| 	}
 | |
| 
 | |
| 	if isOmitEmpty && !hasValue(current) {
 | |
| 		return
 | |
| 	}
 | |
| 	v, kind := ExtractType(current)
 | |
| 
 | |
| 	if e.e.customTypeFuncs != nil {
 | |
| 
 | |
| 		if cf, ok := e.e.customTypeFuncs[v.Type()]; ok {
 | |
| 
 | |
| 			arr, err := cf(v.Interface())
 | |
| 			if err != nil {
 | |
| 				e.setError(namespace, err)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			if idx > -1 {
 | |
| 				namespace = append(namespace, '[')
 | |
| 				namespace = strconv.AppendInt(namespace, int64(idx), 10)
 | |
| 				namespace = append(namespace, ']')
 | |
| 			}
 | |
| 
 | |
| 			e.setVal(namespace, idx, arr...)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch kind {
 | |
| 	case reflect.Ptr, reflect.Interface, reflect.Invalid:
 | |
| 		return
 | |
| 
 | |
| 	case reflect.String:
 | |
| 
 | |
| 		e.setVal(namespace, idx, v.String())
 | |
| 
 | |
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | |
| 
 | |
| 		e.setVal(namespace, idx, strconv.FormatUint(v.Uint(), 10))
 | |
| 
 | |
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | |
| 
 | |
| 		e.setVal(namespace, idx, strconv.FormatInt(v.Int(), 10))
 | |
| 
 | |
| 	case reflect.Float32:
 | |
| 
 | |
| 		e.setVal(namespace, idx, strconv.FormatFloat(v.Float(), 'f', -1, 32))
 | |
| 
 | |
| 	case reflect.Float64:
 | |
| 
 | |
| 		e.setVal(namespace, idx, strconv.FormatFloat(v.Float(), 'f', -1, 64))
 | |
| 
 | |
| 	case reflect.Bool:
 | |
| 
 | |
| 		e.setVal(namespace, idx, strconv.FormatBool(v.Bool()))
 | |
| 
 | |
| 	case reflect.Slice, reflect.Array:
 | |
| 
 | |
| 		if idx == -1 {
 | |
| 
 | |
| 			for i := 0; i < v.Len(); i++ {
 | |
| 				e.setFieldByType(v.Index(i), namespace, i, false)
 | |
| 			}
 | |
| 
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if idx > -1 {
 | |
| 			namespace = append(namespace, '[')
 | |
| 			namespace = strconv.AppendInt(namespace, int64(idx), 10)
 | |
| 			namespace = append(namespace, ']')
 | |
| 		}
 | |
| 
 | |
| 		namespace = append(namespace, '[')
 | |
| 		l := len(namespace)
 | |
| 
 | |
| 		for i := 0; i < v.Len(); i++ {
 | |
| 			namespace = namespace[:l]
 | |
| 			namespace = strconv.AppendInt(namespace, int64(i), 10)
 | |
| 			namespace = append(namespace, ']')
 | |
| 			e.setFieldByType(v.Index(i), namespace, -2, false)
 | |
| 		}
 | |
| 
 | |
| 	case reflect.Map:
 | |
| 
 | |
| 		if idx > -1 {
 | |
| 			namespace = append(namespace, '[')
 | |
| 			namespace = strconv.AppendInt(namespace, int64(idx), 10)
 | |
| 			namespace = append(namespace, ']')
 | |
| 		}
 | |
| 
 | |
| 		var valid bool
 | |
| 		var s string
 | |
| 		l := len(namespace)
 | |
| 
 | |
| 		for _, key := range v.MapKeys() {
 | |
| 
 | |
| 			namespace = namespace[:l]
 | |
| 
 | |
| 			if s, valid = e.getMapKey(key, namespace); !valid {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			namespace = append(namespace, '[')
 | |
| 			namespace = append(namespace, s...)
 | |
| 			namespace = append(namespace, ']')
 | |
| 
 | |
| 			e.setFieldByType(v.MapIndex(key), namespace, -2, false)
 | |
| 		}
 | |
| 
 | |
| 	case reflect.Struct:
 | |
| 
 | |
| 		// if we get here then no custom time function declared so use RFC3339 by default
 | |
| 		if v.Type() == timeType {
 | |
| 
 | |
| 			if idx > -1 {
 | |
| 				namespace = append(namespace, '[')
 | |
| 				namespace = strconv.AppendInt(namespace, int64(idx), 10)
 | |
| 				namespace = append(namespace, ']')
 | |
| 			}
 | |
| 
 | |
| 			e.setVal(namespace, idx, v.Interface().(time.Time).Format(time.RFC3339))
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if idx == -1 {
 | |
| 			e.traverseStruct(v, namespace, idx)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if idx > -1 {
 | |
| 			namespace = append(namespace, '[')
 | |
| 			namespace = strconv.AppendInt(namespace, int64(idx), 10)
 | |
| 			namespace = append(namespace, ']')
 | |
| 		}
 | |
| 
 | |
| 		e.traverseStruct(v, namespace, -2)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (e *encoder) getMapKey(key reflect.Value, namespace []byte) (string, bool) {
 | |
| 
 | |
| 	v, kind := ExtractType(key)
 | |
| 
 | |
| 	if e.e.customTypeFuncs != nil {
 | |
| 
 | |
| 		if cf, ok := e.e.customTypeFuncs[v.Type()]; ok {
 | |
| 			arr, err := cf(v.Interface())
 | |
| 			if err != nil {
 | |
| 				e.setError(namespace, err)
 | |
| 				return "", false
 | |
| 			}
 | |
| 
 | |
| 			return arr[0], true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch kind {
 | |
| 	case reflect.Interface, reflect.Ptr:
 | |
| 		return "", false
 | |
| 
 | |
| 	case reflect.String:
 | |
| 		return v.String(), true
 | |
| 
 | |
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | |
| 		return strconv.FormatUint(v.Uint(), 10), true
 | |
| 
 | |
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | |
| 		return strconv.FormatInt(v.Int(), 10), true
 | |
| 
 | |
| 	case reflect.Float32:
 | |
| 		return strconv.FormatFloat(v.Float(), 'f', -1, 32), true
 | |
| 
 | |
| 	case reflect.Float64:
 | |
| 		return strconv.FormatFloat(v.Float(), 'f', -1, 64), true
 | |
| 
 | |
| 	case reflect.Bool:
 | |
| 		return strconv.FormatBool(v.Bool()), true
 | |
| 
 | |
| 	default:
 | |
| 		e.setError(namespace, fmt.Errorf("Unsupported Map Key '%v' Namespace '%s'", v.String(), namespace))
 | |
| 		return "", false
 | |
| 	}
 | |
| }
 |