Compare commits

..

4 Commits

Author SHA1 Message Date
Aurélien Bombo
d78c170a55 gha: Pin actionlint version
Pin to the latest released version as a security measure.

Signed-off-by: Aurélien Bombo <abombo@microsoft.com>
2026-03-23 12:35:34 -05:00
Aurélien Bombo
8117f4e998 gha: Avoid pesky deployment logs in PRs
GitHub recently announced that developers can now use environments without
auto-deployment, which allows us to avoid the noisy deployment logs in our
PRs:

https://github.blog/changelog/2026-03-19-github-actions-late-march-2026-updates/#github-actions-now-allows-developers-to-use-environments-without-auto-deployment

Signed-off-by: Aurélien Bombo <abombo@microsoft.com>
2026-03-23 12:10:07 -05:00
Steve Horsman
2728b493d5 Merge pull request #12681 from manuelh-dev/mahuber/ci-pip-py-venv
tests: cc: setup function for python venv
2026-03-23 14:33:30 +00:00
Manuel Huber
5765bc97b4 tests: cc: setup function for python venv
We recently had a failure on a new CI runner where
${HOME}/.cicd/venv/bin/activate was not present. The relevant call
originated from ensure_sev_snp_measure. Thus, add a function
ensure_cicd_python_venv before callers to pip install.
Currently, the NVIDIA NIM test and the confidential attestation
tests use pip to install dependencies.

Signed-off-by: Manuel Huber <manuelh@nvidia.com>
2026-03-18 17:07:47 -07:00
511 changed files with 83870 additions and 39088 deletions

View File

@@ -23,3 +23,5 @@ jobs:
- name: Run actionlint
uses: raven-actions/actionlint@e01d1ea33dd6a5ed517d95b4c0c357560ac6f518 # v2.1.1
with:
version: '1.7.11'

View File

@@ -12,7 +12,9 @@ jobs:
runs-on: ubuntu-22.04
permissions:
id-token: write # Used for OIDC access to log into Azure
environment: ci
environment:
name: ci
deployment: false
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:

View File

@@ -58,7 +58,9 @@ jobs:
permissions:
contents: read
id-token: write # Used for OIDC access to log into Azure
environment: ci
environment:
name: ci
deployment: false
env:
DOCKER_REGISTRY: ${{ inputs.registry }}
DOCKER_REPO: ${{ inputs.repo }}

View File

@@ -55,7 +55,9 @@ jobs:
permissions:
id-token: write # Used for OIDC access to log into Azure
environment: ci
environment:
name: ci
deployment: false
env:
DOCKER_REGISTRY: ${{ inputs.registry }}
DOCKER_REPO: ${{ inputs.repo }}

View File

@@ -144,7 +144,9 @@ jobs:
runs-on: ubuntu-24.04
permissions:
contents: read
environment: ci
environment:
name: ci
deployment: false
env:
DOCKER_REGISTRY: ${{ inputs.registry }}
DOCKER_REPO: ${{ inputs.repo }}
@@ -269,7 +271,9 @@ jobs:
runs-on: ubuntu-24.04
permissions:
contents: read
environment: ci
environment:
name: ci
deployment: false
env:
DOCKER_REGISTRY: ${{ inputs.registry }}
DOCKER_REPO: ${{ inputs.repo }}
@@ -379,7 +383,9 @@ jobs:
pull-type:
- default
runs-on: ubuntu-24.04
environment: ci
environment:
name: ci
deployment: false
env:
DOCKER_REGISTRY: ${{ inputs.registry }}
DOCKER_REPO: ${{ inputs.repo }}

View File

@@ -48,7 +48,9 @@ jobs:
- host_os: cbl-mariner
vmm: clh
runs-on: ubuntu-22.04
environment: ci
environment:
name: ci
deployment: false
permissions:
id-token: write # Used for OIDC access to log into Azure
env:

View File

