From 95619b9f36ebb9f73a02cbab67a631c4d532fd49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 24 Mar 2023 09:37:49 +0200 Subject: [PATCH 01/36] chore: add github workflows copied from zitadel/oidc --- .circleci/config.yml | 70 ----------------------- .github/ISSUE_TEMPLATE/bug_report.md | 38 ++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++ .github/dependabot.yml | 16 ++++++ .github/release-drafter.yml | 8 --- .github/workflows/codeql-analysis.yml | 54 +++++++++++++++++ .github/workflows/issue.yml | 18 ++++++ .github/workflows/release.yml | 47 +++++++++++++++ .releaserc.js | 11 ++++ 10 files changed, 205 insertions(+), 78 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/dependabot.yml delete mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/issue.yml create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc.js diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d3dab1a..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,70 +0,0 @@ -version: 2.1 - -jobs: - "test": - parameters: - version: - type: string - default: "latest" - golint: - type: boolean - default: true - modules: - type: boolean - default: true - goproxy: - type: string - default: "" - docker: - - image: "circleci/golang:<< parameters.version >>" - working_directory: /go/src/github.com/gorilla/schema - environment: - GO111MODULE: "on" - GOPROXY: "<< parameters.goproxy >>" - steps: - - checkout - - run: - name: "Print the Go version" - command: > - go version - - run: - name: "Fetch dependencies" - command: > - if [[ << parameters.modules >> = true ]]; then - go mod download - export GO111MODULE=on - else - go get -v ./... - fi - # Only run gofmt, vet & lint against the latest Go version - - run: - name: "Run golint" - command: > - if [ << parameters.version >> = "latest" ] && [ << parameters.golint >> = true ]; then - go get -u golang.org/x/lint/golint - golint ./... - fi - - run: - name: "Run gofmt" - command: > - if [[ << parameters.version >> = "latest" ]]; then - diff -u <(echo -n) <(gofmt -d -e .) - fi - - run: - name: "Run go vet" - command: > - if [[ << parameters.version >> = "latest" ]]; then - go vet -v ./... - fi - - run: - name: "Run go test (+ race detector)" - command: > - go test -v -race ./... - -workflows: - tests: - jobs: - - test: - matrix: - parameters: - version: ["latest", "1.15", "1.14", "1.13", "1.12", "1.11"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..67a8340 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +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. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..118d30e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +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. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..dc0b8bc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 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 + \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index 2db2e13..0000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Config for https://github.com/apps/release-drafter -template: | - - - - ## CHANGELOG - - $CHANGES diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..d2bae79 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,54 @@ +name: "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 diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml new file mode 100644 index 0000000..8671820 --- /dev/null +++ b/.github/workflows/issue.yml @@ -0,0 +1,18 @@ +name: 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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..78c0f79 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,47 @@ +name: 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 -coverpkg=./pkg/... ./pkg/... + - 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 diff --git a/.releaserc.js b/.releaserc.js new file mode 100644 index 0000000..e8eea8e --- /dev/null +++ b/.releaserc.js @@ -0,0 +1,11 @@ +module.exports = { + branches: [ + {name: "main"}, + {name: "next", prerelease: true}, + ], + plugins: [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/github" + ] +}; From 7dafc9a3c39b913845c6218bc40ad32204b5c1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 24 Mar 2023 09:44:54 +0200 Subject: [PATCH 02/36] chore: change package path for testing --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 78c0f79..1136392 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - - run: go test -race -v -coverprofile=profile.cov -coverpkg=./pkg/... ./pkg/... + - run: go test -race -v -coverprofile=profile.cov . - uses: codecov/codecov-action@v3.1.1 with: file: ./profile.cov From 034da48520a4d04cf87a084363f279c08765ccbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 24 Mar 2023 09:57:37 +0200 Subject: [PATCH 03/36] feat: change package path --- README.md | 20 +++++++++----------- doc.go | 18 +++++++++--------- go.mod | 4 ++-- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 82b0411..07fd4c1 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,14 @@ -schema -====== -[![GoDoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema) -[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/schema) -[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge) +# 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) -**The Gorilla project has been archived, and is no longer under active maintainenance. You can read more here: https://github.com/gorilla#gorilla-toolkit** - ---- - -Package gorilla/schema converts structs to and from form values. +Package zitadel/schema converts structs to and from form values. This is a maintained fork of [gorilla/schema](https://github.com/gorilla/schema) ## Example diff --git a/doc.go b/doc.go index aae9f33..d6e689e 100644 --- a/doc.go +++ b/doc.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package gorilla/schema fills a struct with form values. +Package zitadel/schema fills a struct with form values. The basic usage is really simple. Given this struct: @@ -60,14 +60,14 @@ certain fields, use a dash for the name and it will be ignored: 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 + - 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. diff --git a/go.mod b/go.mod index 200e4fa..23d2416 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/gorilla/schema +module github.com/zitadel/schema -go 1.14 +go 1.18 From 02736e4edfe3403d44a311185069684dab729432 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 22 Apr 2023 13:41:48 +0000 Subject: [PATCH 04/36] Bump actions/add-to-project from 0.4.1 to 0.5.0 Bumps [actions/add-to-project](https://github.com/actions/add-to-project) from 0.4.1 to 0.5.0. - [Release notes](https://github.com/actions/add-to-project/releases) - [Commits](https://github.com/actions/add-to-project/compare/v0.4.1...v0.5.0) --- updated-dependencies: - dependency-name: actions/add-to-project dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml index 8671820..362443d 100644 --- a/.github/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -10,7 +10,7 @@ jobs: name: Add issue to project runs-on: ubuntu-latest steps: - - uses: actions/add-to-project@v0.4.1 + - uses: actions/add-to-project@v0.5.0 with: # You can target a repository in a different organization # to the issue From ad1e01f07e5592a34573b8fed4065bd314c76926 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 14:09:22 +0000 Subject: [PATCH 05/36] Bump codecov/codecov-action from 3.1.1 to 3.1.4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.1 to 3.1.4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3.1.1...v3.1.4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1136392..6f39a98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: with: go-version: ${{ matrix.go }} - run: go test -race -v -coverprofile=profile.cov . - - uses: codecov/codecov-action@v3.1.1 + - uses: codecov/codecov-action@v3.1.4 with: file: ./profile.cov name: codecov-go From 5bf4eb6a64a9a5f791e944e7bad18dfab9d85153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Fri, 15 Nov 2024 08:42:47 +0200 Subject: [PATCH 06/36] chore(deps): upgrade go to v1.23 (#6) --- .github/workflows/release.yml | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1136392..4475d6d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - go: ['1.18', '1.19', '1.20'] + go: ['1.22', '1.23'] name: Go ${{ matrix.go }} test steps: - uses: actions/checkout@v3 diff --git a/go.mod b/go.mod index 23d2416..a06c94b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/zitadel/schema -go 1.18 +go 1.22 From fbcdc6aeb24eb19b2bb911c04f8dcc3b8c819ce1 Mon Sep 17 00:00:00 2001 From: Iraq <66622793+kkrime@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:19:29 +0000 Subject: [PATCH 07/36] chore: updating go to 1.23.7 (#16) * chore: updating go to 1.24 * fixup! chore: updating go to 1.24 * fixup! fixup! chore: updating go to 1.24 * fixup! fixup! fixup! chore: updating go to 1.24 --------- Co-authored-by: Iraq Jaber --- .github/workflows/release.yml | 4 ++-- go.mod | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 333e899..2c15850 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,14 +16,14 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - go: ['1.22', '1.23'] + go: ['1.23', '1.24'] name: Go ${{ matrix.go }} test steps: - uses: actions/checkout@v3 - name: Setup go uses: actions/setup-go@v4 with: - go-version: ${{ matrix.go }} + go-version-file: 'go.mod' - run: go test -race -v -coverprofile=profile.cov . - uses: codecov/codecov-action@v3.1.4 with: diff --git a/go.mod b/go.mod index a06c94b..a5ed29b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/zitadel/schema -go 1.22 +go 1.23.7 From f9d9bfab65c61940d270a217f82224695bcd9f04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:21:25 +0000 Subject: [PATCH 08/36] Bump actions/add-to-project from 0.5.0 to 1.0.2 (#7) Bumps [actions/add-to-project](https://github.com/actions/add-to-project) from 0.5.0 to 1.0.2. - [Release notes](https://github.com/actions/add-to-project/releases) - [Commits](https://github.com/actions/add-to-project/compare/v0.5.0...v1.0.2) --- updated-dependencies: - dependency-name: actions/add-to-project dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml index 362443d..3b3b5f9 100644 --- a/.github/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -10,7 +10,7 @@ jobs: name: Add issue to project runs-on: ubuntu-latest steps: - - uses: actions/add-to-project@v0.5.0 + - uses: actions/add-to-project@v1.0.2 with: # You can target a repository in a different organization # to the issue From 47fe27dba89a11c19e16c5b85829b4b90cd380fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:23:02 +0000 Subject: [PATCH 09/36] Bump github/codeql-action from 2 to 3 (#9) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d2bae79..387f15a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,7 +29,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # Override language selection by uncommenting this and choosing your languages with: languages: go @@ -37,7 +37,7 @@ jobs: # 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 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -51,4 +51,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 2774f7e13dc4754e01d0ce52f9db69ba2f27edca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:24:52 +0200 Subject: [PATCH 10/36] Bump actions/setup-go from 4 to 5 (#10) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c15850..0733d3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: 'go.mod' - run: go test -race -v -coverprofile=profile.cov . From 29d6266a42f3f2ff08c27fbda380f0bdc6be1dda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:26:34 +0200 Subject: [PATCH 11/36] Bump actions/checkout from 3 to 4 (#11) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 387f15a..27fa244 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0733d3c..7d0ba08 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: go: ['1.23', '1.24'] name: Go ${{ matrix.go }} test steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup go uses: actions/setup-go@v5 with: @@ -37,7 +37,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Source checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Semantic Release uses: cycjimmy/semantic-release-action@v3 with: From d34ba91b71c14acfab979675d4f44da59522d631 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:28:03 +0000 Subject: [PATCH 12/36] Bump codecov/codecov-action from 3.1.4 to 5.3.1 (#15) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.4 to 5.3.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3.1.4...v5.3.1) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d0ba08..20481df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: with: go-version-file: 'go.mod' - run: go test -race -v -coverprofile=profile.cov . - - uses: codecov/codecov-action@v3.1.4 + - uses: codecov/codecov-action@v5.4.0 with: file: ./profile.cov name: codecov-go From 548ef95e02c01b4e19ad8e20cd2e63b09f3ad5a3 Mon Sep 17 00:00:00 2001 From: Wim Van Laer Date: Thu, 4 Dec 2025 08:30:37 +0100 Subject: [PATCH 13/36] chore: upgrade go to 1.24.10 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a5ed29b..28ea89b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/zitadel/schema -go 1.23.7 +go 1.24.10 From 4786ffbae1e61e91f23a63090a2426a31838f715 Mon Sep 17 00:00:00 2001 From: Wim Van Laer Date: Wed, 17 Dec 2025 11:33:06 +0100 Subject: [PATCH 14/36] ci: use latest ubuntu version for github runners (#29) The github-actions got stuck on initialization and timed out because of it. This was because they used a version of ubuntu which is no longer supported by github. --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20481df..0423e84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: matrix: go: ['1.23', '1.24'] @@ -30,7 +30,7 @@ jobs: file: ./profile.cov name: codecov-go release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 needs: [test] if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }} env: From 33779aa2753bc19f5e19f86447de74bd4b84fafd Mon Sep 17 00:00:00 2001 From: Apoorva Jagtap <35304110+apoorvajagtap@users.noreply.github.com> Date: Wed, 26 Jul 2023 09:23:51 +0200 Subject: [PATCH 15/36] [GPT-98] Update go version & add verification/testing tools (#200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [ ] `make verify` is passing - [ ] `make test` is passing --------- Signed-off-by: Apoorva Jagtap <35304110+apoorvajagtap@users.noreply.github.com> Co-authored-by: Corey Daley (cherry picked from commit 212775c4b75831ca3d86f0b51e8d5867a2329f41) --- .editorconfig | 20 ++++++++++++++ .gitignore | 1 + LICENSE | 2 +- Makefile | 34 +++++++++++++++++++++++ cache.go | 12 ++++----- decoder.go | 2 +- decoder_test.go | 71 +++++++++++++++++++++++++++++++++++-------------- encoder.go | 11 ++++++-- encoder_test.go | 24 ++++++++++++----- 9 files changed, 141 insertions(+), 36 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 Makefile diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6b74c3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +; https://editorconfig.org/ + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go,.gitmodules}] +indent_style = tab +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +eclint_indent_style = unset \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84039fe --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +coverage.coverprofile diff --git a/LICENSE b/LICENSE index 0e5fb87..bb9d80b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. +Copyright (c) 2023 The Gorilla Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..98f5ab7 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') +GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +GO_SEC=$(shell which gosec 2> /dev/null || echo '') +GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest + +GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') +GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest + +.PHONY: golangci-lint +golangci-lint: + $(if $(GO_LINT), ,go install $(GO_LINT_URI)) + @echo "##### Running golangci-lint" + golangci-lint run -v + +.PHONY: gosec +gosec: + $(if $(GO_SEC), ,go install $(GO_SEC_URI)) + @echo "##### Running gosec" + gosec ./... + +.PHONY: govulncheck +govulncheck: + $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) + @echo "##### Running govulncheck" + govulncheck ./... + +.PHONY: verify +verify: golangci-lint gosec govulncheck + +.PHONY: test +test: + @echo "##### Running tests" + go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... \ No newline at end of file diff --git a/cache.go b/cache.go index 0746c12..bf21697 100644 --- a/cache.go +++ b/cache.go @@ -12,7 +12,7 @@ import ( "sync" ) -var invalidPath = errors.New("schema: invalid path") +var errInvalidPath = errors.New("schema: invalid path") // newCache returns a new cache. func newCache() *cache { @@ -53,13 +53,13 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) { keys := strings.Split(p, ".") for i := 0; i < len(keys); i++ { if t.Kind() != reflect.Struct { - return nil, invalidPath + return nil, errInvalidPath } if struc = c.get(t); struc == nil { - return nil, invalidPath + return nil, errInvalidPath } if field = struc.get(keys[i]); field == nil { - return nil, invalidPath + return nil, errInvalidPath } // Valid field. Append index. path = append(path, field.name) @@ -72,10 +72,10 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) { // So checking i+2 is not necessary anymore. i++ if i+1 > len(keys) { - return nil, invalidPath + return nil, errInvalidPath } if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil { - return nil, invalidPath + return nil, errInvalidPath } parts = append(parts, pathPart{ path: path, diff --git a/decoder.go b/decoder.go index 025e438..28b560b 100644 --- a/decoder.go +++ b/decoder.go @@ -193,7 +193,7 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values 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 { + if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous { field.Set(reflect.New(field.Type().Elem())) } } diff --git a/decoder_test.go b/decoder_test.go index 863891f..f89a4c3 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -69,9 +69,7 @@ func (id *S19) UnmarshalText(text []byte) error { if len(buf) > len(*id) { return errors.New("out of range") } - for i := range buf { - (*id)[i] = buf[i] - } + copy((*id)[:], buf) return nil } @@ -673,7 +671,10 @@ func TestEmptyValue(t *testing.T) { "F01": {"", "foo"}, } s := &S5{} - NewDecoder().Decode(s, data) + err := NewDecoder().Decode(s, data) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if len(s.F01) != 1 { t.Errorf("Expected 1 values in F01") } @@ -706,7 +707,10 @@ func TestUnexportedField(t *testing.T) { "id": {"identifier"}, } s := &S6{} - NewDecoder().Decode(s, data) + err := NewDecoder().Decode(s, data) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if s.id != "" { t.Errorf("Unexported field expected to be ignored") } @@ -724,7 +728,10 @@ func TestMultipleValues(t *testing.T) { } s := S7{} - NewDecoder().Decode(&s, data) + err := NewDecoder().Decode(&s, data) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if s.ID != "1" { t.Errorf("Last defined value must be used when multiple values for same field are provided") } @@ -742,7 +749,10 @@ func TestSetAliasTag(t *testing.T) { s := S8{} dec := NewDecoder() dec.SetAliasTag("json") - dec.Decode(&s, data) + err := dec.Decode(&s, data) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if s.ID != "foo" { t.Fatalf("Bad value: got %q, want %q", s.ID, "foo") } @@ -813,7 +823,10 @@ func TestEmbeddedField(t *testing.T) { "Id": {"identifier"}, } s := &S10{} - NewDecoder().Decode(s, data) + err := NewDecoder().Decode(s, data) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if s.Id != "identifier" { t.Errorf("Missing support for embedded fields") } @@ -1148,7 +1161,10 @@ func TestCSVSlice(t *testing.T) { } s := S12A{} - NewDecoder().Decode(&s, data) + err := NewDecoder().Decode(&s, data) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if len(s.ID) != 2 { t.Errorf("Expected two values in the result list, got %+v", s.ID) } @@ -1161,14 +1177,17 @@ type S12B struct { ID []string } -//Decode should not split on , into a slice for string only +// 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) + err := NewDecoder().Decode(&s, data) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if len(s.ID) != 1 { t.Errorf("Expected one value in the result list, got %+v", s.ID) } @@ -1177,7 +1196,7 @@ func TestCSVStringSlice(t *testing.T) { } } -//Invalid data provided by client should not panic (github issue 33) +// Invalid data provided by client should not panic (github issue 33) func TestInvalidDataProvidedByClient(t *testing.T) { defer func() { if r := recover(); r != nil { @@ -1186,7 +1205,7 @@ func TestInvalidDataProvidedByClient(t *testing.T) { }() type S struct { - f string + f string // nolint:unused } data := map[string][]string{ @@ -1202,7 +1221,7 @@ func TestInvalidDataProvidedByClient(t *testing.T) { // underlying cause of error in issue 33 func TestInvalidPathInCacheParsePath(t *testing.T) { type S struct { - f string + f string // nolint:unused } typ := reflect.ValueOf(new(S)).Elem().Type() @@ -1218,7 +1237,10 @@ func TestDecodeToTypedField(t *testing.T) { type Aa bool s1 := &struct{ Aa }{} v1 := map[string][]string{"Aa": {"true"}} - NewDecoder().Decode(s1, v1) + err := NewDecoder().Decode(s1, v1) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if s1.Aa != Aa(true) { t.Errorf("s1: expected %v, got %v", true, s1.Aa) } @@ -1238,7 +1260,10 @@ func TestRegisterConverter(t *testing.T) { 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) + err := decoder.Decode(s1, v1) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if s1.Aa != Aa(1) { t.Errorf("s1.Aa: expected %v, got %v", 1, s1.Aa) @@ -1260,9 +1285,12 @@ func TestRegisterConverterSlice(t *testing.T) { }{} expected := []string{"one", "two", "three"} - decoder.Decode(&result, map[string][]string{ + err := decoder.Decode(&result, map[string][]string{ "multiple": []string{"one,two,three"}, }) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } for i := range expected { if got, want := expected[i], result.Multiple[i]; got != want { t.Errorf("%d: got %s, want %s", i, got, want) @@ -1868,8 +1896,11 @@ func TestRegisterConverterOverridesTextUnmarshaler(t *testing.T) { 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) + v1 := map[string][]string{"MyTime": {"4"}} + err := decoder.Decode(s1, v1) + if err != nil { + t.Fatalf("Failed to decode: %v", err) + } if s1.MyTime != MyTime(ts) { t.Errorf("s1.Aa: expected %v, got %v", ts, s1.MyTime) @@ -1951,7 +1982,7 @@ func TestTextUnmarshalerTypeSliceOfStructs(t *testing.T) { sb := struct { Value S21B }{} - if err := decoder.Decode(&sb, data); err == invalidPath { + if err := decoder.Decode(&sb, data); err == errInvalidPath { t.Fatal("Expecting invalid path error", err) } } diff --git a/encoder.go b/encoder.go index 8f9b9e0..51f0a78 100644 --- a/encoder.go +++ b/encoder.go @@ -3,6 +3,7 @@ package schema import ( "errors" "fmt" + "log" "reflect" "strconv" ) @@ -94,7 +95,10 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { // 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) + err := e.encode(v.Field(i).Elem(), dst) + if err != nil { + log.Fatal(err) + } continue } @@ -112,7 +116,10 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { } if v.Field(i).Type().Kind() == reflect.Struct { - e.encode(v.Field(i), dst) + err := e.encode(v.Field(i), dst) + if err != nil { + log.Fatal(err) + } continue } diff --git a/encoder_test.go b/encoder_test.go index 49f7fa4..07f3776 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -205,7 +205,7 @@ func TestCompatSlices(t *testing.T) { t.Errorf("Dncoder has non-nil error: %v", err) } - if len(src.Ints) != len(dst.Ints) || len(src.Ones) != len(src.Ones) { + if len(src.Ints) != len(dst.Ints) || len(src.Ones) != len(dst.Ones) { t.Fatalf("Expected %v, got %v", src, dst) } @@ -344,7 +344,10 @@ func TestEncoderSetAliasTag(t *testing.T) { } encoder := NewEncoder() encoder.SetAliasTag("json") - encoder.Encode(&s, data) + err := encoder.Encode(&s, data) + if err != nil { + t.Fatalf("Failed to encode: %v", err) + } valExists(t, "id", "foo", data) } @@ -376,7 +379,10 @@ func TestEncoderWithOmitempty(t *testing.T) { } encoder := NewEncoder() - encoder.Encode(&s, vals) + err := encoder.Encode(&s, vals) + if err != nil { + t.Fatalf("Failed to encode: %v", err) + } valNotExists(t, "f01", vals) valExists(t, "f02", "test", vals) @@ -402,7 +408,10 @@ func TestStructPointer(t *testing.T) { } encoder := NewEncoder() - encoder.Encode(&s, vals) + err := encoder.Encode(&s, vals) + if err != nil { + t.Fatalf("Failed to encode: %v", err) + } valExists(t, "F12", "2", vals) valExists(t, "F02", "null", vals) valNotExists(t, "F03", vals) @@ -428,7 +437,10 @@ func TestRegisterEncoderCustomArrayType(t *testing.T) { return fmt.Sprint(value.Interface()) }) - encoder.Encode(s, vals) + err := encoder.Encode(ss[s], vals) + if err != nil { + t.Fatalf("Failed to encode: %v", err) + } } } @@ -466,7 +478,7 @@ func TestRegisterEncoderStructIsZero(t *testing.T) { t.Error("expected tim1 to be present") } - if "2020-08-04T13:30:01Z" != ta[0] { + if ta[0] != "2020-08-04T13:30:01Z" { t.Error("expected correct tim1 time") } From 4770c7b7cd4e2818f48c09bb07eed322a9bf5e84 Mon Sep 17 00:00:00 2001 From: aboy Date: Mon, 31 Jul 2023 02:59:20 +0900 Subject: [PATCH 16/36] fix misspell (#192) ~Fixes #~ **Summary of Changes** 1. fixed misspell `ommited` -> `omitted` > PS: Make sure your PR includes/updates tests! If you need help with this part, just ask! Co-authored-by: Corey Daley (cherry picked from commit 937768dca1f737d172fedb86ab002630ae7d13a9) --- encoder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encoder_test.go b/encoder_test.go index 07f3776..092f0de 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -315,7 +315,7 @@ func valsExist(t *testing.T, key string, expect []string, result map[string][]st 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] + ".") + t.Error("Key not omitted. Expected: empty; got: " + val[0] + ".") } } From 154b12f2dc73948f73d44687909a36984ba5d95e Mon Sep 17 00:00:00 2001 From: Corey Daley Date: Wed, 18 Oct 2023 13:20:12 +0200 Subject: [PATCH 17/36] update GitHub workflows (#205) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What type of PR is this? (check all applicable) - [x] Refactor - [ ] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update - [ ] Go Version Update - [ ] Dependency Update ## Description ## Related Tickets & Documents - Related Issue # - Closes # ## Added/updated tests? - [ ] Yes - [ ] No, and this is why: _please replace this line with details on why tests have not been included_ - [ ] I need help with writing tests ## Run verifications and test - [x] `make verify` is passing - [x] `make test` is passing (cherry picked from commit 9dff76b55e8e2d7a8d557319332b1697b27c3fa9) --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a5ed29b..9747360 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/zitadel/schema -go 1.23.7 +go 1.20 From 8cea7b25e2ee49c55812bb3cecd4b0a2d2f540f3 Mon Sep 17 00:00:00 2001 From: h2570su Date: Thu, 18 Jan 2024 11:58:50 +0800 Subject: [PATCH 18/36] Remove log.Fatal() usage in encoder.go (#207) (cherry picked from commit 4890efcd8442004009ee4fd2f4830138eeb9129e) --- encoder.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/encoder.go b/encoder.go index 51f0a78..52f2c10 100644 --- a/encoder.go +++ b/encoder.go @@ -3,7 +3,6 @@ package schema import ( "errors" "fmt" - "log" "reflect" "strconv" ) @@ -97,7 +96,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { if isValidStructPointer(v.Field(i)) && !e.hasCustomEncoder(v.Field(i).Type()) { err := e.encode(v.Field(i).Elem(), dst) if err != nil { - log.Fatal(err) + errors[v.Field(i).Elem().Type().String()] = err } continue } @@ -118,7 +117,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { if v.Field(i).Type().Kind() == reflect.Struct { err := e.encode(v.Field(i), dst) if err != nil { - log.Fatal(err) + errors[v.Field(i).Type().String()] = err } continue } From a86d42f67fb7db9906f06c8b6ef169efd2fd7d28 Mon Sep 17 00:00:00 2001 From: Zakaria Amine Date: Sat, 10 Feb 2024 03:16:05 +0100 Subject: [PATCH 19/36] Add default tag (#183) **Summary of Changes** this is the implementation for the functionality described in #182 1. add support for an optional `default` tag to allow setting values when decoding in case no value is provided 2. limiting the `default` tag scope to only primitive types and their pointers as well (cherry picked from commit eda9c333a9b5f68bccf17f4b12171280b6976170) --- cache.go | 16 +++- converter.go | 77 ++++++++++++++++ decoder.go | 71 ++++++++++++++ decoder_test.go | 239 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 401 insertions(+), 2 deletions(-) diff --git a/cache.go b/cache.go index bf21697..065b8d6 100644 --- a/cache.go +++ b/cache.go @@ -197,6 +197,7 @@ func (c *cache) createField(field reflect.StructField, parentAlias string) *fiel isSliceOfStructs: isSlice && isStruct, isAnonymous: field.Anonymous, isRequired: options.Contains("required"), + defaultValue: options.getDefaultOptionValue(), } } @@ -246,8 +247,9 @@ type fieldInfo struct { // 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 + isAnonymous bool + isRequired bool + defaultValue string } func (f *fieldInfo) paths(prefix string) []string { @@ -303,3 +305,13 @@ func (o tagOptions) Contains(option string) bool { } return false } + +func (o tagOptions) getDefaultOptionValue() string { + for _, s := range o { + if strings.HasPrefix(s, "default:") { + return strings.Split(s, ":")[1] + } + } + + return "" +} diff --git a/converter.go b/converter.go index 4f2116a..4bae6df 100644 --- a/converter.go +++ b/converter.go @@ -143,3 +143,80 @@ func convertUint64(value string) reflect.Value { } return invalidValue } + +func convertPointer(k reflect.Kind, value string) reflect.Value { + switch k { + case boolType: + if v := convertBool(value); v.IsValid() { + converted := v.Bool() + return reflect.ValueOf(&converted) + } + case float32Type: + if v := convertFloat32(value); v.IsValid() { + converted := float32(v.Float()) + return reflect.ValueOf(&converted) + } + case float64Type: + if v := convertFloat64(value); v.IsValid() { + converted := float64(v.Float()) + return reflect.ValueOf(&converted) + } + case intType: + if v := convertInt(value); v.IsValid() { + converted := int(v.Int()) + return reflect.ValueOf(&converted) + } + case int8Type: + if v := convertInt8(value); v.IsValid() { + converted := int8(v.Int()) + return reflect.ValueOf(&converted) + } + case int16Type: + if v := convertInt16(value); v.IsValid() { + converted := int16(v.Int()) + return reflect.ValueOf(&converted) + } + case int32Type: + if v := convertInt32(value); v.IsValid() { + converted := int32(v.Int()) + return reflect.ValueOf(&converted) + } + case int64Type: + if v := convertInt64(value); v.IsValid() { + converted := int64(v.Int()) + return reflect.ValueOf(&converted) + } + case stringType: + if v := convertString(value); v.IsValid() { + converted := v.String() + return reflect.ValueOf(&converted) + } + case uintType: + if v := convertUint(value); v.IsValid() { + converted := uint(v.Uint()) + return reflect.ValueOf(&converted) + } + case uint8Type: + if v := convertUint8(value); v.IsValid() { + converted := uint8(v.Uint()) + return reflect.ValueOf(&converted) + } + case uint16Type: + if v := convertUint16(value); v.IsValid() { + converted := uint16(v.Uint()) + return reflect.ValueOf(&converted) + } + case uint32Type: + if v := convertUint32(value); v.IsValid() { + converted := uint32(v.Uint()) + return reflect.ValueOf(&converted) + } + case uint64Type: + if v := convertUint64(value); v.IsValid() { + converted := uint64(v.Uint()) + return reflect.ValueOf(&converted) + } + } + + return invalidValue +} diff --git a/decoder.go b/decoder.go index 28b560b..98f072e 100644 --- a/decoder.go +++ b/decoder.go @@ -84,6 +84,7 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { errors[path] = UnknownKeyError{Key: path} } } + errors.merge(d.setDefaults(t, v)) errors.merge(d.checkRequired(t, src)) if len(errors) > 0 { return errors @@ -91,6 +92,76 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { return nil } +//setDefaults sets the default values when the `default` tag is specified, +//default is supported on basic/primitive types and their pointers, +//nested structs can also have default tags +func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { + struc := d.cache.get(t) + if struc == nil { + // unexpect, cache.get never return nil + return MultiError{"default-" + t.Name(): errors.New("cache fail")} + } + + errs := MultiError{} + + for _, f := range struc.fields { + vCurrent := v.FieldByName(f.name) + + if vCurrent.Type().Kind() == reflect.Struct && f.defaultValue == "" { + errs.merge(d.setDefaults(vCurrent.Type(), vCurrent)) + } else if isPointerToStruct(vCurrent) && f.defaultValue == "" { + errs.merge(d.setDefaults(vCurrent.Elem().Type(), vCurrent.Elem())) + } + + if f.defaultValue != "" && f.isRequired { + errs.merge(MultiError{"default-" + f.name: errors.New("required fields cannot have a default value")}) + } else if f.defaultValue != "" && vCurrent.IsZero() && !f.isRequired { + if f.typ.Kind() == reflect.Struct { + errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")}) + } else if f.typ.Kind() == reflect.Slice { + vals := strings.Split(f.defaultValue, "|") + + //check if slice has one of the supported types for defaults + if _, ok := builtinConverters[f.typ.Elem().Kind()]; !ok { + errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")}) + continue + } + + defaultSlice := reflect.MakeSlice(f.typ, 0, cap(vals)) + for _, val := range vals { + //this check is to handle if the wrong value is provided + if convertedVal := builtinConverters[f.typ.Elem().Kind()](val); convertedVal.IsValid() { + defaultSlice = reflect.Append(defaultSlice, convertedVal) + } + } + vCurrent.Set(defaultSlice) + } else if f.typ.Kind() == reflect.Ptr { + t1 := f.typ.Elem() + + if t1.Kind() == reflect.Struct || t1.Kind() == reflect.Slice { + errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")}) + } + + //this check is to handle if the wrong value is provided + if convertedVal := convertPointer(t1.Kind(), f.defaultValue); convertedVal.IsValid() { + vCurrent.Set(convertedVal) + } + } else { + //this check is to handle if the wrong value is provided + if convertedVal := builtinConverters[f.typ.Kind()](f.defaultValue); convertedVal.IsValid() { + vCurrent.Set(builtinConverters[f.typ.Kind()](f.defaultValue)) + } + } + } + } + + return errs +} + +func isPointerToStruct(v reflect.Value) bool { + return !v.IsZero() && v.Type().Kind() == reflect.Ptr && v.Elem().Type().Kind() == reflect.Struct +} + // checkRequired checks whether required fields are empty // // check type t recursively if t has struct fields. diff --git a/decoder_test.go b/decoder_test.go index f89a4c3..3c12218 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2055,3 +2055,242 @@ func TestUnmashalPointerToEmbedded(t *testing.T) { t.Errorf("Expected %v errors, got %v", expected, s.Value) } } + +func TestDefaultValuesAreSet(t *testing.T) { + type N struct { + S1 string `schema:"s1,default:test1"` + I2 int `schema:"i2,default:22"` + R2 []float64 `schema:"r2,default:2|3.5|11.01"` + } + + type D struct { + N + S string `schema:"s,default:test1"` + I int `schema:"i,default:21"` + J int8 `schema:"j,default:2"` + K int16 `schema:"k,default:-455"` + L int32 `schema:"l,default:899"` + M int64 `schema:"m,default:12455"` + B bool `schema:"b,default:false"` + F float64 `schema:"f,default:3.14"` + G float32 `schema:"g,default:19.12"` + U uint `schema:"u,default:1"` + V uint8 `schema:"v,default:190"` + W uint16 `schema:"w,default:20000"` + Y uint32 `schema:"y,default:156666666"` + Z uint64 `schema:"z,default:1545465465465546"` + X []string `schema:"x,default:x1|x2"` + } + + data := map[string][]string{} + + d := D{} + + decoder := NewDecoder() + + if err := decoder.Decode(&d, data); err != nil { + t.Fatal("Error while decoding:", err) + } + + expected := D{ + N: N{ + S1: "test1", + I2: 22, + R2: []float64{2, 3.5, 11.01}, + }, + S: "test1", + I: 21, + J: 2, + K: -455, + L: 899, + M: 12455, + B: false, + F: 3.14, + G: 19.12, + U: 1, + V: 190, + W: 20000, + Y: 156666666, + Z: 1545465465465546, + X: []string{"x1", "x2"}, + } + + if !reflect.DeepEqual(expected, d) { + t.Errorf("Expected %v, got %v", expected, d) + } + + type P struct { + *N + S *string `schema:"s,default:test1"` + I *int `schema:"i,default:21"` + J *int8 `schema:"j,default:2"` + K *int16 `schema:"k,default:-455"` + L *int32 `schema:"l,default:899"` + M *int64 `schema:"m,default:12455"` + B *bool `schema:"b,default:false"` + F *float64 `schema:"f,default:3.14"` + G *float32 `schema:"g,default:19.12"` + U *uint `schema:"u,default:1"` + V *uint8 `schema:"v,default:190"` + W *uint16 `schema:"w,default:20000"` + Y *uint32 `schema:"y,default:156666666"` + Z *uint64 `schema:"z,default:1545465465465546"` + X []string `schema:"x,default:x1|x2"` + } + + p := P{N: &N{}} + + if err := decoder.Decode(&p, data); err != nil { + t.Fatal("Error while decoding:", err) + } + + vExpected := reflect.ValueOf(expected) + vActual := reflect.ValueOf(p) + + i := 0 + + for i < vExpected.NumField() { + if !reflect.DeepEqual(vExpected.Field(i).Interface(), reflect.Indirect(vActual.Field(i)).Interface()) { + t.Errorf("Expected %v, got %v", vExpected.Field(i).Interface(), reflect.Indirect(vActual.Field(i)).Interface()) + } + i++ + } +} + +func TestDefaultValuesAreIgnoredIfValuesAreProvided(t *testing.T) { + type D struct { + S string `schema:"s,default:test1"` + I int `schema:"i,default:21"` + B bool `schema:"b,default:false"` + F float64 `schema:"f,default:3.14"` + U uint `schema:"u,default:1"` + } + + data := map[string][]string{"s": {"s"}, "i": {"1"}, "b": {"true"}, "f": {"0.22"}, "u": {"14"}} + + d := D{} + + decoder := NewDecoder() + + if err := decoder.Decode(&d, data); err != nil { + t.Fatal("Error while decoding:", err) + } + + expected := D{ + S: "s", + I: 1, + B: true, + F: 0.22, + U: 14, + } + + if !reflect.DeepEqual(expected, d) { + t.Errorf("Expected %v, got %v", expected, d) + } +} + +func TestRequiredFieldsCannotHaveDefaults(t *testing.T) { + type D struct { + S string `schema:"s,required,default:test1"` + I int `schema:"i,required,default:21"` + B bool `schema:"b,required,default:false"` + F float64 `schema:"f,required,default:3.14"` + U uint `schema:"u,required,default:1"` + } + + data := map[string][]string{"s": {"s"}, "i": {"1"}, "b": {"true"}, "f": {"0.22"}, "u": {"14"}} + + d := D{} + + decoder := NewDecoder() + + err := decoder.Decode(&d, data) + + expected := "required fields cannot have a default value" + + if err == nil || !strings.Contains(err.Error(), expected) { + t.Errorf("decoding should fail with error msg %s got %q", expected, err) + } + +} + +func TestInvalidDefaultsValuesHaveNoEffect(t *testing.T) { + type D struct { + A []int `schema:"a,default:wrong1|wrong2"` + B bool `schema:"b,default:invalid"` + C *float32 `schema:"c,default:notAFloat"` + //uint types + D uint `schema:"d,default:notUint"` + E uint8 `schema:"e,default:notUint"` + F uint16 `schema:"f,default:notUint"` + G uint32 `schema:"g,default:notUint"` + H uint64 `schema:"h,default:notUint"` + // uint types pointers + I *uint `schema:"i,default:notUint"` + J *uint8 `schema:"j,default:notUint"` + K *uint16 `schema:"k,default:notUint"` + L *uint32 `schema:"l,default:notUint"` + M *uint64 `schema:"m,default:notUint"` + // int types + N int `schema:"n,default:notInt"` + O int8 `schema:"o,default:notInt"` + P int16 `schema:"p,default:notInt"` + Q int32 `schema:"q,default:notInt"` + R int64 `schema:"r,default:notInt"` + // int types pointers + S *int `schema:"s,default:notInt"` + T *int8 `schema:"t,default:notInt"` + U *int16 `schema:"u,default:notInt"` + V *int32 `schema:"v,default:notInt"` + W *int64 `schema:"w,default:notInt"` + // float + X float32 `schema:"c,default:notAFloat"` + Y float64 `schema:"c,default:notAFloat"` + Z *float64 `schema:"c,default:notAFloat"` + } + + d := D{} + + expected := D{A: []int{}} + + data := map[string][]string{} + + decoder := NewDecoder() + + err := decoder.Decode(&d, data) + + if err != nil { + t.Errorf("decoding should succeed but got error: %q", err) + } + + if !reflect.DeepEqual(expected, d) { + t.Errorf("expected %v but got %v", expected, d) + } +} + +func TestDefaultsAreNotSupportedForStructsAndStructSlices(t *testing.T) { + type C struct { + C string `schema:"c"` + } + + type D struct { + S S1 `schema:"s,default:{f1:0}"` + A []C `schema:"a,default:{c:test1}|{c:test2}"` + B []*int `schema:"b,default:12"` + E *C `schema:"e,default:{c:test3}"` + } + + d := D{} + + data := map[string][]string{} + + decoder := NewDecoder() + + err := decoder.Decode(&d, data) + + expected := "default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices" + + if err == nil || !strings.Contains(err.Error(), expected) { + t.Errorf("decoding should fail with error msg %s got %q", expected, err) + } +} From 419a5cc9773f28f570444677f937022a346fc35d Mon Sep 17 00:00:00 2001 From: zak905 Date: Mon, 19 Feb 2024 22:45:14 +0100 Subject: [PATCH 20/36] update readme: add informations about the default tag option usage (cherry picked from commit 1ab5d826e362d6e617da756d140af04ea04ec1f9) --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 07fd4c1..4ceaf09 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,32 @@ The supported field types in the struct are: 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 +## Setting Defaults + +It is possible to set default values when encoding/decoding by using the `default` tag option. The value of `default` is applied when a field has a zero value, a pointer has a nil value, or a slice is empty. + +```go +type Person struct { + Phone string `schema:"phone,default:+123456"` // custom name + Age int `schema:"age,default:21"` + Admin bool `schema:"admin,default:false"` + Balance float64 `schema:"balance,default:10.0"` + Friends []string `schema:friends,default:john|bob` +} +``` + +The `default` tag option is supported for the following types: + +* bool +* float variants (float32, float64) +* int variants (int, int8, int16, int32, int64) +* uint variants (uint, uint8, uint16, uint32, uint64) +* string +* a slice of the above types. As shown in the example above, `|` should be used to separate between slice items. +* a pointer to one of the above types (pointer to slice and slice of pointers are not supported). + +> [!NOTE] +> Because primitive types like int, float, bool, unint and their variants have their default (or zero) values set by Golang, it is not possible to distinguish them from a provided value when decoding/encoding form values. In this case, the value provided by the `default` option tag will be always applied. For example, let's assume that the value submitted in the form for `balance` is `0.0` then the default of `10.0` will be applied, even if `0.0` is part of the form data for the `balance` field. In such cases, it is highly recommended to use pointers to allow schema to distinguish between when a form field has no provided value and when a form has a value equal to the corresponding default set by Golang for a particular type. If the type of the `Balance` field above is changed to `*float64`, then the zero value would be `nil`. In this case, if the form data value for `balance` is `0.0`, then the default will not be applied. ## License From 9c250587ea34a64ce051d25649ec5ae8f93eec25 Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Fri, 5 Apr 2024 08:08:11 +0900 Subject: [PATCH 21/36] fix: if default element type of value are setted in slice , raise error (cherry picked from commit 74876512deb8d80b428ac567e41919a5c471fabf) --- decoder.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/decoder.go b/decoder.go index 98f072e..db9b341 100644 --- a/decoder.go +++ b/decoder.go @@ -92,9 +92,9 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { return nil } -//setDefaults sets the default values when the `default` tag is specified, -//default is supported on basic/primitive types and their pointers, -//nested structs can also have default tags +// setDefaults sets the default values when the `default` tag is specified, +// default is supported on basic/primitive types and their pointers, +// nested structs can also have default tags func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { struc := d.cache.get(t) if struc == nil { @@ -130,9 +130,12 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { defaultSlice := reflect.MakeSlice(f.typ, 0, cap(vals)) for _, val := range vals { //this check is to handle if the wrong value is provided - if convertedVal := builtinConverters[f.typ.Elem().Kind()](val); convertedVal.IsValid() { - defaultSlice = reflect.Append(defaultSlice, convertedVal) + convertedVal := builtinConverters[f.typ.Elem().Kind()](val) + if !convertedVal.IsValid() { + errs.merge(MultiError{"default-" + f.name: errors.New("some elements in the slice are of a different type.")}) + break } + defaultSlice = reflect.Append(defaultSlice, convertedVal) } vCurrent.Set(defaultSlice) } else if f.typ.Kind() == reflect.Ptr { From 95c7b8f17212cce5d25cddfad47212c2199c2961 Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Fri, 5 Apr 2024 08:08:22 +0900 Subject: [PATCH 22/36] fix: add test (cherry picked from commit 993e5b1271c466f5b1cb49aa2c5fd59d4825efe8) --- decoder_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/decoder_test.go b/decoder_test.go index 3c12218..393927d 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2214,9 +2214,55 @@ func TestRequiredFieldsCannotHaveDefaults(t *testing.T) { } +func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { + type D struct { + A []int `schema:"a,default:0|wrong1"` + B []bool `schema:"b,default:true|invalid"` + C []*float32 `schema:"c,default:1.1|notAFloat"` + //uint types + D []uint `schema:"d,default:1|notUint"` + E []uint8 `schema:"e,default:2|notUint"` + F []uint16 `schema:"f,default:3|notUint"` + G []uint32 `schema:"g,default:4|notUint"` + H []uint64 `schema:"h,default:5|notUint"` + // uint types pointers + I []*uint `schema:"i,default:6|notUint"` + J []*uint8 `schema:"j,default:7|notUint"` + K []*uint16 `schema:"k,default:8|notUint"` + L []*uint32 `schema:"l,default:9|notUint"` + M []*uint64 `schema:"m,default:10|notUint"` + // int types + N []int `schema:"n,default:11|notInt"` + O []int8 `schema:"o,default:12|notInt"` + P []int16 `schema:"p,default:13|notInt"` + Q []int32 `schema:"q,default:14|notInt"` + R []int64 `schema:"r,default:15|notInt"` + // int types pointers + S []*int `schema:"s,default:16|notInt"` + T []*int8 `schema:"t,default:17|notInt"` + U []*int16 `schema:"u,default:18|notInt"` + V []*int32 `schema:"v,default:19|notInt"` + W []*int64 `schema:"w,default:20|notInt"` + // float + X []float32 `schema:"c,default:2.2|notAFloat"` + Y []float64 `schema:"c,default:3.3|notAFloat"` + Z []*float64 `schema:"c,default:4.4|notAFloat"` + } + d := D{} + + data := map[string][]string{} + + decoder := NewDecoder() + + err := decoder.Decode(&d, data) + + if err == nil { + t.Error("if a different type exists, error should be raised.") + } +} + func TestInvalidDefaultsValuesHaveNoEffect(t *testing.T) { type D struct { - A []int `schema:"a,default:wrong1|wrong2"` B bool `schema:"b,default:invalid"` C *float32 `schema:"c,default:notAFloat"` //uint types @@ -2251,7 +2297,7 @@ func TestInvalidDefaultsValuesHaveNoEffect(t *testing.T) { d := D{} - expected := D{A: []int{}} + expected := D{} data := map[string][]string{} From 2b94d1c4c5d23a1786b09e4a562aafd79a4bcf5b Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Sat, 6 Apr 2024 10:48:09 +0900 Subject: [PATCH 23/36] fix: test data (cherry picked from commit 4548527ece86d14ce95d409cb90c50fd886b6e9d) --- decoder_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/decoder_test.go b/decoder_test.go index 393927d..9b55c64 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2216,34 +2216,34 @@ func TestRequiredFieldsCannotHaveDefaults(t *testing.T) { func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { type D struct { - A []int `schema:"a,default:0|wrong1"` + A []int `schema:"a,default:0|won"` B []bool `schema:"b,default:true|invalid"` C []*float32 `schema:"c,default:1.1|notAFloat"` - //uint types + // //uint types D []uint `schema:"d,default:1|notUint"` E []uint8 `schema:"e,default:2|notUint"` F []uint16 `schema:"f,default:3|notUint"` G []uint32 `schema:"g,default:4|notUint"` H []uint64 `schema:"h,default:5|notUint"` - // uint types pointers + // // uint types pointers I []*uint `schema:"i,default:6|notUint"` J []*uint8 `schema:"j,default:7|notUint"` - K []*uint16 `schema:"k,default:8|notUint"` - L []*uint32 `schema:"l,default:9|notUint"` - M []*uint64 `schema:"m,default:10|notUint"` - // int types + K []*uint16 `schema:"k,default:12|notUint"` + L []*uint32 `schema:"l,default:129|notUint"` + M []*uint64 `schema:"m,default:11111|notUint"` + // // int types N []int `schema:"n,default:11|notInt"` O []int8 `schema:"o,default:12|notInt"` P []int16 `schema:"p,default:13|notInt"` Q []int32 `schema:"q,default:14|notInt"` R []int64 `schema:"r,default:15|notInt"` - // int types pointers - S []*int `schema:"s,default:16|notInt"` - T []*int8 `schema:"t,default:17|notInt"` - U []*int16 `schema:"u,default:18|notInt"` - V []*int32 `schema:"v,default:19|notInt"` - W []*int64 `schema:"w,default:20|notInt"` - // float + // // int types pointers + S []*int `schema:"s,default:1000|notInt"` + T []*int8 `schema:"t,default:1000|notInt"` + U []*int16 `schema:"u,default:1000|notInt"` + V []*int32 `schema:"v,default:22222|notInt"` + W []*int64 `schema:"w,default:11111|notInt"` + // // float X []float32 `schema:"c,default:2.2|notAFloat"` Y []float64 `schema:"c,default:3.3|notAFloat"` Z []*float64 `schema:"c,default:4.4|notAFloat"` From 1b632a9ad4c13e208228f9298efcc8116f581b4b Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Fri, 12 Apr 2024 23:55:42 +0900 Subject: [PATCH 24/36] fix: decode error message (cherry picked from commit 7d1c58e94f09ec1ce2658477a78cc77aa2dd4e2e) --- decoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoder.go b/decoder.go index db9b341..26f2b38 100644 --- a/decoder.go +++ b/decoder.go @@ -132,7 +132,7 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { //this check is to handle if the wrong value is provided convertedVal := builtinConverters[f.typ.Elem().Kind()](val) if !convertedVal.IsValid() { - errs.merge(MultiError{"default-" + f.name: errors.New("some elements in the slice are of a different type.")}) + errs.merge(MultiError{"default-" + f.name: fmt.Errorf("failed setting default: %s is not compatible with field %s type", val, f.name)}) break } defaultSlice = reflect.Append(defaultSlice, convertedVal) From c9e4629f8bcf904bc2aa425dd745e9fbb120ec88 Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Sun, 14 Apr 2024 11:57:03 +0900 Subject: [PATCH 25/36] fix:test: add assertion (cherry picked from commit c44c90d0d4b9829de378e4686b44cc62e2cf6711) --- decoder_test.go | 55 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/decoder_test.go b/decoder_test.go index 9b55c64..6f1ee7e 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -7,6 +7,7 @@ package schema import ( "encoding/hex" "errors" + "fmt" "reflect" "strings" "testing" @@ -2216,21 +2217,21 @@ func TestRequiredFieldsCannotHaveDefaults(t *testing.T) { func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { type D struct { - A []int `schema:"a,default:0|won"` - B []bool `schema:"b,default:true|invalid"` - C []*float32 `schema:"c,default:1.1|notAFloat"` + A []int `schema:"a,default:0|notInt"` + B []bool `schema:"b,default:true|notInt"` + C []*float32 `schema:"c,default:1.1|notInt"` // //uint types - D []uint `schema:"d,default:1|notUint"` - E []uint8 `schema:"e,default:2|notUint"` - F []uint16 `schema:"f,default:3|notUint"` - G []uint32 `schema:"g,default:4|notUint"` - H []uint64 `schema:"h,default:5|notUint"` + D []uint `schema:"d,default:1|notInt"` + E []uint8 `schema:"e,default:2|notInt"` + F []uint16 `schema:"f,default:3|notInt"` + G []uint32 `schema:"g,default:4|notInt"` + H []uint64 `schema:"h,default:5|notInt"` // // uint types pointers - I []*uint `schema:"i,default:6|notUint"` - J []*uint8 `schema:"j,default:7|notUint"` - K []*uint16 `schema:"k,default:12|notUint"` - L []*uint32 `schema:"l,default:129|notUint"` - M []*uint64 `schema:"m,default:11111|notUint"` + I []*uint `schema:"i,default:6|notInt"` + J []*uint8 `schema:"j,default:7|notInt"` + K []*uint16 `schema:"k,default:12|notInt"` + L []*uint32 `schema:"l,default:129|notInt"` + M []*uint64 `schema:"m,default:11111|notInt"` // // int types N []int `schema:"n,default:11|notInt"` O []int8 `schema:"o,default:12|notInt"` @@ -2244,20 +2245,40 @@ func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { V []*int32 `schema:"v,default:22222|notInt"` W []*int64 `schema:"w,default:11111|notInt"` // // float - X []float32 `schema:"c,default:2.2|notAFloat"` - Y []float64 `schema:"c,default:3.3|notAFloat"` - Z []*float64 `schema:"c,default:4.4|notAFloat"` + X []float32 `schema:"c,default:2.2|notInt"` + Y []float64 `schema:"c,default:3.3|notInt"` + Z []*float64 `schema:"c,default:4.4|notInt"` } d := D{} data := map[string][]string{} + pErrMsg := "default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices" + eng := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" decoder := NewDecoder() err := decoder.Decode(&d, data) if err == nil { - t.Error("if a different type exists, error should be raised.") + t.Error("if a different type exists, error should be raised") + } + + e, ok := err.(MultiError) + if !ok || len(e) != 26 { + t.Errorf("Expected 26 errors, got %#v", err) + } + for _, v := range eng { + fieldKey := "default-" + string(v) + errMsg := fmt.Sprintf("failed setting default: notInt is not compatible with field %s type", string(v)) + if ferr, ok := e[fieldKey]; ok { + // check pointer type error + if ferr.Error() == pErrMsg { + continue + } + if strings.Compare(ferr.Error(), errMsg) != 0 { + t.Errorf("%s: expected %s, got %#v", fieldKey, ferr.Error(), errMsg) + } + } } } From 01a72cd29b786c43a5246fb1156a3d7a0174a4b7 Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Sun, 14 Apr 2024 11:57:41 +0900 Subject: [PATCH 26/36] fix:test: fix comment (cherry picked from commit 50924ff92dda478a664c6c208ea220301a0c1d68) --- decoder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoder_test.go b/decoder_test.go index 6f1ee7e..d728a5d 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2271,7 +2271,7 @@ func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { fieldKey := "default-" + string(v) errMsg := fmt.Sprintf("failed setting default: notInt is not compatible with field %s type", string(v)) if ferr, ok := e[fieldKey]; ok { - // check pointer type error + // check pointer type field if ferr.Error() == pErrMsg { continue } From 4fa7028fe7f4a0b3d5a800ccdd1944cac4f7ec60 Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Wed, 17 Apr 2024 06:56:32 +0900 Subject: [PATCH 27/36] fix delete pointer slice test (cherry picked from commit be699f4f1e911a0cf6b80f3844e710c417ad031b) --- decoder_test.go | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/decoder_test.go b/decoder_test.go index d728a5d..af79549 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2217,42 +2217,27 @@ func TestRequiredFieldsCannotHaveDefaults(t *testing.T) { func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { type D struct { - A []int `schema:"a,default:0|notInt"` - B []bool `schema:"b,default:true|notInt"` - C []*float32 `schema:"c,default:1.1|notInt"` + A []int `schema:"a,default:0|notInt"` + B []bool `schema:"b,default:true|notInt"` // //uint types D []uint `schema:"d,default:1|notInt"` E []uint8 `schema:"e,default:2|notInt"` F []uint16 `schema:"f,default:3|notInt"` G []uint32 `schema:"g,default:4|notInt"` H []uint64 `schema:"h,default:5|notInt"` - // // uint types pointers - I []*uint `schema:"i,default:6|notInt"` - J []*uint8 `schema:"j,default:7|notInt"` - K []*uint16 `schema:"k,default:12|notInt"` - L []*uint32 `schema:"l,default:129|notInt"` - M []*uint64 `schema:"m,default:11111|notInt"` // // int types N []int `schema:"n,default:11|notInt"` O []int8 `schema:"o,default:12|notInt"` P []int16 `schema:"p,default:13|notInt"` Q []int32 `schema:"q,default:14|notInt"` R []int64 `schema:"r,default:15|notInt"` - // // int types pointers - S []*int `schema:"s,default:1000|notInt"` - T []*int8 `schema:"t,default:1000|notInt"` - U []*int16 `schema:"u,default:1000|notInt"` - V []*int32 `schema:"v,default:22222|notInt"` - W []*int64 `schema:"w,default:11111|notInt"` // // float - X []float32 `schema:"c,default:2.2|notInt"` - Y []float64 `schema:"c,default:3.3|notInt"` - Z []*float64 `schema:"c,default:4.4|notInt"` + X []float32 `schema:"c,default:2.2|notInt"` + Y []float64 `schema:"c,default:3.3|notInt"` } d := D{} data := map[string][]string{} - pErrMsg := "default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices" eng := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" decoder := NewDecoder() @@ -2264,17 +2249,13 @@ func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { } e, ok := err.(MultiError) - if !ok || len(e) != 26 { - t.Errorf("Expected 26 errors, got %#v", err) + if !ok || len(e) != 14 { + t.Errorf("Expected 14 errors, got %#v", err) } for _, v := range eng { fieldKey := "default-" + string(v) errMsg := fmt.Sprintf("failed setting default: notInt is not compatible with field %s type", string(v)) if ferr, ok := e[fieldKey]; ok { - // check pointer type field - if ferr.Error() == pErrMsg { - continue - } if strings.Compare(ferr.Error(), errMsg) != 0 { t.Errorf("%s: expected %s, got %#v", fieldKey, ferr.Error(), errMsg) } From 595717facd9c466dad08f298c70b75dab4fea18d Mon Sep 17 00:00:00 2001 From: 0xpeIpeI Date: Wed, 17 Apr 2024 07:12:45 +0900 Subject: [PATCH 28/36] fix: fix assertion test (cherry picked from commit a377fd6ca77773650ff9d8e6ac1e3fe7eb660bf3) --- decoder_test.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/decoder_test.go b/decoder_test.go index af79549..6f71869 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2238,7 +2238,6 @@ func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { d := D{} data := map[string][]string{} - eng := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" decoder := NewDecoder() @@ -2248,17 +2247,20 @@ func TestInvalidDefaultElementInSliceRaiseError(t *testing.T) { t.Error("if a different type exists, error should be raised") } + dType := reflect.TypeOf(d) + e, ok := err.(MultiError) - if !ok || len(e) != 14 { - t.Errorf("Expected 14 errors, got %#v", err) - } - for _, v := range eng { - fieldKey := "default-" + string(v) - errMsg := fmt.Sprintf("failed setting default: notInt is not compatible with field %s type", string(v)) - if ferr, ok := e[fieldKey]; ok { - if strings.Compare(ferr.Error(), errMsg) != 0 { - t.Errorf("%s: expected %s, got %#v", fieldKey, ferr.Error(), errMsg) - } + if !ok || len(e) != dType.NumField() { + t.Errorf("Expected %d errors, got %#v", dType.NumField(), err) + } + + for i := 0; i < dType.NumField(); i++ { + v := dType.Field(i) + fieldKey := "default-" + string(v.Name) + errMsg := fmt.Sprintf("failed setting default: notInt is not compatible with field %s type", string(v.Name)) + ferr := e[fieldKey] + if strings.Compare(ferr.Error(), errMsg) != 0 { + t.Errorf("%s: expected %s, got %#v\n", fieldKey, ferr.Error(), errMsg) } } } From c3913e416b2fdbe4754081550fcded3e44afef49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Wyszy=C5=84ski?= Date: Mon, 3 Jun 2024 06:29:40 +0200 Subject: [PATCH 29/36] fix: indirection through nil pointer to embedded struct (#211) The provided test `TestUnmarshallToEmbeddedNoData` shows that the decoder panics when `setDefaults` encounters a nil pointer to an embedded struct. The fix replaces `FieldByName` with `FieldByIndexErr` to catch this kind of situation and continue with the next field. (cherry picked from commit 180f71e625073e210314dc8ca83dfbbbd0096dfe) --- decoder.go | 17 ++++++++++--- decoder_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/decoder.go b/decoder.go index 26f2b38..ed85641 100644 --- a/decoder.go +++ b/decoder.go @@ -104,6 +104,15 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { errs := MultiError{} + if v.Type().Kind() == reflect.Struct { + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous { + field.Set(reflect.New(field.Type().Elem())) + } + } + } + for _, f := range struc.fields { vCurrent := v.FieldByName(f.name) @@ -121,7 +130,7 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { } else if f.typ.Kind() == reflect.Slice { vals := strings.Split(f.defaultValue, "|") - //check if slice has one of the supported types for defaults + // check if slice has one of the supported types for defaults if _, ok := builtinConverters[f.typ.Elem().Kind()]; !ok { errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")}) continue @@ -129,7 +138,7 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { defaultSlice := reflect.MakeSlice(f.typ, 0, cap(vals)) for _, val := range vals { - //this check is to handle if the wrong value is provided + // this check is to handle if the wrong value is provided convertedVal := builtinConverters[f.typ.Elem().Kind()](val) if !convertedVal.IsValid() { errs.merge(MultiError{"default-" + f.name: fmt.Errorf("failed setting default: %s is not compatible with field %s type", val, f.name)}) @@ -145,12 +154,12 @@ func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError { errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")}) } - //this check is to handle if the wrong value is provided + // this check is to handle if the wrong value is provided if convertedVal := convertPointer(t1.Kind(), f.defaultValue); convertedVal.IsValid() { vCurrent.Set(convertedVal) } } else { - //this check is to handle if the wrong value is provided + // this check is to handle if the wrong value is provided if convertedVal := builtinConverters[f.typ.Kind()](f.defaultValue); convertedVal.IsValid() { vCurrent.Set(builtinConverters[f.typ.Kind()](f.defaultValue)) } diff --git a/decoder_test.go b/decoder_test.go index 6f71869..4f3b72a 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2057,6 +2057,74 @@ func TestUnmashalPointerToEmbedded(t *testing.T) { } } +type S24 struct { + F1 string `schema:"F1"` +} + +type S24e struct { + *S24 + F2 string `schema:"F2"` +} + +func TestUnmarshallToEmbeddedNoData(t *testing.T) { + data := map[string][]string{ + "F3": {"raw a"}, + } + + s := &S24e{} + + decoder := NewDecoder() + err := decoder.Decode(s, data); + + expectedErr := `schema: invalid path "F3"` + if err.Error() != expectedErr { + t.Fatalf("got %q, want %q", err, expectedErr) + } +} +type S25ee struct { + F3 string `schema:"F3"` +} + +type S25e struct { + S25ee + F2 string `schema:"F2"` +} + +type S25 struct { + S25e + F1 string `schema:"F1"` +} + +func TestDoubleEmbedded(t *testing.T){ + data := map[string][]string{ + "F1": {"raw a"}, + "F2": {"raw b"}, + "F3": {"raw c"}, + } + + + s := S25{} + decoder := NewDecoder() + + if err := decoder.Decode(&s, data); err != nil { + t.Fatal("Error while decoding:", err) + } + + expected := S25{ + F1: "raw a", + S25e: S25e{ + F2: "raw b", + S25ee: S25ee{ + F3: "raw c", + }, + }, + } + if !reflect.DeepEqual(expected, s) { + t.Errorf("Expected %v errors, got %v", expected, s) + } + +} + func TestDefaultValuesAreSet(t *testing.T) { type N struct { S1 string `schema:"s1,default:test1"` From be9af1ca12514cf3b1e5e75c14ec84b5edf3594a Mon Sep 17 00:00:00 2001 From: Bharat Rajani Date: Sun, 30 Jun 2024 02:04:06 +0530 Subject: [PATCH 30/36] Merge pull request from GHSA-3669-72x9-r9p3 * fixes the security advisory by limiting the slice creation based on configurable maxSize * address review comment (cherry picked from commit cd59f2f12cbdfa9c06aa63e425d1fe4a806967ff) --- decoder.go | 18 ++++++- decoder_test.go | 125 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 6 deletions(-) diff --git a/decoder.go b/decoder.go index ed85641..54c88ec 100644 --- a/decoder.go +++ b/decoder.go @@ -12,9 +12,13 @@ import ( "strings" ) +const ( + defaultMaxSize = 16000 +) + // NewDecoder returns a new Decoder. func NewDecoder() *Decoder { - return &Decoder{cache: newCache()} + return &Decoder{cache: newCache(), maxSize: defaultMaxSize} } // Decoder decodes values from a map[string][]string to a struct. @@ -22,6 +26,7 @@ type Decoder struct { cache *cache zeroEmpty bool ignoreUnknownKeys bool + maxSize int } // SetAliasTag changes the tag used to locate custom field aliases. @@ -54,6 +59,13 @@ func (d *Decoder) IgnoreUnknownKeys(i bool) { d.ignoreUnknownKeys = i } +// MaxSize limits the size of slices for URL nested arrays or object arrays. +// Choose MaxSize carefully; large values may create many zero-value slice elements. +// Example: "items.100000=apple" would create a slice with 100,000 empty strings. +func (d *Decoder) MaxSize(size int) { + d.maxSize = size +} + // RegisterConverter registers a converter function for a custom type. func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) { d.cache.registerConverter(value, converterFunc) @@ -302,6 +314,10 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values // Slice of structs. Let's go recursive. if len(parts) > 1 { idx := parts[0].index + // a defensive check to avoid creating a large slice based on user input index + if idx > d.maxSize { + return fmt.Errorf("%v index %d is larger than the configured maxSize %d", v.Kind(), idx, d.maxSize) + } if v.IsNil() || v.Len() < idx+1 { value := reflect.MakeSlice(t, idx+1, idx+1) if v.Len() < idx+1 { diff --git a/decoder_test.go b/decoder_test.go index 4f3b72a..d01569e 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -2063,7 +2063,7 @@ type S24 struct { type S24e struct { *S24 - F2 string `schema:"F2"` + F2 string `schema:"F2"` } func TestUnmarshallToEmbeddedNoData(t *testing.T) { @@ -2074,13 +2074,14 @@ func TestUnmarshallToEmbeddedNoData(t *testing.T) { s := &S24e{} decoder := NewDecoder() - err := decoder.Decode(s, data); - + err := decoder.Decode(s, data) + expectedErr := `schema: invalid path "F3"` if err.Error() != expectedErr { t.Fatalf("got %q, want %q", err, expectedErr) } } + type S25ee struct { F3 string `schema:"F3"` } @@ -2095,14 +2096,13 @@ type S25 struct { F1 string `schema:"F1"` } -func TestDoubleEmbedded(t *testing.T){ +func TestDoubleEmbedded(t *testing.T) { data := map[string][]string{ "F1": {"raw a"}, "F2": {"raw b"}, "F3": {"raw c"}, } - s := S25{} decoder := NewDecoder() @@ -2412,3 +2412,118 @@ func TestDefaultsAreNotSupportedForStructsAndStructSlices(t *testing.T) { t.Errorf("decoding should fail with error msg %s got %q", expected, err) } } + +func TestDecoder_MaxSize(t *testing.T) { + t.Parallel() + + type Nested struct { + Val int + NestedValues []struct { + NVal int + } + } + type NestedSlices struct { + Values []Nested + } + + testcases := []struct { + name string + maxSize int + decoderInput func() (dst NestedSlices, src map[string][]string) + expectedDecoded NestedSlices + expectedErr MultiError + }{ + { + name: "no error on decoding under max size", + maxSize: 10, + decoderInput: func() (dst NestedSlices, src map[string][]string) { + return dst, map[string][]string{ + "Values.1.Val": {"132"}, + "Values.1.NestedValues.1.NVal": {"1"}, + "Values.1.NestedValues.2.NVal": {"2"}, + "Values.1.NestedValues.3.NVal": {"3"}, + } + }, + expectedDecoded: NestedSlices{ + Values: []Nested{ + { + Val: 0, + NestedValues: nil, + }, + { + Val: 132, NestedValues: []struct{ NVal int }{ + {NVal: 0}, + {NVal: 1}, + {NVal: 2}, + {NVal: 3}, + }, + }, + }, + }, + expectedErr: nil, + }, + { + name: "error on decoding above max size", + maxSize: 1, + decoderInput: func() (dst NestedSlices, src map[string][]string) { + return dst, map[string][]string{ + "Values.1.Val": {"132"}, + "Values.1.NestedValues.1.NVal": {"1"}, + "Values.1.NestedValues.2.NVal": {"2"}, + "Values.1.NestedValues.3.NVal": {"3"}, + } + }, + expectedErr: MultiError{ + "Values.1.NestedValues.2.NVal": errors.New("slice index 2 is larger than the configured maxSize 1"), + "Values.1.NestedValues.3.NVal": errors.New("slice index 3 is larger than the configured maxSize 1"), + }, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + dec := NewDecoder() + dec.MaxSize(tc.maxSize) + dst, src := tc.decoderInput() + err := dec.Decode(&dst, src) + + if tc.expectedErr != nil { + var gotErr MultiError + if !errors.As(err, &gotErr) { + t.Errorf("decoder error is not of type %T", gotErr) + } + if !reflect.DeepEqual(gotErr, tc.expectedErr) { + t.Errorf("expected %v, got %v", tc.expectedErr, gotErr) + } + } else { + if !reflect.DeepEqual(dst, tc.expectedDecoded) { + t.Errorf("expected %v, got %v", tc.expectedDecoded, dst) + } + } + }) + } +} + +func TestDecoder_SetMaxSize(t *testing.T) { + + t.Run("default maxsize should be equal to given constant", func(t *testing.T) { + t.Parallel() + dec := NewDecoder() + if !reflect.DeepEqual(dec.maxSize, defaultMaxSize) { + t.Errorf("unexpected default max size") + } + }) + + t.Run("configured maxsize should be set properly", func(t *testing.T) { + t.Parallel() + configuredMaxSize := 50 + limitedMaxSizeDecoder := NewDecoder() + limitedMaxSizeDecoder.MaxSize(configuredMaxSize) + if !reflect.DeepEqual(limitedMaxSizeDecoder.maxSize, configuredMaxSize) { + t.Errorf("invalid decoder maxsize, expected: %d, got: %d", + configuredMaxSize, limitedMaxSizeDecoder.maxSize) + } + }) +} From ac7e138212ca391b0b645cb32c456751de24a1da Mon Sep 17 00:00:00 2001 From: Wim Van Laer Date: Mon, 5 Jan 2026 11:18:01 +0100 Subject: [PATCH 31/36] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20481df..3083da5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - go: ['1.23', '1.24'] + go: ['1.24', '1.25'] name: Go ${{ matrix.go }} test steps: - uses: actions/checkout@v4 From d11da69c566607a26a5bde05b9513eb86fbec4f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:20:00 +0000 Subject: [PATCH 32/36] chore: Bump actions/checkout from 4 to 5 (#20) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 27fa244..794ee07 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0423e84..5041916 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: go: ['1.23', '1.24'] name: Go ${{ matrix.go }} test steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup go uses: actions/setup-go@v5 with: @@ -37,7 +37,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Source checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Semantic Release uses: cycjimmy/semantic-release-action@v3 with: From 27f7af4f01fabe0ba58963b8a5c91303d2e56d15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:21:49 +0100 Subject: [PATCH 33/36] chore: Bump cycjimmy/semantic-release-action from 3 to 5 (#22) Bumps [cycjimmy/semantic-release-action](https://github.com/cycjimmy/semantic-release-action) from 3 to 5. - [Release notes](https://github.com/cycjimmy/semantic-release-action/releases) - [Changelog](https://github.com/cycjimmy/semantic-release-action/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/cycjimmy/semantic-release-action/compare/v3...v5) --- updated-dependencies: - dependency-name: cycjimmy/semantic-release-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wim Van Laer --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5041916..35fda24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: - name: Source checkout uses: actions/checkout@v5 - name: Semantic Release - uses: cycjimmy/semantic-release-action@v3 + uses: cycjimmy/semantic-release-action@v5 with: dry_run: false semantic_version: 18.0.1 From ed5b4a77697bccb0c2a889921d58ff4ea5d167fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:24:08 +0100 Subject: [PATCH 34/36] chore: Bump actions/setup-go from 5 to 6 (#23) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wim Van Laer --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35fda24..87e944d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version-file: 'go.mod' - run: go test -race -v -coverprofile=profile.cov . From 28d61cde87a08005403672862daec8105b335b82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:25:39 +0100 Subject: [PATCH 35/36] chore: Bump codecov/codecov-action from 5.4.0 to 5.5.1 (#24) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.0 to 5.5.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.4.0...v5.5.1) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wim Van Laer --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 87e944d..2a096cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: with: go-version-file: 'go.mod' - run: go test -race -v -coverprofile=profile.cov . - - uses: codecov/codecov-action@v5.4.0 + - uses: codecov/codecov-action@v5.5.1 with: file: ./profile.cov name: codecov-go From 66df4d40ae5216ba67be61d1f115ef812ab4c2a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:27:09 +0100 Subject: [PATCH 36/36] chore: Bump github/codeql-action from 3 to 4 (#25) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wim Van Laer --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 794ee07..e9e58f5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,7 +29,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 # Override language selection by uncommenting this and choosing your languages with: languages: go @@ -37,7 +37,7 @@ jobs: # 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@v3 + uses: github/codeql-action/autobuild@v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -51,4 +51,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4