mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 02:02: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>
		
			
				
	
	
		
			275 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			275 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Package form Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values.
 | |
| 
 | |
| 
 | |
| It has the following features:
 | |
| 
 | |
|     - Primitives types cause zero allocations.
 | |
|     - Supports map of almost all types.
 | |
|     - Supports both Numbered and Normal arrays eg. "Array[0]" and just "Array"
 | |
|       with multiple values passed.
 | |
|     - Slice honours the specified index. eg. if "Slice[2]" is the only Slice
 | |
|       value passed down, it will be put at index 2; if slice isn't big enough
 | |
|       it will be expanded.
 | |
|     - Array honours the specified index. eg. if "Array[2]" is the only Array
 | |
|       value passed down, it will be put at index 2; if array isn't big enough
 | |
|       a warning will be printed and value ignored.
 | |
|     - Only creates objects as necessary eg. if no `array` or `map` values are
 | |
|       passed down, the `array` and `map` are left as their default values in
 | |
|       the struct.
 | |
|     - Allows for Custom Type registration.
 | |
|     - Handles time.Time using RFC3339 time format by default,
 | |
|       but can easily be changed by registering a Custom Type, see below.
 | |
|     - Handles Encoding & Decoding of almost all Go types eg. can Decode into
 | |
|       struct, array, map, int... and Encode a struct, array, map, int...
 | |
| 
 | |
| Common Questions
 | |
| 
 | |
| Questions
 | |
| 
 | |
|     Does it support encoding.TextUnmarshaler?
 | |
|     No because TextUnmarshaler only accepts []byte but posted values can have
 | |
|     multiple values, so is not suitable.
 | |
| 
 | |
| 	Mixing array/slice with array[idx]/slice[idx], in which order are they parsed?
 | |
| 	array/slice then array[idx]/slice[idx]
 | |
| 
 | |
| Supported Types
 | |
| 
 | |
| out of the box supported types
 | |
| 
 | |
|     - string
 | |
|     - bool
 | |
|     - int, int8, int16, int32, int64
 | |
|     - uint, uint8, uint16, uint32, uint64
 | |
|     - float32, float64
 | |
|     - struct and anonymous struct
 | |
|     - interface{}
 | |
