mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Add support for profile fields (#1483)
* 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>
This commit is contained in:
28
vendor/github.com/go-playground/form/v4/.gitignore
generated
vendored
Normal file
28
vendor/github.com/go-playground/form/v4/.gitignore
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
old.txt
|
||||
new.txt
|
||||
|
||||
/.idea
|
26
vendor/github.com/go-playground/form/v4/.travis.yml
generated
vendored
Normal file
26
vendor/github.com/go-playground/form/v4/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.13.4
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients: dean.karn@gmail.com
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
before_install:
|
||||
- go install github.com/mattn/goveralls
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
script:
|
||||
- go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./...
|
||||
|
||||
after_success: |
|
||||
goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
|
21
vendor/github.com/go-playground/form/v4/LICENSE
generated
vendored
Normal file
21
vendor/github.com/go-playground/form/v4/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Go Playground
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
333
vendor/github.com/go-playground/form/v4/README.md
generated
vendored
Normal file
333
vendor/github.com/go-playground/form/v4/README.md
generated
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
Package form
|
||||
============
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/form/master/logo.jpg">
|
||||
[](https://github.com/go-playground/form/actions/workflows/workflow.yml)
|
||||
[](https://coveralls.io/github/go-playground/form?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/go-playground/form)
|
||||
[](https://godoc.org/github.com/go-playground/form)
|
||||

|
||||
[](https://gitter.im/go-playground/form?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
Package form Decodes url.Values into Go value(s) and Encodes Go value(s) into url.Values.
|
||||
|
||||
It has the following features:
|
||||
|
||||
- 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
|
||||
|
||||
- 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 )
|
||||
----------
|
||||
|
||||
* `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
|
||||
|
||||
**NOTE**: `map`, `struct` and `slice` nesting are ad infinitum.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use go get.
|
||||
|
||||
go get github.com/go-playground/form
|
||||
|
||||
Then import the form package into your own code.
|
||||
|
||||
import "github.com/go-playground/form/v4"
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
- 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>
|
||||
```
|
||||
|
||||
Examples
|
||||
-------
|
||||
|
||||
Decoding
|
||||
```go
|
||||
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"},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Encoding
|
||||
```go
|
||||
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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
type MyStruct struct {
|
||||
Field string `form:"-"`
|
||||
}
|
||||
```
|
||||
|
||||
Omitempty
|
||||
--------------
|
||||
you can tell form to omit empty fields using `,omitempty` or `FieldName,omitempty` in the tag
|
||||
```go
|
||||
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.
|
||||
```go
|
||||
// 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"}
|
||||
```
|
||||
|
||||
Benchmarks
|
||||
------
|
||||
###### Run on MacBook Pro (15-inch, 2017) using go version go1.10.1 darwin/amd64
|
||||
|
||||
NOTE: the 1 allocation and B/op in the first 4 decodes is actually the struct allocating when passing it in, so primitives are actually zero allocation.
|
||||
|
||||
```go
|
||||
go test -run=NONE -bench=. -benchmem=true
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
pkg: github.com/go-playground/form/benchmarks
|
||||
|
||||
BenchmarkSimpleUserDecodeStruct-8 5000000 236 ns/op 64 B/op 1 allocs/op
|
||||
BenchmarkSimpleUserDecodeStructParallel-8 20000000 82.1 ns/op 64 B/op 1 allocs/op
|
||||
BenchmarkSimpleUserEncodeStruct-8 2000000 627 ns/op 485 B/op 10 allocs/op
|
||||
BenchmarkSimpleUserEncodeStructParallel-8 10000000 223 ns/op 485 B/op 10 allocs/op
|
||||
BenchmarkPrimitivesDecodeStructAllPrimitivesTypes-8 2000000 724 ns/op 96 B/op 1 allocs/op
|
||||
BenchmarkPrimitivesDecodeStructAllPrimitivesTypesParallel-8 10000000 246 ns/op 96 B/op 1 allocs/op
|
||||
BenchmarkPrimitivesEncodeStructAllPrimitivesTypes-8 500000 3187 ns/op 2977 B/op 36 allocs/op
|
||||
BenchmarkPrimitivesEncodeStructAllPrimitivesTypesParallel-8 1000000 1106 ns/op 2977 B/op 36 allocs/op
|
||||
BenchmarkComplexArrayDecodeStructAllTypes-8 100000 13748 ns/op 2248 B/op 121 allocs/op
|
||||
BenchmarkComplexArrayDecodeStructAllTypesParallel-8 500000 4313 ns/op 2249 B/op 121 allocs/op
|
||||
BenchmarkComplexArrayEncodeStructAllTypes-8 200000 10758 ns/op 7113 B/op 104 allocs/op
|
||||
BenchmarkComplexArrayEncodeStructAllTypesParallel-8 500000 3532 ns/op 7113 B/op 104 allocs/op
|
||||
BenchmarkComplexMapDecodeStructAllTypes-8 100000 17644 ns/op 5305 B/op 130 allocs/op
|
||||
BenchmarkComplexMapDecodeStructAllTypesParallel-8 300000 5470 ns/op 5308 B/op 130 allocs/op
|
||||
BenchmarkComplexMapEncodeStructAllTypes-8 200000 11155 ns/op 6971 B/op 129 allocs/op
|
||||
BenchmarkComplexMapEncodeStructAllTypesParallel-8 500000 3768 ns/op 6971 B/op 129 allocs/op
|
||||
BenchmarkDecodeNestedStruct-8 500000 2462 ns/op 384 B/op 14 allocs/op
|
||||
BenchmarkDecodeNestedStructParallel-8 2000000 814 ns/op 384 B/op 14 allocs/op
|
||||
BenchmarkEncodeNestedStruct-8 1000000 1483 ns/op 693 B/op 16 allocs/op
|
||||
BenchmarkEncodeNestedStructParallel-8 3000000 525 ns/op 693 B/op 16 allocs/op
|
||||
```
|
||||
|
||||
Competitor benchmarks can be found [here](https://github.com/go-playground/form/blob/master/benchmarks/benchmarks.md)
|
||||
|
||||
Complimentary Software
|
||||
----------------------
|
||||
|
||||
Here is a list of software that compliments using this library post decoding.
|
||||
|
||||
* [Validator](https://github.com/go-playground/validator) - Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving.
|
||||
* [mold](https://github.com/go-playground/mold) - Is a general library to help modify or set data within data structures and other objects.
|
||||
|
||||
Package Versioning
|
||||
----------
|
||||
I'm jumping on the vendoring bandwagon, you should vendor this package as I will not
|
||||
be creating different version with gopkg.in like allot of my other libraries.
|
||||
|
||||
Why? because my time is spread pretty thin maintaining all of the libraries I have + LIFE,
|
||||
it is so freeing not to worry about it and will help me keep pouring out bigger and better
|
||||
things for you the community.
|
||||
|
||||
License
|
||||
------
|
||||
Distributed under MIT License, please see license file in code for more details.
|
133
vendor/github.com/go-playground/form/v4/cache.go
generated
vendored
Normal file
133
vendor/github.com/go-playground/form/v4/cache.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type cacheFields []cachedField
|
||||
|
||||
func (s cacheFields) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s cacheFields) Less(i, j int) bool {
|
||||
return !s[i].isAnonymous
|
||||
}
|
||||
|
||||
func (s cacheFields) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
type cachedField struct {
|
||||
idx int
|
||||
name string
|
||||
isAnonymous bool
|
||||
isOmitEmpty bool
|
||||
}
|
||||
|
||||
type cachedStruct struct {
|
||||
fields cacheFields
|
||||
}
|
||||
|
||||
type structCacheMap struct {
|
||||
m atomic.Value // map[reflect.Type]*cachedStruct
|
||||
lock sync.Mutex
|
||||
tagFn TagNameFunc
|
||||
}
|
||||
|
||||
// TagNameFunc allows for adding of a custom tag name parser
|
||||
type TagNameFunc func(field reflect.StructField) string
|
||||
|
||||
func newStructCacheMap() *structCacheMap {
|
||||
|
||||
sc := new(structCacheMap)
|
||||
sc.m.Store(make(map[reflect.Type]*cachedStruct))
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
func (s *structCacheMap) Get(key reflect.Type) (value *cachedStruct, ok bool) {
|
||||
value, ok = s.m.Load().(map[reflect.Type]*cachedStruct)[key]
|
||||
return
|
||||
}
|
||||
|
||||
func (s *structCacheMap) Set(key reflect.Type, value *cachedStruct) {
|
||||
|
||||
m := s.m.Load().(map[reflect.Type]*cachedStruct)
|
||||
|
||||
nm := make(map[reflect.Type]*cachedStruct, len(m)+1)
|
||||
for k, v := range m {
|
||||
nm[k] = v
|
||||
}
|
||||
nm[key] = value
|
||||
s.m.Store(nm)
|
||||
}
|
||||
|
||||
func (s *structCacheMap) parseStruct(mode Mode, current reflect.Value, key reflect.Type, tagName string) *cachedStruct {
|
||||
|
||||
s.lock.Lock()
|
||||
|
||||
// could have been multiple trying to access, but once first is done this ensures struct
|
||||
// isn't parsed again.
|
||||
cs, ok := s.Get(key)
|
||||
if ok {
|
||||
s.lock.Unlock()
|
||||
return cs
|
||||
}
|
||||
|
||||
typ := current.Type()
|
||||
cs = &cachedStruct{fields: make([]cachedField, 0, 4)} // init 4, betting most structs decoding into have at aleast 4 fields.
|
||||
|
||||
numFields := current.NumField()
|
||||
|
||||
var fld reflect.StructField
|
||||
var name string
|
||||
var idx int
|
||||
var isOmitEmpty bool
|
||||
|
||||
for i := 0; i < numFields; i++ {
|
||||
isOmitEmpty = false
|
||||
fld = typ.Field(i)
|
||||
|
||||
if fld.PkgPath != blank && !fld.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
if s.tagFn != nil {
|
||||
name = s.tagFn(fld)
|
||||
} else {
|
||||
name = fld.Tag.Get(tagName)
|
||||
}
|
||||
|
||||
if name == ignore {
|
||||
continue
|
||||
}
|
||||
|
||||
if mode == ModeExplicit && len(name) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// check for omitempty
|
||||
if idx = strings.LastIndexByte(name, ','); idx != -1 {
|
||||
isOmitEmpty = name[idx+1:] == "omitempty"
|
||||
name = name[:idx]
|
||||
}
|
||||
|
||||
if len(name) == 0 {
|
||||
name = fld.Name
|
||||
}
|
||||
|
||||
cs.fields = append(cs.fields, cachedField{idx: i, name: name, isAnonymous: fld.Anonymous, isOmitEmpty: isOmitEmpty})
|
||||
}
|
||||
|
||||
sort.Sort(cs.fields)
|
||||
s.Set(typ, cs)
|
||||
|
||||
s.lock.Unlock()
|
||||
|
||||
return cs
|
||||
}
|
748
vendor/github.com/go-playground/form/v4/decoder.go
generated
vendored
Normal file
748
vendor/github.com/go-playground/form/v4/decoder.go
generated
vendored
Normal file
@ -0,0 +1,748 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
errArraySize = "Array size of '%d' is larger than the maximum currently set on the decoder of '%d'. To increase this limit please see, SetMaxArraySize(size uint)"
|
||||
errMissingStartBracket = "Invalid formatting for key '%s' missing '[' bracket"
|
||||
errMissingEndBracket = "Invalid formatting for key '%s' missing ']' bracket"
|
||||
)
|
||||
|
||||
type decoder struct {
|
||||
d *Decoder
|
||||
errs DecodeErrors
|
||||
dm dataMap
|
||||
values url.Values
|
||||
maxKeyLen int
|
||||
namespace []byte
|
||||
}
|
||||
|
||||
func (d *decoder) setError(namespace []byte, err error) {
|
||||
if d.errs == nil {
|
||||
d.errs = make(DecodeErrors)
|
||||
}
|
||||
d.errs[string(namespace)] = err
|
||||
}
|
||||
|
||||
func (d *decoder) findAlias(ns string) *recursiveData {
|
||||
for i := 0; i < len(d.dm); i++ {
|
||||
if d.dm[i].alias == ns {
|
||||
return d.dm[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *decoder) parseMapData() {
|
||||
// already parsed
|
||||
if len(d.dm) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
d.maxKeyLen = 0
|
||||
d.dm = d.dm[0:0]
|
||||
|
||||
var i int
|
||||
var idx int
|
||||
var l int
|
||||
var insideBracket bool
|
||||
var rd *recursiveData
|
||||
var isNum bool
|
||||
|
||||
for k := range d.values {
|
||||
|
||||
if len(k) > d.maxKeyLen {
|
||||
d.maxKeyLen = len(k)
|
||||
}
|
||||
|
||||
for i = 0; i < len(k); i++ {
|
||||
|
||||
switch k[i] {
|
||||
case '[':
|
||||
idx = i
|
||||
insideBracket = true
|
||||
isNum = true
|
||||
case ']':
|
||||
|
||||
if !insideBracket {
|
||||
log.Panicf(errMissingStartBracket, k)
|
||||
}
|
||||
|
||||
if rd = d.findAlias(k[:idx]); rd == nil {
|
||||
|
||||
l = len(d.dm) + 1
|
||||
|
||||
if l > cap(d.dm) {
|
||||
dm := make(dataMap, l)
|
||||
copy(dm, d.dm)
|
||||
rd = new(recursiveData)
|
||||
dm[len(d.dm)] = rd
|
||||
d.dm = dm
|
||||
} else {
|
||||
l = len(d.dm)
|
||||
d.dm = d.dm[:l+1]
|
||||
rd = d.dm[l]
|
||||
rd.sliceLen = 0
|
||||
rd.keys = rd.keys[0:0]
|
||||
}
|
||||
|
||||
rd.alias = k[:idx]
|
||||
}
|
||||
|
||||
// is map + key
|
||||
ke := key{
|
||||
ivalue: -1,
|
||||
value: k[idx+1 : i],
|
||||
searchValue: k[idx : i+1],
|
||||
}
|
||||
|
||||
// is key is number, most likely array key, keep track of just in case an array/slice.
|
||||
if isNum {
|
||||
|
||||
// no need to check for error, it will always pass
|
||||
// as we have done the checking to ensure
|
||||
// the value is a number ahead of time.
|
||||
ke.ivalue, _ = strconv.Atoi(ke.value)
|
||||
|
||||
if ke.ivalue > rd.sliceLen {
|
||||
rd.sliceLen = ke.ivalue
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
rd.keys = append(rd.keys, ke)
|
||||
|
||||
insideBracket = false
|
||||
default:
|
||||
// checking if not a number, 0-9 is 48-57 in byte, see for yourself fmt.Println('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
|
||||
if insideBracket && (k[i] > 57 || k[i] < 48) {
|
||||
isNum = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if still inside bracket, that means no ending bracket was ever specified
|
||||
if insideBracket {
|
||||
log.Panicf(errMissingEndBracket, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) traverseStruct(v reflect.Value, typ reflect.Type, namespace []byte) (set bool) {
|
||||
|
||||
l := len(namespace)
|
||||
first := l == 0
|
||||
|
||||
// anonymous structs will still work for caching as the whole definition is stored
|
||||
// including tags
|
||||
s, ok := d.d.structCache.Get(typ)
|
||||
if !ok {
|
||||
s = d.d.structCache.parseStruct(d.d.mode, v, typ, d.d.tagName)
|
||||
}
|
||||
|
||||
for _, f := range s.fields {
|
||||
namespace = namespace[:l]
|
||||
|
||||
if f.isAnonymous {
|
||||
if d.setFieldByType(v.Field(f.idx), namespace, 0) {
|
||||
set = true
|
||||
}
|
||||
}
|
||||
|
||||
if first {
|
||||
namespace = append(namespace, f.name...)
|
||||
} else {
|
||||
namespace = append(namespace, d.d.namespacePrefix...)
|
||||
namespace = append(namespace, f.name...)
|
||||
namespace = append(namespace, d.d.namespaceSuffix...)
|
||||
}
|
||||
|
||||
if d.setFieldByType(v.Field(f.idx), namespace, 0) {
|
||||
set = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decoder) setFieldByType(current reflect.Value, namespace []byte, idx int) (set bool) {
|
||||
|
||||
var err error
|
||||
v, kind := ExtractType(current)
|
||||
|
||||
arr, ok := d.values[string(namespace)]
|
||||
|
||||
if d.d.customTypeFuncs != nil {
|
||||
|
||||
if ok {
|
||||
if cf, ok := d.d.customTypeFuncs[v.Type()]; ok {
|
||||
val, err := cf(arr[idx:])
|
||||
if err != nil {
|
||||
d.setError(namespace, err)
|
||||
return
|
||||
}
|
||||
|
||||
v.Set(reflect.ValueOf(val))
|
||||
set = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Interface:
|
||||
if !ok || idx == len(arr) {
|
||||
return
|
||||
}
|
||||
v.Set(reflect.ValueOf(arr[idx]))
|
||||
set = true
|
||||
|
||||
case reflect.Ptr:
|
||||
newVal := reflect.New(v.Type().Elem())
|
||||
if set = d.setFieldByType(newVal.Elem(), namespace, idx); set {
|
||||
v.Set(newVal)
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
if !ok || idx == len(arr) {
|
||||
return
|
||||
}
|
||||
v.SetString(arr[idx])
|
||||
set = true
|
||||
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var u64 uint64
|
||||
if u64, err = strconv.ParseUint(arr[idx], 10, 64); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetUint(u64)
|
||||
set = true
|
||||
|
||||
case reflect.Uint8:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var u64 uint64
|
||||
if u64, err = strconv.ParseUint(arr[idx], 10, 8); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetUint(u64)
|
||||
set = true
|
||||
|
||||
case reflect.Uint16:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var u64 uint64
|
||||
if u64, err = strconv.ParseUint(arr[idx], 10, 16); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetUint(u64)
|
||||
set = true
|
||||
|
||||
case reflect.Uint32:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var u64 uint64
|
||||
if u64, err = strconv.ParseUint(arr[idx], 10, 32); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetUint(u64)
|
||||
set = true
|
||||
|
||||
case reflect.Int, reflect.Int64:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var i64 int64
|
||||
if i64, err = strconv.ParseInt(arr[idx], 10, 64); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetInt(i64)
|
||||
set = true
|
||||
|
||||
case reflect.Int8:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var i64 int64
|
||||
if i64, err = strconv.ParseInt(arr[idx], 10, 8); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetInt(i64)
|
||||
set = true
|
||||
|
||||
case reflect.Int16:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var i64 int64
|
||||
if i64, err = strconv.ParseInt(arr[idx], 10, 16); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetInt(i64)
|
||||
set = true
|
||||
|
||||
case reflect.Int32:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var i64 int64
|
||||
if i64, err = strconv.ParseInt(arr[idx], 10, 32); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetInt(i64)
|
||||
set = true
|
||||
|
||||
case reflect.Float32:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var f float64
|
||||
if f, err = strconv.ParseFloat(arr[idx], 32); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetFloat(f)
|
||||
set = true
|
||||
|
||||
case reflect.Float64:
|
||||
if !ok || idx == len(arr) || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
var f float64
|
||||
if f, err = strconv.ParseFloat(arr[idx], 64); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetFloat(f)
|
||||
set = true
|
||||
|
||||
case reflect.Bool:
|
||||
if !ok || idx == len(arr) {
|
||||
return
|
||||
}
|
||||
var b bool
|
||||
if b, err = parseBool(arr[idx]); err != nil {
|
||||
d.setError(namespace, fmt.Errorf("Invalid Boolean Value '%s' Type '%v' Namespace '%s'", arr[idx], v.Type(), string(namespace)))
|
||||
return
|
||||
}
|
||||
v.SetBool(b)
|
||||
set = true
|
||||
|
||||
case reflect.Slice:
|
||||
d.parseMapData()
|
||||
// slice elements could be mixed eg. number and non-numbers Value[0]=[]string{"10"} and Value=[]string{"10","20"}
|
||||
|
||||
if ok && len(arr) > 0 {
|
||||
var varr reflect.Value
|
||||
|
||||
var ol int
|
||||
l := len(arr)
|
||||
|
||||
if v.IsNil() {
|
||||
varr = reflect.MakeSlice(v.Type(), len(arr), len(arr))
|
||||
} else {
|
||||
|
||||
ol = v.Len()
|
||||
l += ol
|
||||
|
||||
if v.Cap() <= l {
|
||||
varr = reflect.MakeSlice(v.Type(), l, l)
|
||||
} else {
|
||||
// preserve predefined capacity, possibly for reuse after decoding
|
||||
varr = reflect.MakeSlice(v.Type(), l, v.Cap())
|
||||
}
|
||||
reflect.Copy(varr, v)
|
||||
}
|
||||
|
||||
for i := ol; i < l; i++ {
|
||||
newVal := reflect.New(v.Type().Elem()).Elem()
|
||||
|
||||
if d.setFieldByType(newVal, namespace, i-ol) {
|
||||
set = true
|
||||
varr.Index(i).Set(newVal)
|
||||
}
|
||||
}
|
||||
|
||||
v.Set(varr)
|
||||
}
|
||||
|
||||
// maybe it's an numbered array i.e. Phone[0].Number
|
||||
if rd := d.findAlias(string(namespace)); rd != nil {
|
||||
|
||||
var varr reflect.Value
|
||||
var kv key
|
||||
|
||||
sl := rd.sliceLen + 1
|
||||
|
||||
// checking below for maxArraySize, but if array exists and already
|
||||
// has sufficient capacity allocated then we do not check as the code
|
||||
// obviously allows a capacity greater than the maxArraySize.
|
||||
|
||||
if v.IsNil() {
|
||||
|
||||
if sl > d.d.maxArraySize {
|
||||
d.setError(namespace, fmt.Errorf(errArraySize, sl, d.d.maxArraySize))
|
||||
return
|
||||
}
|
||||
|
||||
varr = reflect.MakeSlice(v.Type(), sl, sl)
|
||||
|
||||
} else if v.Len() < sl {
|
||||
|
||||
if v.Cap() <= sl {
|
||||
|
||||
if sl > d.d.maxArraySize {
|
||||
d.setError(namespace, fmt.Errorf(errArraySize, sl, d.d.maxArraySize))
|
||||
return
|
||||
}
|
||||
|
||||
varr = reflect.MakeSlice(v.Type(), sl, sl)
|
||||
} else {
|
||||
varr = reflect.MakeSlice(v.Type(), sl, v.Cap())
|
||||
}
|
||||
|
||||
reflect.Copy(varr, v)
|
||||
|
||||
} else {
|
||||
varr = v
|
||||
}
|
||||
|
||||
for i := 0; i < len(rd.keys); i++ {
|
||||
|
||||
kv = rd.keys[i]
|
||||
newVal := reflect.New(varr.Type().Elem()).Elem()
|
||||
|
||||
if kv.ivalue == -1 {
|
||||
d.setError(namespace, fmt.Errorf("invalid slice index '%s'", kv.value))
|
||||
continue
|
||||
}
|
||||
|
||||
if d.setFieldByType(newVal, append(namespace, kv.searchValue...), 0) {
|
||||
set = true
|
||||
varr.Index(kv.ivalue).Set(newVal)
|
||||
}
|
||||
}
|
||||
|
||||
if !set {
|
||||
return
|
||||
}
|
||||
|
||||
v.Set(varr)
|
||||
}
|
||||
|
||||
case reflect.Array:
|
||||
d.parseMapData()
|
||||
|
||||
// array elements could be mixed eg. number and non-numbers Value[0]=[]string{"10"} and Value=[]string{"10","20"}
|
||||
|
||||
if ok && len(arr) > 0 {
|
||||
var varr reflect.Value
|
||||
l := len(arr)
|
||||
overCapacity := v.Len() < l
|
||||
if overCapacity {
|
||||
// more values than array capacity, ignore values over capacity as it's possible some would just want
|
||||
// to grab the first x number of elements; in the future strict mode logic should return an error
|
||||
fmt.Println("warning number of post form array values is larger than array capacity, ignoring overflow values")
|
||||
}
|
||||
varr = reflect.Indirect(reflect.New(reflect.ArrayOf(v.Len(), v.Type().Elem())))
|
||||
reflect.Copy(varr, v)
|
||||
|
||||
if v.Len() < len(arr) {
|
||||
l = v.Len()
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
newVal := reflect.New(v.Type().Elem()).Elem()
|
||||
|
||||
if d.setFieldByType(newVal, namespace, i) {
|
||||
set = true
|
||||
varr.Index(i).Set(newVal)
|
||||
}
|
||||
}
|
||||
v.Set(varr)
|
||||
}
|
||||
|
||||
// maybe it's an numbered array i.e. Phone[0].Number
|
||||
if rd := d.findAlias(string(namespace)); rd != nil {
|
||||
var varr reflect.Value
|
||||
var kv key
|
||||
|
||||
overCapacity := rd.sliceLen >= v.Len()
|
||||
if overCapacity {
|
||||
// more values than array capacity, ignore values over capacity as it's possible some would just want
|
||||
// to grab the first x number of elements; in the future strict mode logic should return an error
|
||||
fmt.Println("warning number of post form array values is larger than array capacity, ignoring overflow values")
|
||||
}
|
||||
varr = reflect.Indirect(reflect.New(reflect.ArrayOf(v.Len(), v.Type().Elem())))
|
||||
reflect.Copy(varr, v)
|
||||
|
||||
for i := 0; i < len(rd.keys); i++ {
|
||||
kv = rd.keys[i]
|
||||
if kv.ivalue >= v.Len() {
|
||||
continue
|
||||
}
|
||||
newVal := reflect.New(varr.Type().Elem()).Elem()
|
||||
|
||||
if kv.ivalue == -1 {
|
||||
d.setError(namespace, fmt.Errorf("invalid array index '%s'", kv.value))
|
||||
continue
|
||||
}
|
||||
|
||||
if d.setFieldByType(newVal, append(namespace, kv.searchValue...), 0) {
|
||||
set = true
|
||||
varr.Index(kv.ivalue).Set(newVal)
|
||||
}
|
||||
}
|
||||
|
||||
if !set {
|
||||
return
|
||||
}
|
||||
v.Set(varr)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
var rd *recursiveData
|
||||
|
||||
d.parseMapData()
|
||||
|
||||
// no natural map support so skip directly to dm lookup
|
||||
if rd = d.findAlias(string(namespace)); rd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var existing bool
|
||||
var kv key
|
||||
var mp reflect.Value
|
||||
var mk reflect.Value
|
||||
|
||||
typ := v.Type()
|
||||
|
||||
if v.IsNil() {
|
||||
mp = reflect.MakeMap(typ)
|
||||
} else {
|
||||
existing = true
|
||||
mp = v
|
||||
}
|
||||
|
||||
for i := 0; i < len(rd.keys); i++ {
|
||||
newVal := reflect.New(typ.Elem()).Elem()
|
||||
mk = reflect.New(typ.Key()).Elem()
|
||||
kv = rd.keys[i]
|
||||
|
||||
if err := d.getMapKey(kv.value, mk, namespace); err != nil {
|
||||
d.setError(namespace, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if d.setFieldByType(newVal, append(namespace, kv.searchValue...), 0) {
|
||||
set = true
|
||||
mp.SetMapIndex(mk, newVal)
|
||||
}
|
||||
}
|
||||
|
||||
if !set || existing {
|
||||
return
|
||||
}
|
||||
|
||||
v.Set(mp)
|
||||
|
||||
case reflect.Struct:
|
||||
typ := v.Type()
|
||||
|
||||
// if we get here then no custom time function declared so use RFC3339 by default
|
||||
if typ == timeType {
|
||||
|
||||
if !ok || len(arr[idx]) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC3339, arr[idx])
|
||||
if err != nil {
|
||||
d.setError(namespace, err)
|
||||
}
|
||||
|
||||
v.Set(reflect.ValueOf(t))
|
||||
set = true
|
||||
return
|
||||
}
|
||||
|
||||
d.parseMapData()
|
||||
|
||||
// we must be recursing infinitly...but that's ok we caught it on the very first overun.
|
||||
if len(namespace) > d.maxKeyLen {
|
||||
return
|
||||
}
|
||||
|
||||
set = d.traverseStruct(v, typ, namespace)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decoder) getMapKey(key string, current reflect.Value, namespace []byte) (err error) {
|
||||
|
||||
v, kind := ExtractType(current)
|
||||
|
||||
if d.d.customTypeFuncs != nil {
|
||||
if cf, ok := d.d.customTypeFuncs[v.Type()]; ok {
|
||||
|
||||
val, er := cf([]string{key})
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
|
||||
v.Set(reflect.ValueOf(val))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Interface:
|
||||
// If interface would have been set on the struct before decoding,
|
||||
// say to a struct value we would not get here but kind would be struct.
|
||||
v.Set(reflect.ValueOf(key))
|
||||
return
|
||||
case reflect.Ptr:
|
||||
newVal := reflect.New(v.Type().Elem())
|
||||
if err = d.getMapKey(key, newVal.Elem(), namespace); err == nil {
|
||||
v.Set(newVal)
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
v.SetString(key)
|
||||
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
|
||||
u64, e := strconv.ParseUint(key, 10, 64)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetUint(u64)
|
||||
|
||||
case reflect.Uint8:
|
||||
|
||||
u64, e := strconv.ParseUint(key, 10, 8)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetUint(u64)
|
||||
|
||||
case reflect.Uint16:
|
||||
|
||||
u64, e := strconv.ParseUint(key, 10, 16)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetUint(u64)
|
||||
|
||||
case reflect.Uint32:
|
||||
|
||||
u64, e := strconv.ParseUint(key, 10, 32)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Unsigned Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetUint(u64)
|
||||
|
||||
case reflect.Int, reflect.Int64:
|
||||
|
||||
i64, e := strconv.ParseInt(key, 10, 64)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetInt(i64)
|
||||
|
||||
case reflect.Int8:
|
||||
|
||||
i64, e := strconv.ParseInt(key, 10, 8)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetInt(i64)
|
||||
|
||||
case reflect.Int16:
|
||||
|
||||
i64, e := strconv.ParseInt(key, 10, 16)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetInt(i64)
|
||||
|
||||
case reflect.Int32:
|
||||
|
||||
i64, e := strconv.ParseInt(key, 10, 32)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Integer Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetInt(i64)
|
||||
|
||||
case reflect.Float32:
|
||||
|
||||
f, e := strconv.ParseFloat(key, 32)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetFloat(f)
|
||||
|
||||
case reflect.Float64:
|
||||
|
||||
f, e := strconv.ParseFloat(key, 64)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Float Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetFloat(f)
|
||||
|
||||
case reflect.Bool:
|
||||
|
||||
b, e := parseBool(key)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("Invalid Boolean Value '%s' Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
return
|
||||
}
|
||||
|
||||
v.SetBool(b)
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported Map Key '%s', Type '%v' Namespace '%s'", key, v.Type(), string(namespace))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
275
vendor/github.com/go-playground/form/v4/doc.go
generated
vendored
Normal file
275
vendor/github.com/go-playground/form/v4/doc.go
generated
vendored
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
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
|
261
vendor/github.com/go-playground/form/v4/encoder.go
generated
vendored
Normal file
261
vendor/github.com/go-playground/form/v4/encoder.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
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
|
||||
}
|
||||
}
|
49
vendor/github.com/go-playground/form/v4/form.go
generated
vendored
Normal file
49
vendor/github.com/go-playground/form/v4/form.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
blank = ""
|
||||
ignore = "-"
|
||||
fieldNS = "Field Namespace:"
|
||||
errorText = " ERROR:"
|
||||
)
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
)
|
||||
|
||||
// Mode specifies which mode the form decoder is to run
|
||||
type Mode uint8
|
||||
|
||||
const (
|
||||
|
||||
// ModeImplicit tries to parse values for all
|
||||
// fields that do not have an ignore '-' tag
|
||||
ModeImplicit Mode = iota
|
||||
|
||||
// ModeExplicit only parses values for field with a field tag
|
||||
// and that tag is not the ignore '-' tag
|
||||
ModeExplicit
|
||||
)
|
||||
|
||||
// AnonymousMode specifies how data should be rolled up
|
||||
// or separated from anonymous structs
|
||||
type AnonymousMode uint8
|
||||
|
||||
const (
|
||||
// AnonymousEmbed embeds anonymous data when encoding
|
||||
// eg. type A struct { Field string }
|
||||
// type B struct { A, Field string }
|
||||
// encode results: url.Values{"Field":[]string{"B FieldVal", "A FieldVal"}}
|
||||
AnonymousEmbed AnonymousMode = iota
|
||||
|
||||
// AnonymousSeparate does not embed anonymous data when encoding
|
||||
// eg. type A struct { Field string }
|
||||
// type B struct { A, Field string }
|
||||
// encode results: url.Values{"Field":[]string{"B FieldVal"}, "A.Field":[]string{"A FieldVal"}}
|
||||
AnonymousSeparate
|
||||
)
|
187
vendor/github.com/go-playground/form/v4/form_decoder.go
generated
vendored
Normal file
187
vendor/github.com/go-playground/form/v4/form_decoder.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DecodeCustomTypeFunc allows for registering/overriding types to be parsed.
|
||||
type DecodeCustomTypeFunc func([]string) (interface{}, error)
|
||||
|
||||
// DecodeErrors is a map of errors encountered during form decoding
|
||||
type DecodeErrors map[string]error
|
||||
|
||||
func (d DecodeErrors) Error() string {
|
||||
buff := bytes.NewBufferString(blank)
|
||||
|
||||
for k, err := range d {
|
||||
buff.WriteString(fieldNS)
|
||||
buff.WriteString(k)
|
||||
buff.WriteString(errorText)
|
||||
buff.WriteString(err.Error())
|
||||
buff.WriteString("\n")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buff.String())
|
||||
}
|
||||
|
||||
// An InvalidDecoderError describes an invalid argument passed to Decode.
|
||||
// (The argument passed to Decode must be a non-nil pointer.)
|
||||
type InvalidDecoderError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *InvalidDecoderError) Error() string {
|
||||
|
||||
if e.Type == nil {
|
||||
return "form: Decode(nil)"
|
||||
}
|
||||
|
||||
if e.Type.Kind() != reflect.Ptr {
|
||||
return "form: Decode(non-pointer " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
return "form: Decode(nil " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
type key struct {
|
||||
ivalue int
|
||||
value string
|
||||
searchValue string
|
||||
}
|
||||
|
||||
type recursiveData struct {
|
||||
alias string
|
||||
sliceLen int
|
||||
keys []key
|
||||
}
|
||||
|
||||
type dataMap []*recursiveData
|
||||
|
||||
// Decoder is the main decode instance
|
||||
type Decoder struct {
|
||||
tagName string
|
||||
mode Mode
|
||||
structCache *structCacheMap
|
||||
customTypeFuncs map[reflect.Type]DecodeCustomTypeFunc
|
||||
maxArraySize int
|
||||
dataPool *sync.Pool
|
||||
namespacePrefix string
|
||||
namespaceSuffix string
|
||||
}
|
||||
|
||||
// NewDecoder creates a new decoder instance with sane defaults
|
||||
func NewDecoder() *Decoder {
|
||||
|
||||
d := &Decoder{
|
||||
tagName: "form",
|
||||
mode: ModeImplicit,
|
||||
structCache: newStructCacheMap(),
|
||||
maxArraySize: 10000,
|
||||
namespacePrefix: ".",
|
||||
}
|
||||
|
||||
d.dataPool = &sync.Pool{New: func() interface{} {
|
||||
return &decoder{
|
||||
d: d,
|
||||
namespace: make([]byte, 0, 64),
|
||||
}
|
||||
}}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// SetTagName sets the given tag name to be used by the decoder.
|
||||
// Default is "form"
|
||||
func (d *Decoder) SetTagName(tagName string) {
|
||||
d.tagName = tagName
|
||||
}
|
||||
|
||||
// SetMode sets the mode the decoder should run
|
||||
// Default is ModeImplicit
|
||||
func (d *Decoder) SetMode(mode Mode) {
|
||||
d.mode = mode
|
||||
}
|
||||
|
||||
// SetNamespacePrefix sets a struct namespace prefix.
|
||||
func (d *Decoder) SetNamespacePrefix(namespacePrefix string) {
|
||||
d.namespacePrefix = namespacePrefix
|
||||
}
|
||||
|
||||
// SetNamespaceSuffix sets a struct namespace suffix.
|
||||
func (d *Decoder) SetNamespaceSuffix(namespaceSuffix string) {
|
||||
d.namespaceSuffix = namespaceSuffix
|
||||
}
|
||||
|
||||
// SetMaxArraySize sets maximum array size that can be created.
|
||||
// This limit is for the array indexing this library supports to
|
||||
// avoid potential DOS or man-in-the-middle attacks using an unusually
|
||||
// high number.
|
||||
// DEFAULT: 10000
|
||||
func (d *Decoder) SetMaxArraySize(size uint) {
|
||||
d.maxArraySize = int(size)
|
||||
}
|
||||
|
||||
// RegisterTagNameFunc registers a custom tag name parser function
|
||||
// NOTE: This method is not thread-safe it is intended that these all be registered prior to any parsing
|
||||
//
|
||||
// ADDITIONAL: once a custom function has been registered the default, or custom set, tag name is ignored
|
||||
// and relies 100% on the function for the name data. The return value WILL BE CACHED and so return value
|
||||
// must be consistent.
|
||||
func (d *Decoder) RegisterTagNameFunc(fn TagNameFunc) {
|
||||
d.structCache.tagFn = fn
|
||||
}
|
||||
|
||||
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types.
|
||||
// NOTE: This method is not thread-safe it is intended that these all be registered prior to any parsing
|
||||
//
|
||||
// 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.
|
||||
func (d *Decoder) RegisterCustomTypeFunc(fn DecodeCustomTypeFunc, types ...interface{}) {
|
||||
|
||||
if d.customTypeFuncs == nil {
|
||||
d.customTypeFuncs = map[reflect.Type]DecodeCustomTypeFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
d.customTypeFuncs[reflect.TypeOf(t)] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// Decode parses the given values and sets the corresponding struct and/or type values
|
||||
//
|
||||
// Decode returns an InvalidDecoderError if interface passed is invalid.
|
||||
func (d *Decoder) Decode(v interface{}, values url.Values) (err error) {
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
|
||||
if val.Kind() != reflect.Ptr || val.IsNil() {
|
||||
return &InvalidDecoderError{reflect.TypeOf(v)}
|
||||
}
|
||||
|
||||
dec := d.dataPool.Get().(*decoder)
|
||||
dec.values = values
|
||||
dec.dm = dec.dm[0:0]
|
||||
|
||||
val = val.Elem()
|
||||
typ := val.Type()
|
||||
|
||||
if val.Kind() == reflect.Struct && typ != timeType {
|
||||
dec.traverseStruct(val, typ, dec.namespace[0:0])
|
||||
} else {
|
||||
dec.setFieldByType(val, dec.namespace[0:0], 0)
|
||||
}
|
||||
|
||||
if len(dec.errs) > 0 {
|
||||
err = dec.errs
|
||||
dec.errs = nil
|
||||
}
|
||||
|
||||
d.dataPool.Put(dec)
|
||||
|
||||
return
|
||||
}
|
157
vendor/github.com/go-playground/form/v4/form_encoder.go
generated
vendored
Normal file
157
vendor/github.com/go-playground/form/v4/form_encoder.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// EncodeCustomTypeFunc allows for registering/overriding types to be parsed.
|
||||
type EncodeCustomTypeFunc func(x interface{}) ([]string, error)
|
||||
|
||||
// EncodeErrors is a map of errors encountered during form encoding
|
||||
type EncodeErrors map[string]error
|
||||
|
||||
func (e EncodeErrors) Error() string {
|
||||
buff := bytes.NewBufferString(blank)
|
||||
|
||||
for k, err := range e {
|
||||
buff.WriteString(fieldNS)
|
||||
buff.WriteString(k)
|
||||
buff.WriteString(errorText)
|
||||
buff.WriteString(err.Error())
|
||||
buff.WriteString("\n")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buff.String())
|
||||
}
|
||||
|
||||
// An InvalidEncodeError describes an invalid argument passed to Encode.
|
||||
type InvalidEncodeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e *InvalidEncodeError) Error() string {
|
||||
|
||||
if e.Type == nil {
|
||||
return "form: Encode(nil)"
|
||||
}
|
||||
|
||||
return "form: Encode(nil " + e.Type.String() + ")"
|
||||
}
|
||||
|
||||
// Encoder is the main encode instance
|
||||
type Encoder struct {
|
||||
tagName string
|
||||
structCache *structCacheMap
|
||||
customTypeFuncs map[reflect.Type]EncodeCustomTypeFunc
|
||||
dataPool *sync.Pool
|
||||
mode Mode
|
||||
embedAnonymous bool
|
||||
namespacePrefix string
|
||||
namespaceSuffix string
|
||||
}
|
||||
|
||||
// NewEncoder creates a new encoder instance with sane defaults
|
||||
func NewEncoder() *Encoder {
|
||||
|
||||
e := &Encoder{
|
||||
tagName: "form",
|
||||
mode: ModeImplicit,
|
||||
structCache: newStructCacheMap(),
|
||||
embedAnonymous: true,
|
||||
namespacePrefix: ".",
|
||||
}
|
||||
|
||||
e.dataPool = &sync.Pool{New: func() interface{} {
|
||||
return &encoder{
|
||||
e: e,
|
||||
namespace: make([]byte, 0, 64),
|
||||
}
|
||||
}}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// SetTagName sets the given tag name to be used by the encoder.
|
||||
// Default is "form"
|
||||
func (e *Encoder) SetTagName(tagName string) {
|
||||
e.tagName = tagName
|
||||
}
|
||||
|
||||
// SetMode sets the mode the encoder should run
|
||||
// Default is ModeImplicit
|
||||
func (e *Encoder) SetMode(mode Mode) {
|
||||
e.mode = mode
|
||||
}
|
||||
|
||||
// SetNamespacePrefix sets a struct namespace prefix.
|
||||
func (e *Encoder) SetNamespacePrefix(namespacePrefix string) {
|
||||
e.namespacePrefix = namespacePrefix
|
||||
}
|
||||
|
||||
// SetNamespaceSuffix sets a struct namespace suffix.
|
||||
func (e *Encoder) SetNamespaceSuffix(namespaceSuffix string) {
|
||||
e.namespaceSuffix = namespaceSuffix
|
||||
}
|
||||
|
||||
// SetAnonymousMode sets the mode the encoder should run
|
||||
// Default is AnonymousEmbed
|
||||
func (e *Encoder) SetAnonymousMode(mode AnonymousMode) {
|
||||
e.embedAnonymous = mode == AnonymousEmbed
|
||||
}
|
||||
|
||||
// RegisterTagNameFunc registers a custom tag name parser function
|
||||
// NOTE: This method is not thread-safe it is intended that these all be registered prior to any parsing
|
||||
//
|
||||
// ADDITIONAL: once a custom function has been registered the default, or custom set, tag name is ignored
|
||||
// and relies 100% on the function for the name data. The return value WILL BE CACHED and so return value
|
||||
// must be consistent.
|
||||
func (e *Encoder) RegisterTagNameFunc(fn TagNameFunc) {
|
||||
e.structCache.tagFn = fn
|
||||
}
|
||||
|
||||
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any parsing
|
||||
func (e *Encoder) RegisterCustomTypeFunc(fn EncodeCustomTypeFunc, types ...interface{}) {
|
||||
|
||||
if e.customTypeFuncs == nil {
|
||||
e.customTypeFuncs = map[reflect.Type]EncodeCustomTypeFunc{}
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
e.customTypeFuncs[reflect.TypeOf(t)] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// Encode encodes the given values and sets the corresponding struct values
|
||||
func (e *Encoder) Encode(v interface{}) (values url.Values, err error) {
|
||||
|
||||
val, kind := ExtractType(reflect.ValueOf(v))
|
||||
|
||||
if kind == reflect.Ptr || kind == reflect.Interface || kind == reflect.Invalid {
|
||||
return nil, &InvalidEncodeError{reflect.TypeOf(v)}
|
||||
}
|
||||
|
||||
enc := e.dataPool.Get().(*encoder)
|
||||
enc.values = make(url.Values)
|
||||
|
||||
if kind == reflect.Struct && val.Type() != timeType {
|
||||
enc.traverseStruct(val, enc.namespace[0:0], -1)
|
||||
} else {
|
||||
enc.setFieldByType(val, enc.namespace[0:0], -1, false)
|
||||
}
|
||||
|
||||
if len(enc.errs) > 0 {
|
||||
err = enc.errs
|
||||
enc.errs = nil
|
||||
}
|
||||
|
||||
values = enc.values
|
||||
|
||||
e.dataPool.Put(enc)
|
||||
|
||||
return
|
||||
}
|
BIN
vendor/github.com/go-playground/form/v4/logo.jpg
generated
vendored
Normal file
BIN
vendor/github.com/go-playground/form/v4/logo.jpg
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
62
vendor/github.com/go-playground/form/v4/util.go
generated
vendored
Normal file
62
vendor/github.com/go-playground/form/v4/util.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ExtractType gets the actual underlying type of field value.
|
||||
// it is exposed for use within you Custom Functions
|
||||
func ExtractType(current reflect.Value) (reflect.Value, reflect.Kind) {
|
||||
|
||||
switch current.Kind() {
|
||||
case reflect.Ptr:
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Ptr
|
||||
}
|
||||
|
||||
return ExtractType(current.Elem())
|
||||
|
||||
case reflect.Interface:
|
||||
|
||||
if current.IsNil() {
|
||||
return current, reflect.Interface
|
||||
}
|
||||
|
||||
return ExtractType(current.Elem())
|
||||
|
||||
default:
|
||||
return current, current.Kind()
|
||||
}
|
||||
}
|
||||
|
||||
func parseBool(str string) (bool, error) {
|
||||
|
||||
switch str {
|
||||
case "1", "t", "T", "true", "TRUE", "True", "on", "yes", "ok":
|
||||
return true, nil
|
||||
case "", "0", "f", "F", "false", "FALSE", "False", "off", "no":
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// strconv.NumError mimicing exactly the strconv.ParseBool(..) error and type
|
||||
// to ensure compatibility with std library and beyond.
|
||||
return false, &strconv.NumError{Func: "ParseBool", Num: str, Err: strconv.ErrSyntax}
|
||||
}
|
||||
|
||||
// hasValue determines if a reflect.Value is it's default value
|
||||
func hasValue(field reflect.Value) bool {
|
||||
switch field.Kind() {
|
||||
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
|
||||
return !field.IsNil()
|
||||
default:
|
||||
if !field.IsValid() {
|
||||
return false
|
||||
}
|
||||
if !field.Type().Comparable() {
|
||||
return true
|
||||
}
|
||||
return field.Interface() != reflect.Zero(field.Type()).Interface()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user