mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 04:42:25 -06: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
 |