diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..efae794 --- /dev/null +++ b/license.txt @@ -0,0 +1,22 @@ +Copyright 2024 (c) surdeus + +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. + diff --git a/schema/.editorconfig b/schema/.editorconfig deleted file mode 100644 index c6b74c3..0000000 --- a/schema/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -; https://editorconfig.org/ - -root = true - -[*] -insert_final_newline = true -charset = utf-8 -trim_trailing_whitespace = true -indent_style = space -indent_size = 2 - -[{Makefile,go.mod,go.sum,*.go,.gitmodules}] -indent_style = tab -indent_size = 4 - -[*.md] -indent_size = 4 -trim_trailing_whitespace = false - -eclint_indent_style = unset \ No newline at end of file diff --git a/schema/cache.go b/schema/cache.go deleted file mode 100644 index bf21697..0000000 --- a/schema/cache.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package schema - -import ( - "errors" - "reflect" - "strconv" - "strings" - "sync" -) - -var errInvalidPath = errors.New("schema: invalid path") - -// newCache returns a new cache. -func newCache() *cache { - c := cache{ - m: make(map[reflect.Type]*structInfo), - regconv: make(map[reflect.Type]Converter), - tag: "schema", - } - return &c -} - -// cache caches meta-data about a struct. -type cache struct { - l sync.RWMutex - m map[reflect.Type]*structInfo - regconv map[reflect.Type]Converter - tag string -} - -// registerConverter registers a converter function for a custom type. -func (c *cache) registerConverter(value interface{}, converterFunc Converter) { - c.regconv[reflect.TypeOf(value)] = converterFunc -} - -// parsePath parses a path in dotted notation verifying that it is a valid -// path to a struct field. -// -// It returns "path parts" which contain indices to fields to be used by -// reflect.Value.FieldByString(). Multiple parts are required for slices of -// structs. -func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) { - var struc *structInfo - var field *fieldInfo - var index64 int64 - var err error - parts := make([]pathPart, 0) - path := make([]string, 0) - keys := strings.Split(p, ".") - for i := 0; i < len(keys); i++ { - if t.Kind() != reflect.Struct { - return nil, errInvalidPath - } - if struc = c.get(t); struc == nil { - return nil, errInvalidPath - } - if field = struc.get(keys[i]); field == nil { - return nil, errInvalidPath - } - // Valid field. Append index. - path = append(path, field.name) - if field.isSliceOfStructs && (!field.unmarshalerInfo.IsValid || (field.unmarshalerInfo.IsValid && field.unmarshalerInfo.IsSliceElement)) { - // Parse a special case: slices of structs. - // i+1 must be the slice index. - // - // Now that struct can implements TextUnmarshaler interface, - // we don't need to force the struct's fields to appear in the path. - // So checking i+2 is not necessary anymore. - i++ - if i+1 > len(keys) { - return nil, errInvalidPath - } - if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil { - return nil, errInvalidPath - } - parts = append(parts, pathPart{ - path: path, - field: field, - index: int(index64), - }) - path = make([]string, 0) - - // Get the next struct type, dropping ptrs. - if field.typ.Kind() == reflect.Ptr { - t = field.typ.Elem() - } else { - t = field.typ - } - if t.Kind() == reflect.Slice { - t = t.Elem() - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - } - } else if field.typ.Kind() == reflect.Ptr { - t = field.typ.Elem() - } else { - t = field.typ - } - } - // Add the remaining. - parts = append(parts, pathPart{ - path: path, - field: field, - index: -1, - }) - return parts, nil -} - -// get returns a cached structInfo, creating it if necessary. -func (c *cache) get(t reflect.Type) *structInfo { - c.l.RLock() - info := c.m[t] - c.l.RUnlock() - if info == nil { - info = c.create(t, "") - c.l.Lock() - c.m[t] = info - c.l.Unlock() - } - return info -} - -// create creates a structInfo with meta-data about a struct. -func (c *cache) create(t reflect.Type, parentAlias string) *structInfo { - info := &structInfo{} - var anonymousInfos []*structInfo - for i := 0; i < t.NumField(); i++ { - if f := c.createField(t.Field(i), parentAlias); f != nil { - info.fields = append(info.fields, f) - if ft := indirectType(f.typ); ft.Kind() == reflect.Struct && f.isAnonymous { - anonymousInfos = append(anonymousInfos, c.create(ft, f.canonicalAlias)) - } - } - } - for i, a := range anonymousInfos { - others := []*structInfo{info} - others = append(others, anonymousInfos[:i]...) - others = append(others, anonymousInfos[i+1:]...) - for _, f := range a.fields { - if !containsAlias(others, f.alias) { - info.fields = append(info.fields, f) - } - } - } - return info -} - -// createField creates a fieldInfo for the given field. -func (c *cache) createField(field reflect.StructField, parentAlias string) *fieldInfo { - alias, options := fieldAlias(field, c.tag) - if alias == "-" { - // Ignore this field. - return nil - } - canonicalAlias := alias - if parentAlias != "" { - canonicalAlias = parentAlias + "." + alias - } - // Check if the type is supported and don't cache it if not. - // First let's get the basic type. - isSlice, isStruct := false, false - ft := field.Type - m := isTextUnmarshaler(reflect.Zero(ft)) - if ft.Kind() == reflect.Ptr { - ft = ft.Elem() - } - if isSlice = ft.Kind() == reflect.Slice; isSlice { - ft = ft.Elem() - if ft.Kind() == reflect.Ptr { - ft = ft.Elem() - } - } - if ft.Kind() == reflect.Array { - ft = ft.Elem() - if ft.Kind() == reflect.Ptr { - ft = ft.Elem() - } - } - if isStruct = ft.Kind() == reflect.Struct; !isStruct { - if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil { - // Type is not supported. - return nil - } - } - - return &fieldInfo{ - typ: field.Type, - name: field.Name, - alias: alias, - canonicalAlias: canonicalAlias, - unmarshalerInfo: m, - isSliceOfStructs: isSlice && isStruct, - isAnonymous: field.Anonymous, - isRequired: options.Contains("required"), - } -} - -// converter returns the converter for a type. -func (c *cache) converter(t reflect.Type) Converter { - return c.regconv[t] -} - -// ---------------------------------------------------------------------------- - -type structInfo struct { - fields []*fieldInfo -} - -func (i *structInfo) get(alias string) *fieldInfo { - for _, field := range i.fields { - if strings.EqualFold(field.alias, alias) { - return field - } - } - return nil -} - -func containsAlias(infos []*structInfo, alias string) bool { - for _, info := range infos { - if info.get(alias) != nil { - return true - } - } - return false -} - -type fieldInfo struct { - typ reflect.Type - // name is the field name in the struct. - name string - alias string - // canonicalAlias is almost the same as the alias, but is prefixed with - // an embedded struct field alias in dotted notation if this field is - // promoted from the struct. - // For instance, if the alias is "N" and this field is an embedded field - // in a struct "X", canonicalAlias will be "X.N". - canonicalAlias string - // unmarshalerInfo contains information regarding the - // encoding.TextUnmarshaler implementation of the field type. - unmarshalerInfo unmarshaler - // isSliceOfStructs indicates if the field type is a slice of structs. - isSliceOfStructs bool - // isAnonymous indicates whether the field is embedded in the struct. - isAnonymous bool - isRequired bool -} - -func (f *fieldInfo) paths(prefix string) []string { - if f.alias == f.canonicalAlias { - return []string{prefix + f.alias} - } - return []string{prefix + f.alias, prefix + f.canonicalAlias} -} - -type pathPart struct { - field *fieldInfo - path []string // path to the field: walks structs using field names. - index int // struct index in slices of structs. -} - -// ---------------------------------------------------------------------------- - -func indirectType(typ reflect.Type) reflect.Type { - if typ.Kind() == reflect.Ptr { - return typ.Elem() - } - return typ -} - -// fieldAlias parses a field tag to get a field alias. -func fieldAlias(field reflect.StructField, tagName string) (alias string, options tagOptions) { - if tag := field.Tag.Get(tagName); tag != "" { - alias, options = parseTag(tag) - } - if alias == "" { - alias = field.Name - } - return alias, options -} - -// tagOptions is the string following a comma in a struct field's tag, or -// the empty string. It does not include the leading comma. -type tagOptions []string - -// parseTag splits a struct field's url tag into its name and comma-separated -// options. -func parseTag(tag string) (string, tagOptions) { - s := strings.Split(tag, ",") - return s[0], s[1:] -} - -// Contains checks whether the tagOptions contains the specified option. -func (o tagOptions) Contains(option string) bool { - for _, s := range o { - if s == option { - return true - } - } - return false -} diff --git a/schema/converter.go b/schema/converter.go deleted file mode 100644 index 4f2116a..0000000 --- a/schema/converter.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package schema - -import ( - "reflect" - "strconv" -) - -type Converter func(string) reflect.Value - -var ( - invalidValue = reflect.Value{} - boolType = reflect.Bool - float32Type = reflect.Float32 - float64Type = reflect.Float64 - intType = reflect.Int - int8Type = reflect.Int8 - int16Type = reflect.Int16 - int32Type = reflect.Int32 - int64Type = reflect.Int64 - stringType = reflect.String - uintType = reflect.Uint - uint8Type = reflect.Uint8 - uint16Type = reflect.Uint16 - uint32Type = reflect.Uint32 - uint64Type = reflect.Uint64 -) - -// Default converters for basic types. -var builtinConverters = map[reflect.Kind]Converter{ - boolType: convertBool, - float32Type: convertFloat32, - float64Type: convertFloat64, - intType: convertInt, - int8Type: convertInt8, - int16Type: convertInt16, - int32Type: convertInt32, - int64Type: convertInt64, - stringType: convertString, - uintType: convertUint, - uint8Type: convertUint8, - uint16Type: convertUint16, - uint32Type: convertUint32, - uint64Type: convertUint64, -} - -func convertBool(value string) reflect.Value { - if value == "on" { - return reflect.ValueOf(true) - } else if v, err := strconv.ParseBool(value); err == nil { - return reflect.ValueOf(v) - } - return invalidValue -} - -func convertFloat32(value string) reflect.Value { - if v, err := strconv.ParseFloat(value, 32); err == nil { - return reflect.ValueOf(float32(v)) - } - return invalidValue -} - -func convertFloat64(value string) reflect.Value { - if v, err := strconv.ParseFloat(value, 64); err == nil { - return reflect.ValueOf(v) - } - return invalidValue -} - -func convertInt(value string) reflect.Value { - if v, err := strconv.ParseInt(value, 10, 0); err == nil { - return reflect.ValueOf(int(v)) - } - return invalidValue -} - -func convertInt8(value string) reflect.Value { - if v, err := strconv.ParseInt(value, 10, 8); err == nil { - return reflect.ValueOf(int8(v)) - } - return invalidValue -} - -func convertInt16(value string) reflect.Value { - if v, err := strconv.ParseInt(value, 10, 16); err == nil { - return reflect.ValueOf(int16(v)) - } - return invalidValue -} - -func convertInt32(value string) reflect.Value { - if v, err := strconv.ParseInt(value, 10, 32); err == nil { - return reflect.ValueOf(int32(v)) - } - return invalidValue -} - -func convertInt64(value string) reflect.Value { - if v, err := strconv.ParseInt(value, 10, 64); err == nil { - return reflect.ValueOf(v) - } - return invalidValue -} - -func convertString(value string) reflect.Value { - return reflect.ValueOf(value) -} - -func convertUint(value string) reflect.Value { - if v, err := strconv.ParseUint(value, 10, 0); err == nil { - return reflect.ValueOf(uint(v)) - } - return invalidValue -} - -func convertUint8(value string) reflect.Value { - if v, err := strconv.ParseUint(value, 10, 8); err == nil { - return reflect.ValueOf(uint8(v)) - } - return invalidValue -} - -func convertUint16(value string) reflect.Value { - if v, err := strconv.ParseUint(value, 10, 16); err == nil { - return reflect.ValueOf(uint16(v)) - } - return invalidValue -} - -func convertUint32(value string) reflect.Value { - if v, err := strconv.ParseUint(value, 10, 32); err == nil { - return reflect.ValueOf(uint32(v)) - } - return invalidValue -} - -func convertUint64(value string) reflect.Value { - if v, err := strconv.ParseUint(value, 10, 64); err == nil { - return reflect.ValueOf(v) - } - return invalidValue -} diff --git a/schema/decoder.go b/schema/decoder.go deleted file mode 100644 index 28b560b..0000000 --- a/schema/decoder.go +++ /dev/null @@ -1,521 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package schema - -import ( - "encoding" - "errors" - "fmt" - "reflect" - "strings" -) - -// NewDecoder returns a new Decoder. -func NewDecoder() *Decoder { - return &Decoder{cache: newCache()} -} - -// Decoder decodes values from a map[string][]string to a struct. -type Decoder struct { - cache *cache - zeroEmpty bool - ignoreUnknownKeys bool -} - -// SetAliasTag changes the tag used to locate custom field aliases. -// The default tag is "schema". -func (d *Decoder) SetAliasTag(tag string) { - d.cache.tag = tag -} - -// ZeroEmpty controls the behaviour when the decoder encounters empty values -// in a map. -// If z is true and a key in the map has the empty string as a value -// then the corresponding struct field is set to the zero value. -// If z is false then empty strings are ignored. -// -// The default value is false, that is empty values do not change -// the value of the struct field. -func (d *Decoder) ZeroEmpty(z bool) { - d.zeroEmpty = z -} - -// IgnoreUnknownKeys controls the behaviour when the decoder encounters unknown -// keys in the map. -// If i is true and an unknown field is encountered, it is ignored. This is -// similar to how unknown keys are handled by encoding/json. -// If i is false then Decode will return an error. Note that any valid keys -// will still be decoded in to the target struct. -// -// To preserve backwards compatibility, the default value is false. -func (d *Decoder) IgnoreUnknownKeys(i bool) { - d.ignoreUnknownKeys = i -} - -// RegisterConverter registers a converter function for a custom type. -func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) { - d.cache.registerConverter(value, converterFunc) -} - -// Decode decodes a map[string][]string to a struct. -// -// The first parameter must be a pointer to a struct. -// -// The second parameter is a map, typically url.Values from an HTTP request. -// Keys are "paths" in dotted notation to the struct fields and nested structs. -// -// See the package documentation for a full explanation of the mechanics. -func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { - v := reflect.ValueOf(dst) - if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { - return errors.New("schema: interface must be a pointer to struct") - } - v = v.Elem() - t := v.Type() - errors := MultiError{} - for path, values := range src { - if parts, err := d.cache.parsePath(path, t); err == nil { - if err = d.decode(v, path, parts, values); err != nil { - errors[path] = err - } - } else if !d.ignoreUnknownKeys { - errors[path] = UnknownKeyError{Key: path} - } - } - errors.merge(d.checkRequired(t, src)) - if len(errors) > 0 { - return errors - } - return nil -} - -// checkRequired checks whether required fields are empty -// -// check type t recursively if t has struct fields. -// -// src is the source map for decoding, we use it here to see if those required fields are included in src -func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string) MultiError { - m, errs := d.findRequiredFields(t, "", "") - for key, fields := range m { - if isEmptyFields(fields, src) { - errs[key] = EmptyFieldError{Key: key} - } - } - return errs -} - -// findRequiredFields recursively searches the struct type t for required fields. -// -// canonicalPrefix and searchPrefix are used to resolve full paths in dotted notation -// for nested struct fields. canonicalPrefix is a complete path which never omits -// any embedded struct fields. searchPrefix is a user-friendly path which may omit -// some embedded struct fields to point promoted fields. -func (d *Decoder) findRequiredFields(t reflect.Type, canonicalPrefix, searchPrefix string) (map[string][]fieldWithPrefix, MultiError) { - struc := d.cache.get(t) - if struc == nil { - // unexpect, cache.get never return nil - return nil, MultiError{canonicalPrefix + "*": errors.New("cache fail")} - } - - m := map[string][]fieldWithPrefix{} - errs := MultiError{} - for _, f := range struc.fields { - if f.typ.Kind() == reflect.Struct { - fcprefix := canonicalPrefix + f.canonicalAlias + "." - for _, fspath := range f.paths(searchPrefix) { - fm, ferrs := d.findRequiredFields(f.typ, fcprefix, fspath+".") - for key, fields := range fm { - m[key] = append(m[key], fields...) - } - errs.merge(ferrs) - } - } - if f.isRequired { - key := canonicalPrefix + f.canonicalAlias - m[key] = append(m[key], fieldWithPrefix{ - fieldInfo: f, - prefix: searchPrefix, - }) - } - } - return m, errs -} - -type fieldWithPrefix struct { - *fieldInfo - prefix string -} - -// isEmptyFields returns true if all of specified fields are empty. -func isEmptyFields(fields []fieldWithPrefix, src map[string][]string) bool { - for _, f := range fields { - for _, path := range f.paths(f.prefix) { - v, ok := src[path] - if ok && !isEmpty(f.typ, v) { - return false - } - for key := range src { - if !isEmpty(f.typ, src[key]) && strings.HasPrefix(key, path) { - return false - } - } - } - } - return true -} - -// isEmpty returns true if value is empty for specific type -func isEmpty(t reflect.Type, value []string) bool { - if len(value) == 0 { - return true - } - switch t.Kind() { - case boolType, float32Type, float64Type, intType, int8Type, int32Type, int64Type, stringType, uint8Type, uint16Type, uint32Type, uint64Type: - return len(value[0]) == 0 - } - return false -} - -// decode fills a struct field using a parsed path. -func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values []string) error { - // Get the field walking the struct fields by index. - for _, name := range parts[0].path { - if v.Type().Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - v = v.Elem() - } - - // alloc embedded structs - if v.Type().Kind() == reflect.Struct { - for i := 0; i < v.NumField(); i++ { - field := v.Field(i) - if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous { - field.Set(reflect.New(field.Type().Elem())) - } - } - } - - v = v.FieldByName(name) - } - // Don't even bother for unexported fields. - if !v.CanSet() { - return nil - } - - // Dereference if needed. - t := v.Type() - if t.Kind() == reflect.Ptr { - t = t.Elem() - if v.IsNil() { - v.Set(reflect.New(t)) - } - v = v.Elem() - } - - // Slice of structs. Let's go recursive. - if len(parts) > 1 { - idx := parts[0].index - if v.IsNil() || v.Len() < idx+1 { - value := reflect.MakeSlice(t, idx+1, idx+1) - if v.Len() < idx+1 { - // Resize it. - reflect.Copy(value, v) - } - v.Set(value) - } - return d.decode(v.Index(idx), path, parts[1:], values) - } - - // Get the converter early in case there is one for a slice type. - conv := d.cache.converter(t) - m := isTextUnmarshaler(v) - if conv == nil && t.Kind() == reflect.Slice && m.IsSliceElement { - var items []reflect.Value - elemT := t.Elem() - isPtrElem := elemT.Kind() == reflect.Ptr - if isPtrElem { - elemT = elemT.Elem() - } - - // Try to get a converter for the element type. - conv := d.cache.converter(elemT) - if conv == nil { - conv = builtinConverters[elemT.Kind()] - if conv == nil { - // As we are not dealing with slice of structs here, we don't need to check if the type - // implements TextUnmarshaler interface - return fmt.Errorf("schema: converter not found for %v", elemT) - } - } - - for key, value := range values { - if value == "" { - if d.zeroEmpty { - items = append(items, reflect.Zero(elemT)) - } - } else if m.IsValid { - u := reflect.New(elemT) - if m.IsSliceElementPtr { - u = reflect.New(reflect.PtrTo(elemT).Elem()) - } - if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)); err != nil { - return ConversionError{ - Key: path, - Type: t, - Index: key, - Err: err, - } - } - if m.IsSliceElementPtr { - items = append(items, u.Elem().Addr()) - } else if u.Kind() == reflect.Ptr { - items = append(items, u.Elem()) - } else { - items = append(items, u) - } - } else if item := conv(value); item.IsValid() { - if isPtrElem { - ptr := reflect.New(elemT) - ptr.Elem().Set(item) - item = ptr - } - if item.Type() != elemT && !isPtrElem { - item = item.Convert(elemT) - } - items = append(items, item) - } else { - if strings.Contains(value, ",") { - values := strings.Split(value, ",") - for _, value := range values { - if value == "" { - if d.zeroEmpty { - items = append(items, reflect.Zero(elemT)) - } - } else if item := conv(value); item.IsValid() { - if isPtrElem { - ptr := reflect.New(elemT) - ptr.Elem().Set(item) - item = ptr - } - if item.Type() != elemT && !isPtrElem { - item = item.Convert(elemT) - } - items = append(items, item) - } else { - return ConversionError{ - Key: path, - Type: elemT, - Index: key, - } - } - } - } else { - return ConversionError{ - Key: path, - Type: elemT, - Index: key, - } - } - } - } - value := reflect.Append(reflect.MakeSlice(t, 0, 0), items...) - v.Set(value) - } else { - val := "" - // Use the last value provided if any values were provided - if len(values) > 0 { - val = values[len(values)-1] - } - - if conv != nil { - if value := conv(val); value.IsValid() { - v.Set(value.Convert(t)) - } else { - return ConversionError{ - Key: path, - Type: t, - Index: -1, - } - } - } else if m.IsValid { - if m.IsPtr { - u := reflect.New(v.Type()) - if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(val)); err != nil { - return ConversionError{ - Key: path, - Type: t, - Index: -1, - Err: err, - } - } - v.Set(reflect.Indirect(u)) - } else { - // If the value implements the encoding.TextUnmarshaler interface - // apply UnmarshalText as the converter - if err := m.Unmarshaler.UnmarshalText([]byte(val)); err != nil { - return ConversionError{ - Key: path, - Type: t, - Index: -1, - Err: err, - } - } - } - } else if val == "" { - if d.zeroEmpty { - v.Set(reflect.Zero(t)) - } - } else if conv := builtinConverters[t.Kind()]; conv != nil { - if value := conv(val); value.IsValid() { - v.Set(value.Convert(t)) - } else { - return ConversionError{ - Key: path, - Type: t, - Index: -1, - } - } - } else { - return fmt.Errorf("schema: converter not found for %v", t) - } - } - return nil -} - -func isTextUnmarshaler(v reflect.Value) unmarshaler { - // Create a new unmarshaller instance - m := unmarshaler{} - if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid { - return m - } - // As the UnmarshalText function should be applied to the pointer of the - // type, we check that type to see if it implements the necessary - // method. - if m.Unmarshaler, m.IsValid = reflect.New(v.Type()).Interface().(encoding.TextUnmarshaler); m.IsValid { - m.IsPtr = true - return m - } - - // if v is []T or *[]T create new T - t := v.Type() - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - if t.Kind() == reflect.Slice { - // Check if the slice implements encoding.TextUnmarshaller - if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid { - return m - } - // If t is a pointer slice, check if its elements implement - // encoding.TextUnmarshaler - m.IsSliceElement = true - if t = t.Elem(); t.Kind() == reflect.Ptr { - t = reflect.PtrTo(t.Elem()) - v = reflect.Zero(t) - m.IsSliceElementPtr = true - m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler) - return m - } - } - - v = reflect.New(t) - m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler) - return m -} - -// TextUnmarshaler helpers ---------------------------------------------------- -// unmarshaller contains information about a TextUnmarshaler type -type unmarshaler struct { - Unmarshaler encoding.TextUnmarshaler - // IsValid indicates whether the resolved type indicated by the other - // flags implements the encoding.TextUnmarshaler interface. - IsValid bool - // IsPtr indicates that the resolved type is the pointer of the original - // type. - IsPtr bool - // IsSliceElement indicates that the resolved type is a slice element of - // the original type. - IsSliceElement bool - // IsSliceElementPtr indicates that the resolved type is a pointer to a - // slice element of the original type. - IsSliceElementPtr bool -} - -// Errors --------------------------------------------------------------------- - -// ConversionError stores information about a failed conversion. -type ConversionError struct { - Key string // key from the source map. - Type reflect.Type // expected type of elem - Index int // index for multi-value fields; -1 for single-value fields. - Err error // low-level error (when it exists) -} - -func (e ConversionError) Error() string { - var output string - - if e.Index < 0 { - output = fmt.Sprintf("schema: error converting value for %q", e.Key) - } else { - output = fmt.Sprintf("schema: error converting value for index %d of %q", - e.Index, e.Key) - } - - if e.Err != nil { - output = fmt.Sprintf("%s. Details: %s", output, e.Err) - } - - return output -} - -// UnknownKeyError stores information about an unknown key in the source map. -type UnknownKeyError struct { - Key string // key from the source map. -} - -func (e UnknownKeyError) Error() string { - return fmt.Sprintf("schema: invalid path %q", e.Key) -} - -// EmptyFieldError stores information about an empty required field. -type EmptyFieldError struct { - Key string // required key in the source map. -} - -func (e EmptyFieldError) Error() string { - return fmt.Sprintf("%v is empty", e.Key) -} - -// MultiError stores multiple decoding errors. -// -// Borrowed from the App Engine SDK. -type MultiError map[string]error - -func (e MultiError) Error() string { - s := "" - for _, err := range e { - s = err.Error() - break - } - switch len(e) { - case 0: - return "(0 errors)" - case 1: - return s - case 2: - return s + " (and 1 other error)" - } - return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1) -} - -func (e MultiError) merge(errors MultiError) { - for key, err := range errors { - if e[key] == nil { - e[key] = err - } - } -} diff --git a/schema/decoder_test.go b/schema/decoder_test.go deleted file mode 100644 index f89a4c3..0000000 --- a/schema/decoder_test.go +++ /dev/null @@ -1,2057 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package schema - -import ( - "encoding/hex" - "errors" - "reflect" - "strings" - "testing" - "time" -) - -type IntAlias int - -type rudeBool bool - -func (id *rudeBool) UnmarshalText(text []byte) error { - value := string(text) - switch { - case strings.EqualFold("Yup", value): - *id = true - case strings.EqualFold("Nope", value): - *id = false - default: - return errors.New("value must be yup or nope") - } - return nil -} - -// All cases we want to cover, in a nutshell. -type S1 struct { - F01 int `schema:"f1"` - F02 *int `schema:"f2"` - F03 []int `schema:"f3"` - F04 []*int `schema:"f4"` - F05 *[]int `schema:"f5"` - F06 *[]*int `schema:"f6"` - F07 S2 `schema:"f7"` - F08 *S1 `schema:"f8"` - F09 int `schema:"-"` - F10 []S1 `schema:"f10"` - F11 []*S1 `schema:"f11"` - F12 *[]S1 `schema:"f12"` - F13 *[]*S1 `schema:"f13"` - F14 int `schema:"f14"` - F15 IntAlias `schema:"f15"` - F16 []IntAlias `schema:"f16"` - F17 S19 `schema:"f17"` - F18 rudeBool `schema:"f18"` - F19 *rudeBool `schema:"f19"` - F20 []rudeBool `schema:"f20"` - F21 []*rudeBool `schema:"f21"` -} - -type S2 struct { - F01 *[]*int `schema:"f1"` -} - -type S19 [2]byte - -func (id *S19) UnmarshalText(text []byte) error { - buf, err := hex.DecodeString(string(text)) - if err != nil { - return err - } - if len(buf) > len(*id) { - return errors.New("out of range") - } - copy((*id)[:], buf) - return nil -} - -func TestAll(t *testing.T) { - v := map[string][]string{ - "f1": {"1"}, - "f2": {"2"}, - "f3": {"31", "32"}, - "f4": {"41", "42"}, - "f5": {"51", "52"}, - "f6": {"61", "62"}, - "f7.f1": {"71", "72"}, - "f8.f8.f7.f1": {"81", "82"}, - "f9": {"9"}, - "f10.0.f10.0.f6": {"101", "102"}, - "f10.0.f10.1.f6": {"103", "104"}, - "f11.0.f11.0.f6": {"111", "112"}, - "f11.0.f11.1.f6": {"113", "114"}, - "f12.0.f12.0.f6": {"121", "122"}, - "f12.0.f12.1.f6": {"123", "124"}, - "f13.0.f13.0.f6": {"131", "132"}, - "f13.0.f13.1.f6": {"133", "134"}, - "f14": {}, - "f15": {"151"}, - "f16": {"161", "162"}, - "f17": {"1a2b"}, - "f18": {"yup"}, - "f19": {"nope"}, - "f20": {"nope", "yup"}, - "f21": {"yup", "nope"}, - } - f2 := 2 - f41, f42 := 41, 42 - f61, f62 := 61, 62 - f71, f72 := 71, 72 - f81, f82 := 81, 82 - f101, f102, f103, f104 := 101, 102, 103, 104 - f111, f112, f113, f114 := 111, 112, 113, 114 - f121, f122, f123, f124 := 121, 122, 123, 124 - f131, f132, f133, f134 := 131, 132, 133, 134 - var f151 IntAlias = 151 - var f161, f162 IntAlias = 161, 162 - var f152, f153 rudeBool = true, false - e := S1{ - F01: 1, - F02: &f2, - F03: []int{31, 32}, - F04: []*int{&f41, &f42}, - F05: &[]int{51, 52}, - F06: &[]*int{&f61, &f62}, - F07: S2{ - F01: &[]*int{&f71, &f72}, - }, - F08: &S1{ - F08: &S1{ - F07: S2{ - F01: &[]*int{&f81, &f82}, - }, - }, - }, - F09: 0, - F10: []S1{ - S1{ - F10: []S1{ - S1{F06: &[]*int{&f101, &f102}}, - S1{F06: &[]*int{&f103, &f104}}, - }, - }, - }, - F11: []*S1{ - &S1{ - F11: []*S1{ - &S1{F06: &[]*int{&f111, &f112}}, - &S1{F06: &[]*int{&f113, &f114}}, - }, - }, - }, - F12: &[]S1{ - S1{ - F12: &[]S1{ - S1{F06: &[]*int{&f121, &f122}}, - S1{F06: &[]*int{&f123, &f124}}, - }, - }, - }, - F13: &[]*S1{ - &S1{ - F13: &[]*S1{ - &S1{F06: &[]*int{&f131, &f132}}, - &S1{F06: &[]*int{&f133, &f134}}, - }, - }, - }, - F14: 0, - F15: f151, - F16: []IntAlias{f161, f162}, - F17: S19{0x1a, 0x2b}, - F18: f152, - F19: &f153, - F20: []rudeBool{f153, f152}, - F21: []*rudeBool{&f152, &f153}, - } - - s := &S1{} - _ = NewDecoder().Decode(s, v) - - vals := func(values []*int) []int { - r := make([]int, len(values)) - for k, v := range values { - r[k] = *v - } - return r - } - - if s.F01 != e.F01 { - t.Errorf("f1: expected %v, got %v", e.F01, s.F01) - } - if s.F02 == nil { - t.Errorf("f2: expected %v, got nil", *e.F02) - } else if *s.F02 != *e.F02 { - t.Errorf("f2: expected %v, got %v", *e.F02, *s.F02) - } - if s.F03 == nil { - t.Errorf("f3: expected %v, got nil", e.F03) - } else if len(s.F03) != 2 || s.F03[0] != e.F03[0] || s.F03[1] != e.F03[1] { - t.Errorf("f3: expected %v, got %v", e.F03, s.F03) - } - if s.F04 == nil { - t.Errorf("f4: expected %v, got nil", e.F04) - } else { - if len(s.F04) != 2 || *(s.F04)[0] != *(e.F04)[0] || *(s.F04)[1] != *(e.F04)[1] { - t.Errorf("f4: expected %v, got %v", vals(e.F04), vals(s.F04)) - } - } - if s.F05 == nil { - t.Errorf("f5: expected %v, got nil", e.F05) - } else { - sF05, eF05 := *s.F05, *e.F05 - if len(sF05) != 2 || sF05[0] != eF05[0] || sF05[1] != eF05[1] { - t.Errorf("f5: expected %v, got %v", eF05, sF05) - } - } - if s.F06 == nil { - t.Errorf("f6: expected %v, got nil", vals(*e.F06)) - } else { - sF06, eF06 := *s.F06, *e.F06 - if len(sF06) != 2 || *(sF06)[0] != *(eF06)[0] || *(sF06)[1] != *(eF06)[1] { - t.Errorf("f6: expected %v, got %v", vals(eF06), vals(sF06)) - } - } - if s.F07.F01 == nil { - t.Errorf("f7.f1: expected %v, got nil", vals(*e.F07.F01)) - } else { - sF07, eF07 := *s.F07.F01, *e.F07.F01 - if len(sF07) != 2 || *(sF07)[0] != *(eF07)[0] || *(sF07)[1] != *(eF07)[1] { - t.Errorf("f7.f1: expected %v, got %v", vals(eF07), vals(sF07)) - } - } - if s.F08 == nil { - t.Errorf("f8: got nil") - } else if s.F08.F08 == nil { - t.Errorf("f8.f8: got nil") - } else if s.F08.F08.F07.F01 == nil { - t.Errorf("f8.f8.f7.f1: expected %v, got nil", vals(*e.F08.F08.F07.F01)) - } else { - sF08, eF08 := *s.F08.F08.F07.F01, *e.F08.F08.F07.F01 - if len(sF08) != 2 || *(sF08)[0] != *(eF08)[0] || *(sF08)[1] != *(eF08)[1] { - t.Errorf("f8.f8.f7.f1: expected %v, got %v", vals(eF08), vals(sF08)) - } - } - if s.F09 != e.F09 { - t.Errorf("f9: expected %v, got %v", e.F09, s.F09) - } - if s.F10 == nil { - t.Errorf("f10: got nil") - } else if len(s.F10) != 1 { - t.Errorf("f10: expected 1 element, got %v", s.F10) - } else { - if len(s.F10[0].F10) != 2 { - t.Errorf("f10.0.f10: expected 1 element, got %v", s.F10[0].F10) - } else { - sF10, eF10 := *s.F10[0].F10[0].F06, *e.F10[0].F10[0].F06 - if sF10 == nil { - t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) - } else { - if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { - t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) - } - } - sF10, eF10 = *s.F10[0].F10[1].F06, *e.F10[0].F10[1].F06 - if sF10 == nil { - t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) - } else { - if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { - t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) - } - } - } - } - if s.F11 == nil { - t.Errorf("f11: got nil") - } else if len(s.F11) != 1 { - t.Errorf("f11: expected 1 element, got %v", s.F11) - } else { - if len(s.F11[0].F11) != 2 { - t.Errorf("f11.0.f11: expected 1 element, got %v", s.F11[0].F11) - } else { - sF11, eF11 := *s.F11[0].F11[0].F06, *e.F11[0].F11[0].F06 - if sF11 == nil { - t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) - } else { - if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { - t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) - } - } - sF11, eF11 = *s.F11[0].F11[1].F06, *e.F11[0].F11[1].F06 - if sF11 == nil { - t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) - } else { - if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { - t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) - } - } - } - } - if s.F12 == nil { - t.Errorf("f12: got nil") - } else if len(*s.F12) != 1 { - t.Errorf("f12: expected 1 element, got %v", *s.F12) - } else { - sF12, eF12 := *(s.F12), *(e.F12) - if len(*sF12[0].F12) != 2 { - t.Errorf("f12.0.f12: expected 1 element, got %v", *sF12[0].F12) - } else { - sF122, eF122 := *(*sF12[0].F12)[0].F06, *(*eF12[0].F12)[0].F06 - if sF122 == nil { - t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) - } else { - if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { - t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) - } - } - sF122, eF122 = *(*sF12[0].F12)[1].F06, *(*eF12[0].F12)[1].F06 - if sF122 == nil { - t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) - } else { - if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { - t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) - } - } - } - } - if s.F13 == nil { - t.Errorf("f13: got nil") - } else if len(*s.F13) != 1 { - t.Errorf("f13: expected 1 element, got %v", *s.F13) - } else { - sF13, eF13 := *(s.F13), *(e.F13) - if len(*sF13[0].F13) != 2 { - t.Errorf("f13.0.f13: expected 1 element, got %v", *sF13[0].F13) - } else { - sF132, eF132 := *(*sF13[0].F13)[0].F06, *(*eF13[0].F13)[0].F06 - if sF132 == nil { - t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) - } else { - if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { - t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) - } - } - sF132, eF132 = *(*sF13[0].F13)[1].F06, *(*eF13[0].F13)[1].F06 - if sF132 == nil { - t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) - } else { - if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { - t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) - } - } - } - } - if s.F14 != e.F14 { - t.Errorf("f14: expected %v, got %v", e.F14, s.F14) - } - if s.F15 != e.F15 { - t.Errorf("f15: expected %v, got %v", e.F15, s.F15) - } - if s.F16 == nil { - t.Errorf("f16: nil") - } else if len(s.F16) != len(e.F16) { - t.Errorf("f16: expected len %d, got %d", len(e.F16), len(s.F16)) - } else if !reflect.DeepEqual(s.F16, e.F16) { - t.Errorf("f16: expected %v, got %v", e.F16, s.F16) - } - if s.F17 != e.F17 { - t.Errorf("f17: expected %v, got %v", e.F17, s.F17) - } - if s.F18 != e.F18 { - t.Errorf("f18: expected %v, got %v", e.F18, s.F18) - } - if *s.F19 != *e.F19 { - t.Errorf("f19: expected %v, got %v", *e.F19, *s.F19) - } - if s.F20 == nil { - t.Errorf("f20: nil") - } else if len(s.F20) != len(e.F20) { - t.Errorf("f20: expected %v, got %v", e.F20, s.F20) - } else if !reflect.DeepEqual(s.F20, e.F20) { - t.Errorf("f20: expected %v, got %v", e.F20, s.F20) - } - if s.F21 == nil { - t.Errorf("f21: nil") - } else if len(s.F21) != len(e.F21) { - t.Errorf("f21: expected length %d, got %d", len(e.F21), len(s.F21)) - } else if !reflect.DeepEqual(s.F21, e.F21) { - t.Errorf("f21: expected %v, got %v", e.F21, s.F21) - } -} - -func BenchmarkAll(b *testing.B) { - v := map[string][]string{ - "f1": {"1"}, - "f2": {"2"}, - "f3": {"31", "32"}, - "f4": {"41", "42"}, - "f5": {"51", "52"}, - "f6": {"61", "62"}, - "f7.f1": {"71", "72"}, - "f8.f8.f7.f1": {"81", "82"}, - "f9": {"9"}, - "f10.0.f10.0.f6": {"101", "102"}, - "f10.0.f10.1.f6": {"103", "104"}, - "f11.0.f11.0.f6": {"111", "112"}, - "f11.0.f11.1.f6": {"113", "114"}, - "f12.0.f12.0.f6": {"121", "122"}, - "f12.0.f12.1.f6": {"123", "124"}, - "f13.0.f13.0.f6": {"131", "132"}, - "f13.0.f13.1.f6": {"133", "134"}, - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - s := &S1{} - _ = NewDecoder().Decode(s, v) - } -} - -// ---------------------------------------------------------------------------- - -type S3 struct { - F01 bool - F02 float32 - F03 float64 - F04 int - F05 int8 - F06 int16 - F07 int32 - F08 int64 - F09 string - F10 uint - F11 uint8 - F12 uint16 - F13 uint32 - F14 uint64 -} - -func TestDefaultConverters(t *testing.T) { - v := map[string][]string{ - "F01": {"true"}, - "F02": {"4.2"}, - "F03": {"4.3"}, - "F04": {"-42"}, - "F05": {"-43"}, - "F06": {"-44"}, - "F07": {"-45"}, - "F08": {"-46"}, - "F09": {"foo"}, - "F10": {"42"}, - "F11": {"43"}, - "F12": {"44"}, - "F13": {"45"}, - "F14": {"46"}, - } - e := S3{ - F01: true, - F02: 4.2, - F03: 4.3, - F04: -42, - F05: -43, - F06: -44, - F07: -45, - F08: -46, - F09: "foo", - F10: 42, - F11: 43, - F12: 44, - F13: 45, - F14: 46, - } - s := &S3{} - _ = NewDecoder().Decode(s, v) - if s.F01 != e.F01 { - t.Errorf("F01: expected %v, got %v", e.F01, s.F01) - } - if s.F02 != e.F02 { - t.Errorf("F02: expected %v, got %v", e.F02, s.F02) - } - if s.F03 != e.F03 { - t.Errorf("F03: expected %v, got %v", e.F03, s.F03) - } - if s.F04 != e.F04 { - t.Errorf("F04: expected %v, got %v", e.F04, s.F04) - } - if s.F05 != e.F05 { - t.Errorf("F05: expected %v, got %v", e.F05, s.F05) - } - if s.F06 != e.F06 { - t.Errorf("F06: expected %v, got %v", e.F06, s.F06) - } - if s.F07 != e.F07 { - t.Errorf("F07: expected %v, got %v", e.F07, s.F07) - } - if s.F08 != e.F08 { - t.Errorf("F08: expected %v, got %v", e.F08, s.F08) - } - if s.F09 != e.F09 { - t.Errorf("F09: expected %v, got %v", e.F09, s.F09) - } - if s.F10 != e.F10 { - t.Errorf("F10: expected %v, got %v", e.F10, s.F10) - } - if s.F11 != e.F11 { - t.Errorf("F11: expected %v, got %v", e.F11, s.F11) - } - if s.F12 != e.F12 { - t.Errorf("F12: expected %v, got %v", e.F12, s.F12) - } - if s.F13 != e.F13 { - t.Errorf("F13: expected %v, got %v", e.F13, s.F13) - } - if s.F14 != e.F14 { - t.Errorf("F14: expected %v, got %v", e.F14, s.F14) - } -} - -func TestOn(t *testing.T) { - v := map[string][]string{ - "F01": {"on"}, - } - s := S3{} - err := NewDecoder().Decode(&s, v) - if err != nil { - t.Fatal(err) - } - if !s.F01 { - t.Fatal("Value was not set to true") - } -} - -// ---------------------------------------------------------------------------- - -func TestInlineStruct(t *testing.T) { - s1 := &struct { - F01 bool - }{} - s2 := &struct { - F01 int - }{} - v1 := map[string][]string{ - "F01": {"true"}, - } - v2 := map[string][]string{ - "F01": {"42"}, - } - decoder := NewDecoder() - _ = decoder.Decode(s1, v1) - if s1.F01 != true { - t.Errorf("s1: expected %v, got %v", true, s1.F01) - } - _ = decoder.Decode(s2, v2) - if s2.F01 != 42 { - t.Errorf("s2: expected %v, got %v", 42, s2.F01) - } -} - -// ---------------------------------------------------------------------------- - -type Foo struct { - F01 int - F02 Bar - Bif []Baz -} - -type Bar struct { - F01 string - F02 string - F03 string - F14 string - S05 string - Str string -} - -type Baz struct { - F99 []string -} - -func TestSimpleExample(t *testing.T) { - data := map[string][]string{ - "F01": {"1"}, - "F02.F01": {"S1"}, - "F02.F02": {"S2"}, - "F02.F03": {"S3"}, - "F02.F14": {"S4"}, - "F02.S05": {"S5"}, - "F02.Str": {"Str"}, - "Bif.0.F99": {"A", "B", "C"}, - } - - e := &Foo{ - F01: 1, - F02: Bar{ - F01: "S1", - F02: "S2", - F03: "S3", - F14: "S4", - S05: "S5", - Str: "Str", - }, - Bif: []Baz{{ - F99: []string{"A", "B", "C"}}, - }, - } - - s := &Foo{} - _ = NewDecoder().Decode(s, data) - - if s.F01 != e.F01 { - t.Errorf("F01: expected %v, got %v", e.F01, s.F01) - } - if s.F02.F01 != e.F02.F01 { - t.Errorf("F02.F01: expected %v, got %v", e.F02.F01, s.F02.F01) - } - if s.F02.F02 != e.F02.F02 { - t.Errorf("F02.F02: expected %v, got %v", e.F02.F02, s.F02.F02) - } - if s.F02.F03 != e.F02.F03 { - t.Errorf("F02.F03: expected %v, got %v", e.F02.F03, s.F02.F03) - } - if s.F02.F14 != e.F02.F14 { - t.Errorf("F02.F14: expected %v, got %v", e.F02.F14, s.F02.F14) - } - if s.F02.S05 != e.F02.S05 { - t.Errorf("F02.S05: expected %v, got %v", e.F02.S05, s.F02.S05) - } - if s.F02.Str != e.F02.Str { - t.Errorf("F02.Str: expected %v, got %v", e.F02.Str, s.F02.Str) - } - if len(s.Bif) != len(e.Bif) { - t.Errorf("Bif len: expected %d, got %d", len(e.Bif), len(s.Bif)) - } else { - if len(s.Bif[0].F99) != len(e.Bif[0].F99) { - t.Errorf("Bif[0].F99 len: expected %d, got %d", len(e.Bif[0].F99), len(s.Bif[0].F99)) - } - } -} - -// ---------------------------------------------------------------------------- - -type S4 struct { - F01 int64 - F02 float64 - F03 bool - F04 rudeBool -} - -func TestConversionError(t *testing.T) { - data := map[string][]string{ - "F01": {"foo"}, - "F02": {"bar"}, - "F03": {"baz"}, - "F04": {"not-a-yes-or-nope"}, - } - s := &S4{} - e := NewDecoder().Decode(s, data) - - m := e.(MultiError) - if len(m) != 4 { - t.Errorf("Expected 3 errors, got %v", m) - } -} - -// ---------------------------------------------------------------------------- - -type S5 struct { - F01 []string -} - -func TestEmptyValue(t *testing.T) { - data := map[string][]string{ - "F01": {"", "foo"}, - } - s := &S5{} - err := NewDecoder().Decode(s, data) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if len(s.F01) != 1 { - t.Errorf("Expected 1 values in F01") - } -} - -func TestEmptyValueZeroEmpty(t *testing.T) { - data := map[string][]string{ - "F01": {"", "foo"}, - } - s := S5{} - d := NewDecoder() - d.ZeroEmpty(true) - err := d.Decode(&s, data) - if err != nil { - t.Fatal(err) - } - if len(s.F01) != 2 { - t.Errorf("Expected 1 values in F01") - } -} - -// ---------------------------------------------------------------------------- - -type S6 struct { - id string -} - -func TestUnexportedField(t *testing.T) { - data := map[string][]string{ - "id": {"identifier"}, - } - s := &S6{} - err := NewDecoder().Decode(s, data) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if s.id != "" { - t.Errorf("Unexported field expected to be ignored") - } -} - -// ---------------------------------------------------------------------------- - -type S7 struct { - ID string -} - -func TestMultipleValues(t *testing.T) { - data := map[string][]string{ - "ID": {"0", "1"}, - } - - s := S7{} - err := NewDecoder().Decode(&s, data) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if s.ID != "1" { - t.Errorf("Last defined value must be used when multiple values for same field are provided") - } -} - -type S8 struct { - ID string `json:"id"` -} - -func TestSetAliasTag(t *testing.T) { - data := map[string][]string{ - "id": {"foo"}, - } - - s := S8{} - dec := NewDecoder() - dec.SetAliasTag("json") - err := dec.Decode(&s, data) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if s.ID != "foo" { - t.Fatalf("Bad value: got %q, want %q", s.ID, "foo") - } -} - -func TestZeroEmpty(t *testing.T) { - data := map[string][]string{ - "F01": {""}, - "F03": {"true"}, - } - s := S4{1, 1, false, false} - d := NewDecoder() - d.ZeroEmpty(true) - - err := d.Decode(&s, data) - if err != nil { - t.Fatal(err) - } - if s.F01 != 0 { - t.Errorf("F01: got %v, want %v", s.F01, 0) - } - if s.F02 != 1 { - t.Errorf("F02: got %v, want %v", s.F02, 1) - } - if s.F03 != true { - t.Errorf("F03: got %v, want %v", s.F03, true) - } -} - -func TestNoZeroEmpty(t *testing.T) { - data := map[string][]string{ - "F01": {""}, - "F03": {"true"}, - } - s := S4{1, 1, false, false} - d := NewDecoder() - d.ZeroEmpty(false) - err := d.Decode(&s, data) - if err != nil { - t.Fatal(err) - } - if s.F01 != 1 { - t.Errorf("F01: got %v, want %v", s.F01, 1) - } - if s.F02 != 1 { - t.Errorf("F02: got %v, want %v", s.F02, 1) - } - if s.F03 != true { - t.Errorf("F03: got %v, want %v", s.F03, true) - } - if s.F04 != false { - t.Errorf("F04: got %v, want %v", s.F04, false) - } -} - -// ---------------------------------------------------------------------------- - -type S9 struct { - Id string -} - -type S10 struct { - S9 -} - -func TestEmbeddedField(t *testing.T) { - data := map[string][]string{ - "Id": {"identifier"}, - } - s := &S10{} - err := NewDecoder().Decode(s, data) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if s.Id != "identifier" { - t.Errorf("Missing support for embedded fields") - } -} - -type S11 struct { - S10 -} - -func TestMultipleLevelEmbeddedField(t *testing.T) { - data := map[string][]string{ - "Id": {"identifier"}, - } - s := &S11{} - err := NewDecoder().Decode(s, data) - if s.Id != "identifier" { - t.Errorf("Missing support for multiple-level embedded fields (%v)", err) - } -} - -func TestInvalidPath(t *testing.T) { - data := map[string][]string{ - "Foo.Bar": {"baz"}, - } - s := S9{} - err := NewDecoder().Decode(&s, data) - expectedErr := `schema: invalid path "Foo.Bar"` - if err.Error() != expectedErr { - t.Fatalf("got %q, want %q", err, expectedErr) - } -} - -func TestInvalidPathIgnoreUnknownKeys(t *testing.T) { - data := map[string][]string{ - "Foo.Bar": {"baz"}, - } - s := S9{} - dec := NewDecoder() - dec.IgnoreUnknownKeys(true) - err := dec.Decode(&s, data) - if err != nil { - t.Fatal(err) - } -} - -// ---------------------------------------------------------------------------- - -type S1NT struct { - F1 int - F2 *int - F3 []int - F4 []*int - F5 *[]int - F6 *[]*int - F7 S2 - F8 *S1 - F9 int `schema:"-"` - F10 []S1 - F11 []*S1 - F12 *[]S1 - F13 *[]*S1 -} - -func TestAllNT(t *testing.T) { - v := map[string][]string{ - "f1": {"1"}, - "f2": {"2"}, - "f3": {"31", "32"}, - "f4": {"41", "42"}, - "f5": {"51", "52"}, - "f6": {"61", "62"}, - "f7.f1": {"71", "72"}, - "f8.f8.f7.f1": {"81", "82"}, - "f9": {"9"}, - "f10.0.f10.0.f6": {"101", "102"}, - "f10.0.f10.1.f6": {"103", "104"}, - "f11.0.f11.0.f6": {"111", "112"}, - "f11.0.f11.1.f6": {"113", "114"}, - "f12.0.f12.0.f6": {"121", "122"}, - "f12.0.f12.1.f6": {"123", "124"}, - "f13.0.f13.0.f6": {"131", "132"}, - "f13.0.f13.1.f6": {"133", "134"}, - } - f2 := 2 - f41, f42 := 41, 42 - f61, f62 := 61, 62 - f71, f72 := 71, 72 - f81, f82 := 81, 82 - f101, f102, f103, f104 := 101, 102, 103, 104 - f111, f112, f113, f114 := 111, 112, 113, 114 - f121, f122, f123, f124 := 121, 122, 123, 124 - f131, f132, f133, f134 := 131, 132, 133, 134 - e := S1NT{ - F1: 1, - F2: &f2, - F3: []int{31, 32}, - F4: []*int{&f41, &f42}, - F5: &[]int{51, 52}, - F6: &[]*int{&f61, &f62}, - F7: S2{ - F01: &[]*int{&f71, &f72}, - }, - F8: &S1{ - F08: &S1{ - F07: S2{ - F01: &[]*int{&f81, &f82}, - }, - }, - }, - F9: 0, - F10: []S1{ - S1{ - F10: []S1{ - S1{F06: &[]*int{&f101, &f102}}, - S1{F06: &[]*int{&f103, &f104}}, - }, - }, - }, - F11: []*S1{ - &S1{ - F11: []*S1{ - &S1{F06: &[]*int{&f111, &f112}}, - &S1{F06: &[]*int{&f113, &f114}}, - }, - }, - }, - F12: &[]S1{ - S1{ - F12: &[]S1{ - S1{F06: &[]*int{&f121, &f122}}, - S1{F06: &[]*int{&f123, &f124}}, - }, - }, - }, - F13: &[]*S1{ - &S1{ - F13: &[]*S1{ - &S1{F06: &[]*int{&f131, &f132}}, - &S1{F06: &[]*int{&f133, &f134}}, - }, - }, - }, - } - - s := &S1NT{} - _ = NewDecoder().Decode(s, v) - - vals := func(values []*int) []int { - r := make([]int, len(values)) - for k, v := range values { - r[k] = *v - } - return r - } - - if s.F1 != e.F1 { - t.Errorf("f1: expected %v, got %v", e.F1, s.F1) - } - if s.F2 == nil { - t.Errorf("f2: expected %v, got nil", *e.F2) - } else if *s.F2 != *e.F2 { - t.Errorf("f2: expected %v, got %v", *e.F2, *s.F2) - } - if s.F3 == nil { - t.Errorf("f3: expected %v, got nil", e.F3) - } else if len(s.F3) != 2 || s.F3[0] != e.F3[0] || s.F3[1] != e.F3[1] { - t.Errorf("f3: expected %v, got %v", e.F3, s.F3) - } - if s.F4 == nil { - t.Errorf("f4: expected %v, got nil", e.F4) - } else { - if len(s.F4) != 2 || *(s.F4)[0] != *(e.F4)[0] || *(s.F4)[1] != *(e.F4)[1] { - t.Errorf("f4: expected %v, got %v", vals(e.F4), vals(s.F4)) - } - } - if s.F5 == nil { - t.Errorf("f5: expected %v, got nil", e.F5) - } else { - sF5, eF5 := *s.F5, *e.F5 - if len(sF5) != 2 || sF5[0] != eF5[0] || sF5[1] != eF5[1] { - t.Errorf("f5: expected %v, got %v", eF5, sF5) - } - } - if s.F6 == nil { - t.Errorf("f6: expected %v, got nil", vals(*e.F6)) - } else { - sF6, eF6 := *s.F6, *e.F6 - if len(sF6) != 2 || *(sF6)[0] != *(eF6)[0] || *(sF6)[1] != *(eF6)[1] { - t.Errorf("f6: expected %v, got %v", vals(eF6), vals(sF6)) - } - } - if s.F7.F01 == nil { - t.Errorf("f7.f1: expected %v, got nil", vals(*e.F7.F01)) - } else { - sF7, eF7 := *s.F7.F01, *e.F7.F01 - if len(sF7) != 2 || *(sF7)[0] != *(eF7)[0] || *(sF7)[1] != *(eF7)[1] { - t.Errorf("f7.f1: expected %v, got %v", vals(eF7), vals(sF7)) - } - } - if s.F8 == nil { - t.Errorf("f8: got nil") - } else if s.F8.F08 == nil { - t.Errorf("f8.f8: got nil") - } else if s.F8.F08.F07.F01 == nil { - t.Errorf("f8.f8.f7.f1: expected %v, got nil", vals(*e.F8.F08.F07.F01)) - } else { - sF8, eF8 := *s.F8.F08.F07.F01, *e.F8.F08.F07.F01 - if len(sF8) != 2 || *(sF8)[0] != *(eF8)[0] || *(sF8)[1] != *(eF8)[1] { - t.Errorf("f8.f8.f7.f1: expected %v, got %v", vals(eF8), vals(sF8)) - } - } - if s.F9 != e.F9 { - t.Errorf("f9: expected %v, got %v", e.F9, s.F9) - } - if s.F10 == nil { - t.Errorf("f10: got nil") - } else if len(s.F10) != 1 { - t.Errorf("f10: expected 1 element, got %v", s.F10) - } else { - if len(s.F10[0].F10) != 2 { - t.Errorf("f10.0.f10: expected 1 element, got %v", s.F10[0].F10) - } else { - sF10, eF10 := *s.F10[0].F10[0].F06, *e.F10[0].F10[0].F06 - if sF10 == nil { - t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) - } else { - if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { - t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) - } - } - sF10, eF10 = *s.F10[0].F10[1].F06, *e.F10[0].F10[1].F06 - if sF10 == nil { - t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) - } else { - if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { - t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) - } - } - } - } - if s.F11 == nil { - t.Errorf("f11: got nil") - } else if len(s.F11) != 1 { - t.Errorf("f11: expected 1 element, got %v", s.F11) - } else { - if len(s.F11[0].F11) != 2 { - t.Errorf("f11.0.f11: expected 1 element, got %v", s.F11[0].F11) - } else { - sF11, eF11 := *s.F11[0].F11[0].F06, *e.F11[0].F11[0].F06 - if sF11 == nil { - t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) - } else { - if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { - t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) - } - } - sF11, eF11 = *s.F11[0].F11[1].F06, *e.F11[0].F11[1].F06 - if sF11 == nil { - t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) - } else { - if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { - t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) - } - } - } - } - if s.F12 == nil { - t.Errorf("f12: got nil") - } else if len(*s.F12) != 1 { - t.Errorf("f12: expected 1 element, got %v", *s.F12) - } else { - sF12, eF12 := *(s.F12), *(e.F12) - if len(*sF12[0].F12) != 2 { - t.Errorf("f12.0.f12: expected 1 element, got %v", *sF12[0].F12) - } else { - sF122, eF122 := *(*sF12[0].F12)[0].F06, *(*eF12[0].F12)[0].F06 - if sF122 == nil { - t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) - } else { - if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { - t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) - } - } - sF122, eF122 = *(*sF12[0].F12)[1].F06, *(*eF12[0].F12)[1].F06 - if sF122 == nil { - t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) - } else { - if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { - t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) - } - } - } - } - if s.F13 == nil { - t.Errorf("f13: got nil") - } else if len(*s.F13) != 1 { - t.Errorf("f13: expected 1 element, got %v", *s.F13) - } else { - sF13, eF13 := *(s.F13), *(e.F13) - if len(*sF13[0].F13) != 2 { - t.Errorf("f13.0.f13: expected 1 element, got %v", *sF13[0].F13) - } else { - sF132, eF132 := *(*sF13[0].F13)[0].F06, *(*eF13[0].F13)[0].F06 - if sF132 == nil { - t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) - } else { - if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { - t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) - } - } - sF132, eF132 = *(*sF13[0].F13)[1].F06, *(*eF13[0].F13)[1].F06 - if sF132 == nil { - t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) - } else { - if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { - t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) - } - } - } - } -} - -// ---------------------------------------------------------------------------- - -type S12A struct { - ID []int -} - -func TestCSVSlice(t *testing.T) { - data := map[string][]string{ - "ID": {"0,1"}, - } - - s := S12A{} - err := NewDecoder().Decode(&s, data) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if len(s.ID) != 2 { - t.Errorf("Expected two values in the result list, got %+v", s.ID) - } - if s.ID[0] != 0 || s.ID[1] != 1 { - t.Errorf("Expected []{0, 1} got %+v", s) - } -} - -type S12B struct { - ID []string -} - -// Decode should not split on , into a slice for string only -func TestCSVStringSlice(t *testing.T) { - data := map[string][]string{ - "ID": {"0,1"}, - } - - s := S12B{} - err := NewDecoder().Decode(&s, data) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if len(s.ID) != 1 { - t.Errorf("Expected one value in the result list, got %+v", s.ID) - } - if s.ID[0] != "0,1" { - t.Errorf("Expected []{0, 1} got %+v", s) - } -} - -// Invalid data provided by client should not panic (github issue 33) -func TestInvalidDataProvidedByClient(t *testing.T) { - defer func() { - if r := recover(); r != nil { - t.Errorf("Panicked calling decoder.Decode: %v", r) - } - }() - - type S struct { - f string // nolint:unused - } - - data := map[string][]string{ - "f.f": {"v"}, - } - - err := NewDecoder().Decode(new(S), data) - if err == nil { - t.Errorf("invalid path in decoder.Decode should return an error.") - } -} - -// underlying cause of error in issue 33 -func TestInvalidPathInCacheParsePath(t *testing.T) { - type S struct { - f string // nolint:unused - } - - typ := reflect.ValueOf(new(S)).Elem().Type() - c := newCache() - _, err := c.parsePath("f.f", typ) - if err == nil { - t.Errorf("invalid path in cache.parsePath should return an error.") - } -} - -// issue 32 -func TestDecodeToTypedField(t *testing.T) { - type Aa bool - s1 := &struct{ Aa }{} - v1 := map[string][]string{"Aa": {"true"}} - err := NewDecoder().Decode(s1, v1) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - if s1.Aa != Aa(true) { - t.Errorf("s1: expected %v, got %v", true, s1.Aa) - } -} - -// issue 37 -func TestRegisterConverter(t *testing.T) { - type Aa int - type Bb int - s1 := &struct { - Aa - Bb - }{} - decoder := NewDecoder() - - decoder.RegisterConverter(s1.Aa, func(s string) reflect.Value { return reflect.ValueOf(1) }) - decoder.RegisterConverter(s1.Bb, func(s string) reflect.Value { return reflect.ValueOf(2) }) - - v1 := map[string][]string{"Aa": {"4"}, "Bb": {"5"}} - err := decoder.Decode(s1, v1) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - - if s1.Aa != Aa(1) { - t.Errorf("s1.Aa: expected %v, got %v", 1, s1.Aa) - } - if s1.Bb != Bb(2) { - t.Errorf("s1.Bb: expected %v, got %v", 2, s1.Bb) - } -} - -// Issue #40 -func TestRegisterConverterSlice(t *testing.T) { - decoder := NewDecoder() - decoder.RegisterConverter([]string{}, func(input string) reflect.Value { - return reflect.ValueOf(strings.Split(input, ",")) - }) - - result := struct { - Multiple []string `schema:"multiple"` - }{} - - expected := []string{"one", "two", "three"} - err := decoder.Decode(&result, map[string][]string{ - "multiple": []string{"one,two,three"}, - }) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - for i := range expected { - if got, want := expected[i], result.Multiple[i]; got != want { - t.Errorf("%d: got %s, want %s", i, got, want) - } - } -} - -func TestRegisterConverterMap(t *testing.T) { - decoder := NewDecoder() - decoder.IgnoreUnknownKeys(false) - decoder.RegisterConverter(map[string]string{}, func(input string) reflect.Value { - m := make(map[string]string) - for _, pair := range strings.Split(input, ",") { - parts := strings.Split(pair, ":") - switch len(parts) { - case 2: - m[parts[0]] = parts[1] - } - } - return reflect.ValueOf(m) - }) - - result := struct { - Multiple map[string]string `schema:"multiple"` - }{} - - err := decoder.Decode(&result, map[string][]string{ - "multiple": []string{"a:one,b:two"}, - }) - if err != nil { - t.Fatal(err) - } - expected := map[string]string{"a": "one", "b": "two"} - for k, v := range expected { - got, ok := result.Multiple[k] - if !ok { - t.Fatalf("got %v, want %v", result.Multiple, expected) - } - if got != v { - t.Errorf("got %s, want %s", got, v) - } - } -} - -type S13 struct { - Value []S14 -} - -type S14 struct { - F1 string - F2 string -} - -func (n *S14) UnmarshalText(text []byte) error { - textParts := strings.Split(string(text), " ") - if len(textParts) < 2 { - return errors.New("Not a valid name!") - } - - n.F1, n.F2 = textParts[0], textParts[len(textParts)-1] - return nil -} - -type S15 struct { - Value []S16 -} - -type S16 struct { - F1 string - F2 string -} - -func TestCustomTypeSlice(t *testing.T) { - data := map[string][]string{ - "Value.0": []string{"Louisa May Alcott"}, - "Value.1": []string{"Florence Nightingale"}, - "Value.2": []string{"Clara Barton"}, - } - - s := S13{} - decoder := NewDecoder() - - if err := decoder.Decode(&s, data); err != nil { - t.Fatal(err) - } - - if len(s.Value) != 3 { - t.Fatalf("Expected 3 values in the result list, got %+v", s.Value) - } - if s.Value[0].F1 != "Louisa" || s.Value[0].F2 != "Alcott" { - t.Errorf("Expected S14{'Louisa', 'Alcott'} got %+v", s.Value[0]) - } - if s.Value[1].F1 != "Florence" || s.Value[1].F2 != "Nightingale" { - t.Errorf("Expected S14{'Florence', 'Nightingale'} got %+v", s.Value[1]) - } - if s.Value[2].F1 != "Clara" || s.Value[2].F2 != "Barton" { - t.Errorf("Expected S14{'Clara', 'Barton'} got %+v", s.Value[2]) - } -} - -func TestCustomTypeSliceWithError(t *testing.T) { - data := map[string][]string{ - "Value.0": []string{"Louisa May Alcott"}, - "Value.1": []string{"Florence Nightingale"}, - "Value.2": []string{"Clara"}, - } - - s := S13{} - decoder := NewDecoder() - - if err := decoder.Decode(&s, data); err == nil { - t.Error("Not detecting error in conversion") - } -} - -func TestNoTextUnmarshalerTypeSlice(t *testing.T) { - data := map[string][]string{ - "Value.0": []string{"Louisa May Alcott"}, - "Value.1": []string{"Florence Nightingale"}, - "Value.2": []string{"Clara Barton"}, - } - - s := S15{} - decoder := NewDecoder() - - if err := decoder.Decode(&s, data); err == nil { - t.Error("Not detecting when there's no converter") - } -} - -// ---------------------------------------------------------------------------- - -type S17 struct { - Value S14 -} - -type S18 struct { - Value S16 -} - -func TestCustomType(t *testing.T) { - data := map[string][]string{ - "Value": []string{"Louisa May Alcott"}, - } - - s := S17{} - decoder := NewDecoder() - - if err := decoder.Decode(&s, data); err != nil { - t.Fatal(err) - } - - if s.Value.F1 != "Louisa" || s.Value.F2 != "Alcott" { - t.Errorf("Expected S14{'Louisa', 'Alcott'} got %+v", s.Value) - } -} - -func TestCustomTypeWithError(t *testing.T) { - data := map[string][]string{ - "Value": []string{"Louisa"}, - } - - s := S17{} - decoder := NewDecoder() - - if err := decoder.Decode(&s, data); err == nil { - t.Error("Not detecting error in conversion") - } -} - -func TestNoTextUnmarshalerType(t *testing.T) { - data := map[string][]string{ - "Value": []string{"Louisa May Alcott"}, - } - - s := S18{} - decoder := NewDecoder() - - if err := decoder.Decode(&s, data); err == nil { - t.Error("Not detecting when there's no converter") - } -} - -func TestExpectedType(t *testing.T) { - data := map[string][]string{ - "bools": []string{"1", "a"}, - "date": []string{"invalid"}, - "Foo.Bar": []string{"a", "b"}, - } - - type B struct { - Bar *int - } - type A struct { - Bools []bool `schema:"bools"` - Date time.Time `schema:"date"` - Foo B - } - - a := A{} - - err := NewDecoder().Decode(&a, data) - - e := err.(MultiError)["bools"].(ConversionError) - if e.Type != reflect.TypeOf(false) && e.Index == 1 { - t.Errorf("Expected bool, index: 1 got %+v, index: %d", e.Type, e.Index) - } - e = err.(MultiError)["date"].(ConversionError) - if e.Type != reflect.TypeOf(time.Time{}) { - t.Errorf("Expected time.Time got %+v", e.Type) - } - e = err.(MultiError)["Foo.Bar"].(ConversionError) - if e.Type != reflect.TypeOf(0) { - t.Errorf("Expected int got %+v", e.Type) - } -} - -type R1 struct { - A string `schema:"a,required"` - B struct { - C int `schema:"c,required"` - D float64 `schema:"d"` - E string `schema:"e,required"` - } `schema:"b"` - F []string `schema:"f,required"` - G []int `schema:"g,othertag"` - H bool `schema:"h,required"` -} - -func TestRequiredField(t *testing.T) { - var a R1 - v := map[string][]string{ - "a": []string{"bbb"}, - "b.c": []string{"88"}, - "b.d": []string{"9"}, - "f": []string{""}, - "h": []string{"true"}, - } - err := NewDecoder().Decode(&a, v) - if err == nil { - t.Errorf("error nil, b.e is empty expect") - return - } - // b.e empty - v["b.e"] = []string{""} // empty string - err = NewDecoder().Decode(&a, v) - if err == nil { - t.Errorf("error nil, b.e is empty expect") - return - } - if expected := `b.e is empty`; err.Error() != expected { - t.Errorf("got %q, want %q", err, expected) - } - - // all fields ok - v["b.e"] = []string{"nonempty"} - err = NewDecoder().Decode(&a, v) - if err != nil { - t.Errorf("error: %v", err) - return - } - - // set f empty - v["f"] = []string{} - err = NewDecoder().Decode(&a, v) - if err == nil { - t.Errorf("error nil, f is empty expect") - return - } - if expected := `f is empty`; err.Error() != expected { - t.Errorf("got %q, want %q", err, expected) - } - v["f"] = []string{"nonempty"} - - // b.c type int with empty string - v["b.c"] = []string{""} - err = NewDecoder().Decode(&a, v) - if err == nil { - t.Errorf("error nil, b.c is empty expect") - return - } - v["b.c"] = []string{"3"} - - // h type bool with empty string - v["h"] = []string{""} - err = NewDecoder().Decode(&a, v) - if err == nil { - t.Errorf("error nil, h is empty expect") - return - } - if expected := `h is empty`; err.Error() != expected { - t.Errorf("got %q, want %q", err, expected) - } -} - -type R2 struct { - A struct { - B int `schema:"b"` - } `schema:"a,required"` -} - -func TestRequiredStructFiled(t *testing.T) { - v := map[string][]string{ - "a.b": []string{"3"}, - } - var a R2 - err := NewDecoder().Decode(&a, v) - if err != nil { - t.Errorf("error: %v", err) - } -} - -func TestRequiredFieldIsMissingCorrectError(t *testing.T) { - type RM1S struct { - A string `schema:"rm1aa,required"` - B string `schema:"rm1bb,required"` - } - type RM1 struct { - RM1S - } - - var a RM1 - v := map[string][]string{ - "rm1aa": {"aaa"}, - } - expectedError := "RM1S.rm1bb is empty" - err := NewDecoder().Decode(&a, v) - if err.Error() != expectedError { - t.Errorf("expected %v, got %v", expectedError, err) - } -} - -type AS1 struct { - A int32 `schema:"a,required"` - E int32 `schema:"e,required"` -} -type AS2 struct { - AS1 - B string `schema:"b,required"` -} -type AS3 struct { - C int32 `schema:"c"` -} - -type AS4 struct { - AS3 - D string `schema:"d"` -} - -func TestAnonymousStructField(t *testing.T) { - patterns := []map[string][]string{ - { - "a": {"1"}, - "e": {"2"}, - "b": {"abc"}, - }, - { - "AS1.a": {"1"}, - "AS1.e": {"2"}, - "b": {"abc"}, - }, - } - for _, v := range patterns { - a := AS2{} - err := NewDecoder().Decode(&a, v) - if err != nil { - t.Errorf("Decode failed %s, %#v", err, v) - continue - } - if a.A != 1 { - t.Errorf("A: expected %v, got %v", 1, a.A) - } - if a.E != 2 { - t.Errorf("E: expected %v, got %v", 2, a.E) - } - if a.B != "abc" { - t.Errorf("B: expected %v, got %v", "abc", a.B) - } - if a.AS1.A != 1 { - t.Errorf("AS1.A: expected %v, got %v", 1, a.AS1.A) - } - if a.AS1.E != 2 { - t.Errorf("AS1.E: expected %v, got %v", 2, a.AS1.E) - } - } - a := AS2{} - err := NewDecoder().Decode(&a, map[string][]string{ - "e": {"2"}, - "b": {"abc"}, - }) - if err == nil { - t.Errorf("error nil, a is empty expect") - } - patterns = []map[string][]string{ - { - "c": {"1"}, - "d": {"abc"}, - }, - { - "AS3.c": {"1"}, - "d": {"abc"}, - }, - } - for _, v := range patterns { - a := AS4{} - err := NewDecoder().Decode(&a, v) - if err != nil { - t.Errorf("Decode failed %s, %#v", err, v) - continue - } - if a.C != 1 { - t.Errorf("C: expected %v, got %v", 1, a.C) - } - if a.D != "abc" { - t.Errorf("D: expected %v, got %v", "abc", a.D) - } - if a.AS3.C != 1 { - t.Errorf("AS3.C: expected %v, got %v", 1, a.AS3.C) - } - } -} - -func TestAmbiguousStructField(t *testing.T) { - type I1 struct { - X int - } - type I2 struct { - I1 - } - type B1 struct { - X bool - } - type B2 struct { - B1 - } - type IB struct { - I1 - B1 - } - type S struct { - I1 - I2 - B1 - B2 - IB - } - dst := S{} - src := map[string][]string{ - "X": {"123"}, - "IB.X": {"123"}, - } - dec := NewDecoder() - dec.IgnoreUnknownKeys(false) - err := dec.Decode(&dst, src) - e, ok := err.(MultiError) - if !ok || len(e) != 2 { - t.Errorf("Expected 2 errors, got %#v", err) - } - if expected := (UnknownKeyError{Key: "X"}); e["X"] != expected { - t.Errorf("X: expected %#v, got %#v", expected, e["X"]) - } - if expected := (UnknownKeyError{Key: "IB.X"}); e["IB.X"] != expected { - t.Errorf("X: expected %#v, got %#v", expected, e["IB.X"]) - } - dec.IgnoreUnknownKeys(true) - err = dec.Decode(&dst, src) - if err != nil { - t.Errorf("Decode failed %v", err) - } - - expected := S{ - I1: I1{X: 123}, - I2: I2{I1: I1{X: 234}}, - B1: B1{X: true}, - B2: B2{B1: B1{X: true}}, - IB: IB{I1: I1{X: 345}, B1: B1{X: true}}, - } - patterns := []map[string][]string{ - { - "I1.X": {"123"}, - "I2.X": {"234"}, - "B1.X": {"true"}, - "B2.X": {"1"}, - "IB.I1.X": {"345"}, - "IB.B1.X": {"on"}, - }, - { - "I1.X": {"123"}, - "I2.I1.X": {"234"}, - "B1.X": {"true"}, - "B2.B1.X": {"1"}, - "IB.I1.X": {"345"}, - "IB.B1.X": {"on"}, - }, - } - for _, src := range patterns { - dst := S{} - dec := NewDecoder() - dec.IgnoreUnknownKeys(false) - err := dec.Decode(&dst, src) - if err != nil { - t.Errorf("Decode failed %v, %#v", err, src) - } - if !reflect.DeepEqual(expected, dst) { - t.Errorf("Expected %+v, got %+v", expected, dst) - } - } -} - -func TestComprehensiveDecodingErrors(t *testing.T) { - type I1 struct { - V int `schema:",required"` - P *int `schema:",required"` - } - type I2 struct { - I1 - J I1 - } - type S1 struct { - V string `schema:"v,required"` - P *string `schema:"p,required"` - } - type S2 struct { - S1 `schema:"s"` - T S1 `schema:"t"` - } - type D struct { - I2 - X S2 `schema:"x"` - Y S2 `schema:"-"` - } - patterns := []map[string][]string{ - { - "V": {"invalid"}, // invalid - "I2.I1.P": {}, // empty - "I2.J.V": {""}, // empty - "I2.J.P": {"123"}, // ok - "x.s.v": {""}, // empty - "x.s.p": {""}, // ok - "x.t.v": {"abc"}, // ok - "x.t.p": {}, // empty - "Y.s.v": {"ignored"}, // unknown - }, - { - "V": {"invalid"}, // invalid - "P": {}, // empty - "J.V": {""}, // empty - "J.P": {"123"}, // ok - "x.v": {""}, // empty - "x.p": {""}, // ok - "x.t.v": {"abc"}, // ok - "x.t.p": {}, // empty - "Y.s.v": {"ignored"}, // unknown - }, - } - for _, src := range patterns { - dst := D{} - dec := NewDecoder() - dec.IgnoreUnknownKeys(false) - err := dec.Decode(&dst, src) - e, ok := err.(MultiError) - if !ok || len(e) != 6 { - t.Errorf("Expected 6 errors, got %#v", err) - } - if cerr, ok := e["V"].(ConversionError); !ok { - t.Errorf("%s: expected %#v, got %#v", "I2.I1.V", ConversionError{Key: "V"}, cerr) - } - if key, expected := "I2.I1.P", (EmptyFieldError{Key: "I2.I1.P"}); e[key] != expected { - t.Errorf("%s: expected %#v, got %#v", key, expected, e[key]) - } - if key, expected := "I2.J.V", (EmptyFieldError{Key: "I2.J.V"}); e[key] != expected { - t.Errorf("%s: expected %#v, got %#v", key, expected, e[key]) - } - if key, expected := "x.s.v", (EmptyFieldError{Key: "x.s.v"}); e[key] != expected { - t.Errorf("%s: expected %#v, got %#v", key, expected, e[key]) - } - if key, expected := "x.t.p", (EmptyFieldError{Key: "x.t.p"}); e[key] != expected { - t.Errorf("%s: expected %#v, got %#v", key, expected, e[key]) - } - if key, expected := "Y.s.v", (UnknownKeyError{Key: "Y.s.v"}); e[key] != expected { - t.Errorf("%s: expected %#v, got %#v", key, expected, e[key]) - } - if expected := 123; dst.I2.J.P == nil || *dst.I2.J.P != expected { - t.Errorf("I2.J.P: expected %#v, got %#v", expected, dst.I2.J.P) - } - if expected := ""; dst.X.S1.P == nil || *dst.X.S1.P != expected { - t.Errorf("X.S1.P: expected %#v, got %#v", expected, dst.X.S1.P) - } - if expected := "abc"; dst.X.T.V != expected { - t.Errorf("X.T.V: expected %#v, got %#v", expected, dst.X.T.V) - } - } -} - -// Test to ensure that a registered converter overrides the default text unmarshaler. -func TestRegisterConverterOverridesTextUnmarshaler(t *testing.T) { - type MyTime time.Time - s1 := &struct { - MyTime - }{} - decoder := NewDecoder() - - ts := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) - decoder.RegisterConverter(s1.MyTime, func(s string) reflect.Value { return reflect.ValueOf(ts) }) - - v1 := map[string][]string{"MyTime": {"4"}} - err := decoder.Decode(s1, v1) - if err != nil { - t.Fatalf("Failed to decode: %v", err) - } - - if s1.MyTime != MyTime(ts) { - t.Errorf("s1.Aa: expected %v, got %v", ts, s1.MyTime) - } -} - -type S20E string - -func (e *S20E) UnmarshalText(text []byte) error { - *e = S20E("x") - return nil -} - -type S20 []S20E - -func (s *S20) UnmarshalText(text []byte) error { - *s = S20{"a", "b", "c"} - return nil -} - -// Test to ensure that when a custom type based on a slice implements an -// encoding.TextUnmarshaler interface that it takes precedence over any -// implementations by its elements. -func TestTextUnmarshalerTypeSlice(t *testing.T) { - data := map[string][]string{ - "Value": []string{"a,b,c"}, - } - s := struct { - Value S20 - }{} - decoder := NewDecoder() - if err := decoder.Decode(&s, data); err != nil { - t.Fatal("Error while decoding:", err) - } - expected := S20{"a", "b", "c"} - if !reflect.DeepEqual(expected, s.Value) { - t.Errorf("Expected %v errors, got %v", expected, s.Value) - } -} - -type S21E struct{ ElementValue string } - -func (e *S21E) UnmarshalText(text []byte) error { - *e = S21E{"x"} - return nil -} - -type S21 []S21E - -func (s *S21) UnmarshalText(text []byte) error { - *s = S21{{"a"}} - return nil -} - -type S21B []S21E - -// Test to ensure that if custom type base on a slice of structs implements an -// encoding.TextUnmarshaler interface it is unaffected by the special path -// requirements imposed on a slice of structs. -func TestTextUnmarshalerTypeSliceOfStructs(t *testing.T) { - data := map[string][]string{ - "Value": []string{"raw a"}, - } - // Implements encoding.TextUnmarshaler, should not throw invalid path - // error. - s := struct { - Value S21 - }{} - decoder := NewDecoder() - if err := decoder.Decode(&s, data); err != nil { - t.Fatal("Error while decoding:", err) - } - expected := S21{{"a"}} - if !reflect.DeepEqual(expected, s.Value) { - t.Errorf("Expected %v errors, got %v", expected, s.Value) - } - // Does not implement encoding.TextUnmarshaler, should throw invalid - // path error. - sb := struct { - Value S21B - }{} - if err := decoder.Decode(&sb, data); err == errInvalidPath { - t.Fatal("Expecting invalid path error", err) - } -} - -type S22 string - -func (s *S22) UnmarshalText(text []byte) error { - *s = S22("a") - return nil -} - -// Test to ensure that when a field that should be decoded into a type -// implementing the encoding.TextUnmarshaler interface is set to an empty value -// that the UnmarshalText method is utilized over other methods of decoding, -// especially including simply setting the zero value. -func TestTextUnmarshalerEmpty(t *testing.T) { - data := map[string][]string{ - "Value": []string{""}, // empty value - } - // Implements encoding.TextUnmarshaler, should use the type's - // UnmarshalText method. - s := struct { - Value S22 - }{} - decoder := NewDecoder() - if err := decoder.Decode(&s, data); err != nil { - t.Fatal("Error while decoding:", err) - } - expected := S22("a") - if expected != s.Value { - t.Errorf("Expected %v errors, got %v", expected, s.Value) - } -} - -type S23n struct { - F2 string `schema:"F2"` - F3 string `schema:"F3"` -} - -type S23e struct { - *S23n - F1 string `schema:"F1"` -} - -type S23 []*S23e - -func TestUnmashalPointerToEmbedded(t *testing.T) { - data := map[string][]string{ - "A.0.F2": []string{"raw a"}, - "A.0.F3": []string{"raw b"}, - } - - // Implements encoding.TextUnmarshaler, should not throw invalid path - // error. - s := struct { - Value S23 `schema:"A"` - }{} - decoder := NewDecoder() - - if err := decoder.Decode(&s, data); err != nil { - t.Fatal("Error while decoding:", err) - } - - expected := S23{ - &S23e{ - S23n: &S23n{"raw a", "raw b"}, - }, - } - if !reflect.DeepEqual(expected, s.Value) { - t.Errorf("Expected %v errors, got %v", expected, s.Value) - } -} diff --git a/schema/doc.go b/schema/doc.go deleted file mode 100644 index aae9f33..0000000 --- a/schema/doc.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package gorilla/schema fills a struct with form values. - -The basic usage is really simple. Given this struct: - - type Person struct { - Name string - Phone string - } - -...we can fill it passing a map to the Decode() function: - - values := map[string][]string{ - "Name": {"John"}, - "Phone": {"999-999-999"}, - } - person := new(Person) - decoder := schema.NewDecoder() - decoder.Decode(person, values) - -This is just a simple example and it doesn't make a lot of sense to create -the map manually. Typically it will come from a http.Request object and -will be of type url.Values, http.Request.Form, or http.Request.MultipartForm: - - func MyHandler(w http.ResponseWriter, r *http.Request) { - err := r.ParseForm() - - if err != nil { - // Handle error - } - - decoder := schema.NewDecoder() - // r.PostForm is a map of our POST form values - err := decoder.Decode(person, r.PostForm) - - if err != nil { - // Handle error - } - - // Do something with person.Name or person.Phone - } - -Note: it is a good idea to set a Decoder instance as a package global, -because it caches meta-data about structs, and an instance can be shared safely: - - var decoder = schema.NewDecoder() - -To define custom names for fields, use a struct tag "schema". To not populate -certain fields, use a dash for the name and it will be ignored: - - type Person struct { - Name string `schema:"name"` // custom name - Phone string `schema:"phone"` // custom name - Admin bool `schema:"-"` // this field is never set - } - -The supported field types in the destination struct are: - - * bool - * float variants (float32, float64) - * int variants (int, int8, int16, int32, int64) - * string - * uint variants (uint, uint8, uint16, uint32, uint64) - * struct - * a pointer to one of the above types - * a slice or a pointer to a slice of one of the above types - -Non-supported types are simply ignored, however custom types can be registered -to be converted. - -To fill nested structs, keys must use a dotted notation as the "path" for the -field. So for example, to fill the struct Person below: - - type Phone struct { - Label string - Number string - } - - type Person struct { - Name string - Phone Phone - } - -...the source map must have the keys "Name", "Phone.Label" and "Phone.Number". -This means that an HTML form to fill a Person struct must look like this: - -
- - - -
- -Single values are filled using the first value for a key from the source map. -Slices are filled using all values for a key from the source map. So to fill -a Person with multiple Phone values, like: - - type Person struct { - Name string - Phones []Phone - } - -...an HTML form that accepts three Phone values would look like this: - -
- - - - - - - -
- -Notice that only for slices of structs the slice index is required. -This is needed for disambiguation: if the nested struct also had a slice -field, we could not translate multiple values to it if we did not use an -index for the parent struct. - -There's also the possibility to create a custom type that implements the -TextUnmarshaler interface, and in this case there's no need to register -a converter, like: - - type Person struct { - Emails []Email - } - - type Email struct { - *mail.Address - } - - func (e *Email) UnmarshalText(text []byte) (err error) { - e.Address, err = mail.ParseAddress(string(text)) - return - } - -...an HTML form that accepts three Email values would look like this: - -
- - - -
-*/ -package schema diff --git a/schema/encoder.go b/schema/encoder.go deleted file mode 100644 index 51f0a78..0000000 --- a/schema/encoder.go +++ /dev/null @@ -1,214 +0,0 @@ -package schema - -import ( - "errors" - "fmt" - "log" - "reflect" - "strconv" -) - -type encoderFunc func(reflect.Value) string - -// Encoder encodes values from a struct into url.Values. -type Encoder struct { - cache *cache - regenc map[reflect.Type]encoderFunc -} - -// NewEncoder returns a new Encoder with defaults. -func NewEncoder() *Encoder { - return &Encoder{cache: newCache(), regenc: make(map[reflect.Type]encoderFunc)} -} - -// Encode encodes a struct into map[string][]string. -// -// Intended for use with url.Values. -func (e *Encoder) Encode(src interface{}, dst map[string][]string) error { - v := reflect.ValueOf(src) - - return e.encode(v, dst) -} - -// RegisterEncoder registers a converter for encoding a custom type. -func (e *Encoder) RegisterEncoder(value interface{}, encoder func(reflect.Value) string) { - e.regenc[reflect.TypeOf(value)] = encoder -} - -// SetAliasTag changes the tag used to locate custom field aliases. -// The default tag is "schema". -func (e *Encoder) SetAliasTag(tag string) { - e.cache.tag = tag -} - -// isValidStructPointer test if input value is a valid struct pointer. -func isValidStructPointer(v reflect.Value) bool { - return v.Type().Kind() == reflect.Ptr && v.Elem().IsValid() && v.Elem().Type().Kind() == reflect.Struct -} - -func isZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Func: - case reflect.Map, reflect.Slice: - return v.IsNil() || v.Len() == 0 - case reflect.Array: - z := true - for i := 0; i < v.Len(); i++ { - z = z && isZero(v.Index(i)) - } - return z - case reflect.Struct: - type zero interface { - IsZero() bool - } - if v.Type().Implements(reflect.TypeOf((*zero)(nil)).Elem()) { - iz := v.MethodByName("IsZero").Call([]reflect.Value{})[0] - return iz.Interface().(bool) - } - z := true - for i := 0; i < v.NumField(); i++ { - z = z && isZero(v.Field(i)) - } - return z - } - // Compare other types directly: - z := reflect.Zero(v.Type()) - return v.Interface() == z.Interface() -} - -func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - if v.Kind() != reflect.Struct { - return errors.New("schema: interface must be a struct") - } - t := v.Type() - - errors := MultiError{} - - for i := 0; i < v.NumField(); i++ { - name, opts := fieldAlias(t.Field(i), e.cache.tag) - if name == "-" { - continue - } - - // Encode struct pointer types if the field is a valid pointer and a struct. - if isValidStructPointer(v.Field(i)) && !e.hasCustomEncoder(v.Field(i).Type()) { - err := e.encode(v.Field(i).Elem(), dst) - if err != nil { - log.Fatal(err) - } - continue - } - - encFunc := typeEncoder(v.Field(i).Type(), e.regenc) - - // Encode non-slice types and custom implementations immediately. - if encFunc != nil { - value := encFunc(v.Field(i)) - if opts.Contains("omitempty") && isZero(v.Field(i)) { - continue - } - - dst[name] = append(dst[name], value) - continue - } - - if v.Field(i).Type().Kind() == reflect.Struct { - err := e.encode(v.Field(i), dst) - if err != nil { - log.Fatal(err) - } - continue - } - - if v.Field(i).Type().Kind() == reflect.Slice { - encFunc = typeEncoder(v.Field(i).Type().Elem(), e.regenc) - } - - if encFunc == nil { - errors[v.Field(i).Type().String()] = fmt.Errorf("schema: encoder not found for %v", v.Field(i)) - continue - } - - // Encode a slice. - if v.Field(i).Len() == 0 && opts.Contains("omitempty") { - continue - } - - dst[name] = []string{} - for j := 0; j < v.Field(i).Len(); j++ { - dst[name] = append(dst[name], encFunc(v.Field(i).Index(j))) - } - } - - if len(errors) > 0 { - return errors - } - return nil -} - -func (e *Encoder) hasCustomEncoder(t reflect.Type) bool { - _, exists := e.regenc[t] - return exists -} - -func typeEncoder(t reflect.Type, reg map[reflect.Type]encoderFunc) encoderFunc { - if f, ok := reg[t]; ok { - return f - } - - switch t.Kind() { - case reflect.Bool: - return encodeBool - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return encodeInt - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return encodeUint - case reflect.Float32: - return encodeFloat32 - case reflect.Float64: - return encodeFloat64 - case reflect.Ptr: - f := typeEncoder(t.Elem(), reg) - return func(v reflect.Value) string { - if v.IsNil() { - return "null" - } - return f(v.Elem()) - } - case reflect.String: - return encodeString - default: - return nil - } -} - -func encodeBool(v reflect.Value) string { - return strconv.FormatBool(v.Bool()) -} - -func encodeInt(v reflect.Value) string { - return strconv.FormatInt(int64(v.Int()), 10) -} - -func encodeUint(v reflect.Value) string { - return strconv.FormatUint(uint64(v.Uint()), 10) -} - -func encodeFloat(v reflect.Value, bits int) string { - return strconv.FormatFloat(v.Float(), 'f', 6, bits) -} - -func encodeFloat32(v reflect.Value) string { - return encodeFloat(v, 32) -} - -func encodeFloat64(v reflect.Value) string { - return encodeFloat(v, 64) -} - -func encodeString(v reflect.Value) string { - return v.String() -} diff --git a/schema/encoder_test.go b/schema/encoder_test.go deleted file mode 100644 index 092f0de..0000000 --- a/schema/encoder_test.go +++ /dev/null @@ -1,525 +0,0 @@ -package schema - -import ( - "fmt" - "reflect" - "testing" - "time" -) - -type E1 struct { - F01 int `schema:"f01"` - F02 int `schema:"-"` - F03 string `schema:"f03"` - F04 string `schema:"f04,omitempty"` - F05 bool `schema:"f05"` - F06 bool `schema:"f06"` - F07 *string `schema:"f07"` - F08 *int8 `schema:"f08"` - F09 float64 `schema:"f09"` - F10 func() `schema:"f10"` - F11 inner -} -type inner struct { - F12 int -} - -func TestFilled(t *testing.T) { - f07 := "seven" - var f08 int8 = 8 - s := &E1{ - F01: 1, - F02: 2, - F03: "three", - F04: "four", - F05: true, - F06: false, - F07: &f07, - F08: &f08, - F09: 1.618, - F10: func() {}, - F11: inner{12}, - } - - vals := make(map[string][]string) - errs := NewEncoder().Encode(s, vals) - - valExists(t, "f01", "1", vals) - valNotExists(t, "f02", vals) - valExists(t, "f03", "three", vals) - valExists(t, "f05", "true", vals) - valExists(t, "f06", "false", vals) - valExists(t, "f07", "seven", vals) - valExists(t, "f08", "8", vals) - valExists(t, "f09", "1.618000", vals) - valExists(t, "F12", "12", vals) - - emptyErr := MultiError{} - if errs.Error() == emptyErr.Error() { - t.Errorf("Expected error got %v", errs) - } -} - -type Aa int - -type E3 struct { - F01 bool `schema:"f01"` - F02 float32 `schema:"f02"` - F03 float64 `schema:"f03"` - F04 int `schema:"f04"` - F05 int8 `schema:"f05"` - F06 int16 `schema:"f06"` - F07 int32 `schema:"f07"` - F08 int64 `schema:"f08"` - F09 string `schema:"f09"` - F10 uint `schema:"f10"` - F11 uint8 `schema:"f11"` - F12 uint16 `schema:"f12"` - F13 uint32 `schema:"f13"` - F14 uint64 `schema:"f14"` - F15 Aa `schema:"f15"` -} - -// Test compatibility with default decoder types. -func TestCompat(t *testing.T) { - src := &E3{ - F01: true, - F02: 4.2, - F03: 4.3, - F04: -42, - F05: -43, - F06: -44, - F07: -45, - F08: -46, - F09: "foo", - F10: 42, - F11: 43, - F12: 44, - F13: 45, - F14: 46, - F15: 1, - } - dst := &E3{} - - vals := make(map[string][]string) - encoder := NewEncoder() - decoder := NewDecoder() - - encoder.RegisterEncoder(src.F15, func(reflect.Value) string { return "1" }) - decoder.RegisterConverter(src.F15, func(string) reflect.Value { return reflect.ValueOf(1) }) - - err := encoder.Encode(src, vals) - if err != nil { - t.Errorf("Encoder has non-nil error: %v", err) - } - err = decoder.Decode(dst, vals) - if err != nil { - t.Errorf("Decoder has non-nil error: %v", err) - } - - if *src != *dst { - t.Errorf("Decoder-Encoder compatibility: expected %v, got %v\n", src, dst) - } -} - -func TestEmpty(t *testing.T) { - s := &E1{ - F01: 1, - F02: 2, - F03: "three", - } - - estr := "schema: encoder not found for " - vals := make(map[string][]string) - err := NewEncoder().Encode(s, vals) - if err.Error() != estr { - t.Errorf("Expected: %s, got %v", estr, err) - } - - valExists(t, "f03", "three", vals) - valNotExists(t, "f04", vals) -} - -func TestStruct(t *testing.T) { - estr := "schema: interface must be a struct" - vals := make(map[string][]string) - err := NewEncoder().Encode("hello world", vals) - - if err.Error() != estr { - t.Errorf("Expected: %s, got %v", estr, err) - } -} - -func TestSlices(t *testing.T) { - type oneAsWord int - ones := []oneAsWord{1, 2} - s1 := &struct { - ones []oneAsWord `schema:"ones"` - ints []int `schema:"ints"` - nonempty []int `schema:"nonempty"` - empty []int `schema:"empty,omitempty"` - }{ones, []int{1, 1}, []int{}, []int{}} - vals := make(map[string][]string) - - encoder := NewEncoder() - encoder.RegisterEncoder(ones[0], func(v reflect.Value) string { return "one" }) - err := encoder.Encode(s1, vals) - if err != nil { - t.Errorf("Encoder has non-nil error: %v", err) - } - - valsExist(t, "ones", []string{"one", "one"}, vals) - valsExist(t, "ints", []string{"1", "1"}, vals) - valsExist(t, "nonempty", []string{}, vals) - valNotExists(t, "empty", vals) -} - -func TestCompatSlices(t *testing.T) { - type oneAsWord int - type s1 struct { - Ones []oneAsWord `schema:"ones"` - Ints []int `schema:"ints"` - } - ones := []oneAsWord{1, 1} - src := &s1{ones, []int{1, 1}} - vals := make(map[string][]string) - dst := &s1{} - - encoder := NewEncoder() - encoder.RegisterEncoder(ones[0], func(v reflect.Value) string { return "one" }) - - decoder := NewDecoder() - decoder.RegisterConverter(ones[0], func(s string) reflect.Value { - if s == "one" { - return reflect.ValueOf(1) - } - return reflect.ValueOf(2) - }) - - err := encoder.Encode(src, vals) - if err != nil { - t.Errorf("Encoder has non-nil error: %v", err) - } - err = decoder.Decode(dst, vals) - if err != nil { - t.Errorf("Dncoder has non-nil error: %v", err) - } - - if len(src.Ints) != len(dst.Ints) || len(src.Ones) != len(dst.Ones) { - t.Fatalf("Expected %v, got %v", src, dst) - } - - for i, v := range src.Ones { - if dst.Ones[i] != v { - t.Fatalf("Expected %v, got %v", v, dst.Ones[i]) - } - } - - for i, v := range src.Ints { - if dst.Ints[i] != v { - t.Fatalf("Expected %v, got %v", v, dst.Ints[i]) - } - } -} - -func TestRegisterEncoder(t *testing.T) { - type oneAsWord int - type twoAsWord int - type oneSliceAsWord []int - - s1 := &struct { - oneAsWord - twoAsWord - oneSliceAsWord - }{1, 2, []int{1, 1}} - v1 := make(map[string][]string) - - encoder := NewEncoder() - encoder.RegisterEncoder(s1.oneAsWord, func(v reflect.Value) string { return "one" }) - encoder.RegisterEncoder(s1.twoAsWord, func(v reflect.Value) string { return "two" }) - encoder.RegisterEncoder(s1.oneSliceAsWord, func(v reflect.Value) string { return "one" }) - - err := encoder.Encode(s1, v1) - if err != nil { - t.Errorf("Encoder has non-nil error: %v", err) - } - - valExists(t, "oneAsWord", "one", v1) - valExists(t, "twoAsWord", "two", v1) - valExists(t, "oneSliceAsWord", "one", v1) -} - -func TestEncoderOrder(t *testing.T) { - type builtinEncoderSimple int - type builtinEncoderSimpleOverridden int - type builtinEncoderSlice []int - type builtinEncoderSliceOverridden []int - type builtinEncoderStruct struct{ nr int } - type builtinEncoderStructOverridden struct{ nr int } - - s1 := &struct { - builtinEncoderSimple `schema:"simple"` - builtinEncoderSimpleOverridden `schema:"simple_overridden"` - builtinEncoderSlice `schema:"slice"` - builtinEncoderSliceOverridden `schema:"slice_overridden"` - builtinEncoderStruct `schema:"struct"` - builtinEncoderStructOverridden `schema:"struct_overridden"` - }{ - 1, - 1, - []int{2}, - []int{2}, - builtinEncoderStruct{3}, - builtinEncoderStructOverridden{3}, - } - v1 := make(map[string][]string) - - encoder := NewEncoder() - encoder.RegisterEncoder(s1.builtinEncoderSimpleOverridden, func(v reflect.Value) string { return "one" }) - encoder.RegisterEncoder(s1.builtinEncoderSliceOverridden, func(v reflect.Value) string { return "two" }) - encoder.RegisterEncoder(s1.builtinEncoderStructOverridden, func(v reflect.Value) string { return "three" }) - - err := encoder.Encode(s1, v1) - if err != nil { - t.Errorf("Encoder has non-nil error: %v", err) - } - - valExists(t, "simple", "1", v1) - valExists(t, "simple_overridden", "one", v1) - valExists(t, "slice", "2", v1) - valExists(t, "slice_overridden", "two", v1) - valExists(t, "nr", "3", v1) - valExists(t, "struct_overridden", "three", v1) -} - -func valExists(t *testing.T, key string, expect string, result map[string][]string) { - valsExist(t, key, []string{expect}, result) -} - -func valsExist(t *testing.T, key string, expect []string, result map[string][]string) { - vals, ok := result[key] - if !ok { - t.Fatalf("Key not found. Expected: %s", key) - } - - if len(expect) != len(vals) { - t.Fatalf("Expected: %v, got: %v", expect, vals) - } - - for i, v := range expect { - if vals[i] != v { - t.Fatalf("Unexpected value. Expected: %v, got %v", v, vals[i]) - } - } -} - -func valNotExists(t *testing.T, key string, result map[string][]string) { - if val, ok := result[key]; ok { - t.Error("Key not omitted. Expected: empty; got: " + val[0] + ".") - } -} - -func valsLength(t *testing.T, expectedLength int, result map[string][]string) { - length := len(result) - if length != expectedLength { - t.Errorf("Expected length of %v, but got %v", expectedLength, length) - } -} - -func noError(t *testing.T, err error) { - if err != nil { - t.Errorf("Unexpected error. Got %v", err) - } -} - -type E4 struct { - ID string `json:"id"` -} - -func TestEncoderSetAliasTag(t *testing.T) { - data := map[string][]string{} - - s := E4{ - ID: "foo", - } - encoder := NewEncoder() - encoder.SetAliasTag("json") - err := encoder.Encode(&s, data) - if err != nil { - t.Fatalf("Failed to encode: %v", err) - } - valExists(t, "id", "foo", data) -} - -type E5 struct { - F01 int `schema:"f01,omitempty"` - F02 string `schema:"f02,omitempty"` - F03 *string `schema:"f03,omitempty"` - F04 *int8 `schema:"f04,omitempty"` - F05 float64 `schema:"f05,omitempty"` - F06 E5F06 `schema:"f06,omitempty"` - F07 E5F06 `schema:"f07,omitempty"` - F08 []string `schema:"f08,omitempty"` - F09 []string `schema:"f09,omitempty"` -} - -type E5F06 struct { - F0601 string `schema:"f0601,omitempty"` -} - -func TestEncoderWithOmitempty(t *testing.T) { - vals := map[string][]string{} - - s := E5{ - F02: "test", - F07: E5F06{ - F0601: "test", - }, - F09: []string{"test"}, - } - - encoder := NewEncoder() - err := encoder.Encode(&s, vals) - if err != nil { - t.Fatalf("Failed to encode: %v", err) - } - - valNotExists(t, "f01", vals) - valExists(t, "f02", "test", vals) - valNotExists(t, "f03", vals) - valNotExists(t, "f04", vals) - valNotExists(t, "f05", vals) - valNotExists(t, "f06", vals) - valExists(t, "f0601", "test", vals) - valNotExists(t, "f08", vals) - valsExist(t, "f09", []string{"test"}, vals) -} - -type E6 struct { - F01 *inner - F02 *inner - F03 *inner `schema:",omitempty"` -} - -func TestStructPointer(t *testing.T) { - vals := map[string][]string{} - s := E6{ - F01: &inner{2}, - } - - encoder := NewEncoder() - err := encoder.Encode(&s, vals) - if err != nil { - t.Fatalf("Failed to encode: %v", err) - } - valExists(t, "F12", "2", vals) - valExists(t, "F02", "null", vals) - valNotExists(t, "F03", vals) -} - -func TestRegisterEncoderCustomArrayType(t *testing.T) { - type CustomInt []int - type S1 struct { - SomeInts CustomInt `schema:",omitempty"` - } - - ss := []S1{ - {}, - {CustomInt{}}, - {CustomInt{1, 2, 3}}, - } - - for s := range ss { - vals := map[string][]string{} - - encoder := NewEncoder() - encoder.RegisterEncoder(CustomInt{}, func(value reflect.Value) string { - return fmt.Sprint(value.Interface()) - }) - - err := encoder.Encode(ss[s], vals) - if err != nil { - t.Fatalf("Failed to encode: %v", err) - } - } -} - -func TestRegisterEncoderStructIsZero(t *testing.T) { - type S1 struct { - SomeTime1 time.Time `schema:"tim1,omitempty"` - SomeTime2 time.Time `schema:"tim2,omitempty"` - } - - ss := []*S1{ - { - SomeTime1: time.Date(2020, 8, 4, 13, 30, 1, 0, time.UTC), - }, - } - - for s := range ss { - vals := map[string][]string{} - - encoder := NewEncoder() - encoder.RegisterEncoder(time.Time{}, func(value reflect.Value) string { - return value.Interface().(time.Time).Format(time.RFC3339Nano) - }) - - err := encoder.Encode(ss[s], vals) - if err != nil { - t.Errorf("Encoder has non-nil error: %v", err) - } - - ta, ok := vals["tim1"] - if !ok { - t.Error("expected tim1 to be present") - } - - if len(ta) != 1 { - t.Error("expected tim1 to be present") - } - - if ta[0] != "2020-08-04T13:30:01Z" { - t.Error("expected correct tim1 time") - } - - _, ok = vals["tim2"] - if ok { - t.Error("expected tim1 not to be present") - } - } -} - -func TestRegisterEncoderWithPtrType(t *testing.T) { - type CustomTime struct { - time time.Time - } - - type S1 struct { - DateStart *CustomTime - DateEnd *CustomTime - Empty *CustomTime `schema:"empty,omitempty"` - } - - ss := S1{ - DateStart: &CustomTime{time: time.Now()}, - DateEnd: nil, - } - - encoder := NewEncoder() - encoder.RegisterEncoder(&CustomTime{}, func(value reflect.Value) string { - if value.IsNil() { - return "" - } - - custom := value.Interface().(*CustomTime) - return custom.time.String() - }) - - vals := map[string][]string{} - err := encoder.Encode(ss, vals) - - noError(t, err) - valsLength(t, 2, vals) - valExists(t, "DateStart", ss.DateStart.time.String(), vals) - valExists(t, "DateEnd", "", vals) -} diff --git a/schema/license.txt b/schema/license.txt deleted file mode 100644 index bb9d80b..0000000 --- a/schema/license.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2023 The Gorilla Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/schema/readme.md b/schema/readme.md deleted file mode 100644 index dbeff3d..0000000 --- a/schema/readme.md +++ /dev/null @@ -1,94 +0,0 @@ -# gorilla/schema - -![testing](https://github.com/gorilla/schema/actions/workflows/test.yml/badge.svg) -[![codecov](https://codecov.io/github/gorilla/schema/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/schema) -[![godoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema) -[![sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge) - - -![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) - -Package gorilla/schema converts structs to and from form values. - -## Example - -Here's a quick example: we parse POST form values and then decode them into a struct: - -```go -// Set a Decoder instance as a package global, because it caches -// meta-data about structs, and an instance can be shared safely. -var decoder = schema.NewDecoder() - -type Person struct { - Name string - Phone string -} - -func MyHandler(w http.ResponseWriter, r *http.Request) { - err := r.ParseForm() - if err != nil { - // Handle error - } - - var person Person - - // r.PostForm is a map of our POST form values - err = decoder.Decode(&person, r.PostForm) - if err != nil { - // Handle error - } - - // Do something with person.Name or person.Phone -} -``` - -Conversely, contents of a struct can be encoded into form values. Here's a variant of the previous example using the Encoder: - -```go -var encoder = schema.NewEncoder() - -func MyHttpRequest() { - person := Person{"Jane Doe", "555-5555"} - form := url.Values{} - - err := encoder.Encode(person, form) - - if err != nil { - // Handle error - } - - // Use form values, for example, with an http client - client := new(http.Client) - res, err := client.PostForm("http://my-api.test", form) -} - -``` - -To define custom names for fields, use a struct tag "schema". To not populate certain fields, use a dash for the name and it will be ignored: - -```go -type Person struct { - Name string `schema:"name,required"` // custom name, must be supplied - Phone string `schema:"phone"` // custom name - Admin bool `schema:"-"` // this field is never set -} -``` - -The supported field types in the struct are: - -* bool -* float variants (float32, float64) -* int variants (int, int8, int16, int32, int64) -* string -* uint variants (uint, uint8, uint16, uint32, uint64) -* struct -* a pointer to one of the above types -* a slice or a pointer to a slice of one of the above types - -Unsupported types are simply ignored, however custom types can be registered to be converted. - -More examples are available on the Gorilla website: https://www.gorillatoolkit.org/pkg/schema - -## License - -BSD licensed. See the LICENSE file for details.