@@ -26,11 +26,11 @@ require (
github.com/docker/go-units v0.5.0
github.com/fsnotify/fsnotify v1.9.0
github.com/go-ini/ini v1.67.0
github.com/go-openapi/errors v0.22.7
github.com/go-openapi/runtime v0.29.3
github.com/go-openapi/strfmt v0.26.0
github.com/go-openapi/errors v0.22.1
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/strfmt v0.23.0
github.com/go-openapi/swag v0.23.1
github.com/go-openapi/validate v0.25.2
github.com/go-openapi/validate v0.24.0
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
github.com/hashicorp/go-multierror v1.1.1
github.com/intel-go/cpuid v0.0.0-20210602155658-5747e5cec0d9
@@ -52,12 +52,12 @@ require (
github.com/urfave/cli v1.22.17
github.com/vishvananda/netlink v1.3.1
github.com/vishvananda/netns v0.0.5
go.opentelemetry.io/otel v1.41.0
go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/otel/exporters/jaeger v1.0.0
go.opentelemetry.io/otel/sdk v1.41.0
go.opentelemetry.io/otel/trace v1.41.0
go.opentelemetry.io/otel/sdk v1.40.0
go.opentelemetry.io/otel/trace v1.40.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sys v0.41.0
golang.org/x/sys v0.40.0
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.10
k8s.io/apimachinery v0.33.0
@@ -72,6 +72,7 @@ require (
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cilium/ebpf v0.16.0 // indirect
@@ -92,21 +93,11 @@ require (
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.24.3 // indirect
github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/jsonreference v0.21.5 // indirect
github.com/go-openapi/loads v0.23.3 // indirect
github.com/go-openapi/spec v0.22.4 // indirect
github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/fileutils v0.25.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
github.com/go-openapi/swag/loading v0.25.5 // indirect
github.com/go-openapi/swag/mangling v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-cmp v0.7.0 // indirect
@@ -117,30 +108,32 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/symlink v0.2.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-tools v0.9.1-0.20250303011046-260e151b8552 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/text v0.33.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect

View File

@@ -17,6 +17,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
@@ -103,58 +105,32 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.24.3 h1:a1hrvMr8X0Xt69KP5uVTu5jH62DscmDifrLzNglAayk=
github.com/go-openapi/analysis v0.24.3/go.mod h1:Nc+dWJ/FxZbhSow5Yh3ozg5CLJioB+XXT6MdLvJUsUw=
github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=
github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w=
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ=
github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA=
github.com/go-openapi/runtime v0.29.3 h1:h5twGaEqxtQg40ePiYm9vFFH1q06Czd7Ot6ufdK0w/Y=
github.com/go-openapi/runtime v0.29.3/go.mod h1:8A1W0/L5eyNJvKciqZtvIVQvYO66NlB7INMSZ9bw/oI=
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
github.com/go-openapi/strfmt v0.26.0 h1:SDdQLyOEqu8W96rO1FRG1fuCtVyzmukky0zcD6gMGLU=
github.com/go-openapi/strfmt v0.26.0/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 h1:NZOrZmIb6PTv5LTFxr5/mKV/FjbUzGE7E6gLz7vFoOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1/go.mod h1:r7dwsujEHawapMsxA69i+XMGZrQ5tRauhLAjV/sxg3Q=
github.com/go-openapi/testify/v2 v2.4.1 h1:zB34HDKj4tHwyUQHrUkpV0Q0iXQ6dUCOQtIqn8hE6Iw=
github.com/go-openapi/testify/v2 v2.4.1/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0=
github.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -226,6 +202,8 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
@@ -245,8 +223,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
@@ -270,9 +248,10 @@ github.com/opencontainers/runtime-tools v0.9.1-0.20250303011046-260e151b8552 h1:
github.com/opencontainers/runtime-tools v0.9.1-0.20250303011046-260e151b8552/go.mod h1:T487Kf80NeF2i0OyVXHiylg217e0buz8pQsa0T791RA=
github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84=
github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -302,6 +281,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -329,6 +309,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
@@ -336,20 +318,20 @@ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbE
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/jaeger v1.0.0 h1:cLhx8llHw02h5JTqGqaRbYn+QVKHmrzD9vEbKnSPk5U=
go.opentelemetry.io/otel/exporters/jaeger v1.0.0/go.mod h1:q10N1AolE1JjqKrFJK2tYw0iZpmX+HBaXBtuCzRnBGQ=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM=
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -367,8 +349,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -381,8 +363,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
@@ -412,14 +394,14 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -429,8 +411,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -0,0 +1,15 @@
bin/
.idea/
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

View File

@@ -0,0 +1,12 @@
language: go
dist: xenial
go:
- '1.10'
- '1.11'
- '1.12'
- '1.13'
- 'tip'
script:
- go test -coverpkg=./... -coverprofile=coverage.info -timeout=5s
- bash <(curl -s https://codecov.io/bash)

View File

@@ -0,0 +1,43 @@
# Contributor Code of Conduct
This project adheres to [The Code Manifesto](http://codemanifesto.com)
as its guidelines for contributor interactions.
## The Code Manifesto
We want to work in an ecosystem that empowers developers to reach their
potential — one that encourages growth and effective collaboration. A space
that is safe for all.
A space such as this benefits everyone that participates in it. It encourages
new developers to enter our field. It is through discussion and collaboration
that we grow, and through growth that we improve.
In the effort to create such a place, we hold to these values:
1. **Discrimination limits us.** This includes discrimination on the basis of
race, gender, sexual orientation, gender identity, age, nationality,
technology and any other arbitrary exclusion of a group of people.
2. **Boundaries honor us.** Your comfort levels are not everyones comfort
levels. Remember that, and if brought to your attention, heed it.
3. **We are our biggest assets.** None of us were born masters of our trade.
Each of us has been helped along the way. Return that favor, when and where
you can.
4. **We are resources for the future.** As an extension of #3, share what you
know. Make yourself a resource to help those that come after you.
5. **Respect defines us.** Treat others as you wish to be treated. Make your
discussions, criticisms and debates from a position of respectfulness. Ask
yourself, is it true? Is it necessary? Is it constructive? Anything less is
unacceptable.
6. **Reactions require grace.** Angry responses are valid, but abusive language
and vindictive actions are toxic. When something happens that offends you,
handle it assertively, but be respectful. Escalate reasonably, and try to
allow the offender an opportunity to explain themselves, and possibly
correct the issue.
7. **Opinions are just that: opinions.** Each and every one of us, due to our
background and upbringing, have varying opinions. That is perfectly
acceptable. Remember this: if you respect your own opinions, you should
respect the opinions of others.
8. **To err is human.** You might not intend it, but mistakes do happen and
contribute to build experience. Tolerate honest mistakes, and don't
hesitate to apologize if you make one yourself.

View File

@@ -0,0 +1,63 @@
#### Support
If you do have a contribution to the package, feel free to create a Pull Request or an Issue.
#### What to contribute
If you don't know what to do, there are some features and functions that need to be done
- [ ] Refactor code
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
- [ ] Create actual list of contributors and projects that currently using this package
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
- [ ] Implement benchmarking
- [ ] Implement batch of examples
- [ ] Look at forks for new features and fixes
#### Advice
Feel free to create what you want, but keep in mind when you implement new features:
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
- Public functions must be documented and described in source file and added to README.md to the list of available functions
- There are must be unit-tests for any new functions and improvements
## Financial contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/govalidator).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
## Credits
### Contributors
Thank you to all the people who have already contributed to govalidator!
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/govalidator#backer)]
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a>
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/govalidator#sponsor))
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a>

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2020 Alex Saskevich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,622 @@
govalidator
===========
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator)
[![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator)
[![Coverage](https://codecov.io/gh/asaskevich/govalidator/branch/master/graph/badge.svg)](https://codecov.io/gh/asaskevich/govalidator) [![Go Report Card](https://goreportcard.com/badge/github.com/asaskevich/govalidator)](https://goreportcard.com/report/github.com/asaskevich/govalidator) [![GoSearch](http://go-search.org/badge?id=github.com%2Fasaskevich%2Fgovalidator)](http://go-search.org/view?id=github.com%2Fasaskevich%2Fgovalidator) [![Backers on Open Collective](https://opencollective.com/govalidator/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/govalidator/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_shield)
A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js).
#### Installation
Make sure that Go is installed on your computer.
Type the following command in your terminal:
go get github.com/asaskevich/govalidator
or you can get specified release of the package with `gopkg.in`:
go get gopkg.in/asaskevich/govalidator.v10
After it the package is ready to use.
#### Import package in your project
Add following line in your `*.go` file:
```go
import "github.com/asaskevich/govalidator"
```
If you are unhappy to use long `govalidator`, you can do something like this:
```go
import (
valid "github.com/asaskevich/govalidator"
)
```
#### Activate behavior to require all fields have a validation tag by default
`SetFieldsRequiredByDefault` causes validation to fail when struct fields do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). A good place to activate this is a package init function or the main() function.
`SetNilPtrAllowedByRequired` causes validation to pass when struct fields marked by `required` are set to nil. This is disabled by default for consistency, but some packages that need to be able to determine between `nil` and `zero value` state can use this. If disabled, both `nil` and `zero` values cause validation errors.
```go
import "github.com/asaskevich/govalidator"
func init() {
govalidator.SetFieldsRequiredByDefault(true)
}
```
Here's some code to explain it:
```go
// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
type exampleStruct struct {
Name string ``
Email string `valid:"email"`
}
// this, however, will only fail when Email is empty or an invalid email address:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email"`
}
// lastly, this will only fail when Email is an invalid email address but not when it's empty:
type exampleStruct2 struct {
Name string `valid:"-"`
Email string `valid:"email,optional"`
}
```
#### Recent breaking changes (see [#123](https://github.com/asaskevich/govalidator/pull/123))
##### Custom validator function signature
A context was added as the second parameter, for structs this is the object being validated this makes dependent validation possible.
```go
import "github.com/asaskevich/govalidator"
// old signature
func(i interface{}) bool
// new signature
func(i interface{}, o interface{}) bool
```
##### Adding a custom validator
This was changed to prevent data races when accessing custom validators.
```go
import "github.com/asaskevich/govalidator"
// before
govalidator.CustomTypeTagMap["customByteArrayValidator"] = func(i interface{}, o interface{}) bool {
// ...
}
// after
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, o interface{}) bool {
// ...
})
```
#### List of functions:
```go
func Abs(value float64) float64
func BlackList(str, chars string) string
func ByteLength(str string, params ...string) bool
func CamelCaseToUnderscore(str string) string
func Contains(str, substring string) bool
func Count(array []interface{}, iterator ConditionIterator) int
func Each(array []interface{}, iterator Iterator)
func ErrorByField(e error, field string) string
func ErrorsByField(e error) map[string]string
func Filter(array []interface{}, iterator ConditionIterator) []interface{}
func Find(array []interface{}, iterator ConditionIterator) interface{}
func GetLine(s string, index int) (string, error)
func GetLines(s string) []string
func HasLowerCase(str string) bool
func HasUpperCase(str string) bool
func HasWhitespace(str string) bool
func HasWhitespaceOnly(str string) bool
func InRange(value interface{}, left interface{}, right interface{}) bool
func InRangeFloat32(value, left, right float32) bool
func InRangeFloat64(value, left, right float64) bool
func InRangeInt(value, left, right interface{}) bool
func IsASCII(str string) bool
func IsAlpha(str string) bool
func IsAlphanumeric(str string) bool
func IsBase64(str string) bool
func IsByteLength(str string, min, max int) bool
func IsCIDR(str string) bool
func IsCRC32(str string) bool
func IsCRC32b(str string) bool
func IsCreditCard(str string) bool
func IsDNSName(str string) bool
func IsDataURI(str string) bool
func IsDialString(str string) bool
func IsDivisibleBy(str, num string) bool
func IsEmail(str string) bool
func IsExistingEmail(email string) bool
func IsFilePath(str string) (bool, int)
func IsFloat(str string) bool
func IsFullWidth(str string) bool
func IsHalfWidth(str string) bool
func IsHash(str string, algorithm string) bool
func IsHexadecimal(str string) bool
func IsHexcolor(str string) bool
func IsHost(str string) bool
func IsIP(str string) bool
func IsIPv4(str string) bool
func IsIPv6(str string) bool
func IsISBN(str string, version int) bool
func IsISBN10(str string) bool
func IsISBN13(str string) bool
func IsISO3166Alpha2(str string) bool
func IsISO3166Alpha3(str string) bool
func IsISO4217(str string) bool
func IsISO693Alpha2(str string) bool
func IsISO693Alpha3b(str string) bool
func IsIn(str string, params ...string) bool
func IsInRaw(str string, params ...string) bool
func IsInt(str string) bool
func IsJSON(str string) bool
func IsLatitude(str string) bool
func IsLongitude(str string) bool
func IsLowerCase(str string) bool
func IsMAC(str string) bool
func IsMD4(str string) bool
func IsMD5(str string) bool
func IsMagnetURI(str string) bool
func IsMongoID(str string) bool
func IsMultibyte(str string) bool
func IsNatural(value float64) bool
func IsNegative(value float64) bool
func IsNonNegative(value float64) bool
func IsNonPositive(value float64) bool
func IsNotNull(str string) bool
func IsNull(str string) bool
func IsNumeric(str string) bool
func IsPort(str string) bool
func IsPositive(value float64) bool
func IsPrintableASCII(str string) bool
func IsRFC3339(str string) bool
func IsRFC3339WithoutZone(str string) bool
func IsRGBcolor(str string) bool
func IsRegex(str string) bool
func IsRequestURI(rawurl string) bool
func IsRequestURL(rawurl string) bool
func IsRipeMD128(str string) bool
func IsRipeMD160(str string) bool
func IsRsaPub(str string, params ...string) bool
func IsRsaPublicKey(str string, keylen int) bool
func IsSHA1(str string) bool
func IsSHA256(str string) bool
func IsSHA384(str string) bool
func IsSHA512(str string) bool
func IsSSN(str string) bool
func IsSemver(str string) bool
func IsTiger128(str string) bool
func IsTiger160(str string) bool
func IsTiger192(str string) bool
func IsTime(str string, format string) bool
func IsType(v interface{}, params ...string) bool
func IsURL(str string) bool
func IsUTFDigit(str string) bool
func IsUTFLetter(str string) bool
func IsUTFLetterNumeric(str string) bool
func IsUTFNumeric(str string) bool
func IsUUID(str string) bool
func IsUUIDv3(str string) bool
func IsUUIDv4(str string) bool
func IsUUIDv5(str string) bool
func IsULID(str string) bool
func IsUnixTime(str string) bool
func IsUpperCase(str string) bool
func IsVariableWidth(str string) bool
func IsWhole(value float64) bool
func LeftTrim(str, chars string) string
func Map(array []interface{}, iterator ResultIterator) []interface{}
func Matches(str, pattern string) bool
func MaxStringLength(str string, params ...string) bool
func MinStringLength(str string, params ...string) bool
func NormalizeEmail(str string) (string, error)
func PadBoth(str string, padStr string, padLen int) string
func PadLeft(str string, padStr string, padLen int) string
func PadRight(str string, padStr string, padLen int) string
func PrependPathToErrors(err error, path string) error
func Range(str string, params ...string) bool
func RemoveTags(s string) string
func ReplacePattern(str, pattern, replace string) string
func Reverse(s string) string
func RightTrim(str, chars string) string
func RuneLength(str string, params ...string) bool
func SafeFileName(str string) string
func SetFieldsRequiredByDefault(value bool)
func SetNilPtrAllowedByRequired(value bool)
func Sign(value float64) float64
func StringLength(str string, params ...string) bool
func StringMatches(s string, params ...string) bool
func StripLow(str string, keepNewLines bool) string
func ToBoolean(str string) (bool, error)
func ToFloat(str string) (float64, error)
func ToInt(value interface{}) (res int64, err error)
func ToJSON(obj interface{}) (string, error)
func ToString(obj interface{}) string
func Trim(str, chars string) string
func Truncate(str string, length int, ending string) string
func TruncatingErrorf(str string, args ...interface{}) error
func UnderscoreToCamelCase(s string) string
func ValidateMap(inputMap map[string]interface{}, validationMap map[string]interface{}) (bool, error)
func ValidateStruct(s interface{}) (bool, error)
func WhiteList(str, chars string) string
type ConditionIterator
type CustomTypeValidator
type Error
func (e Error) Error() string
type Errors
func (es Errors) Error() string
func (es Errors) Errors() []error
type ISO3166Entry
type ISO693Entry
type InterfaceParamValidator
type Iterator
type ParamValidator
type ResultIterator
type UnsupportedTypeError
func (e *UnsupportedTypeError) Error() string
type Validator
```
#### Examples
###### IsURL
```go
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`))
```
###### IsType
```go
println(govalidator.IsType("Bob", "string"))
println(govalidator.IsType(1, "int"))
i := 1
println(govalidator.IsType(&i, "*int"))
```
IsType can be used through the tag `type` which is essential for map validation:
```go
type User struct {
Name string `valid:"type(string)"`
Age int `valid:"type(int)"`
Meta interface{} `valid:"type(string)"`
}
result, err := govalidator.ValidateStruct(User{"Bob", 20, "meta"})
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### ToString
```go
type User struct {
FirstName string
LastName string
}
str := govalidator.ToString(&User{"John", "Juan"})
println(str)
```
###### Each, Map, Filter, Count for slices
Each iterates over the slice/array and calls Iterator for every item
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.Iterator = func(value interface{}, index int) {
println(value.(int))
}
govalidator.Each(data, fn)
```
```go
data := []interface{}{1, 2, 3, 4, 5}
var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} {
return value.(int) * 3
}
_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15}
```
```go
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var fn govalidator.ConditionIterator = func(value interface{}, index int) bool {
return value.(int)%2 == 0
}
_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10}
_ = govalidator.Count(data, fn) // result = 5
```
###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2)
If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this:
```go
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
```
For completely custom validators (interface-based), see below.
Here is a list of available validators for struct fields (validator - used function):
```go
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ulid": IsULID,
```
Validators with parameters
```go
"range(min|max)": Range,
"length(min|max)": ByteLength,
"runelength(min|max)": RuneLength,
"stringlength(min|max)": StringLength,
"matches(pattern)": StringMatches,
"in(string1|string2|...|stringN)": IsIn,
"rsapub(keylength)" : IsRsaPub,
"minstringlength(int): MinStringLength,
"maxstringlength(int): MaxStringLength,
```
Validators with parameters for any type
```go
"type(type)": IsType,
```
And here is small example of usage:
```go
type Post struct {
Title string `valid:"alphanum,required"`
Message string `valid:"duck,ascii"`
Message2 string `valid:"animal(dog)"`
AuthorIP string `valid:"ipv4"`
Date string `valid:"-"`
}
post := &Post{
Title: "My Example Post",
Message: "duck",
Message2: "dog",
AuthorIP: "123.234.54.3",
}
// Add your own struct validation tags
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
return str == "duck"
})
// Add your own struct validation tags with parameter
govalidator.ParamTagMap["animal"] = govalidator.ParamValidator(func(str string, params ...string) bool {
species := params[0]
return str == species
})
govalidator.ParamTagRegexMap["animal"] = regexp.MustCompile("^animal\\((\\w+)\\)$")
result, err := govalidator.ValidateStruct(post)
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### ValidateMap [#2](https://github.com/asaskevich/govalidator/pull/338)
If you want to validate maps, you can use the map to be validated and a validation map that contain the same tags used in ValidateStruct, both maps have to be in the form `map[string]interface{}`
So here is small example of usage:
```go
var mapTemplate = map[string]interface{}{
"name":"required,alpha",
"family":"required,alpha",
"email":"required,email",
"cell-phone":"numeric",
"address":map[string]interface{}{
"line1":"required,alphanum",
"line2":"alphanum",
"postal-code":"numeric",
},
}
var inputMap = map[string]interface{}{
"name":"Bob",
"family":"Smith",
"email":"foo@bar.baz",
"address":map[string]interface{}{
"line1":"",
"line2":"",
"postal-code":"",
},
}
result, err := govalidator.ValidateMap(inputMap, mapTemplate)
if err != nil {
println("error: " + err.Error())
}
println(result)
```
###### WhiteList
```go
// Remove all characters from string ignoring characters between "a" and "z"
println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa")
```
###### Custom validation functions
Custom validation using your own domain specific validators is also available - here's an example of how to use it:
```go
import "github.com/asaskevich/govalidator"
type CustomByteArray [6]byte // custom types are supported and can be validated
type StructWithCustomByteArray struct {
ID CustomByteArray `valid:"customByteArrayValidator,customMinLengthValidator"` // multiple custom validators are possible as well and will be evaluated in sequence
Email string `valid:"email"`
CustomMinLength int `valid:"-"`
}
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, context interface{}) bool {
switch v := context.(type) { // you can type switch on the context interface being validated
case StructWithCustomByteArray:
// you can check and validate against some other field in the context,
// return early or not validate against the context at all your choice
case SomeOtherType:
// ...
default:
// expecting some other type? Throw/panic here or continue
}
switch v := i.(type) { // type switch on the struct field being validated
case CustomByteArray:
for _, e := range v { // this validator checks that the byte array is not empty, i.e. not all zeroes
if e != 0 {
return true
}
}
}
return false
})
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", func(i interface{}, context interface{}) bool {
switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation
case StructWithCustomByteArray:
return len(v.ID) >= v.CustomMinLength
}
return false
})
```
###### Loop over Error()
By default .Error() returns all errors in a single String. To access each error you can do this:
```go
if err != nil {
errs := err.(govalidator.Errors).Errors()
for _, e := range errs {
fmt.Println(e.Error())
}
}
```
###### Custom error messages
Custom error messages are supported via annotations by adding the `~` separator - here's an example of how to use it:
```go
type Ticket struct {
Id int64 `json:"id"`
FirstName string `json:"firstname" valid:"required~First name is blank"`
}
```
#### Notes
Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator).
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator).
#### Support
If you do have a contribution to the package, feel free to create a Pull Request or an Issue.
#### What to contribute
If you don't know what to do, there are some features and functions that need to be done
- [ ] Refactor code
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
- [ ] Create actual list of contributors and projects that currently using this package
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
- [ ] Implement fuzzing testing
- [ ] Implement some struct/map/array utilities
- [ ] Implement map/array validation
- [ ] Implement benchmarking
- [ ] Implement batch of examples
- [ ] Look at forks for new features and fixes
#### Advice
Feel free to create what you want, but keep in mind when you implement new features:
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
- Public functions must be documented and described in source file and added to README.md to the list of available functions
- There are must be unit-tests for any new functions and improvements
## Credits
### Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors)
* [Daniel Lohse](https://github.com/annismckenzie)
* [Attila Oláh](https://github.com/attilaolah)
* [Daniel Korner](https://github.com/Dadie)
* [Steven Wilkin](https://github.com/stevenwilkin)
* [Deiwin Sarjas](https://github.com/deiwin)
* [Noah Shibley](https://github.com/slugmobile)
* [Nathan Davies](https://github.com/nathj07)
* [Matt Sanford](https://github.com/mzsanford)
* [Simon ccl1115](https://github.com/ccl1115)
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/govalidator#backer)]
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a>
### Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/govalidator#sponsor)]
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a>
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_large)

View File

@@ -0,0 +1,87 @@
package govalidator
// Iterator is the function that accepts element of slice/array and its index
type Iterator func(interface{}, int)
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
type ResultIterator func(interface{}, int) interface{}
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
type ConditionIterator func(interface{}, int) bool
// ReduceIterator is the function that accepts two element of slice/array and returns result of merging those values
type ReduceIterator func(interface{}, interface{}) interface{}
// Some validates that any item of array corresponds to ConditionIterator. Returns boolean.
func Some(array []interface{}, iterator ConditionIterator) bool {
res := false
for index, data := range array {
res = res || iterator(data, index)
}
return res
}
// Every validates that every item of array corresponds to ConditionIterator. Returns boolean.
func Every(array []interface{}, iterator ConditionIterator) bool {
res := true
for index, data := range array {
res = res && iterator(data, index)
}
return res
}
// Reduce boils down a list of values into a single value by ReduceIterator
func Reduce(array []interface{}, iterator ReduceIterator, initialValue interface{}) interface{} {
for _, data := range array {
initialValue = iterator(initialValue, data)
}
return initialValue
}
// Each iterates over the slice and apply Iterator to every item
func Each(array []interface{}, iterator Iterator) {
for index, data := range array {
iterator(data, index)
}
}
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
func Map(array []interface{}, iterator ResultIterator) []interface{} {
var result = make([]interface{}, len(array))
for index, data := range array {
result[index] = iterator(data, index)
}
return result
}
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
func Find(array []interface{}, iterator ConditionIterator) interface{} {
for index, data := range array {
if iterator(data, index) {
return data
}
}
return nil
}
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
func Filter(array []interface{}, iterator ConditionIterator) []interface{} {
var result = make([]interface{}, 0)
for index, data := range array {
if iterator(data, index) {
result = append(result, data)
}
}
return result
}
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
func Count(array []interface{}, iterator ConditionIterator) int {
count := 0
for index, data := range array {
if iterator(data, index) {
count = count + 1
}
}
return count
}

View File

@@ -0,0 +1,81 @@
package govalidator
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
)
// ToString convert the input to a string.
func ToString(obj interface{}) string {
res := fmt.Sprintf("%v", obj)
return res
}
// ToJSON convert the input to a valid JSON string
func ToJSON(obj interface{}) (string, error) {
res, err := json.Marshal(obj)
if err != nil {
res = []byte("")
}
return string(res), err
}
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
func ToFloat(value interface{}) (res float64, err error) {
val := reflect.ValueOf(value)
switch value.(type) {
case int, int8, int16, int32, int64:
res = float64(val.Int())
case uint, uint8, uint16, uint32, uint64:
res = float64(val.Uint())
case float32, float64:
res = val.Float()
case string:
res, err = strconv.ParseFloat(val.String(), 64)
if err != nil {
res = 0
}
default:
err = fmt.Errorf("ToInt: unknown interface type %T", value)
res = 0
}
return
}
// ToInt convert the input string or any int type to an integer type 64, or 0 if the input is not an integer.
func ToInt(value interface{}) (res int64, err error) {
val := reflect.ValueOf(value)
switch value.(type) {
case int, int8, int16, int32, int64:
res = val.Int()
case uint, uint8, uint16, uint32, uint64:
res = int64(val.Uint())
case float32, float64:
res = int64(val.Float())
case string:
if IsInt(val.String()) {
res, err = strconv.ParseInt(val.String(), 0, 64)
if err != nil {
res = 0
}
} else {
err = fmt.Errorf("ToInt: invalid numeric format %g", value)
res = 0
}
default:
err = fmt.Errorf("ToInt: unknown interface type %T", value)
res = 0
}
return
}
// ToBoolean convert the input string to a boolean.
func ToBoolean(str string) (bool, error) {
return strconv.ParseBool(str)
}

View File

@@ -0,0 +1,3 @@
package govalidator
// A package of validators and sanitizers for strings, structures and collections.

View File

@@ -0,0 +1,47 @@
package govalidator
import (
"sort"
"strings"
)
// Errors is an array of multiple errors and conforms to the error interface.
type Errors []error
// Errors returns itself.
func (es Errors) Errors() []error {
return es
}
func (es Errors) Error() string {
var errs []string
for _, e := range es {
errs = append(errs, e.Error())
}
sort.Strings(errs)
return strings.Join(errs, ";")
}
// Error encapsulates a name, an error and whether there's a custom error message or not.
type Error struct {
Name string
Err error
CustomErrorMessageExists bool
// Validator indicates the name of the validator that failed
Validator string
Path []string
}
func (e Error) Error() string {
if e.CustomErrorMessageExists {
return e.Err.Error()
}
errName := e.Name
if len(e.Path) > 0 {
errName = strings.Join(append(e.Path, e.Name), ".")
}
return errName + ": " + e.Err.Error()
}

View File

@@ -0,0 +1,100 @@
package govalidator
import (
"math"
)
// Abs returns absolute value of number
func Abs(value float64) float64 {
return math.Abs(value)
}
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
func Sign(value float64) float64 {
if value > 0 {
return 1
} else if value < 0 {
return -1
} else {
return 0
}
}
// IsNegative returns true if value < 0
func IsNegative(value float64) bool {
return value < 0
}
// IsPositive returns true if value > 0
func IsPositive(value float64) bool {
return value > 0
}
// IsNonNegative returns true if value >= 0
func IsNonNegative(value float64) bool {
return value >= 0
}
// IsNonPositive returns true if value <= 0
func IsNonPositive(value float64) bool {
return value <= 0
}
// InRangeInt returns true if value lies between left and right border
func InRangeInt(value, left, right interface{}) bool {
value64, _ := ToInt(value)
left64, _ := ToInt(left)
right64, _ := ToInt(right)
if left64 > right64 {
left64, right64 = right64, left64
}
return value64 >= left64 && value64 <= right64
}
// InRangeFloat32 returns true if value lies between left and right border
func InRangeFloat32(value, left, right float32) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRangeFloat64 returns true if value lies between left and right border
func InRangeFloat64(value, left, right float64) bool {
if left > right {
left, right = right, left
}
return value >= left && value <= right
}
// InRange returns true if value lies between left and right border, generic type to handle int, float32, float64 and string.
// All types must the same type.
// False if value doesn't lie in range or if it incompatible or not comparable
func InRange(value interface{}, left interface{}, right interface{}) bool {
switch value.(type) {
case int:
intValue, _ := ToInt(value)
intLeft, _ := ToInt(left)
intRight, _ := ToInt(right)
return InRangeInt(intValue, intLeft, intRight)
case float32, float64:
intValue, _ := ToFloat(value)
intLeft, _ := ToFloat(left)
intRight, _ := ToFloat(right)
return InRangeFloat64(intValue, intLeft, intRight)
case string:
return value.(string) >= left.(string) && value.(string) <= right.(string)
default:
return false
}
}
// IsWhole returns true if value is whole number
func IsWhole(value float64) bool {
return math.Remainder(value, 1) == 0
}
// IsNatural returns true if value is natural number (positive and whole)
func IsNatural(value float64) bool {
return IsWhole(value) && IsPositive(value)
}

View File

@@ -0,0 +1,113 @@
package govalidator
import "regexp"
// Basic regular expressions for validating strings
const (
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$"
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
ISBN13 string = "^(?:[0-9]{13})$"
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
Alpha string = "^[a-zA-Z]+$"
Alphanumeric string = "^[a-zA-Z0-9]+$"
Numeric string = "^[0-9]+$"
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
Hexadecimal string = "^[0-9a-fA-F]+$"
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
ASCII string = "^[\x00-\x7F]+$"
Multibyte string = "[^\x00-\x7F]"
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
PrintableASCII string = "^[\x20-\x7E]+$"
DataURI string = "^data:.+\\/(.+);base64$"
MagnetURI string = "^magnet:\\?xt=urn:[a-zA-Z0-9]+:[a-zA-Z0-9]{32,40}&dn=.+&tr=.+$"
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
URLUsername string = `(\S+(:\S*)?@)`
URLPath string = `((\/|\?|#)[^\s]*)`
URLPort string = `(:(\d{1,5}))`
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
URL = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixPath string = `^(/[^/\x00]*)+/?$`
WinARPath string = `^(?:(?:[a-zA-Z]:|\\\\[a-z0-9_.$●-]+\\[a-z0-9_.$●-]+)\\|\\?[^\\/:*?"<>|\r\n]+\\?)(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
UnixARPath string = `^((\.{0,2}/)?([^/\x00]*))+/?$`
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
tagName string = "valid"
hasLowerCase string = ".*[[:lower:]]"
hasUpperCase string = ".*[[:upper:]]"
hasWhitespace string = ".*[[:space:]]"
hasWhitespaceOnly string = "^[[:space:]]+$"
IMEI string = "^[0-9a-f]{14}$|^\\d{15}$|^\\d{18}$"
IMSI string = "^\\d{14,15}$"
E164 string = `^\+?[1-9]\d{1,14}$`
)
// Used by IsFilePath func
const (
// Unknown is unresolved OS type
Unknown = iota
// Win is Windows type
Win
// Unix is *nix OS types
Unix
)
var (
userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
rxEmail = regexp.MustCompile(Email)
rxCreditCard = regexp.MustCompile(CreditCard)
rxISBN10 = regexp.MustCompile(ISBN10)
rxISBN13 = regexp.MustCompile(ISBN13)
rxUUID3 = regexp.MustCompile(UUID3)
rxUUID4 = regexp.MustCompile(UUID4)
rxUUID5 = regexp.MustCompile(UUID5)
rxUUID = regexp.MustCompile(UUID)
rxAlpha = regexp.MustCompile(Alpha)
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
rxNumeric = regexp.MustCompile(Numeric)
rxInt = regexp.MustCompile(Int)
rxFloat = regexp.MustCompile(Float)
rxHexadecimal = regexp.MustCompile(Hexadecimal)
rxHexcolor = regexp.MustCompile(Hexcolor)
rxRGBcolor = regexp.MustCompile(RGBcolor)
rxASCII = regexp.MustCompile(ASCII)
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
rxMultibyte = regexp.MustCompile(Multibyte)
rxFullWidth = regexp.MustCompile(FullWidth)
rxHalfWidth = regexp.MustCompile(HalfWidth)
rxBase64 = regexp.MustCompile(Base64)
rxDataURI = regexp.MustCompile(DataURI)
rxMagnetURI = regexp.MustCompile(MagnetURI)
rxLatitude = regexp.MustCompile(Latitude)
rxLongitude = regexp.MustCompile(Longitude)
rxDNSName = regexp.MustCompile(DNSName)
rxURL = regexp.MustCompile(URL)
rxSSN = regexp.MustCompile(SSN)
rxWinPath = regexp.MustCompile(WinPath)
rxUnixPath = regexp.MustCompile(UnixPath)
rxARWinPath = regexp.MustCompile(WinARPath)
rxARUnixPath = regexp.MustCompile(UnixARPath)
rxSemver = regexp.MustCompile(Semver)
rxHasLowerCase = regexp.MustCompile(hasLowerCase)
rxHasUpperCase = regexp.MustCompile(hasUpperCase)
rxHasWhitespace = regexp.MustCompile(hasWhitespace)
rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly)
rxIMEI = regexp.MustCompile(IMEI)
rxIMSI = regexp.MustCompile(IMSI)
rxE164 = regexp.MustCompile(E164)
)

View File

@@ -0,0 +1,656 @@
package govalidator
import (
"reflect"
"regexp"
"sort"
"sync"
)
// Validator is a wrapper for a validator function that returns bool and accepts string.
type Validator func(str string) bool
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
// The second parameter should be the context (in the case of validating a struct: the whole object being validated).
type CustomTypeValidator func(i interface{}, o interface{}) bool
// ParamValidator is a wrapper for validator functions that accept additional parameters.
type ParamValidator func(str string, params ...string) bool
// InterfaceParamValidator is a wrapper for functions that accept variants parameters for an interface value
type InterfaceParamValidator func(in interface{}, params ...string) bool
type tagOptionsMap map[string]tagOption
func (t tagOptionsMap) orderedKeys() []string {
var keys []string
for k := range t {
keys = append(keys, k)
}
sort.Slice(keys, func(a, b int) bool {
return t[keys[a]].order < t[keys[b]].order
})
return keys
}
type tagOption struct {
name string
customErrorMessage string
order int
}
// UnsupportedTypeError is a wrapper for reflect.Type
type UnsupportedTypeError struct {
Type reflect.Type
}
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements the methods to sort by string.
type stringValues []reflect.Value
// InterfaceParamTagMap is a map of functions accept variants parameters for an interface value
var InterfaceParamTagMap = map[string]InterfaceParamValidator{
"type": IsType,
}
// InterfaceParamTagRegexMap maps interface param tags to their respective regexes.
var InterfaceParamTagRegexMap = map[string]*regexp.Regexp{
"type": regexp.MustCompile(`^type\((.*)\)$`),
}
// ParamTagMap is a map of functions accept variants parameters
var ParamTagMap = map[string]ParamValidator{
"length": ByteLength,
"range": Range,
"runelength": RuneLength,
"stringlength": StringLength,
"matches": StringMatches,
"in": IsInRaw,
"rsapub": IsRsaPub,
"minstringlength": MinStringLength,
"maxstringlength": MaxStringLength,
}
// ParamTagRegexMap maps param tags to their respective regexes.
var ParamTagRegexMap = map[string]*regexp.Regexp{
"range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"),
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"),
"runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"),
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
"in": regexp.MustCompile(`^in\((.*)\)`),
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
"rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"),
"minstringlength": regexp.MustCompile("^minstringlength\\((\\d+)\\)$"),
"maxstringlength": regexp.MustCompile("^maxstringlength\\((\\d+)\\)$"),
}
type customTypeTagMap struct {
validators map[string]CustomTypeValidator
sync.RWMutex
}
func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) {
tm.RLock()
defer tm.RUnlock()
v, ok := tm.validators[name]
return v, ok
}
func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) {
tm.Lock()
defer tm.Unlock()
tm.validators[name] = ctv
}
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
// `type UUID [16]byte` (this would be handled as an array of bytes).
var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)}
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
var TagMap = map[string]Validator{
"email": IsEmail,
"url": IsURL,
"dialstring": IsDialString,
"requrl": IsRequestURL,
"requri": IsRequestURI,
"alpha": IsAlpha,
"utfletter": IsUTFLetter,
"alphanum": IsAlphanumeric,
"utfletternum": IsUTFLetterNumeric,
"numeric": IsNumeric,
"utfnumeric": IsUTFNumeric,
"utfdigit": IsUTFDigit,
"hexadecimal": IsHexadecimal,
"hexcolor": IsHexcolor,
"rgbcolor": IsRGBcolor,
"lowercase": IsLowerCase,
"uppercase": IsUpperCase,
"int": IsInt,
"float": IsFloat,
"null": IsNull,
"notnull": IsNotNull,
"uuid": IsUUID,
"uuidv3": IsUUIDv3,
"uuidv4": IsUUIDv4,
"uuidv5": IsUUIDv5,
"creditcard": IsCreditCard,
"isbn10": IsISBN10,
"isbn13": IsISBN13,
"json": IsJSON,
"multibyte": IsMultibyte,
"ascii": IsASCII,
"printableascii": IsPrintableASCII,
"fullwidth": IsFullWidth,
"halfwidth": IsHalfWidth,
"variablewidth": IsVariableWidth,
"base64": IsBase64,
"datauri": IsDataURI,
"ip": IsIP,
"port": IsPort,
"ipv4": IsIPv4,
"ipv6": IsIPv6,
"dns": IsDNSName,
"host": IsHost,
"mac": IsMAC,
"latitude": IsLatitude,
"longitude": IsLongitude,
"ssn": IsSSN,
"semver": IsSemver,
"rfc3339": IsRFC3339,
"rfc3339WithoutZone": IsRFC3339WithoutZone,
"ISO3166Alpha2": IsISO3166Alpha2,
"ISO3166Alpha3": IsISO3166Alpha3,
"ISO4217": IsISO4217,
"IMEI": IsIMEI,
"ulid": IsULID,
}
// ISO3166Entry stores country codes
type ISO3166Entry struct {
EnglishShortName string
FrenchShortName string
Alpha2Code string
Alpha3Code string
Numeric string
}
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
var ISO3166List = []ISO3166Entry{
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"},
{"Albania", "Albanie (l')", "AL", "ALB", "008"},
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"},
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"},
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"},
{"Andorra", "Andorre (l')", "AD", "AND", "020"},
{"Angola", "Angola (l')", "AO", "AGO", "024"},
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"},
{"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"},
{"Argentina", "Argentine (l')", "AR", "ARG", "032"},
{"Australia", "Australie (l')", "AU", "AUS", "036"},
{"Austria", "Autriche (l')", "AT", "AUT", "040"},
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"},
{"Bahrain", "Bahreïn", "BH", "BHR", "048"},
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"},
{"Armenia", "Arménie (l')", "AM", "ARM", "051"},
{"Barbados", "Barbade (la)", "BB", "BRB", "052"},
{"Belgium", "Belgique (la)", "BE", "BEL", "056"},
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"},
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"},
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"},
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"},
{"Botswana", "Botswana (le)", "BW", "BWA", "072"},
{"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"},
{"Brazil", "Brésil (le)", "BR", "BRA", "076"},
{"Belize", "Belize (le)", "BZ", "BLZ", "084"},
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"},
{"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"},
{"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"},
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"},
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"},
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"},
{"Burundi", "Burundi (le)", "BI", "BDI", "108"},
{"Belarus", "Bélarus (le)", "BY", "BLR", "112"},
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"},
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"},
{"Canada", "Canada (le)", "CA", "CAN", "124"},
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"},
{"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"},
{"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"},
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"},
{"Chad", "Tchad (le)", "TD", "TCD", "148"},
{"Chile", "Chili (le)", "CL", "CHL", "152"},
{"China", "Chine (la)", "CN", "CHN", "156"},
{"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"},
{"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"},
{"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"},
{"Colombia", "Colombie (la)", "CO", "COL", "170"},
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"},
{"Mayotte", "Mayotte", "YT", "MYT", "175"},
{"Congo (the)", "Congo (le)", "CG", "COG", "178"},
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"},
{"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"},
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"},
{"Croatia", "Croatie (la)", "HR", "HRV", "191"},
{"Cuba", "Cuba", "CU", "CUB", "192"},
{"Cyprus", "Chypre", "CY", "CYP", "196"},
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"},
{"Benin", "Bénin (le)", "BJ", "BEN", "204"},
{"Denmark", "Danemark (le)", "DK", "DNK", "208"},
{"Dominica", "Dominique (la)", "DM", "DMA", "212"},
{"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"},
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"},
{"El Salvador", "El Salvador", "SV", "SLV", "222"},
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"},
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"},
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"},
{"Estonia", "Estonie (l')", "EE", "EST", "233"},
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"},
{"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"},
{"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"},
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"},
{"Finland", "Finlande (la)", "FI", "FIN", "246"},
{"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"},
{"France", "France (la)", "FR", "FRA", "250"},
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"},
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"},
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"},
{"Djibouti", "Djibouti", "DJ", "DJI", "262"},
{"Gabon", "Gabon (le)", "GA", "GAB", "266"},
{"Georgia", "Géorgie (la)", "GE", "GEO", "268"},
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"},
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"},
{"Germany", "Allemagne (l')", "DE", "DEU", "276"},
{"Ghana", "Ghana (le)", "GH", "GHA", "288"},
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"},
{"Kiribati", "Kiribati", "KI", "KIR", "296"},
{"Greece", "Grèce (la)", "GR", "GRC", "300"},
{"Greenland", "Groenland (le)", "GL", "GRL", "304"},
{"Grenada", "Grenade (la)", "GD", "GRD", "308"},
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"},
{"Guam", "Guam", "GU", "GUM", "316"},
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"},
{"Guinea", "Guinée (la)", "GN", "GIN", "324"},
{"Guyana", "Guyana (le)", "GY", "GUY", "328"},
{"Haiti", "Haïti", "HT", "HTI", "332"},
{"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"},
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"},
{"Honduras", "Honduras (le)", "HN", "HND", "340"},
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"},
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"},
{"Iceland", "Islande (l')", "IS", "ISL", "352"},
{"India", "Inde (l')", "IN", "IND", "356"},
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"},
{"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"},
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"},
{"Ireland", "Irlande (l')", "IE", "IRL", "372"},
{"Israel", "Israël", "IL", "ISR", "376"},
{"Italy", "Italie (l')", "IT", "ITA", "380"},
{"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"},
{"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"},
{"Japan", "Japon (le)", "JP", "JPN", "392"},
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"},
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"},
{"Kenya", "Kenya (le)", "KE", "KEN", "404"},
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"},
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"},
{"Kuwait", "Koweït (le)", "KW", "KWT", "414"},
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"},
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"},
{"Lebanon", "Liban (le)", "LB", "LBN", "422"},
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"},
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"},
{"Liberia", "Libéria (le)", "LR", "LBR", "430"},
{"Libya", "Libye (la)", "LY", "LBY", "434"},
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"},
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"},
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"},
{"Macao", "Macao", "MO", "MAC", "446"},
{"Madagascar", "Madagascar", "MG", "MDG", "450"},
{"Malawi", "Malawi (le)", "MW", "MWI", "454"},
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"},
{"Maldives", "Maldives (les)", "MV", "MDV", "462"},
{"Mali", "Mali (le)", "ML", "MLI", "466"},
{"Malta", "Malte", "MT", "MLT", "470"},
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"},
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"},
{"Mauritius", "Maurice", "MU", "MUS", "480"},
{"Mexico", "Mexique (le)", "MX", "MEX", "484"},
{"Monaco", "Monaco", "MC", "MCO", "492"},
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"},
{"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"},
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"},
{"Montserrat", "Montserrat", "MS", "MSR", "500"},
{"Morocco", "Maroc (le)", "MA", "MAR", "504"},
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"},
{"Oman", "Oman", "OM", "OMN", "512"},
{"Namibia", "Namibie (la)", "NA", "NAM", "516"},
{"Nauru", "Nauru", "NR", "NRU", "520"},
{"Nepal", "Népal (le)", "NP", "NPL", "524"},
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"},
{"Curaçao", "Curaçao", "CW", "CUW", "531"},
{"Aruba", "Aruba", "AW", "ABW", "533"},
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"},
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"},
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"},
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"},
{"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"},
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"},
{"Niger (the)", "Niger (le)", "NE", "NER", "562"},
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"},
{"Niue", "Niue", "NU", "NIU", "570"},
{"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"},
{"Norway", "Norvège (la)", "NO", "NOR", "578"},
{"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"},
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"},
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"},
{"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"},
{"Palau", "Palaos (les)", "PW", "PLW", "585"},
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"},
{"Panama", "Panama (le)", "PA", "PAN", "591"},
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"},
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"},
{"Peru", "Pérou (le)", "PE", "PER", "604"},
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"},
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"},
{"Poland", "Pologne (la)", "PL", "POL", "616"},
{"Portugal", "Portugal (le)", "PT", "PRT", "620"},
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"},
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"},
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"},
{"Qatar", "Qatar (le)", "QA", "QAT", "634"},
{"Réunion", "Réunion (La)", "RE", "REU", "638"},
{"Romania", "Roumanie (la)", "RO", "ROU", "642"},
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"},
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"},
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"},
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"},
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"},
{"Anguilla", "Anguilla", "AI", "AIA", "660"},
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"},
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"},
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"},
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"},
{"San Marino", "Saint-Marin", "SM", "SMR", "674"},
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"},
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"},
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"},
{"Serbia", "Serbie (la)", "RS", "SRB", "688"},
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"},
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"},
{"Singapore", "Singapour", "SG", "SGP", "702"},
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"},
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"},
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"},
{"Somalia", "Somalie (la)", "SO", "SOM", "706"},
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"},
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"},
{"Spain", "Espagne (l')", "ES", "ESP", "724"},
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"},
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"},
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"},
{"Suriname", "Suriname (le)", "SR", "SUR", "740"},
{"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"},
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"},
{"Sweden", "Suède (la)", "SE", "SWE", "752"},
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"},
{"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"},
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"},
{"Thailand", "Thaïlande (la)", "TH", "THA", "764"},
{"Togo", "Togo (le)", "TG", "TGO", "768"},
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"},
{"Tonga", "Tonga (les)", "TO", "TON", "776"},
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"},
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"},
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"},
{"Turkey", "Turquie (la)", "TR", "TUR", "792"},
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"},
{"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"},
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"},
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"},
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"},
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'exRépublique yougoslave de)", "MK", "MKD", "807"},
{"Egypt", "Égypte (l')", "EG", "EGY", "818"},
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"},
{"Guernsey", "Guernesey", "GG", "GGY", "831"},
{"Jersey", "Jersey", "JE", "JEY", "832"},
{"Isle of Man", "Île de Man", "IM", "IMN", "833"},
{"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"},
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"},
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"},
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"},
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"},
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"},
{"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"},
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"},
{"Samoa", "Samoa (le)", "WS", "WSM", "882"},
{"Yemen", "Yémen (le)", "YE", "YEM", "887"},
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"},
}
// ISO4217List is the list of ISO currency codes
var ISO4217List = []string{
"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN",
"BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK",
"DJF", "DKK", "DOP", "DZD",
"EGP", "ERN", "ETB", "EUR",
"FJD", "FKP",
"GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD",
"HKD", "HNL", "HRK", "HTG", "HUF",
"IDR", "ILS", "INR", "IQD", "IRR", "ISK",
"JMD", "JOD", "JPY",
"KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT",
"LAK", "LBP", "LKR", "LRD", "LSL", "LYD",
"MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN",
"NAD", "NGN", "NIO", "NOK", "NPR", "NZD",
"OMR",
"PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG",
"QAR",
"RON", "RSD", "RUB", "RWF",
"SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "STN", "SVC", "SYP", "SZL",
"THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS",
"UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS",
"VEF", "VES", "VND", "VUV",
"WST",
"XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX",
"YER",
"ZAR", "ZMW", "ZWL",
}
// ISO693Entry stores ISO language codes
type ISO693Entry struct {
Alpha3bCode string
Alpha2Code string
English string
}
//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json
var ISO693List = []ISO693Entry{
{Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"},
{Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"},
{Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"},
{Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"},
{Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"},
{Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"},
{Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"},
{Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"},
{Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"},
{Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"},
{Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"},
{Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"},
{Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"},
{Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"},
{Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"},
{Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"},
{Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"},
{Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"},
{Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"},
{Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"},
{Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"},
{Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"},
{Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"},
{Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"},
{Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"},
{Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"},
{Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"},
{Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"},
{Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"},
{Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"},
{Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"},
{Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"},
{Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"},
{Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"},
{Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"},
{Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"},
{Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"},
{Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"},
{Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"},
{Alpha3bCode: "eng", Alpha2Code: "en", English: "English"},
{Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"},
{Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"},
{Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"},
{Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"},
{Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"},
{Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"},
{Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"},
{Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"},
{Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"},
{Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"},
{Alpha3bCode: "ger", Alpha2Code: "de", English: "German"},
{Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"},
{Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"},
{Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"},
{Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"},
{Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"},
{Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"},
{Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"},
{Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"},
{Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"},
{Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"},
{Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"},
{Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"},
{Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"},
{Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"},
{Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"},
{Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"},
{Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"},
{Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"},
{Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"},
{Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"},
{Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"},
{Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"},
{Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"},
{Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"},
{Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"},
{Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"},
{Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"},
{Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"},
{Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"},
{Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"},
{Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"},
{Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"},
{Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"},
{Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"},
{Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"},
{Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"},
{Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"},
{Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"},
{Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"},
{Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"},
{Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"},
{Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"},
{Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"},
{Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"},
{Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"},
{Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"},
{Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"},
{Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"},
{Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"},
{Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"},
{Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"},
{Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"},
{Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"},
{Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"},
{Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"},
{Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"},
{Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"},
{Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"},
{Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"},
{Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"},
{Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"},
{Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"},
{Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"},
{Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"},
{Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"},
{Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"},
{Alpha3bCode: "nob", Alpha2Code: "nb", English: "Bokmål, Norwegian; Norwegian Bokmål"},
{Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"},
{Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"},
{Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"},
{Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"},
{Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"},
{Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"},
{Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"},
{Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"},
{Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"},
{Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"},
{Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"},
{Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"},
{Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"},
{Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"},
{Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"},
{Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"},
{Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"},
{Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"},
{Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"},
{Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"},
{Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"},
{Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"},
{Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"},
{Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"},
{Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"},
{Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"},
{Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"},
{Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"},
{Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"},
{Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"},
{Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"},
{Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"},
{Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"},
{Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"},
{Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"},
{Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"},
{Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"},
{Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"},
{Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"},
{Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"},
{Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"},
{Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"},
{Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"},
{Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"},
{Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"},
{Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"},
{Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"},
{Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"},
{Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"},
{Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"},
{Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"},
{Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"},
{Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"},
{Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"},
{Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"},
{Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"},
{Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"},
{Alpha3bCode: "vol", Alpha2Code: "vo", English: "Volapük"},
{Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"},
{Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"},
{Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"},
{Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"},
{Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"},
{Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"},
{Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"},
{Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"},
}

View File

@@ -0,0 +1,270 @@
package govalidator
import (
"errors"
"fmt"
"html"
"math"
"path"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
// Contains checks if the string contains the substring.
func Contains(str, substring string) bool {
return strings.Contains(str, substring)
}
// Matches checks if string matches the pattern (pattern is regular expression)
// In case of error return false
func Matches(str, pattern string) bool {
match, _ := regexp.MatchString(pattern, str)
return match
}
// LeftTrim trims characters from the left side of the input.
// If second argument is empty, it will remove leading spaces.
func LeftTrim(str, chars string) string {
if chars == "" {
return strings.TrimLeftFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("^[" + chars + "]+")
return r.ReplaceAllString(str, "")
}
// RightTrim trims characters from the right side of the input.
// If second argument is empty, it will remove trailing spaces.
func RightTrim(str, chars string) string {
if chars == "" {
return strings.TrimRightFunc(str, unicode.IsSpace)
}
r, _ := regexp.Compile("[" + chars + "]+$")
return r.ReplaceAllString(str, "")
}
// Trim trims characters from both sides of the input.
// If second argument is empty, it will remove spaces.
func Trim(str, chars string) string {
return LeftTrim(RightTrim(str, chars), chars)
}
// WhiteList removes characters that do not appear in the whitelist.
func WhiteList(str, chars string) string {
pattern := "[^" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// BlackList removes characters that appear in the blacklist.
func BlackList(str, chars string) string {
pattern := "[" + chars + "]+"
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, "")
}
// StripLow removes characters with a numerical value < 32 and 127, mostly control characters.
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
func StripLow(str string, keepNewLines bool) string {
chars := ""
if keepNewLines {
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
} else {
chars = "\x00-\x1F\x7F"
}
return BlackList(str, chars)
}
// ReplacePattern replaces regular expression pattern in string
func ReplacePattern(str, pattern, replace string) string {
r, _ := regexp.Compile(pattern)
return r.ReplaceAllString(str, replace)
}
// Escape replaces <, >, & and " with HTML entities.
var Escape = html.EscapeString
func addSegment(inrune, segment []rune) []rune {
if len(segment) == 0 {
return inrune
}
if len(inrune) != 0 {
inrune = append(inrune, '_')
}
inrune = append(inrune, segment...)
return inrune
}
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
// Ex.: my_func => MyFunc
func UnderscoreToCamelCase(s string) string {
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
}
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
// Ex.: MyFunc => my_func
func CamelCaseToUnderscore(str string) string {
var output []rune
var segment []rune
for _, r := range str {
// not treat number as separate segment
if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) {
output = addSegment(output, segment)
segment = nil
}
segment = append(segment, unicode.ToLower(r))
}
output = addSegment(output, segment)
return string(output)
}
// Reverse returns reversed string
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
// GetLines splits string by "\n" and return array of lines
func GetLines(s string) []string {
return strings.Split(s, "\n")
}
// GetLine returns specified line of multiline string
func GetLine(s string, index int) (string, error) {
lines := GetLines(s)
if index < 0 || index >= len(lines) {
return "", errors.New("line index out of bounds")
}
return lines[index], nil
}
// RemoveTags removes all tags from HTML string
func RemoveTags(s string) string {
return ReplacePattern(s, "<[^>]*>", "")
}
// SafeFileName returns safe string that can be used in file names
func SafeFileName(str string) string {
name := strings.ToLower(str)
name = path.Clean(path.Base(name))
name = strings.Trim(name, " ")
separators, err := regexp.Compile(`[ &_=+:]`)
if err == nil {
name = separators.ReplaceAllString(name, "-")
}
legal, err := regexp.Compile(`[^[:alnum:]-.]`)
if err == nil {
name = legal.ReplaceAllString(name, "")
}
for strings.Contains(name, "--") {
name = strings.Replace(name, "--", "-", -1)
}
return name
}
// NormalizeEmail canonicalize an email address.
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
// normalized to @gmail.com.
func NormalizeEmail(str string) (string, error) {
if !IsEmail(str) {
return "", fmt.Errorf("%s is not an email", str)
}
parts := strings.Split(str, "@")
parts[0] = strings.ToLower(parts[0])
parts[1] = strings.ToLower(parts[1])
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
parts[1] = "gmail.com"
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
}
return strings.Join(parts, "@"), nil
}
// Truncate a string to the closest length without breaking words.
func Truncate(str string, length int, ending string) string {
var aftstr, befstr string
if len(str) > length {
words := strings.Fields(str)
before, present := 0, 0
for i := range words {
befstr = aftstr
before = present
aftstr = aftstr + words[i] + " "
present = len(aftstr)
if present > length && i != 0 {
if (length - before) < (present - length) {
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
}
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
}
}
}
return str
}
// PadLeft pads left side of a string if size of string is less then indicated pad length
func PadLeft(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, false)
}
// PadRight pads right side of a string if size of string is less then indicated pad length
func PadRight(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, false, true)
}
// PadBoth pads both sides of a string if size of string is less then indicated pad length
func PadBoth(str string, padStr string, padLen int) string {
return buildPadStr(str, padStr, padLen, true, true)
}
// PadString either left, right or both sides.
// Note that padding string can be unicode and more then one character
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
// When padded length is less then the current string size
if padLen < utf8.RuneCountInString(str) {
return str
}
padLen -= utf8.RuneCountInString(str)
targetLen := padLen
targetLenLeft := targetLen
targetLenRight := targetLen
if padLeft && padRight {
targetLenLeft = padLen / 2
targetLenRight = padLen - targetLenLeft
}
strToRepeatLen := utf8.RuneCountInString(padStr)
repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen)))
repeatedString := strings.Repeat(padStr, repeatTimes)
leftSide := ""
if padLeft {
leftSide = repeatedString[0:targetLenLeft]
}
rightSide := ""
if padRight {
rightSide = repeatedString[0:targetLenRight]
}
return leftSide + str + rightSide
}
// TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object
func TruncatingErrorf(str string, args ...interface{}) error {
n := strings.Count(str, "%s")
return fmt.Errorf(str, args[:n]...)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
box: golang
build:
steps:
- setup-go-workspace
- script:
name: go get
code: |
go version
go get -t ./...
- script:
name: go test
code: |
go test -race -v ./...

View File

@@ -1,26 +0,0 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Set default charset
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
charset = utf-8
# Tab indentation (no size specified)
[*.go]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

View File

@@ -1,6 +1,5 @@
*.out
secrets.yml
coverage.out
coverage.txt
*.cov
.idea
.env
.mcp.json
.claude/

View File

@@ -1,70 +1,61 @@
version: "2"
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
linters:
default: all
enable-all: true
disable:
- depguard
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gomoddirectives
- exhaustruct
- nlreturn
- nonamedreturns
- noinlineerr
- paralleltest
- recvcheck
- testpackage
- thelper
- tparallel
- varnamelen
- gocognit
- whitespace
- wrapcheck
- wsl
- wsl_v5
settings:
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
cyclop:
max-complexity: 25
gocyclo:
min-complexity: 25
gocognit:
min-complexity: 35
exhaustive:
default-signifies-exhaustive: true
default-case-required: true
lll:
line-length: 180
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@@ -23,9 +23,7 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
@@ -57,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <ivan+abuse@flanders.co.nz>. All
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -70,7 +68,7 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [<http://contributor-covenant.org/version/1/4>][version]
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,27 +0,0 @@
# Contributors
- Repository: ['go-openapi/analysis']
| Total Contributors | Total Contributions |
| --- | --- |
| 15 | 202 |
| Username | All Time Contribution Count | All Commits |
| --- | --- | --- |
| @fredbi | 99 | <https://github.com/go-openapi/analysis/commits?author=fredbi> |
| @casualjim | 70 | <https://github.com/go-openapi/analysis/commits?author=casualjim> |
| @keramix | 9 | <https://github.com/go-openapi/analysis/commits?author=keramix> |
| @youyuanwu | 8 | <https://github.com/go-openapi/analysis/commits?author=youyuanwu> |
| @msample | 3 | <https://github.com/go-openapi/analysis/commits?author=msample> |
| @kul-amr | 3 | <https://github.com/go-openapi/analysis/commits?author=kul-amr> |
| @mbohlool | 2 | <https://github.com/go-openapi/analysis/commits?author=mbohlool> |
| @Copilot | 1 | <https://github.com/go-openapi/analysis/commits?author=Copilot> |
| @danielfbm | 1 | <https://github.com/go-openapi/analysis/commits?author=danielfbm> |
| @gregmarr | 1 | <https://github.com/go-openapi/analysis/commits?author=gregmarr> |
| @guillemj | 1 | <https://github.com/go-openapi/analysis/commits?author=guillemj> |
| @knweiss | 1 | <https://github.com/go-openapi/analysis/commits?author=knweiss> |
| @tklauser | 1 | <https://github.com/go-openapi/analysis/commits?author=tklauser> |
| @cuishuang | 1 | <https://github.com/go-openapi/analysis/commits?author=cuishuang> |
| @ujjwalsh | 1 | <https://github.com/go-openapi/analysis/commits?author=ujjwalsh> |
_this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_

View File

@@ -1,46 +1,22 @@
# analysis
# OpenAPI analysis [![Build Status](https://github.com/go-openapi/analysis/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/analysis/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/analysis/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/analysis)
<!-- Badges: status -->
[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url]
<!-- Badges: release & docker images -->
<!-- Badges: code quality -->
<!-- Badges: license & compliance -->
[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url]
<!-- Badges: documentation & support -->
<!-- Badges: others & stats -->
[![GoDoc][godoc-badge]][godoc-url] [![Discord Channel][discord-badge]][discord-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge]
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/analysis/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/analysis.svg)](https://pkg.go.dev/github.com/go-openapi/analysis)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/analysis)](https://goreportcard.com/report/github.com/go-openapi/analysis)
---
A foundational library to analyze an OAI specification document for easier reasoning about the content.
## Announcements
* **2025-12-19** : new community chat on discord
* a new discord community channel is available to be notified of changes and support users
* our venerable Slack channel remains open, and will be eventually discontinued on **2026-03-31**
You may join the discord community by clicking the invite link on the discord badge (also above). [![Discord Channel][discord-badge]][discord-url]
Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-url]
## Status
API is stable.
## Import this library in your project
```cmd
go get github.com/go-openapi/analysis
```
## What's inside
## What's inside?
* An analyzer providing methods to walk the functional content of a specification
* A spec flattener producing a self-contained document bundle, while preserving `$ref`s
* A spec merger ("mixin") to merge several spec documents into a primary spec
* A spec "fixer" ensuring that response descriptions are non empty
[Documentation](https://pkg.go.dev/github.com/go-openapi/analysis)
## FAQ
* Does this library support OpenAPI 3?
@@ -49,79 +25,3 @@ go get github.com/go-openapi/analysis
> This package currently only supports OpenAPI 2.0 (aka Swagger 2.0).
> There is no plan to make it evolve toward supporting OpenAPI 3.x.
> This [discussion thread](https://github.com/go-openapi/spec/issues/21) relates the full story.
## Change log
See <https://github.com/go-openapi/analysis/releases>
<!--
## References
-->
## Licensing
This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE).
<!--
See the license NOTICE, which recalls the licensing terms of all the pieces of software
on top of which it has been built.
-->
<!--
## Limitations
-->
## Other documentation
* [All-time contributors](./CONTRIBUTORS.md)
* [Contributing guidelines](.github/CONTRIBUTING.md)
* [Maintainers documentation](docs/MAINTAINERS.md)
* [Code style](docs/STYLE.md)
## Cutting a new release
Maintainers can cut a new release by either:
* running [this workflow](https://github.com/go-openapi/analysis/actions/workflows/bump-release.yml)
* or pushing a semver tag
* signed tags are preferred
* The tag message is prepended to release notes
<!-- Badges: status -->
[test-badge]: https://github.com/go-openapi/analysis/actions/workflows/go-test.yml/badge.svg
[test-url]: https://github.com/go-openapi/analysis/actions/workflows/go-test.yml
[cov-badge]: https://codecov.io/gh/go-openapi/analysis/branch/master/graph/badge.svg
[cov-url]: https://codecov.io/gh/go-openapi/analysis
[vuln-scan-badge]: https://github.com/go-openapi/analysis/actions/workflows/scanner.yml/badge.svg
[vuln-scan-url]: https://github.com/go-openapi/analysis/actions/workflows/scanner.yml
[codeql-badge]: https://github.com/go-openapi/analysis/actions/workflows/codeql.yml/badge.svg
[codeql-url]: https://github.com/go-openapi/analysis/actions/workflows/codeql.yml
<!-- Badges: release & docker images -->
[release-badge]: https://badge.fury.io/gh/go-openapi%2Fanalysis.svg
[release-url]: https://badge.fury.io/gh/go-openapi%2Fanalysis
<!-- Badges: code quality -->
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/analysis
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/analysis
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/analysis
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/analysis
<!-- Badges: documentation & support -->
[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/analysis
[godoc-url]: http://pkg.go.dev/github.com/go-openapi/analysis
[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
[discord-url]: https://discord.gg/twZ9BwT3
<!-- Badges: license & compliance -->
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
[license-url]: https://github.com/go-openapi/analysis/?tab=Apache-2.0-1-ov-file#readme
<!-- Badges: others & stats -->
[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/analysis
[goversion-url]: https://github.com/go-openapi/analysis/blob/master/go.mod
[top-badge]: https://img.shields.io/github/languages/top/go-openapi/analysis
[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/analysis/latest

View File

@@ -1,37 +0,0 @@
# Security Policy
This policy outlines the commitment and practices of the go-openapi maintainers regarding security.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.x | :white_check_mark: |
## Vulnerability checks in place
This repository uses automated vulnerability scans, at every merged commit and at least once a week.
We use:
* [`GitHub CodeQL`][codeql-url]
* [`trivy`][trivy-url]
* [`govulncheck`][govulncheck-url]
Reports are centralized in github security reports and visible only to the maintainers.
## Reporting a vulnerability
If you become aware of a security vulnerability that affects the current repository,
**please report it privately to the maintainers**
rather than opening a publicly visible GitHub issue.
Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url].
> [!NOTE]
> On Github, navigate to the project's "Security" tab then click on "Report a vulnerability".
[codeql-url]: https://github.com/github/codeql
[trivy-url]: https://trivy.dev/docs/latest/getting-started
[govulncheck-url]: https://go.dev/blog/govulncheck
[github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package analysis
@@ -9,4 +20,4 @@ import (
"github.com/go-openapi/analysis/internal/debug"
)
var debugLog = debug.GetLogger("analysis", os.Getenv("SWAGGER_DEBUG") != "") //nolint:gochecknoglobals // it's okay to use a private global for logging
var debugLog = debug.GetLogger("analysis", os.Getenv("SWAGGER_DEBUG") != "")

View File

@@ -1,31 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package analysis provides methods to work with a Swagger specification document from
// package go-openapi/spec.
//
// # Analyzing a specification
//
// An analysed specification object (type Spec) provides methods to work with swagger definition.
//
// # Flattening or expanding a specification
//
// Flattening a specification bundles all remote $ref in the main spec document.
// Depending on flattening options, additional preprocessing may take place:
//
// - full flattening: replacing all inline complex constructs by a named entry in #/definitions
// - expand: replace all $ref's in the document by their expanded content
//
// # Merging several specifications
//
// [Mixin] several specifications merges all Swagger constructs, and warns about found conflicts.
//
// # Fixing a specification
//
// Unmarshalling a specification with golang [json] unmarshalling may lead to
// some unwanted result on present but empty fields.
//
// # Analyzing a Swagger schema
//
// Swagger schemas are analyzed to determine their complexity and qualify their content.
/*
Package analysis provides methods to work with a Swagger specification document from
package go-openapi/spec.
## Analyzing a specification
An analysed specification object (type Spec) provides methods to work with swagger definition.
## Flattening or expanding a specification
Flattening a specification bundles all remote $ref in the main spec document.
Depending on flattening options, additional preprocessing may take place:
- full flattening: replacing all inline complex constructs by a named entry in #/definitions
- expand: replace all $ref's in the document by their expanded content
## Merging several specifications
Mixin several specifications merges all Swagger constructs, and warns about found conflicts.
## Fixing a specification
Unmarshalling a specification with golang json unmarshalling may lead to
some unwanted result on present but empty fields.
## Analyzing a Swagger schema
Swagger schemas are analyzed to determine their complexity and qualify their content.
*/
package analysis

View File

@@ -1,56 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package analysis
import (
"errors"
"fmt"
)
type analysisError string
const (
ErrAnalysis analysisError = "analysis error"
ErrNoSchema analysisError = "no schema to analyze"
)
func (e analysisError) Error() string {
return string(e)
}
func ErrAtKey(key string, err error) error {
return errors.Join(
fmt.Errorf("key %s: %w", key, err),
ErrAnalysis,
)
}
func ErrInvalidRef(key string) error {
return fmt.Errorf("invalid reference: %q: %w", key, ErrAnalysis)
}
func ErrInvalidParameterRef(key string) error {
return fmt.Errorf("resolved reference is not a parameter: %q: %w", key, ErrAnalysis)
}
func ErrResolveSchema(err error) error {
return errors.Join(
fmt.Errorf("could not resolve schema: %w", err),
ErrAnalysis,
)
}
func ErrRewriteRef(key string, target any, err error) error {
return errors.Join(
fmt.Errorf("failed to rewrite ref for key %q at %v: %w", key, target, err),
ErrAnalysis,
)
}
func ErrInlineDefinition(key string, err error) error {
return errors.Join(
fmt.Errorf("error while creating definition %q from inline schema: %w", key, err),
ErrAnalysis,
)
}

View File

@@ -1,5 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package analysis
@@ -61,7 +72,7 @@ func FixEmptyDescs(rs *spec.Responses) {
// Response object if it doesn't already have one and isn't a
// ref. No-op on nil input.
func FixEmptyDesc(rs *spec.Response) {
if rs == nil || rs.Description != "" || rs.Ref.GetURL() != nil {
if rs == nil || rs.Description != "" || rs.Ref.Ref.GetURL() != nil {
return
}
rs.Description = "(empty)"

View File

@@ -1,12 +1,23 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package analysis
import (
"fmt"
"log"
"path"
"slices"
"sort"
"strings"
@@ -21,7 +32,7 @@ import (
const definitionsPath = "#/definitions"
// newRef stores information about refs created during the flattening process.
// newRef stores information about refs created during the flattening process
type newRef struct {
key string
newName string
@@ -32,7 +43,7 @@ type newRef struct {
parents []string
}
// context stores intermediary results from flatten.
// context stores intermediary results from flatten
type context struct {
newRefs map[string]*newRef
warnings []string
@@ -41,9 +52,9 @@ type context struct {
func newContext() *context {
return &context{
newRefs: make(map[string]*newRef, allocMediumMap),
newRefs: make(map[string]*newRef, 150),
warnings: make([]string, 0),
resolved: make(map[string]string, allocMediumMap),
resolved: make(map[string]string, 50),
}
}
@@ -52,15 +63,13 @@ func newContext() *context {
// There is a minimal and a full flattening mode.
//
// Minimally flattening a spec means:
//
// - Expanding parameters, responses, path items, parameter items and header items (references to schemas are left
// unscathed)
// - Importing external ([http], file) references so they become internal to the document
// - Importing external (http, file) references so they become internal to the document
// - Moving every JSON pointer to a $ref to a named definition (i.e. the reworked spec does not contain pointers
// like "$ref": "#/definitions/myObject/allOfs/1")
//
// A minimally flattened spec thus guarantees the following properties:
//
// - all $refs point to a local definition (i.e. '#/definitions/...')
// - definitions are unique
//
@@ -72,7 +81,6 @@ func newContext() *context {
// Minimal flattening is necessary and sufficient for codegen rendering using go-swagger.
//
// Fully flattening a spec means:
//
// - Moving every complex inline schema to be a definition with an auto-generated name in a depth-first fashion.
//
// By complex, we mean every JSON object with some properties.
@@ -83,7 +91,6 @@ func newContext() *context {
// have been created.
//
// Available flattening options:
//
// - Minimal: stops flattening after minimal $ref processing, leaving schema constructs untouched
// - Expand: expand all $ref's in the document (inoperant if Minimal set to true)
// - Verbose: croaks about name conflicts detected
@@ -91,9 +98,8 @@ func newContext() *context {
//
// NOTE: expansion removes all $ref save circular $ref, which remain in place
//
// Desirable future additions: additional options.
//
// - PropagateNameExtensions: ensure that created entries properly follow naming rules when their parent have set a
// TODO: additional options
// - ProgagateNameExtensions: ensure that created entries properly follow naming rules when their parent have set a
// x-go-name extension
// - LiftAllOfs:
// - limit the flattening of allOf members when simple objects
@@ -174,7 +180,7 @@ func expand(opts *FlattenOpts) error {
}
// normalizeRef strips the current file from any absolute file $ref. This works around issue go-openapi/spec#76:
// leading absolute file in $ref is stripped.
// leading absolute file in $ref is stripped
func normalizeRef(opts *FlattenOpts) error {
debugLog("normalizeRef")
@@ -245,7 +251,7 @@ func nameInlinedSchemas(opts *FlattenOpts) error {
asch, err := Schema(SchemaOpts{Schema: sch.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
if err != nil {
return ErrAtKey(key, err)
return fmt.Errorf("schema analysis [%s]: %w", key, err)
}
if asch.isAnalyzedAsComplex() { // move complex schemas to definitions
@@ -314,7 +320,7 @@ func importNewRef(entry sortref.RefRevIdx, refStr string, opts *FlattenOpts) err
sch, err := spec.ResolveRefWithBase(opts.Swagger(), &entry.Ref, opts.ExpandOpts(false))
if err != nil {
return ErrResolveSchema(err)
return fmt.Errorf("could not resolve schema: %w", err)
}
// at this stage only $ref analysis matters
@@ -329,7 +335,7 @@ func importNewRef(entry sortref.RefRevIdx, refStr string, opts *FlattenOpts) err
// now rewrite those refs with rebase
for key, ref := range partialAnalyzer.references.allRefs {
if err := replace.UpdateRef(sch, key, spec.MustCreateRef(normalize.RebaseRef(entry.Ref.String(), ref.String()))); err != nil {
return ErrRewriteRef(key, entry.Ref.String(), err)
return fmt.Errorf("failed to rewrite ref for key %q at %s: %w", key, entry.Ref.String(), err)
}
}
@@ -423,7 +429,7 @@ func importExternalReferences(opts *FlattenOpts) (bool, error) {
ref := spec.MustCreateRef(r.path)
sch, err := spec.ResolveRefWithBase(opts.Swagger(), &ref, opts.ExpandOpts(false))
if err != nil {
return false, ErrResolveSchema(err)
return false, fmt.Errorf("could not resolve schema: %w", err)
}
r.schema = sch
@@ -496,25 +502,14 @@ func stripPointersAndOAIGen(opts *FlattenOpts) error {
// pointer and name resolution again.
func stripOAIGen(opts *FlattenOpts) (bool, error) {
debugLog("stripOAIGen")
// Ensure the spec analysis is fresh, as previous steps (namePointers, etc.) might have modified refs.
opts.Spec.reload()
replacedWithComplex := false
// figure out referers of OAIGen definitions (doing it before the ref start mutating)
// Sort keys to ensure deterministic processing order
sortedKeys := make([]string, 0, len(opts.flattenContext.newRefs))
for k := range opts.flattenContext.newRefs {
sortedKeys = append(sortedKeys, k)
}
sort.Strings(sortedKeys)
for _, k := range sortedKeys {
r := opts.flattenContext.newRefs[k]
for _, r := range opts.flattenContext.newRefs {
updateRefParents(opts.Spec.references.allRefs, r)
}
for _, k := range sortedKeys {
for k := range opts.flattenContext.newRefs {
r := opts.flattenContext.newRefs[k]
debugLog("newRefs[%s]: isOAIGen: %t, resolved: %t, name: %s, path:%s, #parents: %d, parents: %v, ref: %s",
k, r.isOAIGen, r.resolved, r.newName, r.path, len(r.parents), r.parents, r.schema.Ref.String())
@@ -537,7 +532,7 @@ func stripOAIGen(opts *FlattenOpts) (bool, error) {
return replacedWithComplex, nil
}
// updateRefParents updates all parents of an updated $ref.
// updateRefParents updates all parents of an updated $ref
func updateRefParents(allRefs map[string]spec.Ref, r *newRef) {
if !r.isOAIGen || r.resolved { // bail on already resolved entries (avoid looping)
return
@@ -547,7 +542,14 @@ func updateRefParents(allRefs map[string]spec.Ref, r *newRef) {
continue
}
found := slices.Contains(r.parents, k)
found := false
for _, p := range r.parents {
if p == k {
found = true
break
}
}
if !found {
r.parents = append(r.parents, k)
}
@@ -596,19 +598,6 @@ func stripOAIGenForRef(opts *FlattenOpts, k string, r *newRef) (bool, error) {
replacedWithComplex = true
}
}
// update parents of the target ref (pr[0]) if it is also a newRef (OAIGen)
// This ensures that if the target is later deleted/merged, it knows about these new referers.
for _, nr := range opts.flattenContext.newRefs {
if nr.path == pr[0] && nr.isOAIGen && !nr.resolved {
for _, p := range pr[1:] {
if !slices.Contains(nr.parents, p) {
nr.parents = append(nr.parents, p)
}
}
break
}
}
}
// remove OAIGen definition
@@ -616,15 +605,7 @@ func stripOAIGenForRef(opts *FlattenOpts, k string, r *newRef) (bool, error) {
delete(opts.Swagger().Definitions, path.Base(r.path))
// propagate changes in ref index for keys which have this one as a parent
// Sort keys to ensure deterministic update order
propagateKeys := make([]string, 0, len(opts.flattenContext.newRefs))
for k := range opts.flattenContext.newRefs {
propagateKeys = append(propagateKeys, k)
}
sort.Strings(propagateKeys)
for _, kk := range propagateKeys {
value := opts.flattenContext.newRefs[kk]
for kk, value := range opts.flattenContext.newRefs {
if kk == k || !value.isOAIGen || value.resolved {
continue
}
@@ -662,7 +643,7 @@ func stripOAIGenForRef(opts *FlattenOpts, k string, r *newRef) (bool, error) {
}
debugLog("re-inlined schema: parent: %s, %t", pr[0], asch.isAnalyzedAsComplex())
replacedWithComplex = replacedWithComplex || path.Dir(pr[0]) != definitionsPath && asch.isAnalyzedAsComplex()
replacedWithComplex = replacedWithComplex || !(path.Dir(pr[0]) == definitionsPath) && asch.isAnalyzedAsComplex()
}
return replacedWithComplex, nil
@@ -685,7 +666,7 @@ func namePointers(opts *FlattenOpts) error {
result, err := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), ref)
if err != nil {
return ErrAtKey(k, err)
return fmt.Errorf("at %s, %w", k, err)
}
replacingRef := result.Ref
@@ -716,7 +697,7 @@ func namePointers(opts *FlattenOpts) error {
// update current replacement, which may have been updated by previous changes of deeper elements
result, erd := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), v.Ref)
if erd != nil {
return ErrAtKey(key, erd)
return fmt.Errorf("at %s, %w", key, erd)
}
if opts.flattenContext != nil {
@@ -762,9 +743,9 @@ func flattenAnonPointer(key string, v SchemaRef, refsToReplace map[string]Schema
// qualify the expanded schema
asch, ers := Schema(SchemaOpts{Schema: v.Schema, Root: opts.Swagger(), BasePath: opts.BasePath})
if ers != nil {
return ErrAtKey(key, ers)
return fmt.Errorf("schema analysis [%s]: %w", key, ers)
}
callers := make([]string, 0, allocMediumMap)
callers := make([]string, 0, 64)
debugLog("looking for callers")
@@ -772,7 +753,7 @@ func flattenAnonPointer(key string, v SchemaRef, refsToReplace map[string]Schema
for k, w := range an.references.allRefs {
r, err := replace.DeepestRef(opts.Swagger(), opts.ExpandOpts(false), w)
if err != nil {
return ErrAtKey(key, err)
return fmt.Errorf("at %s, %w", key, err)
}
if opts.flattenContext != nil {

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package analysis
import (
@@ -14,10 +11,10 @@ import (
"github.com/go-openapi/analysis/internal/flatten/schutils"
"github.com/go-openapi/analysis/internal/flatten/sortref"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag/mangling"
"github.com/go-openapi/swag"
)
// InlineSchemaNamer finds a new name for an inlined type.
// InlineSchemaNamer finds a new name for an inlined type
type InlineSchemaNamer struct {
Spec *spec.Swagger
Operations map[string]operations.OpRef
@@ -25,7 +22,7 @@ type InlineSchemaNamer struct {
opts *FlattenOpts
}
// Name yields a new name for the inline schema.
// Name yields a new name for the inline schema
func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *AnalyzedSchema) error {
debugLog("naming inlined schema at %s", key)
@@ -46,7 +43,7 @@ func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *Ana
debugLog("rewriting schema to ref: key=%s with new name: %s", key, newName)
if err := replace.RewriteSchemaToRef(isn.Spec, key,
spec.MustCreateRef(path.Join(definitionsPath, newName))); err != nil {
return ErrInlineDefinition(newName, err)
return fmt.Errorf("error while creating definition %q from inline schema: %w", newName, err)
}
// rewrite any dependent $ref pointing to this place,
@@ -57,7 +54,7 @@ func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *Ana
for k, v := range an.references.allRefs {
r, erd := replace.DeepestRef(isn.opts.Swagger(), isn.opts.ExpandOpts(false), v)
if erd != nil {
return ErrAtKey(k, erd)
return fmt.Errorf("at %s, %w", k, erd)
}
if isn.opts.flattenContext != nil {
@@ -108,7 +105,7 @@ func (isn *InlineSchemaNamer) Name(key string, schema *spec.Schema, aschema *Ana
return nil
}
// uniqifyName yields a unique name for a definition.
// uniqifyName yields a unique name for a definition
func uniqifyName(definitions spec.Definitions, name string) (string, bool) {
isOAIGen := false
if name == "" {
@@ -230,24 +227,19 @@ func namesForOperation(parts sortref.SplitKey, operations map[string]operations.
return baseNames, startIndex
}
const (
minStartIndex = 2
minSegments = 2
)
func namesForDefinition(parts sortref.SplitKey) ([][]string, int) {
nm := parts.DefinitionName()
if nm != "" {
return [][]string{{parts.DefinitionName()}}, minStartIndex
return [][]string{{parts.DefinitionName()}}, 2
}
return [][]string{}, 0
}
// partAdder knows how to interpret a schema when it comes to build a name from parts.
// partAdder knows how to interpret a schema when it comes to build a name from parts
func partAdder(aschema *AnalyzedSchema) sortref.PartAdder {
return func(part string) []string {
segments := make([]string, 0, minSegments)
segments := make([]string, 0, 2)
if part == "items" || part == "additionalItems" {
if aschema.IsTuple || aschema.IsTupleWithExtra {
@@ -273,9 +265,8 @@ func mangler(o *FlattenOpts) func(string) string {
if o.KeepNames {
return func(in string) string { return in }
}
mangler := mangling.NewNameMangler()
return mangler.ToJSONName
return swag.ToJSONName
}
func nameFromRef(ref spec.Ref, o *FlattenOpts) string {

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package analysis
import (
@@ -35,7 +32,7 @@ type FlattenOpts struct {
_ struct{} // require keys
}
// ExpandOpts creates a spec.[spec.ExpandOptions] to configure expanding a specification document.
// ExpandOpts creates a spec.ExpandOptions to configure expanding a specification document.
func (f *FlattenOpts) ExpandOpts(skipSchemas bool) *spec.ExpandOptions {
return &spec.ExpandOptions{
RelativeBase: f.BasePath,
@@ -44,13 +41,13 @@ func (f *FlattenOpts) ExpandOpts(skipSchemas bool) *spec.ExpandOptions {
}
}
// Swagger gets the swagger specification for this flatten operation.
// Swagger gets the swagger specification for this flatten operation
func (f *FlattenOpts) Swagger() *spec.Swagger {
return f.Spec.spec
}
// croak logs notifications and warnings about valid, but possibly unwanted constructs resulting
// from flattening a spec.
// from flattening a spec
func (f *FlattenOpts) croak() {
if !f.Verbose {
return

View File

@@ -1,6 +0,0 @@
go 1.24.0
use (
.
./internal/testintegration
)

View File

@@ -1,29 +0,0 @@
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=

View File

@@ -1,5 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package debug
@@ -11,18 +22,20 @@ import (
"runtime"
)
var output = os.Stdout //nolint:gochecknoglobals // this is on purpose to be overridable during tests
var (
output = os.Stdout
)
// GetLogger provides a prefix debug logger.
func GetLogger(prefix string, debug bool) func(string, ...any) {
// GetLogger provides a prefix debug logger
func GetLogger(prefix string, debug bool) func(string, ...interface{}) {
if debug {
logger := log.New(output, prefix+":", log.LstdFlags)
return func(msg string, args ...any) {
return func(msg string, args ...interface{}) {
_, file1, pos1, _ := runtime.Caller(1)
logger.Printf("%s:%d: %s", filepath.Base(file1), pos1, fmt.Sprintf(msg, args...))
}
}
return func(_ string, _ ...any) {}
return func(_ string, _ ...interface{}) {}
}

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package normalize
import (
@@ -17,9 +14,8 @@ import (
// NOTE: does not support JSONschema ID for $ref (we assume we are working with swagger specs here).
//
// NOTE(windows):
//
// - refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
// - "/ in paths may appear as escape sequences.
// * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
// * "/ in paths may appear as escape sequences
func RebaseRef(baseRef string, ref string) string {
baseRef, _ = url.PathUnescape(baseRef)
ref, _ = url.PathUnescape(ref)
@@ -70,9 +66,8 @@ func RebaseRef(baseRef string, ref string) string {
// Path renders absolute path on remote file refs
//
// NOTE(windows):
//
// - refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
// - "/ in paths may appear as escape sequences.
// * refs are assumed to have been normalized with drive letter lower cased (from go-openapi/spec)
// * "/ in paths may appear as escape sequences
func Path(ref spec.Ref, basePath string) string {
uri, _ := url.PathUnescape(ref.String())
if ref.HasFragmentOnly || filepath.IsAbs(uri) {

View File

@@ -1,25 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package operations
import (
"path"
"slices"
"sort"
"strings"
"github.com/go-openapi/jsonpointer"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag/mangling"
"github.com/go-openapi/swag"
)
// AllOpRefsByRef returns an index of sortable operations.
// AllOpRefsByRef returns an index of sortable operations
func AllOpRefsByRef(specDoc Provider, operationIDs []string) map[string]OpRef {
return OpRefsByRef(GatherOperations(specDoc, operationIDs))
}
// OpRefsByRef indexes a map of sortable operations.
// OpRefsByRef indexes a map of sortable operations
func OpRefsByRef(oprefs map[string]OpRef) map[string]OpRef {
result := make(map[string]OpRef, len(oprefs))
for _, v := range oprefs {
@@ -29,7 +25,7 @@ func OpRefsByRef(oprefs map[string]OpRef) map[string]OpRef {
return result
}
// OpRef is an indexable, sortable operation.
// OpRef is an indexable, sortable operation
type OpRef struct {
Method string
Path string
@@ -39,28 +35,27 @@ type OpRef struct {
Ref spec.Ref
}
// OpRefs is a sortable collection of operations.
// OpRefs is a sortable collection of operations
type OpRefs []OpRef
func (o OpRefs) Len() int { return len(o) }
func (o OpRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o OpRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
// Provider knows how to collect operations from a spec.
// Provider knows how to collect operations from a spec
type Provider interface {
Operations() map[string]map[string]*spec.Operation
}
// GatherOperations builds a map of sorted operations from a spec.
// GatherOperations builds a map of sorted operations from a spec
func GatherOperations(specDoc Provider, operationIDs []string) map[string]OpRef {
var oprefs OpRefs
mangler := mangling.NewNameMangler()
for method, pathItem := range specDoc.Operations() {
for pth, operation := range pathItem {
vv := *operation
oprefs = append(oprefs, OpRef{
Key: mangler.ToGoName(strings.ToLower(method) + " " + pth),
Key: swag.ToGoName(strings.ToLower(method) + " " + pth),
Method: method,
Path: pth,
ID: vv.ID,
@@ -84,7 +79,7 @@ func GatherOperations(specDoc Provider, operationIDs []string) map[string]OpRef
nm = opr.Key
}
if len(operationIDs) == 0 || slices.Contains(operationIDs, opr.ID) || slices.Contains(operationIDs, nm) {
if len(operationIDs) == 0 || swag.ContainsStrings(operationIDs, opr.ID) || swag.ContainsStrings(operationIDs, nm) {
opr.ID = nm
opr.Op.ID = nm
operations[nm] = opr

View File

@@ -1,64 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package replace
import (
"errors"
"fmt"
)
type replaceError string
const (
ErrReplace replaceError = "flatten replace error"
ErrUnexpectedType replaceError = "unexpected type used in getPointerFromKey"
)
func (e replaceError) Error() string {
return string(e)
}
func ErrNoSchemaWithRef(key string, value any) error {
return fmt.Errorf("no schema with ref found at %s for %T: %w", key, value, ErrReplace)
}
func ErrNoSchema(key string) error {
return fmt.Errorf("no schema found at %s: %w", key, ErrReplace)
}
func ErrNotANumber(key string, err error) error {
return errors.Join(
ErrReplace,
fmt.Errorf("%s not a number: %w", key, err),
)
}
func ErrUnhandledParentRewrite(key string, value any) error {
return fmt.Errorf("unhandled parent schema rewrite %s: %T: %w", key, value, ErrReplace)
}
func ErrUnhandledParentType(key string, value any) error {
return fmt.Errorf("unhandled type for parent of %s: %T: %w", key, value, ErrReplace)
}
func ErrNoParent(key string, err error) error {
return errors.Join(
fmt.Errorf("can't get parent for %s: %w", key, err),
ErrReplace,
)
}
func ErrUnhandledContainerType(key string, value any) error {
return fmt.Errorf("unhandled container type at %s: %T: %w", key, value, ErrReplace)
}
func ErrCyclicChain(key string) error {
return fmt.Errorf("cannot resolve cyclic chain of pointers under %s: %w", key, ErrReplace)
}
func ErrInvalidPointerType(key string, value any, err error) error {
return fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%w): %w",
key, value, err, ErrReplace,
)
}

View File

@@ -1,11 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package replace
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
@@ -17,15 +13,11 @@ import (
"github.com/go-openapi/spec"
)
const (
definitionsPath = "#/definitions"
allocMediumMap = 64
)
const definitionsPath = "#/definitions"
//nolint:gochecknoglobals // it's okay to use a private global for logging
var debugLog = debug.GetLogger("analysis/flatten/replace", os.Getenv("SWAGGER_DEBUG") != "")
// RewriteSchemaToRef replaces a schema with a Ref.
// RewriteSchemaToRef replaces a schema with a Ref
func RewriteSchemaToRef(sp *spec.Swagger, key string, ref spec.Ref) error {
debugLog("rewriting schema to ref for %s with %s", key, ref.String())
_, value, err := getPointerFromKey(sp, key)
@@ -49,10 +41,10 @@ func RewriteSchemaToRef(sp *spec.Swagger, key string, ref spec.Ref) error {
if refable.Schema != nil {
refable.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
}
case map[string]any: // this happens e.g. if a schema points to an extension unmarshaled as map[string]interface{}
case map[string]interface{}: // this happens e.g. if a schema points to an extension unmarshaled as map[string]interface{}
return rewriteParentRef(sp, key, ref)
default:
return ErrNoSchemaWithRef(key, value)
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
@@ -77,7 +69,7 @@ func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
case *spec.Responses:
statusCode, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(key[1:], err)
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
resp := container.StatusCodeResponses[statusCode]
resp.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
@@ -101,7 +93,7 @@ func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
case []spec.Parameter:
idx, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(key[1:], err)
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
param := container[idx]
param.Schema = &spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
@@ -116,7 +108,7 @@ func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
case []spec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(key[1:], err)
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
@@ -124,63 +116,60 @@ func rewriteParentRef(sp *spec.Swagger, key string, ref spec.Ref) error {
// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
idx, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(key[1:], err)
return fmt.Errorf("%s not a number: %w", key[1:], err)
}
container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case spec.SchemaProperties:
container[entry] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
case *any:
case *interface{}:
*container = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
default:
return ErrUnhandledParentRewrite(key, pvalue)
return fmt.Errorf("unhandled parent schema rewrite %s (%T)", key, pvalue)
}
return nil
}
// getPointerFromKey retrieves the content of the JSON pointer "key".
func getPointerFromKey(sp any, key string) (string, any, error) {
// getPointerFromKey retrieves the content of the JSON pointer "key"
func getPointerFromKey(sp interface{}, key string) (string, interface{}, error) {
switch sp.(type) {
case *spec.Schema:
case *spec.Swagger:
default:
panic(ErrUnexpectedType)
panic("unexpected type used in getPointerFromKey")
}
if key == "#/" {
return "", sp, nil
}
// unescape chars in key, e.g. "{}" from path params
pth, err := url.PathUnescape(key[1:])
if err != nil {
return "", nil, errors.Join(err, ErrReplace)
}
pth, _ := url.PathUnescape(key[1:])
ptr, err := jsonpointer.New(pth)
if err != nil {
return "", nil, errors.Join(err, ErrReplace)
return "", nil, err
}
value, _, err := ptr.Get(sp)
if err != nil {
debugLog("error when getting key: %s with path: %s", key, pth)
return "", nil, errors.Join(err, ErrReplace)
return "", nil, err
}
return pth, value, nil
}
// getParentFromKey retrieves the container of the JSON pointer "key".
func getParentFromKey(sp any, key string) (string, string, any, error) {
// getParentFromKey retrieves the container of the JSON pointer "key"
func getParentFromKey(sp interface{}, key string) (string, string, interface{}, error) {
switch sp.(type) {
case *spec.Schema:
case *spec.Swagger:
default:
panic(ErrUnexpectedType)
panic("unexpected type used in getPointerFromKey")
}
// unescape chars in key, e.g. "{}" from path params
pth, _ := url.PathUnescape(key[1:])
@@ -190,23 +179,23 @@ func getParentFromKey(sp any, key string) (string, string, any, error) {
pptr, err := jsonpointer.New(parent)
if err != nil {
return "", "", nil, errors.Join(err, ErrReplace)
return "", "", nil, err
}
pvalue, _, err := pptr.Get(sp)
if err != nil {
return "", "", nil, ErrNoParent(parent, err)
return "", "", nil, fmt.Errorf("can't get parent for %s: %w", parent, err)
}
return parent, entry, pvalue, nil
}
// UpdateRef replaces a ref by another one.
func UpdateRef(sp any, key string, ref spec.Ref) error {
// UpdateRef replaces a ref by another one
func UpdateRef(sp interface{}, key string, ref spec.Ref) error {
switch sp.(type) {
case *spec.Schema:
case *spec.Swagger:
default:
panic(ErrUnexpectedType)
panic("unexpected type used in getPointerFromKey")
}
debugLog("updating ref for %s with %s", key, ref.String())
pth, value, err := getPointerFromKey(sp, key)
@@ -229,7 +218,7 @@ func UpdateRef(sp any, key string, ref spec.Ref) error {
debugLog("rewriting holder for %T", refable)
_, entry, pvalue, erp := getParentFromKey(sp, key)
if erp != nil {
return erp
return err
}
switch container := pvalue.(type) {
case spec.Definitions:
@@ -241,7 +230,7 @@ func UpdateRef(sp any, key string, ref spec.Ref) error {
case []spec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(pth, err)
return fmt.Errorf("%s not a number: %w", pth, err)
}
container[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
@@ -249,7 +238,7 @@ func UpdateRef(sp any, key string, ref spec.Ref) error {
// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
idx, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(pth, err)
return fmt.Errorf("%s not a number: %w", pth, err)
}
container.Schemas[idx] = spec.Schema{SchemaProps: spec.SchemaProps{Ref: ref}}
@@ -259,17 +248,17 @@ func UpdateRef(sp any, key string, ref spec.Ref) error {
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
default:
return ErrUnhandledContainerType(key, value)
return fmt.Errorf("unhandled container type at %s: %T", key, value)
}
default:
return ErrNoSchemaWithRef(key, value)
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
// UpdateRefWithSchema replaces a ref with a schema (i.e. re-inline schema).
// UpdateRefWithSchema replaces a ref with a schema (i.e. re-inline schema)
func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
debugLog("updating ref for %s with schema", key)
pth, value, err := getPointerFromKey(sp, key)
@@ -283,9 +272,8 @@ func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
case spec.Schema:
_, entry, pvalue, erp := getParentFromKey(sp, key)
if erp != nil {
return erp
return err
}
switch container := pvalue.(type) {
case spec.Definitions:
container[entry] = *sch
@@ -296,7 +284,7 @@ func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
case []spec.Schema:
idx, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(pth, err)
return fmt.Errorf("%s not a number: %w", pth, err)
}
container[idx] = *sch
@@ -304,7 +292,7 @@ func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
// NOTE: this is necessarily an array - otherwise, the parent would be *Schema
idx, err := strconv.Atoi(entry)
if err != nil {
return ErrNotANumber(pth, err)
return fmt.Errorf("%s not a number: %w", pth, err)
}
container.Schemas[idx] = *sch
@@ -314,7 +302,7 @@ func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
// NOTE: can't have case *spec.SchemaOrBool = parent in this case is *Schema
default:
return ErrUnhandledParentType(key, value)
return fmt.Errorf("unhandled type for parent of [%s]: %T", key, value)
}
case *spec.SchemaOrArray:
*refable.Schema = *sch
@@ -322,13 +310,13 @@ func UpdateRefWithSchema(sp *spec.Swagger, key string, sch *spec.Schema) error {
case *spec.SchemaOrBool:
*refable.Schema = *sch
default:
return ErrNoSchemaWithRef(key, value)
return fmt.Errorf("no schema with ref found at %s for %T", key, value)
}
return nil
}
// DeepestRefResult holds the results from [DeepestRef] analysis.
// DeepestRefResult holds the results from DeepestRef analysis
type DeepestRefResult struct {
Ref spec.Ref
Schema *spec.Schema
@@ -336,13 +324,10 @@ type DeepestRefResult struct {
}
// DeepestRef finds the first definition ref, from a cascade of nested refs which are not definitions.
//
// - if no definition is found, returns the deepest ref.
// - pointers to external files are expanded
//
// NOTE: all external $ref's are assumed to be already expanded at this stage.
//
//nolint:gocognit,gocyclo,cyclop // definitely needs a refactoring, in a follow-up PR
func DeepestRef(sp *spec.Swagger, opts *spec.ExpandOptions, ref spec.Ref) (*DeepestRefResult, error) {
if !ref.HasFragmentOnly {
// we found an external $ref, which is odd at this stage:
@@ -351,8 +336,8 @@ func DeepestRef(sp *spec.Swagger, opts *spec.ExpandOptions, ref spec.Ref) (*Deep
}
currentRef := ref
visited := make(map[string]bool, allocMediumMap)
warnings := make([]string, 0)
visited := make(map[string]bool, 64)
warnings := make([]string, 0, 2)
DOWNREF:
for currentRef.String() != "" {
@@ -362,7 +347,8 @@ DOWNREF:
}
if _, beenThere := visited[currentRef.String()]; beenThere {
return nil, ErrCyclicChain(currentRef.String())
return nil,
fmt.Errorf("cannot resolve cyclic chain of pointers under %s", currentRef.String())
}
visited[currentRef.String()] = true
@@ -399,14 +385,15 @@ DOWNREF:
case spec.Response:
// a pointer points to a schema initially marshalled in responses section...
// Attempt to convert this to a schema. If this fails, the spec is invalid
asJSON, err := refable.MarshalJSON()
if err != nil {
return nil, ErrInvalidPointerType(currentRef.String(), value, err)
}
asJSON, _ := refable.MarshalJSON()
var asSchema spec.Schema
if err = asSchema.UnmarshalJSON(asJSON); err != nil {
return nil, ErrInvalidPointerType(currentRef.String(), value, err)
err := asSchema.UnmarshalJSON(asJSON)
if err != nil {
return nil,
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (response) interpreted as schema", currentRef.String()))
@@ -418,13 +405,13 @@ DOWNREF:
case spec.Parameter:
// a pointer points to a schema initially marshalled in parameters section...
// Attempt to convert this to a schema. If this fails, the spec is invalid
asJSON, err := refable.MarshalJSON()
if err != nil {
return nil, ErrInvalidPointerType(currentRef.String(), value, err)
}
asJSON, _ := refable.MarshalJSON()
var asSchema spec.Schema
if err = asSchema.UnmarshalJSON(asJSON); err != nil {
return nil, ErrInvalidPointerType(currentRef.String(), value, err)
if err := asSchema.UnmarshalJSON(asJSON); err != nil {
return nil,
fmt.Errorf("invalid type for resolved JSON pointer %s. Expected a schema a, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (parameter) interpreted as schema", currentRef.String()))
@@ -440,13 +427,13 @@ DOWNREF:
break DOWNREF
}
asJSON, err := json.Marshal(refable)
if err != nil {
return nil, ErrInvalidPointerType(currentRef.String(), value, err)
}
asJSON, _ := json.Marshal(refable)
var asSchema spec.Schema
if err = asSchema.UnmarshalJSON(asJSON); err != nil {
return nil, ErrInvalidPointerType(currentRef.String(), value, err)
if err := asSchema.UnmarshalJSON(asJSON); err != nil {
return nil,
fmt.Errorf("unhandled type to resolve JSON pointer %s. Expected a Schema, got: %T (%v)",
currentRef.String(), value, err,
)
}
warnings = append(warnings, fmt.Sprintf("found $ref %q (%T) interpreted as schema", currentRef.String(), refable))
@@ -464,7 +451,7 @@ DOWNREF:
}
if sch == nil {
return nil, ErrNoSchema(currentRef.String())
return nil, fmt.Errorf("no schema found at %s", currentRef.String())
}
return &DeepestRefResult{Ref: currentRef, Schema: sch, Warnings: warnings}, nil

View File

@@ -1,34 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Package schutils provides tools to save or clone a schema
// when flattening a spec.
package schutils
import (
"github.com/go-openapi/spec"
"github.com/go-openapi/swag/jsonutils"
"github.com/go-openapi/swag"
)
const allocLargeMap = 150
// Save registers a schema as an entry in spec #/definitions.
// Save registers a schema as an entry in spec #/definitions
func Save(sp *spec.Swagger, name string, schema *spec.Schema) {
if schema == nil {
return
}
if sp.Definitions == nil {
sp.Definitions = make(map[string]spec.Schema, allocLargeMap)
sp.Definitions = make(map[string]spec.Schema, 150)
}
sp.Definitions[name] = *schema
}
// Clone deep-clones a schema.
// Clone deep-clones a schema
func Clone(schema *spec.Schema) *spec.Schema {
var sch spec.Schema
_ = jsonutils.FromDynamicJSON(schema, &sch)
_ = swag.FromDynamicJSON(schema, &sch)
return &sch
}

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package sortref
import (
@@ -20,8 +17,12 @@ const (
definitions = "definitions"
)
//nolint:gochecknoglobals // it's okay to store small indexes like this as private globals
var (
ignoredKeys map[string]struct{}
validMethods map[string]struct{}
)
func init() {
ignoredKeys = map[string]struct{}{
"schema": {},
"properties": {},
@@ -39,15 +40,15 @@ var (
"PUT": {},
"DELETE": {},
}
)
}
// Key represent a key item constructed from /-separated segments.
// Key represent a key item constructed from /-separated segments
type Key struct {
Segments int
Key string
}
// Keys is a sortable collable collection of Keys.
// Keys is a sortable collable collection of Keys
type Keys []Key
func (k Keys) Len() int { return len(k) }
@@ -56,10 +57,10 @@ func (k Keys) Less(i, j int) bool {
return k[i].Segments > k[j].Segments || (k[i].Segments == k[j].Segments && k[i].Key < k[j].Key)
}
// KeyParts construct a [SplitKey] with all its /-separated segments decomposed. It is sortable.
// KeyParts construct a SplitKey with all its /-separated segments decomposed. It is sortable.
func KeyParts(key string) SplitKey {
var res []string
for part := range strings.SplitSeq(key[1:], "/") {
for _, part := range strings.Split(key[1:], "/") {
if part != "" {
res = append(res, jsonpointer.Unescape(part))
}
@@ -71,12 +72,12 @@ func KeyParts(key string) SplitKey {
// SplitKey holds of the parts of a /-separated key, so that their location may be determined.
type SplitKey []string
// IsDefinition is true when the split key is in the #/definitions section of a spec.
// IsDefinition is true when the split key is in the #/definitions section of a spec
func (s SplitKey) IsDefinition() bool {
return len(s) > 1 && s[0] == definitions
}
// DefinitionName yields the name of the definition.
// DefinitionName yields the name of the definition
func (s SplitKey) DefinitionName() string {
if !s.IsDefinition() {
return ""
@@ -85,105 +86,6 @@ func (s SplitKey) DefinitionName() string {
return s[1]
}
// PartAdder know how to construct the components of a new name.
type PartAdder func(string) []string
// BuildName builds a name from segments.
func (s SplitKey) BuildName(segments []string, startIndex int, adder PartAdder) string {
for i, part := range s[startIndex:] {
if _, ignored := ignoredKeys[part]; !ignored || s.isKeyName(startIndex+i) {
segments = append(segments, adder(part)...)
}
}
return strings.Join(segments, " ")
}
// IsOperation is true when the split key is in the operations section.
func (s SplitKey) IsOperation() bool {
return len(s) > 1 && s[0] == paths
}
// IsSharedOperationParam is true when the split key is in the parameters section of a path.
func (s SplitKey) IsSharedOperationParam() bool {
return len(s) > 2 && s[0] == paths && s[2] == parameters
}
// IsSharedParam is true when the split key is in the #/parameters section of a spec.
func (s SplitKey) IsSharedParam() bool {
return len(s) > 1 && s[0] == parameters
}
// IsOperationParam is true when the split key is in the parameters section of an operation.
func (s SplitKey) IsOperationParam() bool {
return len(s) > 3 && s[0] == paths && s[3] == parameters
}
// IsOperationResponse is true when the split key is in the responses section of an operation.
func (s SplitKey) IsOperationResponse() bool {
return len(s) > 3 && s[0] == paths && s[3] == responses
}
// IsSharedResponse is true when the split key is in the #/responses section of a spec.
func (s SplitKey) IsSharedResponse() bool {
return len(s) > 1 && s[0] == responses
}
// IsDefaultResponse is true when the split key is the default response for an operation.
func (s SplitKey) IsDefaultResponse() bool {
return len(s) > 4 && s[0] == paths && s[3] == responses && s[4] == "default"
}
// IsStatusCodeResponse is true when the split key is an operation response with a status code.
func (s SplitKey) IsStatusCodeResponse() bool {
isInt := func() bool {
_, err := strconv.Atoi(s[4])
return err == nil
}
return len(s) > 4 && s[0] == paths && s[3] == responses && isInt()
}
// ResponseName yields either the status code or "Default" for a response.
func (s SplitKey) ResponseName() string {
if s.IsStatusCodeResponse() {
code, _ := strconv.Atoi(s[4])
return http.StatusText(code)
}
if s.IsDefaultResponse() {
return "Default"
}
return ""
}
// PathItemRef constructs a $ref object from a split key of the form /{path}/{method}.
func (s SplitKey) PathItemRef() spec.Ref {
const minValidPathItems = 3
if len(s) < minValidPathItems {
return spec.Ref{}
}
pth, method := s[1], s[2]
if _, isValidMethod := validMethods[strings.ToUpper(method)]; !isValidMethod && !strings.HasPrefix(method, "x-") {
return spec.Ref{}
}
return spec.MustCreateRef("#" + path.Join("/", paths, jsonpointer.Escape(pth), strings.ToUpper(method)))
}
// PathRef constructs a $ref object from a split key of the form /paths/{reference}.
func (s SplitKey) PathRef() spec.Ref {
if !s.IsOperation() {
return spec.Ref{}
}
return spec.MustCreateRef("#" + path.Join("/", paths, jsonpointer.Escape(s[1])))
}
func (s SplitKey) isKeyName(i int) bool {
if i <= 0 {
return false
@@ -199,3 +101,101 @@ func (s SplitKey) isKeyName(i int) bool {
return count%2 != 0
}
// PartAdder know how to construct the components of a new name
type PartAdder func(string) []string
// BuildName builds a name from segments
func (s SplitKey) BuildName(segments []string, startIndex int, adder PartAdder) string {
for i, part := range s[startIndex:] {
if _, ignored := ignoredKeys[part]; !ignored || s.isKeyName(startIndex+i) {
segments = append(segments, adder(part)...)
}
}
return strings.Join(segments, " ")
}
// IsOperation is true when the split key is in the operations section
func (s SplitKey) IsOperation() bool {
return len(s) > 1 && s[0] == paths
}
// IsSharedOperationParam is true when the split key is in the parameters section of a path
func (s SplitKey) IsSharedOperationParam() bool {
return len(s) > 2 && s[0] == paths && s[2] == parameters
}
// IsSharedParam is true when the split key is in the #/parameters section of a spec
func (s SplitKey) IsSharedParam() bool {
return len(s) > 1 && s[0] == parameters
}
// IsOperationParam is true when the split key is in the parameters section of an operation
func (s SplitKey) IsOperationParam() bool {
return len(s) > 3 && s[0] == paths && s[3] == parameters
}
// IsOperationResponse is true when the split key is in the responses section of an operation
func (s SplitKey) IsOperationResponse() bool {
return len(s) > 3 && s[0] == paths && s[3] == responses
}
// IsSharedResponse is true when the split key is in the #/responses section of a spec
func (s SplitKey) IsSharedResponse() bool {
return len(s) > 1 && s[0] == responses
}
// IsDefaultResponse is true when the split key is the default response for an operation
func (s SplitKey) IsDefaultResponse() bool {
return len(s) > 4 && s[0] == paths && s[3] == responses && s[4] == "default"
}
// IsStatusCodeResponse is true when the split key is an operation response with a status code
func (s SplitKey) IsStatusCodeResponse() bool {
isInt := func() bool {
_, err := strconv.Atoi(s[4])
return err == nil
}
return len(s) > 4 && s[0] == paths && s[3] == responses && isInt()
}
// ResponseName yields either the status code or "Default" for a response
func (s SplitKey) ResponseName() string {
if s.IsStatusCodeResponse() {
code, _ := strconv.Atoi(s[4])
return http.StatusText(code)
}
if s.IsDefaultResponse() {
return "Default"
}
return ""
}
// PathItemRef constructs a $ref object from a split key of the form /{path}/{method}
func (s SplitKey) PathItemRef() spec.Ref {
if len(s) < 3 {
return spec.Ref{}
}
pth, method := s[1], s[2]
if _, isValidMethod := validMethods[strings.ToUpper(method)]; !isValidMethod && !strings.HasPrefix(method, "x-") {
return spec.Ref{}
}
return spec.MustCreateRef("#" + path.Join("/", paths, jsonpointer.Escape(pth), strings.ToUpper(method)))
}
// PathRef constructs a $ref object from a split key of the form /paths/{reference}
func (s SplitKey) PathRef() spec.Ref {
if !s.IsOperation() {
return spec.Ref{}
}
return spec.MustCreateRef("#" + path.Join("/", paths, jsonpointer.Escape(s[1])))
}

View File

@@ -1,12 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package sortref
import (
"iter"
"reflect"
"slices"
"sort"
"strings"
@@ -14,6 +9,10 @@ import (
"github.com/go-openapi/spec"
)
var depthGroupOrder = []string{
"sharedParam", "sharedResponse", "sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition",
}
type mapIterator struct {
len int
mapIter *reflect.MapIter
@@ -31,7 +30,7 @@ func (i *mapIterator) Key() string {
return i.mapIter.Key().String()
}
func mustMapIterator(anyMap any) *mapIterator {
func mustMapIterator(anyMap interface{}) *mapIterator {
val := reflect.ValueOf(anyMap)
return &mapIterator{mapIter: val.MapRange(), len: val.Len()}
@@ -40,8 +39,8 @@ func mustMapIterator(anyMap any) *mapIterator {
// DepthFirst sorts a map of anything. It groups keys by category
// (shared params, op param, statuscode response, default response, definitions)
// sort groups internally by number of parts in the key and lexical names
// flatten groups into a single list of keys.
func DepthFirst(in any) []string {
// flatten groups into a single list of keys
func DepthFirst(in interface{}) []string {
iterator := mustMapIterator(in)
sorted := make([]string, 0, iterator.Len())
grouped := make(map[string]Keys, iterator.Len())
@@ -75,7 +74,7 @@ func DepthFirst(in any) []string {
grouped[pk] = append(grouped[pk], Key{Segments: len(split), Key: k})
}
for pk := range depthGroupOrder() {
for _, pk := range depthGroupOrder {
res := grouped[pk]
sort.Sort(res)
@@ -87,12 +86,6 @@ func DepthFirst(in any) []string {
return sorted
}
func depthGroupOrder() iter.Seq[string] {
return slices.Values([]string{
"sharedParam", "sharedResponse", "sharedOpParam", "opParam", "codeResponse", "defaultResponse", "definition",
})
}
// topMostRefs is able to sort refs by hierarchical then lexicographic order,
// yielding refs ordered breadth-first.
type topmostRefs []string
@@ -108,7 +101,7 @@ func (k topmostRefs) Less(i, j int) bool {
return li < lj
}
// TopmostFirst sorts references by depth.
// TopmostFirst sorts references by depth
func TopmostFirst(refs []string) []string {
res := topmostRefs(refs)
sort.Sort(res)
@@ -116,13 +109,13 @@ func TopmostFirst(refs []string) []string {
return res
}
// RefRevIdx is a reverse index for references.
// RefRevIdx is a reverse index for references
type RefRevIdx struct {
Ref spec.Ref
Keys []string
}
// ReverseIndex builds a reverse index for references in schemas.
// ReverseIndex builds a reverse index for references in schemas
func ReverseIndex(schemas map[string]spec.Ref, basePath string) map[string]RefRevIdx {
collected := make(map[string]RefRevIdx)
for key, schRef := range schemas {

View File

@@ -1,12 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package analysis
import (
"fmt"
"reflect"
"slices"
"github.com/go-openapi/spec"
)
@@ -18,13 +28,12 @@ import (
// needed.
//
// The following parts of primary are subject to merge, filling empty details
//
// - Info
// - BasePath
// - Host
// - ExternalDocs
//
// Consider calling [FixEmptyResponseDescriptions]() on the modified primary
// Consider calling FixEmptyResponseDescriptions() on the modified primary
// if you read them from storage and they are valid to start with.
//
// Entries in "paths", "definitions", "parameters" and "responses" are
@@ -40,7 +49,7 @@ import (
// etc). Ensure they are canonical if your downstream tools do
// key normalization of any form.
//
// Merging schemes ([http], https), and consumers/producers do not account for
// Merging schemes (http, https), and consumers/producers do not account for
// collisions.
func Mixin(primary *spec.Swagger, mixins ...*spec.Swagger) []string {
skipped := make([]string, 0, len(mixins))
@@ -239,7 +248,14 @@ func mergeResponses(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
func mergeConsumes(primary *spec.Swagger, m *spec.Swagger) []string {
for _, v := range m.Consumes {
found := slices.Contains(primary.Consumes, v)
found := false
for _, vv := range primary.Consumes {
if v == vv {
found = true
break
}
}
if found {
// no warning here: we just skip it
@@ -253,7 +269,14 @@ func mergeConsumes(primary *spec.Swagger, m *spec.Swagger) []string {
func mergeProduces(primary *spec.Swagger, m *spec.Swagger) []string {
for _, v := range m.Produces {
found := slices.Contains(primary.Produces, v)
found := false
for _, vv := range primary.Produces {
if v == vv {
found = true
break
}
}
if found {
// no warning here: we just skip it
@@ -294,7 +317,14 @@ func mergeTags(primary *spec.Swagger, m *spec.Swagger) (skipped []string) {
func mergeSchemes(primary *spec.Swagger, m *spec.Swagger) []string {
for _, v := range m.Schemes {
found := slices.Contains(primary.Schemes, v)
found := false
for _, vv := range primary.Schemes {
if v == vv {
found = true
break
}
}
if found {
// no warning here: we just skip it
@@ -361,7 +391,7 @@ func mergeInfo(primary *spec.Info, m *spec.Info) []string {
}
if primary.Title == "" {
primary.Title = m.Title
primary.Description = m.Description
}
if primary.TermsOfService == "" {
@@ -444,23 +474,23 @@ func initPrimary(primary *spec.Swagger) {
}
if primary.Security == nil {
primary.Security = make([]map[string][]string, 0, allocSmallMap)
primary.Security = make([]map[string][]string, 0, 10)
}
if primary.Produces == nil {
primary.Produces = make([]string, 0, allocSmallMap)
primary.Produces = make([]string, 0, 10)
}
if primary.Consumes == nil {
primary.Consumes = make([]string, 0, allocSmallMap)
primary.Consumes = make([]string, 0, 10)
}
if primary.Tags == nil {
primary.Tags = make([]spec.Tag, 0, allocSmallMap)
primary.Tags = make([]spec.Tag, 0, 10)
}
if primary.Schemes == nil {
primary.Schemes = make([]string, 0, allocSmallMap)
primary.Schemes = make([]string, 0, 10)
}
if primary.Paths == nil {

View File

@@ -1,17 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package analysis
import (
"errors"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
)
// SchemaOpts configures the schema analyzer.
// SchemaOpts configures the schema analyzer
type SchemaOpts struct {
Schema *spec.Schema
Root any
Root interface{}
BasePath string
_ struct{}
}
@@ -20,7 +19,7 @@ type SchemaOpts struct {
// patterns.
func Schema(opts SchemaOpts) (*AnalyzedSchema, error) {
if opts.Schema == nil {
return nil, ErrNoSchema
return nil, errors.New("no schema to analyze")
}
a := &AnalyzedSchema{
@@ -52,10 +51,10 @@ func Schema(opts SchemaOpts) (*AnalyzedSchema, error) {
return a, nil
}
// AnalyzedSchema indicates what the schema represents.
// AnalyzedSchema indicates what the schema represents
type AnalyzedSchema struct {
schema *spec.Schema
root any
root interface{}
basePath string
hasProps bool
@@ -78,7 +77,7 @@ type AnalyzedSchema struct {
IsEnum bool
}
// Inherits copies value fields from other onto this schema.
// Inherits copies value fields from other onto this schema
func (a *AnalyzedSchema) inherits(other *AnalyzedSchema) {
if other == nil {
return

View File

@@ -1,26 +0,0 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Set default charset
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
charset = utf-8
# Tab indentation (no size specified)
[*.go]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

View File

@@ -1,7 +1,2 @@
*.out
*.cov
.idea
.env
.mcp.json
.claude/
settings.local.json
secrets.yml
coverage.out

View File

@@ -1,71 +1,55 @@
version: "2"
linters-settings:
gocyclo:
min-complexity: 45
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
linters:
default: all
enable-all: true
disable:
- depguard
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- exhaustruct
- nlreturn
- nonamedreturns
- noinlineerr
- paralleltest
- recvcheck
- testpackage
- thelper
- tparallel
- varnamelen
- gocognit
- whitespace
- wrapcheck
- wsl
- wsl_v5
settings:
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
cyclop:
max-complexity: 20
gocyclo:
min-complexity: 20
exhaustive:
default-signifies-exhaustive: true
default-case-required: true
lll:
line-length: 180
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- revive
text: "avoid package names that conflict with Go standard library package names"
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
- wrapcheck
- testpackage
- nlreturn
- errorlint
- nestif
- godot
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
- paralleltest
- tparallel
- thelper
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
#- deadcode
#- interfacer
#- scopelint
#- varcheck
#- structcheck
#- golint
#- nosnakecase
#- maligned
#- goerr113
#- ifshort
#- gomnd
#- exhaustivestruct

View File

@@ -23,9 +23,7 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
@@ -57,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <ivan+abuse@flanders.co.nz>. All
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -70,7 +68,7 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [<http://contributor-covenant.org/version/1/4>][version]
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,25 +0,0 @@
# Contributors
- Repository: ['go-openapi/errors']
| Total Contributors | Total Contributions |
| --- | --- |
| 13 | 110 |
| Username | All Time Contribution Count | All Commits |
| --- | --- | --- |
| @casualjim | 58 | <https://github.com/go-openapi/errors/commits?author=casualjim> |
| @fredbi | 36 | <https://github.com/go-openapi/errors/commits?author=fredbi> |
| @youyuanwu | 5 | <https://github.com/go-openapi/errors/commits?author=youyuanwu> |
| @alexandear | 2 | <https://github.com/go-openapi/errors/commits?author=alexandear> |
| @fiorix | 1 | <https://github.com/go-openapi/errors/commits?author=fiorix> |
| @ligustah | 1 | <https://github.com/go-openapi/errors/commits?author=ligustah> |
| @artemseleznev | 1 | <https://github.com/go-openapi/errors/commits?author=artemseleznev> |
| @gautierdelorme | 1 | <https://github.com/go-openapi/errors/commits?author=gautierdelorme> |
| @guillemj | 1 | <https://github.com/go-openapi/errors/commits?author=guillemj> |
| @maxatome | 1 | <https://github.com/go-openapi/errors/commits?author=maxatome> |
| @Simon-Li | 1 | <https://github.com/go-openapi/errors/commits?author=Simon-Li> |
| @aokumasan | 1 | <https://github.com/go-openapi/errors/commits?author=aokumasan> |
| @ujjwalsh | 1 | <https://github.com/go-openapi/errors/commits?author=ujjwalsh> |
_this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_

View File

@@ -1,118 +1,8 @@
# errors
# OpenAPI errors [![Build Status](https://github.com/go-openapi/errors/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/errors/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/errors/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/errors)
<!-- Badges: status -->
[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url]
<!-- Badges: release & docker images -->
<!-- Badges: code quality -->
<!-- Badges: license & compliance -->
[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url]
<!-- Badges: documentation & support -->
<!-- Badges: others & stats -->
[![GoDoc][godoc-badge]][godoc-url] [![Discord Channel][discord-badge]][discord-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge]
---
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/errors/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/errors.svg)](https://pkg.go.dev/github.com/go-openapi/errors)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/errors)](https://goreportcard.com/report/github.com/go-openapi/errors)
Shared errors and error interface used throughout the various libraries found in the go-openapi toolkit.
## Announcements
* **2025-12-19** : new community chat on discord
* a new discord community channel is available to be notified of changes and support users
* our venerable Slack channel remains open, and will be eventually discontinued on **2026-03-31**
You may join the discord community by clicking the invite link on the discord badge (also above). [![Discord Channel][discord-badge]][discord-url]
Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-url]
## Status
API is stable.
## Import this library in your project
```cmd
go get github.com/go-openapi/errors
```
## Basic usage
```go
const url = "https://www.example.com/#"
errGeneric := New(401,"onvalid argument: %s", url)
errNotFound := NotFound("resource not found: %s", url)
errNotImplemented := NotImplemented("method: %s", url)
```
## Change log
See <https://github.com/go-openapi/errors/releases>
<!--
## References
-->
## Licensing
This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE).
<!--
## Limitations
-->
## Other documentation
* [All-time contributors](./CONTRIBUTORS.md)
* [Contributing guidelines](.github/CONTRIBUTING.md)
* [Maintainers documentation](docs/MAINTAINERS.md)
* [Code style](docs/STYLE.md)
## Cutting a new release
Maintainers can cut a new release by either:
* running [this workflow](https://github.com/go-openapi/errors/actions/workflows/bump-release.yml)
* or pushing a semver tag
* signed tags are preferred
* The tag message is prepended to release notes
<!-- Badges: status -->
[test-badge]: https://github.com/go-openapi/errors/actions/workflows/go-test.yml/badge.svg
[test-url]: https://github.com/go-openapi/errors/actions/workflows/go-test.yml
[cov-badge]: https://codecov.io/gh/go-openapi/errors/branch/master/graph/badge.svg
[cov-url]: https://codecov.io/gh/go-openapi/errors
[vuln-scan-badge]: https://github.com/go-openapi/errors/actions/workflows/scanner.yml/badge.svg
[vuln-scan-url]: https://github.com/go-openapi/errors/actions/workflows/scanner.yml
[codeql-badge]: https://github.com/go-openapi/errors/actions/workflows/codeql.yml/badge.svg
[codeql-url]: https://github.com/go-openapi/errors/actions/workflows/codeql.yml
<!-- Badges: release & docker images -->
[release-badge]: https://badge.fury.io/gh/go-openapi%2Ferrors.svg
[release-url]: https://badge.fury.io/gh/go-openapi%2Ferrors
<!-- Badges: code quality -->
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/errors
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/errors
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/errors
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/errors
<!-- Badges: documentation & support -->
[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/errors
[godoc-url]: http://pkg.go.dev/github.com/go-openapi/errors
[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
[discord-url]: https://discord.gg/twZ9BwT3
<!-- Badges: license & compliance -->
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
[license-url]: https://github.com/go-openapi/errors/?tab=Apache-2.0-1-ov-file#readme
<!-- Badges: others & stats -->
[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/errors
[goversion-url]: https://github.com/go-openapi/errors/blob/master/go.mod
[top-badge]: https://img.shields.io/github/languages/top/go-openapi/errors
[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/errors/latest

View File

@@ -1,37 +0,0 @@
# Security Policy
This policy outlines the commitment and practices of the go-openapi maintainers regarding security.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.x | :white_check_mark: |
## Vulnerability checks in place
This repository uses automated vulnerability scans, at every merged commit and at least once a week.
We use:
* [`GitHub CodeQL`][codeql-url]
* [`trivy`][trivy-url]
* [`govulncheck`][govulncheck-url]
Reports are centralized in github security reports and visible only to the maintainers.
## Reporting a vulnerability
If you become aware of a security vulnerability that affects the current repository,
**please report it privately to the maintainers**
rather than opening a publicly visible GitHub issue.
Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url].
> [!NOTE]
> On Github, navigate to the project's "Security" tab then click on "Report a vulnerability".
[codeql-url]: https://github.com/github/codeql
[trivy-url]: https://trivy.dev/docs/latest/getting-started
[govulncheck-url]: https://go.dev/blog/govulncheck
[github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability

View File

@@ -1,11 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
@@ -13,11 +23,9 @@ import (
)
// DefaultHTTPCode is used when the error Code cannot be used as an HTTP code.
//
//nolint:gochecknoglobals // it should have been a constant in the first place, but now it is mutable so we have to leave it here or introduce a breaking change.
var DefaultHTTPCode = http.StatusUnprocessableEntity
// Error represents a error interface all swagger framework errors implement.
// Error represents a error interface all swagger framework errors implement
type Error interface {
error
Code() int32
@@ -28,26 +36,24 @@ type apiError struct {
message string
}
// Error implements the standard error interface.
func (a *apiError) Error() string {
return a.message
}
// Code returns the HTTP status code associated with this error.
func (a *apiError) Code() int32 {
return a.code
}
// MarshalJSON implements the JSON encoding interface.
// MarshalJSON implements the JSON encoding interface
func (a apiError) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
return json.Marshal(map[string]interface{}{
"code": a.code,
"message": a.message,
})
}
// New creates a new API error with a code and a message.
func New(code int32, message string, args ...any) Error {
// New creates a new API error with a code and a message
func New(code int32, message string, args ...interface{}) Error {
if len(args) > 0 {
return &apiError{
code: code,
@@ -60,39 +66,38 @@ func New(code int32, message string, args ...any) Error {
}
}
// NotFound creates a new not found error.
func NotFound(message string, args ...any) Error {
// NotFound creates a new not found error
func NotFound(message string, args ...interface{}) Error {
if message == "" {
message = "Not found"
}
return New(http.StatusNotFound, message, args...)
return New(http.StatusNotFound, fmt.Sprintf(message, args...))
}
// NotImplemented creates a new not implemented error.
// NotImplemented creates a new not implemented error
func NotImplemented(message string) Error {
return New(http.StatusNotImplemented, "%s", message)
return New(http.StatusNotImplemented, message)
}
// MethodNotAllowedError represents an error for when the path matches but the method doesn't.
// MethodNotAllowedError represents an error for when the path matches but the method doesn't
type MethodNotAllowedError struct {
code int32
Allowed []string
message string
}
// Error implements the standard error interface.
func (m *MethodNotAllowedError) Error() string {
return m.message
}
// Code returns 405 (Method Not Allowed) as the HTTP status code.
// Code the error code
func (m *MethodNotAllowedError) Code() int32 {
return m.code
}
// MarshalJSON implements the JSON encoding interface.
// MarshalJSON implements the JSON encoding interface
func (m MethodNotAllowedError) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
return json.Marshal(map[string]interface{}{
"code": m.code,
"message": m.message,
"allowed": m.Allowed,
@@ -110,33 +115,25 @@ func errorAsJSON(err Error) []byte {
func flattenComposite(errs *CompositeError) *CompositeError {
var res []error
for _, err := range errs.Errors {
if err == nil {
continue
for _, er := range errs.Errors {
switch e := er.(type) {
case *CompositeError:
if e != nil && len(e.Errors) > 0 {
flat := flattenComposite(e)
if len(flat.Errors) > 0 {
res = append(res, flat.Errors...)
}
}
default:
if e != nil {
res = append(res, e)
}
}
e := &CompositeError{}
if !errors.As(err, &e) {
res = append(res, err)
continue
}
if len(e.Errors) == 0 {
res = append(res, e)
continue
}
flat := flattenComposite(e)
res = append(res, flat.Errors...)
}
return CompositeValidationError(res...)
}
// MethodNotAllowed creates a new method not allowed error.
// MethodNotAllowed creates a new method not allowed error
func MethodNotAllowed(requested string, allow []string) Error {
msg := fmt.Sprintf("method %s is not allowed, but [%s] are", requested, strings.Join(allow, ","))
return &MethodNotAllowedError{
@@ -146,59 +143,43 @@ func MethodNotAllowed(requested string, allow []string) Error {
}
}
// ServeError implements the [http] error handler interface.
// ServeError implements the http error handler interface
func ServeError(rw http.ResponseWriter, r *http.Request, err error) {
rw.Header().Set("Content-Type", "application/json")
if err == nil {
rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
return
}
errComposite := &CompositeError{}
errMethodNotAllowed := &MethodNotAllowedError{}
var errError Error
switch {
case errors.As(err, &errComposite):
er := flattenComposite(errComposite)
switch e := err.(type) {
case *CompositeError:
er := flattenComposite(e)
// strips composite errors to first element only
if len(er.Errors) > 0 {
ServeError(rw, r, er.Errors[0])
return
} else {
// guard against empty CompositeError (invalid construct)
ServeError(rw, r, nil)
}
// guard against empty CompositeError (invalid construct)
ServeError(rw, r, nil)
case errors.As(err, &errMethodNotAllowed):
rw.Header().Add("Allow", strings.Join(errMethodNotAllowed.Allowed, ","))
rw.WriteHeader(asHTTPCode(int(errMethodNotAllowed.Code())))
case *MethodNotAllowedError:
rw.Header().Add("Allow", strings.Join(e.Allowed, ","))
rw.WriteHeader(asHTTPCode(int(e.Code())))
if r == nil || r.Method != http.MethodHead {
_, _ = rw.Write(errorAsJSON(errMethodNotAllowed))
_, _ = rw.Write(errorAsJSON(e))
}
case errors.As(err, &errError):
value := reflect.ValueOf(errError)
case Error:
value := reflect.ValueOf(e)
if value.Kind() == reflect.Ptr && value.IsNil() {
rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
return
}
rw.WriteHeader(asHTTPCode(int(errError.Code())))
rw.WriteHeader(asHTTPCode(int(e.Code())))
if r == nil || r.Method != http.MethodHead {
_, _ = rw.Write(errorAsJSON(errError))
_, _ = rw.Write(errorAsJSON(e))
}
case nil:
rw.WriteHeader(http.StatusInternalServerError)
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "Unknown error")))
default:
rw.WriteHeader(http.StatusInternalServerError)
if r == nil || r.Method != http.MethodHead {
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, "%v", err)))
_, _ = rw.Write(errorAsJSON(New(http.StatusInternalServerError, err.Error())))
}
}
}

View File

@@ -1,11 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import "net/http"
// Unauthenticated returns an unauthenticated error.
// Unauthenticated returns an unauthenticated error
func Unauthenticated(scheme string) Error {
return New(http.StatusUnauthorized, "unauthenticated for %s", scheme)
}

View File

@@ -1,13 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package errors provides an Error interface and several concrete types
// implementing this interface to manage API errors and JSON-schema validation
// errors.
//
// A middleware handler [ServeError]() is provided to serve the errors types
// it defines.
//
// It is used throughout the various go-openapi toolkit libraries.
// (https://github.com/go-openapi).
/*
Package errors provides an Error interface and several concrete types
implementing this interface to manage API errors and JSON-schema validation
errors.
A middleware handler ServeError() is provided to serve the errors types
it defines.
It is used throughout the various go-openapi toolkit libraries
(https://github.com/go-openapi).
*/
package errors

View File

@@ -1,5 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
@@ -9,30 +20,28 @@ import (
"net/http"
)
// Validation represents a failure of a precondition.
type Validation struct { //nolint: errname // changing the name to abide by the naming rule would bring a breaking change.
// Validation represents a failure of a precondition
type Validation struct { //nolint: errname
code int32
Name string
In string
Value any
Value interface{}
message string
Values []any
Values []interface{}
}
// Error implements the standard error interface.
func (e *Validation) Error() string {
return e.message
}
// Code returns the HTTP status code for this validation error.
// Returns 422 (Unprocessable Entity) by default.
// Code the error code
func (e *Validation) Code() int32 {
return e.code
}
// MarshalJSON implements the JSON encoding interface.
// MarshalJSON implements the JSON encoding interface
func (e Validation) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
return json.Marshal(map[string]interface{}{
"code": e.code,
"message": e.message,
"in": e.In,
@@ -42,7 +51,7 @@ func (e Validation) MarshalJSON() ([]byte, error) {
})
}
// ValidateName sets the name for a validation or updates it for a nested property.
// ValidateName sets the name for a validation or updates it for a nested property
func (e *Validation) ValidateName(name string) *Validation {
if name != "" {
if e.Name == "" {
@@ -61,9 +70,9 @@ const (
responseFormatFail = `unsupported media type requested, only %v are available`
)
// InvalidContentType error for an invalid content type.
// InvalidContentType error for an invalid content type
func InvalidContentType(value string, allowed []string) *Validation {
values := make([]any, 0, len(allowed))
values := make([]interface{}, 0, len(allowed))
for _, v := range allowed {
values = append(values, v)
}
@@ -77,9 +86,9 @@ func InvalidContentType(value string, allowed []string) *Validation {
}
}
// InvalidResponseFormat error for an unacceptable response format request.
// InvalidResponseFormat error for an unacceptable response format request
func InvalidResponseFormat(value string, allowed []string) *Validation {
values := make([]any, 0, len(allowed))
values := make([]interface{}, 0, len(allowed))
for _, v := range allowed {
values = append(values, v)
}

View File

@@ -1,5 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
@@ -10,14 +21,13 @@ import (
)
// APIVerificationFailed is an error that contains all the missing info for a mismatched section
// between the api registrations and the api spec.
// between the api registrations and the api spec
type APIVerificationFailed struct { //nolint: errname
Section string `json:"section,omitempty"`
MissingSpecification []string `json:"missingSpecification,omitempty"`
MissingRegistration []string `json:"missingRegistration,omitempty"`
}
// Error implements the standard error interface.
func (v *APIVerificationFailed) Error() string {
buf := bytes.NewBuffer(nil)
@@ -25,7 +35,7 @@ func (v *APIVerificationFailed) Error() string {
hasSpecMissing := len(v.MissingSpecification) > 0
if hasRegMissing {
fmt.Fprintf(buf, "missing [%s] %s registrations", strings.Join(v.MissingRegistration, ", "), v.Section)
buf.WriteString(fmt.Sprintf("missing [%s] %s registrations", strings.Join(v.MissingRegistration, ", "), v.Section))
}
if hasRegMissing && hasSpecMissing {
@@ -33,7 +43,7 @@ func (v *APIVerificationFailed) Error() string {
}
if hasSpecMissing {
fmt.Fprintf(buf, "missing from spec file [%s] %s", strings.Join(v.MissingSpecification, ", "), v.Section)
buf.WriteString(fmt.Sprintf("missing from spec file [%s] %s", strings.Join(v.MissingSpecification, ", "), v.Section))
}
return buf.String()

View File

@@ -1,5 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
@@ -9,7 +20,7 @@ import (
"net/http"
)
// ParseError represents a parsing error.
// ParseError represents a parsing error
type ParseError struct {
code int32
Name string
@@ -19,7 +30,37 @@ type ParseError struct {
message string
}
// NewParseError creates a new parse error.
func (e *ParseError) Error() string {
return e.message
}
// Code returns the http status code for this error
func (e *ParseError) Code() int32 {
return e.code
}
// MarshalJSON implements the JSON encoding interface
func (e ParseError) MarshalJSON() ([]byte, error) {
var reason string
if e.Reason != nil {
reason = e.Reason.Error()
}
return json.Marshal(map[string]interface{}{
"code": e.code,
"message": e.message,
"in": e.In,
"name": e.Name,
"value": e.Value,
"reason": reason,
})
}
const (
parseErrorTemplContent = `parsing %s %s from %q failed, because %s`
parseErrorTemplContentNoIn = `parsing %s from %q failed, because %s`
)
// NewParseError creates a new parse error
func NewParseError(name, in, value string, reason error) *ParseError {
var msg string
if in == "" {
@@ -36,34 +77,3 @@ func NewParseError(name, in, value string, reason error) *ParseError {
message: msg,
}
}
// Error implements the standard error interface.
func (e *ParseError) Error() string {
return e.message
}
// Code returns 400 (Bad Request) as the HTTP status code for parsing errors.
func (e *ParseError) Code() int32 {
return e.code
}
// MarshalJSON implements the JSON encoding interface.
func (e ParseError) MarshalJSON() ([]byte, error) {
var reason string
if e.Reason != nil {
reason = e.Reason.Error()
}
return json.Marshal(map[string]any{
"code": e.code,
"message": e.message,
"in": e.In,
"name": e.Name,
"value": e.Value,
"reason": reason,
})
}
const (
parseErrorTemplContent = `parsing %s %s from %q failed, because %s`
parseErrorTemplContentNoIn = `parsing %s from %q failed, because %s`
)

View File

@@ -1,11 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
@@ -63,15 +73,14 @@ const (
const maximumValidHTTPCode = 600
// All code responses can be used to differentiate errors for different handling
// by the consuming program.
// by the consuming program
const (
// CompositeErrorCode remains 422 for backwards-compatibility
// and to separate it from validation errors with cause.
// and to separate it from validation errors with cause
CompositeErrorCode = http.StatusUnprocessableEntity
// InvalidTypeCode is used for any subclass of invalid types.
// InvalidTypeCode is used for any subclass of invalid types
InvalidTypeCode = maximumValidHTTPCode + iota
// RequiredFailCode indicates a required field is missing.
RequiredFailCode
TooLongFailCode
TooShortFailCode
@@ -92,26 +101,22 @@ const (
ReadOnlyFailCode
)
// CompositeError is an error that groups several errors together.
// CompositeError is an error that groups several errors together
type CompositeError struct {
Errors []error
code int32
message string
}
// Code returns the HTTP status code for this composite error.
// Code for this error
func (c *CompositeError) Code() int32 {
return c.code
}
// Error implements the standard error interface.
func (c *CompositeError) Error() string {
if len(c.Errors) > 0 {
msgs := []string{c.message + ":"}
for _, e := range c.Errors {
if e == nil {
continue
}
msgs = append(msgs, e.Error())
}
return strings.Join(msgs, "\n")
@@ -119,21 +124,20 @@ func (c *CompositeError) Error() string {
return c.message
}
// Unwrap implements the [errors.Unwrap] interface.
func (c *CompositeError) Unwrap() []error {
return c.Errors
}
// MarshalJSON implements the JSON encoding interface.
// MarshalJSON implements the JSON encoding interface
func (c CompositeError) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
return json.Marshal(map[string]interface{}{
"code": c.code,
"message": c.message,
"errors": c.Errors,
})
}
// CompositeValidationError an error to wrap a bunch of other errors.
// CompositeValidationError an error to wrap a bunch of other errors
func CompositeValidationError(errors ...error) *CompositeError {
return &CompositeError{
code: CompositeErrorCode,
@@ -142,33 +146,20 @@ func CompositeValidationError(errors ...error) *CompositeError {
}
}
// ValidateName recursively sets the name for all validations or updates them for nested properties.
// ValidateName recursively sets the name for all validations or updates them for nested properties
func (c *CompositeError) ValidateName(name string) *CompositeError {
for i, e := range c.Errors {
if e == nil {
continue
}
ce := &CompositeError{}
if errors.As(e, &ce) {
c.Errors[i] = ce.ValidateName(name)
continue
}
ve := &Validation{}
if errors.As(e, &ve) {
if ve, ok := e.(*Validation); ok {
c.Errors[i] = ve.ValidateName(name)
continue
} else if ce, ok := e.(*CompositeError); ok {
c.Errors[i] = ce.ValidateName(name)
}
}
return c
}
// FailedAllPatternProperties an error for when the property doesn't match a pattern.
// FailedAllPatternProperties an error for when the property doesn't match a pattern
func FailedAllPatternProperties(name, in, key string) *Validation {
msg := fmt.Sprintf(failedAllPatternProps, name, key, in)
if in == "" {
@@ -183,7 +174,7 @@ func FailedAllPatternProperties(name, in, key string) *Validation {
}
}
// PropertyNotAllowed an error for when the property doesn't match a pattern.
// PropertyNotAllowed an error for when the property doesn't match a pattern
func PropertyNotAllowed(name, in, key string) *Validation {
msg := fmt.Sprintf(unallowedProperty, name, key, in)
if in == "" {
@@ -198,7 +189,7 @@ func PropertyNotAllowed(name, in, key string) *Validation {
}
}
// TooFewProperties an error for an object with too few properties.
// TooFewProperties an error for an object with too few properties
func TooFewProperties(name, in string, n int64) *Validation {
msg := fmt.Sprintf(tooFewProperties, name, in, n)
if in == "" {
@@ -213,7 +204,7 @@ func TooFewProperties(name, in string, n int64) *Validation {
}
}
// TooManyProperties an error for an object with too many properties.
// TooManyProperties an error for an object with too many properties
func TooManyProperties(name, in string, n int64) *Validation {
msg := fmt.Sprintf(tooManyProperties, name, in, n)
if in == "" {
@@ -228,7 +219,7 @@ func TooManyProperties(name, in string, n int64) *Validation {
}
}
// AdditionalItemsNotAllowed an error for invalid additional items.
// AdditionalItemsNotAllowed an error for invalid additional items
func AdditionalItemsNotAllowed(name, in string) *Validation {
msg := fmt.Sprintf(noAdditionalItems, name, in)
if in == "" {
@@ -242,7 +233,7 @@ func AdditionalItemsNotAllowed(name, in string) *Validation {
}
}
// InvalidCollectionFormat another flavor of invalid type error.
// InvalidCollectionFormat another flavor of invalid type error
func InvalidCollectionFormat(name, in, format string) *Validation {
return &Validation{
code: InvalidTypeCode,
@@ -253,7 +244,7 @@ func InvalidCollectionFormat(name, in, format string) *Validation {
}
}
// InvalidTypeName an error for when the type is invalid.
// InvalidTypeName an error for when the type is invalid
func InvalidTypeName(typeName string) *Validation {
return &Validation{
code: InvalidTypeCode,
@@ -262,8 +253,8 @@ func InvalidTypeName(typeName string) *Validation {
}
}
// InvalidType creates an error for when the type is invalid.
func InvalidType(name, in, typeName string, value any) *Validation {
// InvalidType creates an error for when the type is invalid
func InvalidType(name, in, typeName string, value interface{}) *Validation {
var message string
if in != "" {
@@ -293,9 +284,10 @@ func InvalidType(name, in, typeName string, value any) *Validation {
Value: value,
message: message,
}
}
// DuplicateItems error for when an array contains duplicates.
// DuplicateItems error for when an array contains duplicates
func DuplicateItems(name, in string) *Validation {
msg := fmt.Sprintf(uniqueFail, name, in)
if in == "" {
@@ -309,8 +301,8 @@ func DuplicateItems(name, in string) *Validation {
}
}
// TooManyItems error for when an array contains too many items.
func TooManyItems(name, in string, maximum int64, value any) *Validation {
// TooManyItems error for when an array contains too many items
func TooManyItems(name, in string, maximum int64, value interface{}) *Validation {
msg := fmt.Sprintf(maximumItemsFail, name, in, maximum)
if in == "" {
msg = fmt.Sprintf(maximumItemsFailNoIn, name, maximum)
@@ -325,8 +317,8 @@ func TooManyItems(name, in string, maximum int64, value any) *Validation {
}
}
// TooFewItems error for when an array contains too few items.
func TooFewItems(name, in string, minimum int64, value any) *Validation {
// TooFewItems error for when an array contains too few items
func TooFewItems(name, in string, minimum int64, value interface{}) *Validation {
msg := fmt.Sprintf(minItemsFail, name, in, minimum)
if in == "" {
msg = fmt.Sprintf(minItemsFailNoIn, name, minimum)
@@ -340,8 +332,8 @@ func TooFewItems(name, in string, minimum int64, value any) *Validation {
}
}
// ExceedsMaximumInt error for when maximum validation fails.
func ExceedsMaximumInt(name, in string, maximum int64, exclusive bool, value any) *Validation {
// ExceedsMaximumInt error for when maximumimum validation fails
func ExceedsMaximumInt(name, in string, maximum int64, exclusive bool, value interface{}) *Validation {
var message string
if in == "" {
m := maximumIncFailNoIn
@@ -365,8 +357,8 @@ func ExceedsMaximumInt(name, in string, maximum int64, exclusive bool, value any
}
}
// ExceedsMaximumUint error for when maximum validation fails.
func ExceedsMaximumUint(name, in string, maximum uint64, exclusive bool, value any) *Validation {
// ExceedsMaximumUint error for when maximumimum validation fails
func ExceedsMaximumUint(name, in string, maximum uint64, exclusive bool, value interface{}) *Validation {
var message string
if in == "" {
m := maximumIncFailNoIn
@@ -390,8 +382,8 @@ func ExceedsMaximumUint(name, in string, maximum uint64, exclusive bool, value a
}
}
// ExceedsMaximum error for when maximum validation fails.
func ExceedsMaximum(name, in string, maximum float64, exclusive bool, value any) *Validation {
// ExceedsMaximum error for when maximumimum validation fails
func ExceedsMaximum(name, in string, maximum float64, exclusive bool, value interface{}) *Validation {
var message string
if in == "" {
m := maximumIncFailNoIn
@@ -415,8 +407,8 @@ func ExceedsMaximum(name, in string, maximum float64, exclusive bool, value any)
}
}
// ExceedsMinimumInt error for when minimum validation fails.
func ExceedsMinimumInt(name, in string, minimum int64, exclusive bool, value any) *Validation {
// ExceedsMinimumInt error for when minimum validation fails
func ExceedsMinimumInt(name, in string, minimum int64, exclusive bool, value interface{}) *Validation {
var message string
if in == "" {
m := minIncFailNoIn
@@ -440,8 +432,8 @@ func ExceedsMinimumInt(name, in string, minimum int64, exclusive bool, value any
}
}
// ExceedsMinimumUint error for when minimum validation fails.
func ExceedsMinimumUint(name, in string, minimum uint64, exclusive bool, value any) *Validation {
// ExceedsMinimumUint error for when minimum validation fails
func ExceedsMinimumUint(name, in string, minimum uint64, exclusive bool, value interface{}) *Validation {
var message string
if in == "" {
m := minIncFailNoIn
@@ -465,8 +457,8 @@ func ExceedsMinimumUint(name, in string, minimum uint64, exclusive bool, value a
}
}
// ExceedsMinimum error for when minimum validation fails.
func ExceedsMinimum(name, in string, minimum float64, exclusive bool, value any) *Validation {
// ExceedsMinimum error for when minimum validation fails
func ExceedsMinimum(name, in string, minimum float64, exclusive bool, value interface{}) *Validation {
var message string
if in == "" {
m := minIncFailNoIn
@@ -490,8 +482,8 @@ func ExceedsMinimum(name, in string, minimum float64, exclusive bool, value any)
}
}
// NotMultipleOf error for when multiple of validation fails.
func NotMultipleOf(name, in string, multiple, value any) *Validation {
// NotMultipleOf error for when multiple of validation fails
func NotMultipleOf(name, in string, multiple, value interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(multipleOfFailNoIn, name, multiple)
@@ -507,8 +499,8 @@ func NotMultipleOf(name, in string, multiple, value any) *Validation {
}
}
// EnumFail error for when an enum validation fails.
func EnumFail(name, in string, value any, values []any) *Validation {
// EnumFail error for when an enum validation fails
func EnumFail(name, in string, value interface{}, values []interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(enumFailNoIn, name, values)
@@ -526,8 +518,8 @@ func EnumFail(name, in string, value any, values []any) *Validation {
}
}
// Required error for when a value is missing.
func Required(name, in string, value any) *Validation {
// Required error for when a value is missing
func Required(name, in string, value interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(requiredFailNoIn, name)
@@ -543,8 +535,8 @@ func Required(name, in string, value any) *Validation {
}
}
// ReadOnly error for when a value is present in request.
func ReadOnly(name, in string, value any) *Validation {
// ReadOnly error for when a value is present in request
func ReadOnly(name, in string, value interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(readOnlyFailNoIn, name)
@@ -560,8 +552,8 @@ func ReadOnly(name, in string, value any) *Validation {
}
}
// TooLong error for when a string is too long.
func TooLong(name, in string, maximum int64, value any) *Validation {
// TooLong error for when a string is too long
func TooLong(name, in string, maximum int64, value interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(tooLongMessageNoIn, name, maximum)
@@ -577,8 +569,8 @@ func TooLong(name, in string, maximum int64, value any) *Validation {
}
}
// TooShort error for when a string is too short.
func TooShort(name, in string, minimum int64, value any) *Validation {
// TooShort error for when a string is too short
func TooShort(name, in string, minimum int64, value interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(tooShortMessageNoIn, name, minimum)
@@ -597,7 +589,7 @@ func TooShort(name, in string, minimum int64, value any) *Validation {
// FailedPattern error for when a string fails a regex pattern match
// the pattern that is returned is the ECMA syntax version of the pattern not the golang version.
func FailedPattern(name, in, pattern string, value any) *Validation {
func FailedPattern(name, in, pattern string, value interface{}) *Validation {
var msg string
if in == "" {
msg = fmt.Sprintf(patternFailNoIn, name, pattern)
@@ -615,8 +607,8 @@ func FailedPattern(name, in, pattern string, value any) *Validation {
}
// MultipleOfMustBePositive error for when a
// multipleOf factor is negative.
func MultipleOfMustBePositive(name, in string, factor any) *Validation {
// multipleOf factor is negative
func MultipleOfMustBePositive(name, in string, factor interface{}) *Validation {
return &Validation{
code: MultipleOfMustBePositiveCode,
Name: name,

View File

@@ -1,181 +0,0 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
header = """
"""
footer = """
-----
**[{{ remote.github.repo }}]({{ self::remote_url() }}) license terms**
[![License][license-badge]][license-url]
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
[license-url]: {{ self::remote_url() }}/?tab=Apache-2.0-1-ov-file#readme
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
"""
body = """
{%- if version %}
## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{%- else %}
## [unreleased]
{%- endif %}
{%- if message %}
{%- raw %}\n{% endraw %}
{{ message }}
{%- raw %}\n{% endraw %}
{%- endif %}
{%- if version %}
{%- if previous.version %}
**Full Changelog**: <{{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}>
{%- endif %}
{%- else %}
{%- raw %}\n{% endraw %}
{%- endif %}
{%- if statistics %}{% if statistics.commit_count %}
{%- raw %}\n{% endraw %}
{{ statistics.commit_count }} commits in this release.
{%- raw %}\n{% endraw %}
{%- endif %}{% endif %}
-----
{%- for group, commits in commits | group_by(attribute="group") %}
{%- raw %}\n{% endraw %}
### {{ group | upper_first }}
{%- raw %}\n{% endraw %}
{%- for commit in commits %}
{%- if commit.remote.pr_title %}
{%- set commit_message = commit.remote.pr_title %}
{%- else %}
{%- set commit_message = commit.message %}
{%- endif %}
* {{ commit_message | split(pat="\n") | first | trim }}
{%- if commit.remote.username %}
{%- raw %} {% endraw %}by [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }})
{%- endif %}
{%- if commit.remote.pr_number %}
{%- raw %} {% endraw %}in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }})
{%- endif %}
{%- raw %} {% endraw %}[...]({{ self::remote_url() }}/commit/{{ commit.id }})
{%- endfor %}
{%- endfor %}
{%- if github %}
{%- raw %}\n{% endraw -%}
{%- set all_contributors = github.contributors | length %}
{%- if github.contributors | filter(attribute="username", value="dependabot[bot]") | length < all_contributors %}
-----
### People who contributed to this release
{% endif %}
{%- for contributor in github.contributors | filter(attribute="username") | sort(attribute="username") %}
{%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %}
* [@{{ contributor.username }}](https://github.com/{{ contributor.username }})
{%- endif %}
{%- endfor %}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
-----
{%- raw %}\n{% endraw %}
### New Contributors
{%- endif %}
{%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
{%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %}
* @{{ contributor.username }} made their first contribution
{%- if contributor.pr_number %}
in [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- raw %}\n{% endraw %}
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
"""
# Remove leading and trailing whitespaces from the changelog's body.
trim = true
# Render body even when there are no releases to process.
render_always = true
# An array of regex based postprocessors to modify the changelog.
postprocessors = [
# Replace the placeholder <REPO> with a URL.
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
]
# output file path
# output = "test.md"
[git]
# Parse commits according to the conventional commits specification.
# See https://www.conventionalcommits.org
conventional_commits = false
# Exclude commits that do not match the conventional commits specification.
filter_unconventional = false
# Require all commits to be conventional.
# Takes precedence over filter_unconventional.
require_conventional = false
# Split commits on newlines, treating each line as an individual commit.
split_commits = false
# An array of regex based parsers to modify commit messages prior to further processing.
commit_preprocessors = [
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
# Check spelling of the commit message using https://github.com/crate-ci/typos.
# If the spelling is incorrect, it will be fixed automatically.
#{ pattern = '.*', replace_command = 'typos --write-changes -' }
]
# Prevent commits that are breaking from being excluded by commit parsers.
protect_breaking_commits = false
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^[Cc]hore\\([Rr]elease\\): prepare for", skip = true },
{ message = "(^[Mm]erge)|([Mm]erge conflict)", skip = true },
{ field = "author.name", pattern = "dependabot*", group = "<!-- 0A -->Updates" },
{ message = "([Ss]ecurity)|([Vv]uln)", group = "<!-- 08 -->Security" },
{ body = "(.*[Ss]ecurity)|([Vv]uln)", group = "<!-- 08 -->Security" },
{ message = "([Cc]hore\\(lint\\))|(style)|(lint)|(codeql)|(golangci)", group = "<!-- 05 -->Code quality" },
{ message = "(^[Dd]oc)|((?i)readme)|(badge)|(typo)|(documentation)", group = "<!-- 03 -->Documentation" },
{ message = "(^[Ff]eat)|(^[Ee]nhancement)", group = "<!-- 00 -->Implemented enhancements" },
{ message = "(^ci)|(\\(ci\\))|(fixup\\s+ci)|(fix\\s+ci)|(license)|(example)", group = "<!-- 07 -->Miscellaneous tasks" },
{ message = "^test", group = "<!-- 06 -->Testing" },
{ message = "(^fix)|(panic)", group = "<!-- 01 -->Fixed bugs" },
{ message = "(^refact)|(rework)", group = "<!-- 02 -->Refactor" },
{ message = "(^[Pp]erf)|(performance)", group = "<!-- 04 -->Performance" },
{ message = "(^[Cc]hore)", group = "<!-- 07 -->Miscellaneous tasks" },
{ message = "^[Rr]evert", group = "<!-- 09 -->Reverted changes" },
{ message = "(upgrade.*?go)|(go\\s+version)", group = "<!-- 0A -->Updates" },
{ message = ".*", group = "<!-- 0B -->Other" },
]
# Exclude commits that are not matched by any commit parser.
filter_commits = false
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
link_parsers = []
# Include only the tags that belong to the current branch.
use_branch_tags = false
# Order releases topologically instead of chronologically.
topo_order = false
# Order releases topologically instead of chronologically.
topo_order_commits = true
# Order of commits in each group/release within the changelog.
# Allowed values: newest, oldest
sort_commits = "newest"
# Process submodules commits
recurse_submodules = false
#[remote.github]
#owner = "go-openapi"

View File

@@ -1,6 +1 @@
*.out
*.cov
.idea
.env
.mcp.json
.claude/
secrets.yml

View File

@@ -1,67 +1,61 @@
version: "2"
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
linters:
default: all
enable-all: true
disable:
- depguard
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- exhaustruct
- nlreturn
- nonamedreturns
- noinlineerr
- paralleltest
- recvcheck
- testpackage
- thelper
- tparallel
- varnamelen
- gocognit
- whitespace
- wrapcheck
- wsl
- wsl_v5
settings:
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
cyclop:
max-complexity: 20
gocyclo:
min-complexity: 20
exhaustive:
default-signifies-exhaustive: true
default-case-required: true
lll:
line-length: 180
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@@ -23,9 +23,7 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
@@ -57,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <ivan+abuse@flanders.co.nz>. All
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -70,7 +68,7 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [<http://contributor-covenant.org/version/1/4>][version]
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,24 +0,0 @@
# Contributors
- Repository: ['go-openapi/jsonpointer']
| Total Contributors | Total Contributions |
| --- | --- |
| 12 | 101 |
| Username | All Time Contribution Count | All Commits |
| --- | --- | --- |
| @fredbi | 54 | <https://github.com/go-openapi/jsonpointer/commits?author=fredbi> |
| @casualjim | 33 | <https://github.com/go-openapi/jsonpointer/commits?author=casualjim> |
| @magodo | 3 | <https://github.com/go-openapi/jsonpointer/commits?author=magodo> |
| @youyuanwu | 3 | <https://github.com/go-openapi/jsonpointer/commits?author=youyuanwu> |
| @gaiaz-iusipov | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=gaiaz-iusipov> |
| @gbjk | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=gbjk> |
| @gordallott | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=gordallott> |
| @ianlancetaylor | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=ianlancetaylor> |
| @mfleader | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=mfleader> |
| @Neo2308 | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=Neo2308> |
| @olivierlemasle | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=olivierlemasle> |
| @testwill | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=testwill> |
_this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_

View File

@@ -1,3 +1,4 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

View File

@@ -1,39 +0,0 @@
Copyright 2015-2025 go-swagger maintainers
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
This software library, github.com/go-openapi/jsonpointer, includes software developed
by the go-swagger and go-openapi maintainers ("go-swagger maintainers").
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this software except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
This software is copied from, derived from, and inspired by other original software products.
It ships with copies of other software which license terms are recalled below.
The original software was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com).
github.com/sigh-399/jsonpointer
===========================
// SPDX-FileCopyrightText: Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
// SPDX-License-Identifier: Apache-2.0
Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,158 +1,19 @@
# jsonpointer
# gojsonpointer [![Build Status](https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/jsonpointer/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonpointer)
<!-- Badges: status -->
[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url]
<!-- Badges: release & docker images -->
<!-- Badges: code quality -->
<!-- Badges: license & compliance -->
[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url]
<!-- Badges: documentation & support -->
<!-- Badges: others & stats -->
[![GoDoc][godoc-badge]][godoc-url] [![Discord Channel][discord-badge]][discord-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge]
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonpointer/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/jsonpointer.svg)](https://pkg.go.dev/github.com/go-openapi/jsonpointer)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/jsonpointer)](https://goreportcard.com/report/github.com/go-openapi/jsonpointer)
---
An implementation of JSON Pointer for golang, which supports go `struct`.
## Announcements
* **2025-12-19** : new community chat on discord
* a new discord community channel is available to be notified of changes and support users
* our venerable Slack channel remains open, and will be eventually discontinued on **2026-03-31**
You may join the discord community by clicking the invite link on the discord badge (also above). [![Discord Channel][discord-badge]][discord-url]
Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-url]
An implementation of JSON Pointer - Go language
## Status
Completed YES
API is stable.
## Import this library in your project
```cmd
go get github.com/go-openapi/jsonpointer
```
## Basic usage
See also some [examples](./examples_test.go)
### Retrieving a value
```go
import (
"github.com/go-openapi/jsonpointer"
)
var doc any
...
pointer, err := jsonpointer.New("/foo/1")
if err != nil {
... // error: e.g. invalid JSON pointer specification
}
value, kind, err := pointer.Get(doc)
if err != nil {
... // error: e.g. key not found, index out of bounds, etc.
}
...
```
### Setting a value
```go
...
var doc any
...
pointer, err := jsonpointer.New("/foo/1")
if err != nil {
... // error: e.g. invalid JSON pointer specification
}
doc, err = p.Set(doc, "value")
if err != nil {
... // error: e.g. key not found, index out of bounds, etc.
}
```
## Change log
See <https://github.com/go-openapi/jsonpointer/releases>
Tested YES
## References
http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
<https://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07>
also known as [RFC6901](https://www.rfc-editor.org/rfc/rfc6901)
## Licensing
This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE).
See the license [NOTICE](./NOTICE), which recalls the licensing terms of all the pieces of software
on top of which it has been built.
## Limitations
The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array,
the reference token MUST contain either...' is not implemented.
That is because our implementation of the JSON pointer only supports explicit references to array elements:
the provision in the spec to resolve non-existent members as "the last element in the array",
using the special trailing character "-" is not implemented.
## Other documentation
* [All-time contributors](./CONTRIBUTORS.md)
* [Contributing guidelines](.github/CONTRIBUTING.md)
* [Maintainers documentation](docs/MAINTAINERS.md)
* [Code style](docs/STYLE.md)
## Cutting a new release
Maintainers can cut a new release by either:
* running [this workflow](https://github.com/go-openapi/jsonpointer/actions/workflows/bump-release.yml)
* or pushing a semver tag
* signed tags are preferred
* The tag message is prepended to release notes
<!-- Badges: status -->
[test-badge]: https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml/badge.svg
[test-url]: https://github.com/go-openapi/jsonpointer/actions/workflows/go-test.yml
[cov-badge]: https://codecov.io/gh/go-openapi/jsonpointer/branch/master/graph/badge.svg
[cov-url]: https://codecov.io/gh/go-openapi/jsonpointer
[vuln-scan-badge]: https://github.com/go-openapi/jsonpointer/actions/workflows/scanner.yml/badge.svg
[vuln-scan-url]: https://github.com/go-openapi/jsonpointer/actions/workflows/scanner.yml
[codeql-badge]: https://github.com/go-openapi/jsonpointer/actions/workflows/codeql.yml/badge.svg
[codeql-url]: https://github.com/go-openapi/jsonpointer/actions/workflows/codeql.yml
<!-- Badges: release & docker images -->
[release-badge]: https://badge.fury.io/gh/go-openapi%2Fjsonpointer.svg
[release-url]: https://badge.fury.io/gh/go-openapi%2Fjsonpointer
<!-- Badges: code quality -->
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/jsonpointer
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/jsonpointer
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/jsonpointer
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/jsonpointer
<!-- Badges: documentation & support -->
[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/jsonpointer
[godoc-url]: http://pkg.go.dev/github.com/go-openapi/jsonpointer
[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
[discord-url]: https://discord.gg/twZ9BwT3
<!-- Badges: license & compliance -->
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
[license-url]: https://github.com/go-openapi/jsonpointer/?tab=Apache-2.0-1-ov-file#readme
<!-- Badges: others & stats -->
[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/jsonpointer
[goversion-url]: https://github.com/go-openapi/jsonpointer/blob/master/go.mod
[top-badge]: https://img.shields.io/github/languages/top/go-openapi/jsonpointer
[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/jsonpointer/latest
### Note
The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array, the reference token MUST contain either...' is not implemented.

View File

@@ -1,37 +0,0 @@
# Security Policy
This policy outlines the commitment and practices of the go-openapi maintainers regarding security.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| O.x | :white_check_mark: |
## Vulnerability checks in place
This repository uses automated vulnerability scans, at every merged commit and at least once a week.
We use:
* [`GitHub CodeQL`][codeql-url]
* [`trivy`][trivy-url]
* [`govulncheck`][govulncheck-url]
Reports are centralized in github security reports and visible only to the maintainers.
## Reporting a vulnerability
If you become aware of a security vulnerability that affects the current repository,
**please report it privately to the maintainers**
rather than opening a publicly visible GitHub issue.
Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url].
> [!NOTE]
> On Github, navigate to the project's "Security" tab then click on "Report a vulnerability".
[codeql-url]: https://github.com/github/codeql
[trivy-url]: https://trivy.dev/docs/latest/getting-started
[govulncheck-url]: https://go.dev/blog/govulncheck
[github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability

View File

@@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package jsonpointer
import "fmt"
type pointerError string
func (e pointerError) Error() string {
return string(e)
}
const (
// ErrPointer is a sentinel error raised by all errors from this package.
ErrPointer pointerError = "JSON pointer error"
// ErrInvalidStart states that a JSON pointer must start with a separator ("/").
ErrInvalidStart pointerError = `JSON pointer must be empty or start with a "` + pointerSeparator
// ErrUnsupportedValueType indicates that a value of the wrong type is being set.
ErrUnsupportedValueType pointerError = "only structs, pointers, maps and slices are supported for setting values"
)
func errNoKey(key string) error {
return fmt.Errorf("object has no key %q: %w", key, ErrPointer)
}
func errOutOfBounds(length, idx int) error {
return fmt.Errorf("index out of bounds array[0,%d] index '%d': %w", length-1, idx, ErrPointer)
}
func errInvalidReference(token string) error {
return fmt.Errorf("invalid token reference %q: %w", token, ErrPointer)
}

View File

@@ -1,7 +1,28 @@
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// author sigu-399
// author-github https://github.com/sigu-399
// author-mail sigu.399@gmail.com
//
// repository-name jsonpointer
// repository-desc An implementation of JSON Pointer - Go language
//
// description Main and unique file.
//
// created 25-02-2013
// Package jsonpointer provides a golang implementation for json pointers.
package jsonpointer
import (
@@ -12,101 +33,353 @@ import (
"strconv"
"strings"
"github.com/go-openapi/swag/jsonname"
"github.com/go-openapi/swag"
)
const (
emptyPointer = ``
pointerSeparator = `/`
invalidStart = `JSON pointer must be empty or start with a "` + pointerSeparator
notFound = `Can't find the pointer in the document`
)
// JSONPointable is an interface for structs to implement,
// when they need to customize the json pointer process or want to avoid the use of reflection.
var jsonPointableType = reflect.TypeOf(new(JSONPointable)).Elem()
var jsonSetableType = reflect.TypeOf(new(JSONSetable)).Elem()
// JSONPointable is an interface for structs to implement when they need to customize the
// json pointer process
type JSONPointable interface {
// JSONLookup returns a value pointed at this (unescaped) key.
JSONLookup(key string) (any, error)
JSONLookup(string) (any, error)
}
// JSONSetable is an interface for structs to implement,
// when they need to customize the json pointer process or want to avoid the use of reflection.
// JSONSetable is an interface for structs to implement when they need to customize the
// json pointer process
type JSONSetable interface {
// JSONSet sets the value pointed at the (unescaped) key.
JSONSet(key string, value any) error
JSONSet(string, any) error
}
// Pointer is a representation of a json pointer.
//
// Use [Pointer.Get] to retrieve a value or [Pointer.Set] to set a value.
//
// It works with any go type interpreted as a JSON document, which means:
//
// - if a type implements [JSONPointable], its [JSONPointable.JSONLookup] method is used to resolve [Pointer.Get]
// - if a type implements [JSONSetable], its [JSONPointable.JSONSet] method is used to resolve [Pointer.Set]
// - a go map[K]V is interpreted as an object, with type K assignable to a string
// - a go slice []T is interpreted as an array
// - a go struct is interpreted as an object, with exported fields interpreted as keys
// - promoted fields from an embedded struct are traversed
// - scalars (e.g. int, float64 ...), channels, functions and go arrays cannot be traversed
//
// For struct s resolved by reflection, key mappings honor the conventional struct tag `json`.
//
// Fields that do not specify a `json` tag, or specify an empty one, or are tagged as `json:"-"` are ignored.
//
// # Limitations
//
// - Unlike go standard marshaling, untagged fields do not default to the go field name and are ignored.
// - anonymous fields are not traversed if untagged
// New creates a new json pointer for the given string
func New(jsonPointerString string) (Pointer, error) {
var p Pointer
err := p.parse(jsonPointerString)
return p, err
}
// Pointer the json pointer reprsentation
type Pointer struct {
referenceTokens []string
}
// New creates a new json pointer from its string representation.
func New(jsonPointerString string) (Pointer, error) {
var p Pointer
err := p.parse(jsonPointerString)
// "Constructor", parses the given string JSON pointer
func (p *Pointer) parse(jsonPointerString string) error {
return p, err
}
var err error
// Get uses the pointer to retrieve a value from a JSON document.
//
// It returns the value with its type as a [reflect.Kind] or an error.
func (p *Pointer) Get(document any) (any, reflect.Kind, error) {
return p.get(document, jsonname.DefaultJSONNameProvider)
}
// Set uses the pointer to set a value from a data type
// that represent a JSON document.
//
// It returns the updated document.
func (p *Pointer) Set(document any, value any) (any, error) {
return document, p.set(document, value, jsonname.DefaultJSONNameProvider)
}
// DecodedTokens returns the decoded (unescaped) tokens of this JSON pointer.
func (p *Pointer) DecodedTokens() []string {
result := make([]string, 0, len(p.referenceTokens))
for _, token := range p.referenceTokens {
result = append(result, Unescape(token))
if jsonPointerString != emptyPointer {
if !strings.HasPrefix(jsonPointerString, pointerSeparator) {
err = errors.New(invalidStart)
} else {
referenceTokens := strings.Split(jsonPointerString, pointerSeparator)
p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...)
}
}
return err
}
// Get uses the pointer to retrieve a value from a JSON document
func (p *Pointer) Get(document any) (any, reflect.Kind, error) {
return p.get(document, swag.DefaultJSONNameProvider)
}
// Set uses the pointer to set a value from a JSON document
func (p *Pointer) Set(document any, value any) (any, error) {
return document, p.set(document, value, swag.DefaultJSONNameProvider)
}
// GetForToken gets a value for a json pointer token 1 level deep
func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) {
return getSingleImpl(document, decodedToken, swag.DefaultJSONNameProvider)
}
// SetForToken gets a value for a json pointer token 1 level deep
func SetForToken(document any, decodedToken string, value any) (any, error) {
return document, setSingleImpl(document, value, decodedToken, swag.DefaultJSONNameProvider)
}
func isNil(input any) bool {
if input == nil {
return true
}
kind := reflect.TypeOf(input).Kind()
switch kind { //nolint:exhaustive
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
return reflect.ValueOf(input).IsNil()
default:
return false
}
}
func getSingleImpl(node any, decodedToken string, nameProvider *swag.NameProvider) (any, reflect.Kind, error) {
rValue := reflect.Indirect(reflect.ValueOf(node))
kind := rValue.Kind()
if isNil(node) {
return nil, kind, fmt.Errorf("nil value has not field %q", decodedToken)
}
switch typed := node.(type) {
case JSONPointable:
r, err := typed.JSONLookup(decodedToken)
if err != nil {
return nil, kind, err
}
return r, kind, nil
case *any: // case of a pointer to interface, that is not resolved by reflect.Indirect
return getSingleImpl(*typed, decodedToken, nameProvider)
}
switch kind { //nolint:exhaustive
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return nil, kind, fmt.Errorf("object has no field %q", decodedToken)
}
fld := rValue.FieldByName(nm)
return fld.Interface(), kind, nil
case reflect.Map:
kv := reflect.ValueOf(decodedToken)
mv := rValue.MapIndex(kv)
if mv.IsValid() {
return mv.Interface(), kind, nil
}
return nil, kind, fmt.Errorf("object has no key %q", decodedToken)
case reflect.Slice:
tokenIndex, err := strconv.Atoi(decodedToken)
if err != nil {
return nil, kind, err
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return nil, kind, fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength-1, tokenIndex)
}
elem := rValue.Index(tokenIndex)
return elem.Interface(), kind, nil
default:
return nil, kind, fmt.Errorf("invalid token reference %q", decodedToken)
}
}
func setSingleImpl(node, data any, decodedToken string, nameProvider *swag.NameProvider) error {
rValue := reflect.Indirect(reflect.ValueOf(node))
if ns, ok := node.(JSONSetable); ok { // pointer impl
return ns.JSONSet(decodedToken, data)
}
if rValue.Type().Implements(jsonSetableType) {
return node.(JSONSetable).JSONSet(decodedToken, data)
}
switch rValue.Kind() { //nolint:exhaustive
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return fmt.Errorf("object has no field %q", decodedToken)
}
fld := rValue.FieldByName(nm)
if fld.IsValid() {
fld.Set(reflect.ValueOf(data))
}
return nil
case reflect.Map:
kv := reflect.ValueOf(decodedToken)
rValue.SetMapIndex(kv, reflect.ValueOf(data))
return nil
case reflect.Slice:
tokenIndex, err := strconv.Atoi(decodedToken)
if err != nil {
return err
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
}
elem := rValue.Index(tokenIndex)
if !elem.CanSet() {
return fmt.Errorf("can't set slice index %s to %v", decodedToken, data)
}
elem.Set(reflect.ValueOf(data))
return nil
default:
return fmt.Errorf("invalid token reference %q", decodedToken)
}
}
func (p *Pointer) get(node any, nameProvider *swag.NameProvider) (any, reflect.Kind, error) {
if nameProvider == nil {
nameProvider = swag.DefaultJSONNameProvider
}
kind := reflect.Invalid
// Full document when empty
if len(p.referenceTokens) == 0 {
return node, kind, nil
}
for _, token := range p.referenceTokens {
decodedToken := Unescape(token)
r, knd, err := getSingleImpl(node, decodedToken, nameProvider)
if err != nil {
return nil, knd, err
}
node = r
}
rValue := reflect.ValueOf(node)
kind = rValue.Kind()
return node, kind, nil
}
func (p *Pointer) set(node, data any, nameProvider *swag.NameProvider) error {
knd := reflect.ValueOf(node).Kind()
if knd != reflect.Ptr && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
return errors.New("only structs, pointers, maps and slices are supported for setting values")
}
if nameProvider == nil {
nameProvider = swag.DefaultJSONNameProvider
}
// Full document when empty
if len(p.referenceTokens) == 0 {
return nil
}
lastI := len(p.referenceTokens) - 1
for i, token := range p.referenceTokens {
isLastToken := i == lastI
decodedToken := Unescape(token)
if isLastToken {
return setSingleImpl(node, data, decodedToken, nameProvider)
}
rValue := reflect.Indirect(reflect.ValueOf(node))
kind := rValue.Kind()
if rValue.Type().Implements(jsonPointableType) {
r, err := node.(JSONPointable).JSONLookup(decodedToken)
if err != nil {
return err
}
fld := reflect.ValueOf(r)
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr {
node = fld.Addr().Interface()
continue
}
node = r
continue
}
switch kind { //nolint:exhaustive
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return fmt.Errorf("object has no field %q", decodedToken)
}
fld := rValue.FieldByName(nm)
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Ptr {
node = fld.Addr().Interface()
continue
}
node = fld.Interface()
case reflect.Map:
kv := reflect.ValueOf(decodedToken)
mv := rValue.MapIndex(kv)
if !mv.IsValid() {
return fmt.Errorf("object has no key %q", decodedToken)
}
if mv.CanAddr() && mv.Kind() != reflect.Interface && mv.Kind() != reflect.Map && mv.Kind() != reflect.Slice && mv.Kind() != reflect.Ptr {
node = mv.Addr().Interface()
continue
}
node = mv.Interface()
case reflect.Slice:
tokenIndex, err := strconv.Atoi(decodedToken)
if err != nil {
return err
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return fmt.Errorf("index out of bounds array[0,%d] index '%d'", sLength, tokenIndex)
}
elem := rValue.Index(tokenIndex)
if elem.CanAddr() && elem.Kind() != reflect.Interface && elem.Kind() != reflect.Map && elem.Kind() != reflect.Slice && elem.Kind() != reflect.Ptr {
node = elem.Addr().Interface()
continue
}
node = elem.Interface()
default:
return fmt.Errorf("invalid token reference %q", decodedToken)
}
}
return nil
}
// DecodedTokens returns the decoded tokens
func (p *Pointer) DecodedTokens() []string {
result := make([]string, 0, len(p.referenceTokens))
for _, t := range p.referenceTokens {
result = append(result, Unescape(t))
}
return result
}
// IsEmpty returns true if this is an empty json pointer.
//
// This indicates that it points to the root document.
// IsEmpty returns true if this is an empty json pointer
// this indicates that it points to the root document
func (p *Pointer) IsEmpty() bool {
return len(p.referenceTokens) == 0
}
// String representation of a pointer.
// Pointer to string representation function
func (p *Pointer) String() string {
if len(p.referenceTokens) == 0 {
return emptyPointer
}
return pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator)
pointerString := pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator)
return pointerString
}
func (p *Pointer) Offset(document string) (int64, error) {
@@ -131,328 +404,15 @@ func (p *Pointer) Offset(document string) (int64, error) {
return 0, err
}
default:
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
return 0, fmt.Errorf("invalid token %#v", tk)
}
default:
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
return 0, fmt.Errorf("invalid token %#v", tk)
}
}
return offset, nil
}
// "Constructor", parses the given string JSON pointer.
func (p *Pointer) parse(jsonPointerString string) error {
if jsonPointerString == emptyPointer {
return nil
}
if !strings.HasPrefix(jsonPointerString, pointerSeparator) {
// non empty pointer must start with "/"
return errors.Join(ErrInvalidStart, ErrPointer)
}
referenceTokens := strings.Split(jsonPointerString, pointerSeparator)
p.referenceTokens = append(p.referenceTokens, referenceTokens[1:]...)
return nil
}
func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, reflect.Kind, error) {
if nameProvider == nil {
nameProvider = jsonname.DefaultJSONNameProvider
}
kind := reflect.Invalid
// full document when empty
if len(p.referenceTokens) == 0 {
return node, kind, nil
}
for _, token := range p.referenceTokens {
decodedToken := Unescape(token)
r, knd, err := getSingleImpl(node, decodedToken, nameProvider)
if err != nil {
return nil, knd, err
}
node = r
}
rValue := reflect.ValueOf(node)
kind = rValue.Kind()
return node, kind, nil
}
func (p *Pointer) set(node, data any, nameProvider *jsonname.NameProvider) error {
knd := reflect.ValueOf(node).Kind()
if knd != reflect.Pointer && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
return errors.Join(
fmt.Errorf("unexpected type: %T", node), //nolint:err113 // err wrapping is carried out by errors.Join, not fmt.Errorf.
ErrUnsupportedValueType,
ErrPointer,
)
}
l := len(p.referenceTokens)
// full document when empty
if l == 0 {
return nil
}
if nameProvider == nil {
nameProvider = jsonname.DefaultJSONNameProvider
}
var decodedToken string
lastIndex := l - 1
if lastIndex > 0 { // skip if we only have one token in pointer
for _, token := range p.referenceTokens[:lastIndex] {
decodedToken = Unescape(token)
next, err := p.resolveNodeForToken(node, decodedToken, nameProvider)
if err != nil {
return err
}
node = next
}
}
// last token
decodedToken = Unescape(p.referenceTokens[lastIndex])
return setSingleImpl(node, data, decodedToken, nameProvider)
}
func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvider *jsonname.NameProvider) (next any, err error) {
// check for nil during traversal
if isNil(node) {
return nil, fmt.Errorf("cannot traverse through nil value at %q: %w", decodedToken, ErrPointer)
}
pointable, ok := node.(JSONPointable)
if ok {
r, err := pointable.JSONLookup(decodedToken)
if err != nil {
return nil, err
}
fld := reflect.ValueOf(r)
if fld.CanAddr() && fld.Kind() != reflect.Interface && fld.Kind() != reflect.Map && fld.Kind() != reflect.Slice && fld.Kind() != reflect.Pointer {
return fld.Addr().Interface(), nil
}
return r, nil
}
rValue := reflect.Indirect(reflect.ValueOf(node))
kind := rValue.Kind()
switch kind {
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return nil, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
}
return typeFromValue(rValue.FieldByName(nm)), nil
case reflect.Map:
kv := reflect.ValueOf(decodedToken)
mv := rValue.MapIndex(kv)
if !mv.IsValid() {
return nil, errNoKey(decodedToken)
}
return typeFromValue(mv), nil
case reflect.Slice:
tokenIndex, err := strconv.Atoi(decodedToken)
if err != nil {
return nil, errors.Join(err, ErrPointer)
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return nil, errOutOfBounds(sLength, tokenIndex)
}
return typeFromValue(rValue.Index(tokenIndex)), nil
default:
return nil, errInvalidReference(decodedToken)
}
}
func isNil(input any) bool {
if input == nil {
return true
}
kind := reflect.TypeOf(input).Kind()
switch kind {
case reflect.Pointer, reflect.Map, reflect.Slice, reflect.Chan:
return reflect.ValueOf(input).IsNil()
default:
return false
}
}
func typeFromValue(v reflect.Value) any {
if v.CanAddr() && v.Kind() != reflect.Interface && v.Kind() != reflect.Map && v.Kind() != reflect.Slice && v.Kind() != reflect.Pointer {
return v.Addr().Interface()
}
return v.Interface()
}
// GetForToken gets a value for a json pointer token 1 level deep.
func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) {
return getSingleImpl(document, decodedToken, jsonname.DefaultJSONNameProvider)
}
// SetForToken sets a value for a json pointer token 1 level deep.
func SetForToken(document any, decodedToken string, value any) (any, error) {
return document, setSingleImpl(document, value, decodedToken, jsonname.DefaultJSONNameProvider)
}
func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NameProvider) (any, reflect.Kind, error) {
rValue := reflect.Indirect(reflect.ValueOf(node))
kind := rValue.Kind()
if isNil(node) {
return nil, kind, fmt.Errorf("nil value has no field %q: %w", decodedToken, ErrPointer)
}
switch typed := node.(type) {
case JSONPointable:
r, err := typed.JSONLookup(decodedToken)
if err != nil {
return nil, kind, err
}
return r, kind, nil
case *any: // case of a pointer to interface, that is not resolved by reflect.Indirect
return getSingleImpl(*typed, decodedToken, nameProvider)
}
switch kind {
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return nil, kind, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
}
fld := rValue.FieldByName(nm)
return fld.Interface(), kind, nil
case reflect.Map:
kv := reflect.ValueOf(decodedToken)
mv := rValue.MapIndex(kv)
if mv.IsValid() {
return mv.Interface(), kind, nil
}
return nil, kind, errNoKey(decodedToken)
case reflect.Slice:
tokenIndex, err := strconv.Atoi(decodedToken)
if err != nil {
return nil, kind, errors.Join(err, ErrPointer)
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return nil, kind, errOutOfBounds(sLength, tokenIndex)
}
elem := rValue.Index(tokenIndex)
return elem.Interface(), kind, nil
default:
return nil, kind, errInvalidReference(decodedToken)
}
}
func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.NameProvider) error {
// check for nil to prevent panic when calling rValue.Type()
if isNil(node) {
return fmt.Errorf("cannot set field %q on nil value: %w", decodedToken, ErrPointer)
}
if ns, ok := node.(JSONSetable); ok {
return ns.JSONSet(decodedToken, data)
}
rValue := reflect.Indirect(reflect.ValueOf(node))
switch rValue.Kind() {
case reflect.Struct:
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
if !ok {
return fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
}
fld := rValue.FieldByName(nm)
if !fld.CanSet() {
return fmt.Errorf("can't set struct field %s to %v: %w", nm, data, ErrPointer)
}
value := reflect.ValueOf(data)
valueType := value.Type()
assignedType := fld.Type()
if !valueType.AssignableTo(assignedType) {
return fmt.Errorf("can't set value with type %T to field %s with type %v: %w", data, nm, assignedType, ErrPointer)
}
fld.Set(value)
return nil
case reflect.Map:
kv := reflect.ValueOf(decodedToken)
rValue.SetMapIndex(kv, reflect.ValueOf(data))
return nil
case reflect.Slice:
tokenIndex, err := strconv.Atoi(decodedToken)
if err != nil {
return errors.Join(err, ErrPointer)
}
sLength := rValue.Len()
if tokenIndex < 0 || tokenIndex >= sLength {
return errOutOfBounds(sLength, tokenIndex)
}
elem := rValue.Index(tokenIndex)
if !elem.CanSet() {
return fmt.Errorf("can't set slice index %s to %v: %w", decodedToken, data, ErrPointer)
}
value := reflect.ValueOf(data)
valueType := value.Type()
assignedType := elem.Type()
if !valueType.AssignableTo(assignedType) {
return fmt.Errorf("can't set value with type %T to slice element %d with type %v: %w", data, tokenIndex, assignedType, ErrPointer)
}
elem.Set(value)
return nil
default:
return errInvalidReference(decodedToken)
}
}
func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
for dec.More() {
offset := dec.InputOffset()
@@ -477,17 +437,16 @@ func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
return offset, nil
}
default:
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
return 0, fmt.Errorf("invalid token %#v", tk)
}
}
return 0, fmt.Errorf("token reference %q not found: %w", decodedToken, ErrPointer)
return 0, fmt.Errorf("token reference %q not found", decodedToken)
}
func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
idx, err := strconv.Atoi(decodedToken)
if err != nil {
return 0, fmt.Errorf("token reference %q is not a number: %w: %w", decodedToken, err, ErrPointer)
return 0, fmt.Errorf("token reference %q is not a number: %v", decodedToken, err)
}
var i int
for i = 0; i < idx && dec.More(); i++ {
@@ -511,14 +470,12 @@ func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
}
if !dec.More() {
return 0, fmt.Errorf("token reference %q not found: %w", decodedToken, ErrPointer)
return 0, fmt.Errorf("token reference %q not found", decodedToken)
}
return dec.InputOffset(), nil
}
// drainSingle drains a single level of object or array.
//
// The decoder has to guarantee the beginning delim (i.e. '{' or '[') has been consumed.
func drainSingle(dec *json.Decoder) error {
for dec.More() {
@@ -540,15 +497,14 @@ func drainSingle(dec *json.Decoder) error {
}
}
// consumes the ending delim
// Consumes the ending delim
if _, err := dec.Token(); err != nil {
return err
}
return nil
}
// JSON pointer encoding:
// Specific JSON pointer encoding here
// ~0 => ~
// ~1 => /
// ... and vice versa
@@ -560,24 +516,16 @@ const (
decRefTok1 = `/`
)
var (
encRefTokReplacer = strings.NewReplacer(encRefTok1, decRefTok1, encRefTok0, decRefTok0) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global
decRefTokReplacer = strings.NewReplacer(decRefTok1, encRefTok1, decRefTok0, encRefTok0) //nolint:gochecknoglobals // it's okay to declare a replacer as a private global
)
// Unescape unescapes a json pointer reference token string to the original representation.
// Unescape unescapes a json pointer reference token string to the original representation
func Unescape(token string) string {
return encRefTokReplacer.Replace(token)
step1 := strings.ReplaceAll(token, encRefTok1, decRefTok1)
step2 := strings.ReplaceAll(step1, encRefTok0, decRefTok0)
return step2
}
// Escape escapes a pointer reference token string.
//
// The JSONPointer specification defines "/" as a separator and "~" as an escape prefix.
//
// Keys containing such characters are escaped with the following rules:
//
// - "~" is escaped as "~0"
// - "/" is escaped as "~1"
// Escape escapes a pointer reference token string
func Escape(token string) string {
return decRefTokReplacer.Replace(token)
step1 := strings.ReplaceAll(token, decRefTok0, encRefTok0)
step2 := strings.ReplaceAll(step1, decRefTok1, encRefTok1)
return step2
}