|     - time.Time` - by default using RFC3339
 | |
|     - a `pointer` to one of the above types
 | |
|     - slice, array
 | |
|     - map
 | |
|     - `custom types` can override any of the above types
 | |
|     - many other types may be supported inherently (eg. bson.ObjectId is
 | |
|       type ObjectId string, which will get populated by the string type
 | |
| 
 | |
|     **NOTE**: map, struct and slice nesting are ad infinitum.
 | |
| 
 | |
| Usage
 | |
| 
 | |
| symbols
 | |
| 
 | |
|     - Use symbol `.` for separating fields/structs. (eg. `structfield.field`)
 | |
|     - Use `[index or key]` for access to index of a slice/array or key for map.
 | |
|       (eg. `arrayfield[0]`, `mapfield[keyvalue]`)
 | |
| 
 | |
| html
 | |
| 
 | |
|     <form method="POST">
 | |
|         <input type="text" name="Name" value="joeybloggs"/>
 | |
|         <input type="text" name="Age" value="3"/>
 | |
|         <input type="text" name="Gender" value="Male"/>
 | |
|         <input type="text" name="Address[0].Name" value="26 Here Blvd."/>
 | |
|         <input type="text" name="Address[0].Phone" value="9(999)999-9999"/>
 | |
|         <input type="text" name="Address[1].Name" value="26 There Blvd."/>
 | |
|         <input type="text" name="Address[1].Phone" value="1(111)111-1111"/>
 | |
|         <input type="text" name="active" value="true"/>
 | |
|         <input type="text" name="MapExample[key]" value="value"/>
 | |
|         <input type="text" name="NestedMap[key][key]" value="value"/>
 | |
|         <input type="text" name="NestedArray[0][0]" value="value"/>
 | |
|         <input type="submit"/>
 | |
|     </form>
 | |
| 
 | |
| Example
 | |
| 
 | |
| example decoding the above HTML
 | |
| 
 | |
|     package main
 | |
| 
 | |
|     import (
 | |
|         "fmt"
 | |
|         "log"
 | |
|         "net/url"
 | |
| 
 | |
|         "github.com/go-playground/form/v4"
 | |
|     )
 | |
| 
 | |
|     // Address contains address information
 | |
|     type Address struct {
 | |
|         Name  string
 | |
|         Phone string
 | |
|     }
 | |
| 
 | |
|     // User contains user information
 | |
|     type User struct {
 | |
|         Name        string
 | |
|         Age         uint8
 | |
|         Gender      string
 | |
|         Address     []Address
 | |
|         Active      bool `form:"active"`
 | |
|         MapExample  map[string]string
 | |
|         NestedMap   map[string]map[string]string
 | |
|         NestedArray [][]string
 | |
|     }
 | |
| 
 | |
|     // use a single instance of Decoder, it caches struct info
 | |
|     var decoder *form.Decoder
 | |
| 
 | |
|     func main() {
 | |
|         decoder = form.NewDecoder()
 | |
| 
 | |
|         // this simulates the results of http.Request's ParseForm() function
 | |
|         values := parseForm()
 | |
| 
 | |
|         var user User
 | |
| 
 | |
|         // must pass a pointer
 | |
|         err := decoder.Decode(&user, values)
 | |
|         if err != nil {
 | |
|             log.Panic(err)
 | |
|         }
 | |
| 
 | |
|         fmt.Printf("%#v\n", user)
 | |
|     }
 | |
| 
 | |
|     // this simulates the results of http.Request's ParseForm() function
 | |
|     func parseForm() url.Values {
 | |
|         return url.Values{
 | |
|             "Name":                []string{"joeybloggs"},
 | |
|             "Age":                 []string{"3"},
 | |
|             "Gender":              []string{"Male"},
 | |
|             "Address[0].Name":     []string{"26 Here Blvd."},
 | |
|             "Address[0].Phone":    []string{"9(999)999-9999"},
 | |
|             "Address[1].Name":     []string{"26 There Blvd."},
 | |
|             "Address[1].Phone":    []string{"1(111)111-1111"},
 | |
|             "active":              []string{"true"},
 | |
|             "MapExample[key]":     []string{"value"},
 | |
|             "NestedMap[key][key]": []string{"value"},
 | |
|             "NestedArray[0][0]":   []string{"value"},
 | |
|         }
 | |
|     }
 | |
| 
 | |
| example encoding
 | |
| 
 | |
|     package main
 | |
| 
 | |
|     import (
 | |
|         "fmt"
 | |
|         "log"
 | |
| 
 | |
|         "github.com/go-playground/form/v4"
 | |
|     )
 | |
| 
 | |
|     // Address contains address information
 | |
|     type Address struct {
 | |
|         Name  string
 | |
|         Phone string
 | |
|     }
 | |
| 
 | |
|     // User contains user information
 | |
|     type User struct {
 | |
|         Name        string
 | |
|         Age         uint8
 | |
|         Gender      string
 | |
|         Address     []Address
 | |
|         Active      bool `form:"active"`
 | |
|         MapExample  map[string]string
 | |
|         NestedMap   map[string]map[string]string
 | |
|         NestedArray [][]string
 | |
|     }
 | |
| 
 | |
|     // use a single instance of Encoder, it caches struct info
 | |
|     var encoder *form.Encoder
 | |
| 
 | |
|     func main() {
 | |
|         encoder = form.NewEncoder()
 | |
| 
 | |
|         user := User{
 | |
|             Name:   "joeybloggs",
 | |
|             Age:    3,
 | |
|             Gender: "Male",
 | |
|             Address: []Address{
 | |
|                 {Name: "26 Here Blvd.", Phone: "9(999)999-9999"},
 | |
|                 {Name: "26 There Blvd.", Phone: "1(111)111-1111"},
 | |
|             },
 | |
|             Active:      true,
 | |
|             MapExample:  map[string]string{"key": "value"},
 | |
|             NestedMap:   map[string]map[string]string{"key": {"key": "value"}},
 | |
|             NestedArray: [][]string{{"value"}},
 | |
|         }
 | |
| 
 | |
|         // must pass a pointer
 | |
|         values, err := encoder.Encode(&user)
 | |
|         if err != nil {
 | |
|             log.Panic(err)
 | |
|         }
 | |
| 
 | |
|         fmt.Printf("%#v\n", values)
 | |
|     }
 | |
| 
 | |
| 
 | |
| Registering Custom Types
 | |
| 
 | |
| Decoder
 | |
| 
 | |
|     decoder.RegisterCustomTypeFunc(func(vals []string) (interface{}, error) {
 | |
|             return time.Parse("2006-01-02", vals[0])
 | |
|         }, time.Time{})
 | |
| 
 | |
|     ADDITIONAL: if a struct type is registered, the function will only be called
 | |
|     if a url.Value exists for the struct and not just the struct fields
 | |
|     eg. url.Values{"User":"Name%3Djoeybloggs"} will call the custom type function
 | |
|     with 'User' as the type, however url.Values{"User.Name":"joeybloggs"} will not.
 | |
| 
 | |
| Encoder
 | |
| 
 | |
|     encoder.RegisterCustomTypeFunc(func(x interface{}) ([]string, error) {
 | |
|             return []string{x.(time.Time).Format("2006-01-02")}, nil
 | |
|         }, time.Time{})
 | |
| 
 | |
| 
 | |
| Ignoring Fields
 | |
| 
 | |
| you can tell form to ignore fields using `-` in the tag
 | |
| 
 | |
|     type MyStruct struct {
 | |
|         Field string `form:"-"`
 | |
|     }
 | |
| 
 | |
| Omitempty
 | |
| 
 | |
| you can tell form to omit empty fields using `,omitempty` or `FieldName,omitempty` in the tag
 | |
| 
 | |
|     type MyStruct struct {
 | |
|         Field  string `form:",omitempty"`
 | |
|         Field2 string `form:"CustomFieldName,omitempty"`
 | |
|     }
 | |
| 
 | |
| 
 | |
| Notes
 | |
| 
 | |
| To maximize compatibility with other systems the Encoder attempts
 | |
| to avoid using array indexes in url.Values if at all possible.
 | |
| 
 | |
|     eg.
 | |
| 
 | |
|     // A struct field of
 | |
|     Field []string{"1", "2", "3"}
 | |
| 
 | |
|     // will be output a url.Value as
 | |
|     "Field": []string{"1", "2", "3"}
 | |
| 
 | |
|     and not
 | |
|     "Field[0]": []string{"1"}
 | |
|     "Field[1]": []string{"2"}
 | |
|     "Field[2]": []string{"3"}
 | |
| 
 | |
|     // however there are times where it is unavoidable, like with pointers
 | |
|     i := int(1)
 | |
|     Field []*string{nil, nil, &i}
 | |
| 
 | |
|     // to avoid index 1 and 2 must use index
 | |
|     "Field[2]": []string{"1"}
 | |
| 
 | |
| */
 | |
| package form
 |