pax_global_header00006660000000000000000000000064145775447760014544gustar00rootroot0000000000000052 comment=d3ba891226fb711df748f45c49c68b81fac57fb9 golang-github-zitadel-schema-1.3.0/000077500000000000000000000000001457754477600171645ustar00rootroot00000000000000golang-github-zitadel-schema-1.3.0/.github/000077500000000000000000000000001457754477600205245ustar00rootroot00000000000000golang-github-zitadel-schema-1.3.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001457754477600227075ustar00rootroot00000000000000golang-github-zitadel-schema-1.3.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000015011457754477600253760ustar00rootroot00000000000000--- name: 🐛 Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. golang-github-zitadel-schema-1.3.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000331457754477600246730ustar00rootroot00000000000000blank_issues_enabled: true golang-github-zitadel-schema-1.3.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011411457754477600264310ustar00rootroot00000000000000--- name: 🚀 Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. golang-github-zitadel-schema-1.3.0/.github/dependabot.yml000066400000000000000000000004441457754477600233560ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily time: '04:00' open-pull-requests-limit: 10 commit-message: prefix: chore include: scope - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly golang-github-zitadel-schema-1.3.0/.github/stale.yml000066400000000000000000000005271457754477600223630ustar00rootroot00000000000000daysUntilStale: 60 daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - v2 - needs-review - work-required staleLabel: stale markComment: > This issue has been automatically marked as stale because it hasn't seen a recent update. It'll be automatically closed in a few days. closeComment: false golang-github-zitadel-schema-1.3.0/.github/workflows/000077500000000000000000000000001457754477600225615ustar00rootroot00000000000000golang-github-zitadel-schema-1.3.0/.github/workflows/codeql-analysis.yml000066400000000000000000000031521457754477600263750ustar00rootroot00000000000000name: "Code scanning - action" on: push: branches: [main,next] pull_request: # The branches below must be a subset of the branches above branches: [main,next] schedule: - cron: '0 11 * * 0' jobs: CodeQL-Build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 # Override language selection by uncommenting this and choosing your languages with: languages: go # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 golang-github-zitadel-schema-1.3.0/.github/workflows/issue.yml000066400000000000000000000007151457754477600244370ustar00rootroot00000000000000name: Add new issues to product management project on: issues: types: - opened jobs: add-to-project: name: Add issue to project runs-on: ubuntu-latest steps: - uses: actions/add-to-project@v0.4.1 with: # You can target a repository in a different organization # to the issue project-url: https://github.com/orgs/zitadel/projects/2 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} golang-github-zitadel-schema-1.3.0/.github/workflows/release.yml000066400000000000000000000021731457754477600247270ustar00rootroot00000000000000name: Release on: push: branches: - main - next tags-ignore: - '**' pull_request: branches: - '**' workflow_dispatch: jobs: test: runs-on: ubuntu-20.04 strategy: matrix: go: ['1.18', '1.19', '1.20'] name: Go ${{ matrix.go }} test steps: - uses: actions/checkout@v3 - name: Setup go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - run: go test -race -v -coverprofile=profile.cov . - uses: codecov/codecov-action@v3.1.1 with: file: ./profile.cov name: codecov-go release: runs-on: ubuntu-20.04 needs: [test] if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Source checkout uses: actions/checkout@v3 - name: Semantic Release uses: cycjimmy/semantic-release-action@v3 with: dry_run: false semantic_version: 18.0.1 extra_plugins: | @semantic-release/exec@6.0.3 golang-github-zitadel-schema-1.3.0/.releaserc.js000066400000000000000000000004111457754477600215410ustar00rootroot00000000000000module.exports = { branches: [ {name: "main"}, {name: "next", prerelease: true}, ], plugins: [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/github" ] }; golang-github-zitadel-schema-1.3.0/LICENSE000066400000000000000000000027041457754477600201740ustar00rootroot00000000000000Copyright (c) 2012 Rodrigo Moraes. 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. golang-github-zitadel-schema-1.3.0/README.md000066400000000000000000000062111457754477600204430ustar00rootroot00000000000000# schema [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Release](https://github.com/zitadel/schema/workflows/Release/badge.svg)](https://github.com/zitadel/schema/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/zitadel/schema.svg)](https://pkg.go.dev/github.com/zitadel/schema) [![license](https://badgen.net/github/license/zitadel/schema/)](https://github.com/zitadel/schema/blob/master/LICENSE) [![release](https://badgen.net/github/release/zitadel/schema/stable)](https://github.com/zitadel/schema/releases) [![Go Report Card](https://goreportcard.com/badge/github.com/zitadel/schema)](https://goreportcard.com/report/github.com/zitadel/schema) [![codecov](https://codecov.io/gh/zitadel/schema/branch/main/graph/badge.svg?token=5QS2VEMCt2)](https://codecov.io/gh/zitadel/schema) Package zitadel/schema converts structs to and from form values. This is a maintained fork of [gorilla/schema](https://github.com/gorilla/schema) ## 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. golang-github-zitadel-schema-1.3.0/cache.go000066400000000000000000000175711457754477600205710ustar00rootroot00000000000000// 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 invalidPath = 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, invalidPath } if struc = c.get(t); struc == nil { return nil, invalidPath } if field = struc.get(keys[i]); field == nil { return nil, invalidPath } // 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, invalidPath } if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil { return nil, invalidPath } 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 } golang-github-zitadel-schema-1.3.0/converter.go000066400000000000000000000067051457754477600215320ustar00rootroot00000000000000// 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 } golang-github-zitadel-schema-1.3.0/decoder.go000066400000000000000000000337421457754477600211310ustar00rootroot00000000000000// 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 == true { 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 } } } golang-github-zitadel-schema-1.3.0/decoder_test.go000066400000000000000000001351131457754477600221630ustar00rootroot00000000000000// 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") } for i := range buf { (*id)[i] = buf[i] } 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{} NewDecoder().Decode(s, data) 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{} NewDecoder().Decode(s, data) 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{} NewDecoder().Decode(&s, data) 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") dec.Decode(&s, data) 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{} NewDecoder().Decode(s, data) 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{} NewDecoder().Decode(&s, data) 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{} NewDecoder().Decode(&s, data) 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 } 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 } 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"}} NewDecoder().Decode(s1, v1) 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"}} decoder.Decode(s1, v1) 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"} decoder.Decode(&result, map[string][]string{ "multiple": []string{"one,two,three"}, }) 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"}, "Bb": {"5"}} decoder.Decode(s1, v1) 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 == invalidPath { 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) } } golang-github-zitadel-schema-1.3.0/doc.go000066400000000000000000000100141457754477600202540ustar00rootroot00000000000000// 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 zitadel/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 golang-github-zitadel-schema-1.3.0/encoder.go000066400000000000000000000114031457754477600211310ustar00rootroot00000000000000package schema import ( "errors" "fmt" "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()) { e.encode(v.Field(i).Elem(), dst) 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 { e.encode(v.Field(i), dst) 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() } golang-github-zitadel-schema-1.3.0/encoder_test.go000066400000000000000000000266671457754477600222120ustar00rootroot00000000000000package 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(src.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 ommited. 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") encoder.Encode(&s, data) 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() encoder.Encode(&s, vals) 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() encoder.Encode(&s, vals) 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()) }) encoder.Encode(s, vals) } } 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 "2020-08-04T13:30:01Z" != ta[0] { 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) } golang-github-zitadel-schema-1.3.0/go.mod000066400000000000000000000000521457754477600202670ustar00rootroot00000000000000module github.com/zitadel/schema go 1.18