View File

@@ -1,26 +0,0 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Set default charset
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
charset = utf-8
# Tab indentation (no size specified)
[*.go]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

View File

@@ -1,6 +1 @@
*.out
*.cov
.idea
.env
.mcp.json
.claude/
secrets.yml

View File

@@ -1,67 +1,61 @@
version: "2"
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
linters:
default: all
enable-all: true
disable:
- depguard
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- exhaustruct
- nlreturn
- nonamedreturns
- noinlineerr
- paralleltest
- recvcheck
- testpackage
- thelper
- tparallel
- varnamelen
- gocognit
- whitespace
- wrapcheck
- wsl
- wsl_v5
settings:
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
cyclop:
max-complexity: 20
gocyclo:
min-complexity: 20
exhaustive:
default-signifies-exhaustive: true
default-case-required: true
lll:
line-length: 180
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@@ -23,9 +23,7 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
@@ -57,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <ivan+abuse@flanders.co.nz>. All
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -70,7 +68,7 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [<http://contributor-covenant.org/version/1/4>][version]
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,21 +0,0 @@
# Contributors
- Repository: ['go-openapi/jsonreference']
| Total Contributors | Total Contributions |
| --- | --- |
| 9 | 73 |
| Username | All Time Contribution Count | All Commits |
| --- | --- | --- |
| @fredbi | 36 | https://github.com/go-openapi/jsonreference/commits?author=fredbi |
| @casualjim | 25 | https://github.com/go-openapi/jsonreference/commits?author=casualjim |
| @youyuanwu | 5 | https://github.com/go-openapi/jsonreference/commits?author=youyuanwu |
| @olivierlemasle | 2 | https://github.com/go-openapi/jsonreference/commits?author=olivierlemasle |
| @apelisse | 1 | https://github.com/go-openapi/jsonreference/commits?author=apelisse |
| @gbjk | 1 | https://github.com/go-openapi/jsonreference/commits?author=gbjk |
| @honza | 1 | https://github.com/go-openapi/jsonreference/commits?author=honza |
| @Neo2308 | 1 | https://github.com/go-openapi/jsonreference/commits?author=Neo2308 |
| @erraggy | 1 | https://github.com/go-openapi/jsonreference/commits?author=erraggy |
_this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_

View File

@@ -1,39 +0,0 @@
Copyright 2015-2025 go-swagger maintainers
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
This software library, github.com/go-openapi/jsonreference, includes software developed
by the go-swagger and go-openapi maintainers ("go-swagger maintainers").
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this software except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
This software is copied from, derived from, and inspired by other original software products.
It ships with copies of other software which license terms are recalled below.
The original software was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com).
github.com/sigh-399/jsonreference
===========================
// SPDX-FileCopyrightText: Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
// SPDX-License-Identifier: Apache-2.0
Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,127 +1,19 @@
# jsonreference
# gojsonreference [![Build Status](https://github.com/go-openapi/jsonreference/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/jsonreference/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/jsonreference/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/jsonreference)
<!-- Badges: status -->
[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url]
<!-- Badges: release & docker images -->
<!-- Badges: code quality -->
<!-- Badges: license & compliance -->
[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url]
<!-- Badges: documentation & support -->
<!-- Badges: others & stats -->
[![GoDoc][godoc-badge]][godoc-url] [![Discord Channel][discord-badge]][discord-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge]
[![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/jsonreference/master/LICENSE)
[![Go Reference](https://pkg.go.dev/badge/github.com/go-openapi/jsonreference.svg)](https://pkg.go.dev/github.com/go-openapi/jsonreference)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/jsonreference)](https://goreportcard.com/report/github.com/go-openapi/jsonreference)
---
An implementation of JSON Reference for golang.
## Announcements
* **2025-12-19** : new community chat on discord
* a new discord community channel is available to be notified of changes and support users
* our venerable Slack channel remains open, and will be eventually discontinued on **2026-03-31**
You may join the discord community by clicking the invite link on the discord badge (also above). [![Discord Channel][discord-badge]][discord-url]
Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-url]
An implementation of JSON Reference - Go language
## Status
API is stable.
## Import this library in your project
```cmd
go get github.com/go-openapi/jsonreference
```
Feature complete. Stable API
## Dependencies
* <https://github.com/go-openapi/jsonpointer>
## Basic usage
```go
// Creating a new reference
ref, err := jsonreference.New("http://example.com/doc.json#/definitions/Pet")
// Fragment-only reference
fragRef := jsonreference.MustCreateRef("#/definitions/Pet")
// Resolving references
parent, _ := jsonreference.New("http://example.com/base.json")
child, _ := jsonreference.New("#/definitions/Pet")
resolved, _ := parent.Inherits(child)
// Result: "http://example.com/base.json#/definitions/Pet"
```
## Change log
See <https://github.com/go-openapi/jsonreference/releases>
* https://github.com/go-openapi/jsonpointer
## References
* <http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07>
* <http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03>
## Licensing
This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE).
See the license [NOTICE](./NOTICE), which recalls the licensing terms of all the pieces of software
on top of which it has been built.
## Other documentation
* [All-time contributors](./CONTRIBUTORS.md)
* [Contributing guidelines](.github/CONTRIBUTING.md)
* [Maintainers documentation](docs/MAINTAINERS.md)
* [Code style](docs/STYLE.md)
## Cutting a new release
Maintainers can cut a new release by either:
* running [this workflow](https://github.com/go-openapi/jsonreference/actions/workflows/bump-release.yml)
* or pushing a semver tag
* signed tags are preferred
* The tag message is prepended to release notes
<!-- Badges: status -->
[test-badge]: https://github.com/go-openapi/jsonreference/actions/workflows/go-test.yml/badge.svg
[test-url]: https://github.com/go-openapi/jsonreference/actions/workflows/go-test.yml
[cov-badge]: https://codecov.io/gh/go-openapi/jsonreference/branch/master/graph/badge.svg
[cov-url]: https://codecov.io/gh/go-openapi/jsonreference
[vuln-scan-badge]: https://github.com/go-openapi/jsonreference/actions/workflows/scanner.yml/badge.svg
[vuln-scan-url]: https://github.com/go-openapi/jsonreference/actions/workflows/scanner.yml
[codeql-badge]: https://github.com/go-openapi/jsonreference/actions/workflows/codeql.yml/badge.svg
[codeql-url]: https://github.com/go-openapi/jsonreference/actions/workflows/codeql.yml
<!-- Badges: release & docker images -->
[release-badge]: https://badge.fury.io/gh/go-openapi%2Fjsonreference.svg
[release-url]: https://badge.fury.io/gh/go-openapi%2Fjsonreference
[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fjsonreference.svg
[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fjsonreference
<!-- Badges: code quality -->
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/jsonreference
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/jsonreference
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/jsonreference
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/jsonreference
<!-- Badges: documentation & support -->
[doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgoswagger.io%2Fgo-openapi%2F
[doc-url]: https://goswagger.io/go-openapi
[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/jsonreference
[godoc-url]: http://pkg.go.dev/github.com/go-openapi/jsonreference
[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
[discord-url]: https://discord.gg/twZ9BwT3
<!-- Badges: license & compliance -->
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
[license-url]: https://github.com/go-openapi/jsonreference/?tab=Apache-2.0-1-ov-file#readme
<!-- Badges: others & stats -->
[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/jsonreference
[goversion-url]: https://github.com/go-openapi/jsonreference/blob/master/go.mod
[top-badge]: https://img.shields.io/github/languages/top/go-openapi/jsonreference
[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/jsonreference/latest
* http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07
* http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03

View File

@@ -1,37 +0,0 @@
# Security Policy
This policy outlines the commitment and practices of the go-openapi maintainers regarding security.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| O.x | :white_check_mark: |
## Vulnerability checks in place
This repository uses automated vulnerability scans, at every merged commit and at least once a week.
We use:
* [`GitHub CodeQL`][codeql-url]
* [`trivy`][trivy-url]
* [`govulncheck`][govulncheck-url]
Reports are centralized in github security reports and visible only to the maintainers.
## Reporting a vulnerability
If you become aware of a security vulnerability that affects the current repository,
**please report it privately to the maintainers**
rather than opening a publicly visible GitHub issue.
Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url].
> [!NOTE]
> On Github, navigate to the project's "Security" tab then click on "Report a vulnerability".
[codeql-url]: https://github.com/github/codeql
[trivy-url]: https://trivy.dev/docs/latest/getting-started
[govulncheck-url]: https://go.dev/blog/govulncheck
[github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability

View File

@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package internal
import (
@@ -14,11 +11,9 @@ const (
defaultHTTPSPort = ":443"
)
// Regular expressions used by the normalizations.
var (
rxPort = regexp.MustCompile(`(:\d+)/?$`)
rxDupSlashes = regexp.MustCompile(`/{2,}`)
)
// Regular expressions used by the normalizations
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
// NormalizeURL will normalize the specified URL
// This was added to replace a previous call to the no longer maintained purell library:

View File

@@ -1,5 +1,27 @@
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// author sigu-399
// author-github https://github.com/sigu-399
// author-mail sigu.399@gmail.com
//
// repository-name jsonreference
// repository-desc An implementation of JSON Reference - Go language
//
// description Main and unique file.
//
// created 26-02-2013
package jsonreference
@@ -16,10 +38,26 @@ const (
fragmentRune = `#`
)
// ErrChildURL is raised when there is no child.
var ErrChildURL = errors.New("child url is nil")
// New creates a new reference for the given string
func New(jsonReferenceString string) (Ref, error) {
// Ref represents a json reference object.
var r Ref
err := r.parse(jsonReferenceString)
return r, err
}
// MustCreateRef parses the ref string and panics when it's invalid.
// Use the New method for a version that returns an error
func MustCreateRef(ref string) Ref {
r, err := New(ref)
if err != nil {
panic(err)
}
return r
}
// Ref represents a json reference object
type Ref struct {
referenceURL *url.URL
referencePointer jsonpointer.Pointer
@@ -31,36 +69,19 @@ type Ref struct {
HasFullFilePath bool
}
// New creates a new reference for the given string.
func New(jsonReferenceString string) (Ref, error) {
var r Ref
err := r.parse(jsonReferenceString)
return r, err
}
// MustCreateRef parses the ref string and panics when it's invalid.
// Use the New method for a version that returns an error.
func MustCreateRef(ref string) Ref {
r, err := New(ref)
if err != nil {
panic(err)
}
return r
}
// GetURL gets the URL for this reference.
// GetURL gets the URL for this reference
func (r *Ref) GetURL() *url.URL {
return r.referenceURL
}
// GetPointer gets the json pointer for this reference.
// GetPointer gets the json pointer for this reference
func (r *Ref) GetPointer() *jsonpointer.Pointer {
return &r.referencePointer
}
// String returns the best version of the url for this reference.
// String returns the best version of the url for this reference
func (r *Ref) String() string {
if r.referenceURL != nil {
return r.referenceURL.String()
}
@@ -72,7 +93,7 @@ func (r *Ref) String() string {
return r.referencePointer.String()
}
// IsRoot returns true if this reference is a root document.
// IsRoot returns true if this reference is a root document
func (r *Ref) IsRoot() bool {
return r.referenceURL != nil &&
!r.IsCanonical() &&
@@ -80,32 +101,14 @@ func (r *Ref) IsRoot() bool {
r.referenceURL.Fragment == ""
}
// IsCanonical returns true when this pointer starts with http(s):// or file://.
// IsCanonical returns true when this pointer starts with http(s):// or file://
func (r *Ref) IsCanonical() bool {
return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullURL)
}
// Inherits creates a new reference from a parent and a child
// If the child cannot inherit from the parent, an error is returned.
func (r *Ref) Inherits(child Ref) (*Ref, error) {
childURL := child.GetURL()
parentURL := r.GetURL()
if childURL == nil {
return nil, ErrChildURL
}
if parentURL == nil {
return &child, nil
}
ref, err := New(parentURL.ResolveReference(childURL).String())
if err != nil {
return nil, err
}
return &ref, nil
}
// "Constructor", parses the given string JSON reference.
// "Constructor", parses the given string JSON reference
func (r *Ref) parse(jsonReferenceString string) error {
parsed, err := url.Parse(jsonReferenceString)
if err != nil {
return err
@@ -134,3 +137,22 @@ func (r *Ref) parse(jsonReferenceString string) error {
return nil
}
// Inherits creates a new reference from a parent and a child
// If the child cannot inherit from the parent, an error is returned
func (r *Ref) Inherits(child Ref) (*Ref, error) {
childURL := child.GetURL()
parentURL := r.GetURL()
if childURL == nil {
return nil, errors.New("child url is nil")
}
if parentURL == nil {
return &child, nil
}
ref, err := New(parentURL.ResolveReference(childURL).String())
if err != nil {
return nil, err
}
return &ref, nil
}

View File

@@ -1,5 +1,4 @@
*.out
*.cov
.idea
.env
.mcp.json
secrets.yml
coverage.out
profile.cov
profile.out

View File

@@ -1,69 +1,61 @@
version: "2"
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
linters:
default: all
enable-all: true
disable:
- depguard
- funlen
- gochecknoglobals # on this repo, it is hard to refactor without globals/inits and no breaking change
- maligned
- unparam
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- exhaustruct
- nlreturn
- nonamedreturns
- noinlineerr
- paralleltest
- recvcheck
- testpackage
- thelper
- tparallel
- varnamelen
- gocognit
- whitespace
- wrapcheck
- wsl
- wsl_v5
settings:
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
cyclop:
max-complexity: 20
gocyclo:
min-complexity: 20
exhaustive:
default-signifies-exhaustive: true
default-case-required: true
lll:
line-length: 180
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

View File

@@ -0,0 +1,25 @@
after_success:
- bash <(curl -s https://codecov.io/bash)
go:
- 1.16.x
- 1.x
install:
- go get gotest.tools/gotestsum
language: go
arch:
- amd64
- ppc64le
jobs:
include:
# include linting job, but only for latest go version and amd64 arch
- go: 1.x
arch: amd64
install:
go get github.com/golangci/golangci-lint/cmd/golangci-lint
script:
- golangci-lint run --new-from-rev master
notifications:
slack:
secure: OxkPwVp35qBTUilgWC8xykSj+sGMcj0h8IIOKD+Rflx2schZVlFfdYdyVBM+s9OqeOfvtuvnR9v1Ye2rPKAvcjWdC4LpRGUsgmItZaI6Um8Aj6+K9udCw5qrtZVfOVmRu8LieH//XznWWKdOultUuniW0MLqw5+II87Gd00RWbCGi0hk0PykHe7uK+PDA2BEbqyZ2WKKYCvfB3j+0nrFOHScXqnh0V05l2E83J4+Sgy1fsPy+1WdX58ZlNBG333ibaC1FS79XvKSmTgKRkx3+YBo97u6ZtUmJa5WZjf2OdLG3KIckGWAv6R5xgxeU31N0Ng8L332w/Edpp2O/M2bZwdnKJ8hJQikXIAQbICbr+lTDzsoNzMdEIYcHpJ5hjPbiUl3Bmd+Jnsjf5McgAZDiWIfpCKZ29tPCEkVwRsOCqkyPRMNMzHHmoja495P5jR+ODS7+J8RFg5xgcnOgpP9D4Wlhztlf5WyZMpkLxTUD+bZq2SRf50HfHFXTkfq22zPl3d1eq0yrLwh/Z/fWKkfb6SyysROL8y6s8u3dpFX1YHSg0BR6i913h4aoZw9B2BG27cafLLTwKYsp2dFo1PWl4O6u9giFJIeqwloZHLKKrwh0cBFhB7RH0I58asxkZpCH6uWjJierahmHe7iS+E6i+9oCHkOZ59hmCYNimIs3hM=
script:
- gotestsum -f short-verbose -- -race -timeout=20m -coverprofile=coverage.txt -covermode=atomic ./...

View File

@@ -23,9 +23,7 @@ include:
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
@@ -57,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <ivan+abuse@flanders.co.nz>. All
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
@@ -70,7 +68,7 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [<http://contributor-covenant.org/version/1/4>][version]
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,26 +0,0 @@
# Contributors
- Repository: ['go-openapi/loads']
| Total Contributors | Total Contributions |
| --- | --- |
| 14 | 123 |
| Username | All Time Contribution Count | All Commits |
| --- | --- | --- |
| @casualjim | 48 | <https://github.com/go-openapi/loads/commits?author=casualjim> |
| @fredbi | 45 | <https://github.com/go-openapi/loads/commits?author=fredbi> |
| @youyuanwu | 6 | <https://github.com/go-openapi/loads/commits?author=youyuanwu> |
| @vburenin | 4 | <https://github.com/go-openapi/loads/commits?author=vburenin> |
| @keramix | 4 | <https://github.com/go-openapi/loads/commits?author=keramix> |
| @orisano | 3 | <https://github.com/go-openapi/loads/commits?author=orisano> |
| @GlenDC | 3 | <https://github.com/go-openapi/loads/commits?author=GlenDC> |
| @pengsrc | 2 | <https://github.com/go-openapi/loads/commits?author=pengsrc> |
| @a2800276 | 2 | <https://github.com/go-openapi/loads/commits?author=a2800276> |
| @tklauser | 2 | <https://github.com/go-openapi/loads/commits?author=tklauser> |
| @hypnoglow | 1 | <https://github.com/go-openapi/loads/commits?author=hypnoglow> |
| @koron | 1 | <https://github.com/go-openapi/loads/commits?author=koron> |
| @kreativka | 1 | <https://github.com/go-openapi/loads/commits?author=kreativka> |
| @petrkotas | 1 | <https://github.com/go-openapi/loads/commits?author=petrkotas> |
_this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_

View File

@@ -1,118 +1,6 @@
# Loads OAI specs
# Loads OAI specs [![Build Status](https://github.com/go-openapi/loads/actions/workflows/go-test.yml/badge.svg)](https://github.com/go-openapi/loads/actions?query=workflow%3A"go+test") [![codecov](https://codecov.io/gh/go-openapi/loads/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/loads)
<!-- Badges: status -->
[![Tests][test-badge]][test-url] [![Coverage][cov-badge]][cov-url] [![CI vuln scan][vuln-scan-badge]][vuln-scan-url] [![CodeQL][codeql-badge]][codeql-url]
<!-- Badges: release & docker images -->
<!-- Badges: code quality -->
<!-- Badges: license & compliance -->
[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url]
<!-- Badges: documentation & support -->
<!-- Badges: others & stats -->
[![GoDoc][godoc-badge]][godoc-url] [![Discord Channel][discord-badge]][discord-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge]
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/loads/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-openapi/loads?status.svg)](http://godoc.org/github.com/go-openapi/loads)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/loads)](https://goreportcard.com/report/github.com/go-openapi/loads)
---
Loads OAI v2 API specification documents from local or remote locations.
Supports JSON and YAML documents.
## Announcements
* **2025-12-19** : new community chat on discord
* a new discord community channel is available to be notified of changes and support users
* our venerable Slack channel remains open, and will be eventually discontinued on **2026-03-31**
You may join the discord community by clicking the invite link on the discord badge (also above). [![Discord Channel][discord-badge]][discord-url]
Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-url]
## Status
API is stable.
## Import this library in your project
```cmd
go get github.com/go-openapi/loads
```
## Basic usage
```go
import (
"github.com/go-openapi/loads"
)
...
// loads a YAML spec from a http file
doc, err := loads.Spec(ts.URL)
...
// retrieves the object model for the API specification
spec := doc.Spec()
...
```
See also the provided [examples](https://pkg.go.dev/github.com/go-openapi/loads#pkg-examples).
## Change log
See <https://github.com/go-openapi/loads/releases>
## Licensing
This library ships under the [SPDX-License-Identifier: Apache-2.0](./LICENSE).
## Other documentation
* [All-time contributors](./CONTRIBUTORS.md)
* [Contributing guidelines](.github/CONTRIBUTING.md)
* [Maintainers documentation](docs/MAINTAINERS.md)
* [Code style](docs/STYLE.md)
## Cutting a new release
Maintainers can cut a new release by either:
* running [this workflow](https://github.com/go-openapi/loads/actions/workflows/bump-release.yml)
* or pushing a semver tag
* signed tags are preferred
* The tag message is prepended to release notes
<!-- Badges: status -->
[test-badge]: https://github.com/go-openapi/loads/actions/workflows/go-test.yml/badge.svg
[test-url]: https://github.com/go-openapi/loads/actions/workflows/go-test.yml
[cov-badge]: https://codecov.io/gh/go-openapi/loads/branch/master/graph/badge.svg
[cov-url]: https://codecov.io/gh/go-openapi/loads
[vuln-scan-badge]: https://github.com/go-openapi/loads/actions/workflows/scanner.yml/badge.svg
[vuln-scan-url]: https://github.com/go-openapi/loads/actions/workflows/scanner.yml
[codeql-badge]: https://github.com/go-openapi/loads/actions/workflows/codeql.yml/badge.svg
[codeql-url]: https://github.com/go-openapi/loads/actions/workflows/codeql.yml
<!-- Badges: release & docker images -->
[release-badge]: https://badge.fury.io/gh/go-openapi%2Floads.svg
[release-url]: https://badge.fury.io/gh/go-openapi%2Floads
<!-- Badges: code quality -->
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/loads
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/loads
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/loads
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/loads
<!-- Badges: documentation & support -->
[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/loads
[godoc-url]: http://pkg.go.dev/github.com/go-openapi/loads
[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
[discord-url]: https://discord.gg/twZ9BwT3
<!-- Badges: license & compliance -->
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
[license-url]: https://github.com/go-openapi/loads/?tab=Apache-2.0-1-ov-file#readme
<!-- Badges: others & stats -->
[goversion-badge]: https://img.shields.io/github/go-mod/go-version/go-openapi/loads
[goversion-url]: https://github.com/go-openapi/loads/blob/master/go.mod
[top-badge]: https://img.shields.io/github/languages/top/go-openapi/loads
[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/loads/latest
Loading of OAI specification documents from local or remote locations. Supports JSON and YAML documents.

View File

@@ -1,37 +0,0 @@
# Security Policy
This policy outlines the commitment and practices of the go-openapi maintainers regarding security.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.x | :white_check_mark: |
## Vulnerability checks in place
This repository uses automated vulnerability scans, at every merged commit and at least once a week.
We use:
* [`GitHub CodeQL`][codeql-url]
* [`trivy`][trivy-url]
* [`govulncheck`][govulncheck-url]
Reports are centralized in github security reports and visible only to the maintainers.
## Reporting a vulnerability
If you become aware of a security vulnerability that affects the current repository,
**please report it privately to the maintainers**
rather than opening a publicly visible GitHub issue.
Please follow the instructions provided by github to [Privately report a security vulnerability][github-guidance-url].
> [!NOTE]
> On Github, navigate to the project's "Security" tab then click on "Report a vulnerability".
[codeql-url]: https://github.com/github/codeql
[trivy-url]: https://trivy.dev/docs/latest/getting-started
[govulncheck-url]: https://go.dev/blog/govulncheck
[github-guidance-url]: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability

View File

@@ -1,9 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package loads provides document loading methods for swagger (OAI v2) API specifications.
// Package loads provides document loading methods for swagger (OAI) specifications.
//
// It is used by other go-openapi packages to load and run analysis on local or remote spec documents.
//
// Loaders support JSON and YAML documents.
package loads

View File

@@ -1,18 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package loads
type loaderError string
func (e loaderError) Error() string {
return string(e)
}
const (
// ErrLoads is an error returned by the loads package.
ErrLoads loaderError = "loaderrs error"
// ErrNoLoader indicates that no configured loader matched the input.
ErrNoLoader loaderError = "no loader matched"
)

View File

@@ -1,24 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package loads
import (
"encoding/json"
"errors"
"net/url"
"slices"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag/loading"
"github.com/go-openapi/swag"
)
// Default chain of loaders, defined at the package level.
//
// By default this matches json and yaml documents.
//
// May be altered with AddLoader().
var loaders *loader
var (
// Default chain of loaders, defined at the package level.
//
// By default this matches json and yaml documents.
//
// May be altered with AddLoader().
loaders *loader
)
func init() {
jsonLoader := &loader{
@@ -32,8 +30,8 @@ func init() {
loaders = jsonLoader.WithHead(&loader{
DocLoaderWithMatch: DocLoaderWithMatch{
Match: loading.YAMLMatcher,
Fn: loading.YAMLDoc,
Match: swag.YAMLMatcher,
Fn: swag.YAMLDoc,
},
})
@@ -41,10 +39,10 @@ func init() {
spec.PathLoader = loaders.Load
}
// DocLoader represents a doc loader type.
type DocLoader func(string, ...loading.Option) (json.RawMessage, error)
// DocLoader represents a doc loader type
type DocLoader func(string) (json.RawMessage, error)
// DocMatcher represents a predicate to check if a loader matches.
// DocMatcher represents a predicate to check if a loader matches
type DocMatcher func(string) bool
// DocLoaderWithMatch describes a loading function for a given extension match.
@@ -53,7 +51,7 @@ type DocLoaderWithMatch struct {
Match DocMatcher
}
// NewDocLoaderWithMatch builds a [DocLoaderWithMatch] to be used in load options.
// NewDocLoaderWithMatch builds a DocLoaderWithMatch to be used in load options
func NewDocLoaderWithMatch(fn DocLoader, matcher DocMatcher) DocLoaderWithMatch {
return DocLoaderWithMatch{
Fn: fn,
@@ -63,13 +61,10 @@ func NewDocLoaderWithMatch(fn DocLoader, matcher DocMatcher) DocLoaderWithMatch
type loader struct {
DocLoaderWithMatch
loadingOptions []loading.Option
Next *loader
}
// WithHead adds a loader at the head of the current stack.
// WithHead adds a loader at the head of the current stack
func (l *loader) WithHead(head *loader) *loader {
if head == nil {
return l
@@ -78,27 +73,27 @@ func (l *loader) WithHead(head *loader) *loader {
return head
}
// WithNext adds a loader at the trail of the current stack.
// WithNext adds a loader at the trail of the current stack
func (l *loader) WithNext(next *loader) *loader {
l.Next = next
return next
}
// Load the raw document from path.
// Load the raw document from path
func (l *loader) Load(path string) (json.RawMessage, error) {
_, erp := url.Parse(path)
if erp != nil {
return nil, errors.Join(erp, ErrLoads)
return nil, erp
}
var lastErr error = ErrNoLoader // default error if no match was found
lastErr := errors.New("no loader matched") // default error if no match was found
for ldr := l; ldr != nil; ldr = ldr.Next {
if ldr.Match != nil && !ldr.Match(path) {
continue
}
// try then move to next one if there is an error
b, err := ldr.Fn(path, l.loadingOptions...)
b, err := ldr.Fn(path)
if err == nil {
return b, nil
}
@@ -106,29 +101,14 @@ func (l *loader) Load(path string) (json.RawMessage, error) {
lastErr = err
}
return nil, errors.Join(lastErr, ErrLoads)
return nil, lastErr
}
func (l *loader) clone() *loader {
if l == nil {
return nil
}
return &loader{
DocLoaderWithMatch: l.DocLoaderWithMatch,
loadingOptions: slices.Clone(l.loadingOptions),
Next: l.Next.clone(),
}
}
// JSONDoc loads a json document from either a file or a remote URL.
//
// See [loading.Option] for available options (e.g. configuring authentication,
// headers or using embedded file system resources).
func JSONDoc(path string, opts ...loading.Option) (json.RawMessage, error) {
data, err := loading.LoadFromFileOrHTTP(path, opts...)
// JSONDoc loads a json document from either a file or a remote url
func JSONDoc(path string) (json.RawMessage, error) {
data, err := swag.LoadFromFileOrHTTP(path)
if err != nil {
return nil, errors.Join(err, ErrLoads)
return nil, err
}
return json.RawMessage(data), nil
}
@@ -137,10 +117,9 @@ func JSONDoc(path string, opts ...loading.Option) (json.RawMessage, error) {
//
// This sets the configuration at the package level.
//
// # Concurrency
//
// This function updates the default loader used by [github.com/go-openapi/spec].
// Since this sets package level globals, you shouldn't call this concurrently.
// NOTE:
// - this updates the default loader used by github.com/go-openapi/spec
// - since this sets package level globals, you shouln't call this concurrently
func AddLoader(predicate DocMatcher, load DocLoader) {
loaders = loaders.WithHead(&loader{
DocLoaderWithMatch: DocLoaderWithMatch{

View File

@@ -1,13 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package loads
import "github.com/go-openapi/swag/loading"
type options struct {
loader *loader
loadingOptions []loading.Option
loader *loader
}
func defaultOptions() *options {
@@ -22,16 +16,13 @@ func loaderFromOptions(options []LoaderOption) *loader {
apply(opts)
}
l := opts.loader.clone()
l.loadingOptions = opts.loadingOptions
return l
return opts.loader
}
// LoaderOption allows to fine-tune the spec loader behavior.
// LoaderOption allows to fine-tune the spec loader behavior
type LoaderOption func(*options)
// WithDocLoader sets a custom loader for loading specs.
// WithDocLoader sets a custom loader for loading specs
func WithDocLoader(l DocLoader) LoaderOption {
return func(opt *options) {
if l == nil {
@@ -48,7 +39,7 @@ func WithDocLoader(l DocLoader) LoaderOption {
// WithDocLoaderMatches sets a chain of custom loaders for loading specs
// for different extension matches.
//
// Loaders are executed in the order of provided [DocLoaderWithMatch] 'es.
// Loaders are executed in the order of provided DocLoaderWithMatch'es.
func WithDocLoaderMatches(l ...DocLoaderWithMatch) LoaderOption {
return func(opt *options) {
var final, prev *loader
@@ -68,10 +59,3 @@ func WithDocLoaderMatches(l ...DocLoaderWithMatch) LoaderOption {
opt.loader = final
}
}
// WithLoadingOptions adds some [loading.Option] to be added when calling a registered loader.
func WithLoadingOptions(loadingOptions ...loading.Option) LoaderOption {
return func(opt *options) {
opt.loadingOptions = loadingOptions
}
}

View File

@@ -1,5 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package loads
@@ -7,21 +18,19 @@ import (
"bytes"
"encoding/gob"
"encoding/json"
"errors"
"fmt"
"maps"
"github.com/go-openapi/analysis"
"github.com/go-openapi/spec"
"github.com/go-openapi/swag/yamlutils"
"github.com/go-openapi/swag"
)
func init() {
gob.Register(map[string]any{})
gob.Register([]any{})
gob.Register(map[string]interface{}{})
gob.Register([]interface{}{})
}
// Document represents a swagger spec document.
// Document represents a swagger spec document
type Document struct {
// specAnalyzer
Analyzer *analysis.Spec
@@ -33,21 +42,14 @@ type Document struct {
raw json.RawMessage
}
// JSONSpec loads a spec from a JSON document, using the [JSONDoc] loader.
//
// A set of [loading.Option] may be passed to this loader using [WithLoadingOptions].
func JSONSpec(path string, opts ...LoaderOption) (*Document, error) {
var o options
for _, apply := range opts {
apply(&o)
}
data, err := JSONDoc(path, o.loadingOptions...)
// JSONSpec loads a spec from a json document
func JSONSpec(path string, options ...LoaderOption) (*Document, error) {
data, err := JSONDoc(path)
if err != nil {
return nil, err
}
// convert to json
doc, err := Analyzed(data, "", opts...)
doc, err := Analyzed(data, "", options...)
if err != nil {
return nil, err
}
@@ -57,8 +59,8 @@ func JSONSpec(path string, opts ...LoaderOption) (*Document, error) {
return doc, nil
}
// Embedded returns a Document based on embedded specs (i.e. as a [json.RawMessage]). No analysis is required.
func Embedded(orig, flat json.RawMessage, opts ...LoaderOption) (*Document, error) {
// Embedded returns a Document based on embedded specs. No analysis is required
func Embedded(orig, flat json.RawMessage, options ...LoaderOption) (*Document, error) {
var origSpec, flatSpec spec.Swagger
if err := json.Unmarshal(orig, &origSpec); err != nil {
return nil, err
@@ -70,22 +72,20 @@ func Embedded(orig, flat json.RawMessage, opts ...LoaderOption) (*Document, erro
raw: orig,
origSpec: &origSpec,
spec: &flatSpec,
pathLoader: loaderFromOptions(opts),
pathLoader: loaderFromOptions(options),
}, nil
}
// Spec loads a new spec document from a local or remote path.
//
// By default it uses a JSON or YAML loader, with auto-detection based on the resource extension.
func Spec(path string, opts ...LoaderOption) (*Document, error) {
ldr := loaderFromOptions(opts)
// Spec loads a new spec document from a local or remote path
func Spec(path string, options ...LoaderOption) (*Document, error) {
ldr := loaderFromOptions(options)
b, err := ldr.Load(path)
if err != nil {
return nil, err
}
document, err := Analyzed(b, "", opts...)
document, err := Analyzed(b, "", options...)
if err != nil {
return nil, err
}
@@ -96,13 +96,13 @@ func Spec(path string, opts ...LoaderOption) (*Document, error) {
return document, nil
}
// Analyzed creates a new analyzed spec document for a root [json.RawMessage].
// Analyzed creates a new analyzed spec document for a root json.RawMessage.
func Analyzed(data json.RawMessage, version string, options ...LoaderOption) (*Document, error) {
if version == "" {
version = "2.0"
}
if version != "2.0" {
return nil, fmt.Errorf("spec version %q is not supported: %w", version, ErrLoads)
return nil, fmt.Errorf("spec version %q is not supported", version)
}
raw, err := trimData(data) // trim blanks, then convert yaml docs into json
@@ -112,12 +112,12 @@ func Analyzed(data json.RawMessage, version string, options ...LoaderOption) (*D
swspec := new(spec.Swagger)
if err = json.Unmarshal(raw, swspec); err != nil {
return nil, errors.Join(err, ErrLoads)
return nil, err
}
origsqspec, err := cloneSpec(swspec)
if err != nil {
return nil, errors.Join(err, ErrLoads)
return nil, err
}
d := &Document{
@@ -143,20 +143,20 @@ func trimData(in json.RawMessage) (json.RawMessage, error) {
}
// assume yaml doc: convert it to json
yml, err := yamlutils.BytesToYAMLDoc(trimmed)
yml, err := swag.BytesToYAMLDoc(trimmed)
if err != nil {
return nil, fmt.Errorf("analyzed: %w: %w", err, ErrLoads)
return nil, fmt.Errorf("analyzed: %v", err)
}
d, err := yamlutils.YAMLToJSON(yml)
d, err := swag.YAMLToJSON(yml)
if err != nil {
return nil, fmt.Errorf("analyzed: %w: %w", err, ErrLoads)
return nil, fmt.Errorf("analyzed: %v", err)
}
return d, nil
}
// Expanded expands the $ref fields in the spec [Document] and returns a new expanded [Document].
// Expanded expands the $ref fields in the spec document and returns a new spec document
func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
swspec := new(spec.Swagger)
if err := json.Unmarshal(d.raw, swspec); err != nil {
@@ -200,55 +200,55 @@ func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
return dd, nil
}
// BasePath the base path for the API specified by this spec.
// BasePath the base path for the API specified by this spec
func (d *Document) BasePath() string {
if d.spec == nil {
return ""
}
return d.spec.BasePath
}
// Version returns the OpenAPI version of this spec (e.g. 2.0).
// Version returns the version of this spec
func (d *Document) Version() string {
return d.spec.Swagger
}
// Schema returns the swagger 2.0 meta-schema.
// Schema returns the swagger 2.0 schema
func (d *Document) Schema() *spec.Schema {
return d.schema
}
// Spec returns the swagger object model for this API specification.
// Spec returns the swagger spec object model
func (d *Document) Spec() *spec.Swagger {
return d.spec
}
// Host returns the host for the API.
// Host returns the host for the API
func (d *Document) Host() string {
return d.spec.Host
}
// Raw returns the raw swagger spec as json bytes.
// Raw returns the raw swagger spec as json bytes
func (d *Document) Raw() json.RawMessage {
return d.raw
}
// OrigSpec yields the original spec.
// OrigSpec yields the original spec
func (d *Document) OrigSpec() *spec.Swagger {
return d.origSpec
}
// ResetDefinitions yields a shallow copy with the models reset to the original spec.
// ResetDefinitions gives a shallow copy with the models reset to the original spec
func (d *Document) ResetDefinitions() *Document {
d.spec.Definitions = make(map[string]spec.Schema, len(d.origSpec.Definitions))
maps.Copy(d.spec.Definitions, d.origSpec.Definitions)
defs := make(map[string]spec.Schema, len(d.origSpec.Definitions))
for k, v := range d.origSpec.Definitions {
defs[k] = v
}
d.spec.Definitions = defs
return d
}
// Pristine creates a new pristine document instance based on the input data.
// Pristine creates a new pristine document instance based on the input data
func (d *Document) Pristine() *Document {
raw, _ := json.Marshal(d.Spec()) //nolint:errchkjson // the spec always marshals to JSON
raw, _ := json.Marshal(d.Spec())
dd, _ := Analyzed(raw, d.Version())
dd.pathLoader = d.pathLoader
dd.specFilePath = d.specFilePath
@@ -256,7 +256,7 @@ func (d *Document) Pristine() *Document {
return dd
}
// SpecFilePath returns the file path of the spec if one is defined.
// SpecFilePath returns the file path of the spec if one is defined
func (d *Document) SpecFilePath() string {
return d.specFilePath
}
@@ -271,6 +271,5 @@ func cloneSpec(src *spec.Swagger) (*spec.Swagger, error) {
if err := gob.NewDecoder(&b).Decode(&dst); err != nil {
return nil, err
}
return &dst, nil
}

View File

@@ -1,5 +1,5 @@
*.out
secrets.yml
coverage.out
*.cov
.idea
.env
.mcp.json
*.out
playground

View File

@@ -1,77 +1,62 @@
version: "2"
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 45
maligned:
suggest-new: true
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
linters:
default: all
enable-all: true
disable:
- cyclop
- depguard
- err113 # disabled temporarily: there are just too many issues to address
- errchkjson
- errorlint
- exhaustruct
- forcetypeassert
- funlen
- gochecknoglobals
- gochecknoinits
- gocognit
- godot
- godox
- gomoddirectives # moved to mono-repo, multi-modules, so replace directives are needed
- gosmopolitan
- inamedparam
- ireturn
- lll
- musttag
- nestif
- nilerr # nilerr crashes on this repo
- nlreturn
- noinlineerr
- nonamedreturns
- paralleltest
- recvcheck
- testpackage
- thelper
- tparallel
- maligned
- unparam
- varnamelen
- lll
- gochecknoinits
- gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wrapcheck
- wsl
- wsl_v5
settings:
dupl:
threshold: 200
goconst:
min-len: 2
min-occurrences: 3
gocyclo:
min-complexity: 45
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
max-issues-per-linter: 0
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 0
- wrapcheck
- testpackage
- nlreturn
- gomnd
- exhaustivestruct
- goerr113
- errorlint
- nestif
- godot
- gofumpt
- paralleltest
- tparallel
- thelper
- ifshort
- exhaustruct
- varnamelen
- gci
- depguard
- errchkjson
- inamedparam
- nonamedreturns
- musttag
- ireturn
- forcetypeassert
- cyclop
# deprecated linters
- deadcode
- interfacer
- scopelint
- varcheck
- structcheck
- golint
- nosnakecase

Some files were not shown because too many files have changed in this diff Show More