mirror of
https://github.com/moby/buildkit.git
synced 2026-11-06 05:16:45 +00:00
vendor: update deps to match moby
- github.com/Microsoft/hcsshim v0.15.0-rc.4 -> v0.15.0 - google.golang.org/grpc v1.84.0 -> v1.85.0 - golang.org/x/net v0.61.0 -> v0.62.0 - go.opentelemetry.io/otel v1.46.0 -> v1.47.0 - go.opentelemetry.io/contrib v0.71.0 -> v0.72.0 - github.com/prometheus/client_golang v1.24.1 -> v1.25.0 - github.com/prometheus/procfs v0.21.1 -> v0.22.0 - github.com/go-openapi/* to moby versions Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
354 files changed
+12340
-5796
No files matched your search
@@ -7,7 +7,7 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0
|
||||
github.com/Microsoft/go-winio v0.6.4
|
||||
github.com/Microsoft/hcsshim v0.15.0-rc.4
|
||||
github.com/Microsoft/hcsshim v0.15.0
|
||||
github.com/ProtonMail/go-crypto v1.4.1
|
||||
github.com/agext/levenshtein v1.2.3
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
|
||||
@@ -70,8 +70,8 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/profile v1.7.0
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10
|
||||
github.com/prometheus/client_golang v1.24.1
|
||||
github.com/prometheus/procfs v0.21.1
|
||||
github.com/prometheus/client_golang v1.25.0
|
||||
github.com/prometheus/procfs v0.22.0
|
||||
github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
|
||||
github.com/sigstore/sigstore-go v1.2.2
|
||||
github.com/sirupsen/logrus v1.10.2
|
||||
@@ -88,31 +88,31 @@ require (
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/vishvananda/netns v0.0.5
|
||||
go.etcd.io/bbolt v1.5.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.71.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0
|
||||
go.opentelemetry.io/otel v1.46.0
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.72.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0
|
||||
go.opentelemetry.io/otel v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.68.0
|
||||
go.opentelemetry.io/otel/metric v1.46.0
|
||||
go.opentelemetry.io/otel/sdk v1.46.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0
|
||||
go.opentelemetry.io/otel/trace v1.46.0
|
||||
go.opentelemetry.io/proto/otlp v1.11.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.69.0
|
||||
go.opentelemetry.io/otel/metric v1.47.0
|
||||
go.opentelemetry.io/otel/sdk v1.47.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0
|
||||
go.opentelemetry.io/otel/trace v1.47.0
|
||||
go.opentelemetry.io/proto/otlp v1.11.1
|
||||
golang.org/x/crypto v0.58.0
|
||||
golang.org/x/exp v0.0.0-20260603202125-055de637280b
|
||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba
|
||||
golang.org/x/mod v0.42.0
|
||||
golang.org/x/net v0.61.0
|
||||
golang.org/x/net v0.62.0
|
||||
golang.org/x/sync v0.24.0
|
||||
golang.org/x/sys v0.49.0
|
||||
golang.org/x/time v0.16.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5
|
||||
google.golang.org/grpc v1.84.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941
|
||||
google.golang.org/grpc v1.85.0
|
||||
google.golang.org/protobuf v1.36.12
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.78
|
||||
tags.cncf.io/container-device-interface v1.1.1
|
||||
@@ -124,7 +124,7 @@ require (
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.1.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.8 // indirect
|
||||
@@ -159,36 +159,36 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.25.5 // indirect
|
||||
github.com/go-openapi/errors v0.22.8 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/loads v0.25.0 // indirect
|
||||
github.com/go-openapi/runtime v0.33.0 // indirect
|
||||
github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect
|
||||
github.com/go-openapi/spec v0.22.9 // indirect
|
||||
github.com/go-openapi/strfmt v0.27.0 // indirect
|
||||
github.com/go-openapi/swag v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/conv v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/pools v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
|
||||
github.com/go-openapi/validate v0.26.1 // indirect
|
||||
github.com/go-openapi/analysis v1.0.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.9 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.1 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.1 // indirect
|
||||
github.com/go-openapi/loads v0.25.2 // indirect
|
||||
github.com/go-openapi/runtime v0.33.2 // indirect
|
||||
github.com/go-openapi/runtime/server-middleware v0.33.2 // indirect
|
||||
github.com/go-openapi/spec v1.0.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.27.2 // indirect
|
||||
github.com/go-openapi/swag v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/conv v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/loading v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/pools v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.29.2 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.29.2 // indirect
|
||||
github.com/go-openapi/validate v1.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/certificate-transparency-go v1.3.3 // indirect
|
||||
github.com/google/go-containerregistry v0.21.7 // indirect
|
||||
github.com/google/go-containerregistry v0.22.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 // indirect
|
||||
github.com/hanwen/go-fuse/v2 v2.11.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/in-toto/attestation v1.2.0 // indirect
|
||||
@@ -196,11 +196,11 @@ require (
|
||||
github.com/moby/sys/mount v0.3.5 // indirect
|
||||
github.com/moby/sys/sequential v0.7.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/v2 v2.1.2 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/prometheus/client_model v0.6.3 // indirect
|
||||
github.com/prometheus/common v0.71.0 // indirect
|
||||
github.com/prometheus/common v0.72.0 // indirect
|
||||
github.com/prometheus/otlptranslator v1.0.0 // indirect
|
||||
github.com/sasha-s/go-deadlock v0.3.5 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.11.0 // indirect
|
||||
@@ -216,10 +216,11 @@ require (
|
||||
github.com/vbatts/tar-split v0.12.3 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
go.opentelemetry.io/otel/log v1.47.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.6 // indirect
|
||||
golang.org/x/term v0.47.0 // indirect
|
||||
golang.org/x/text v0.43.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.78 // indirect
|
||||
|
||||
@@ -47,8 +47,8 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.6.4 h1:kJF2pamtYCWXf0xAT8sAkwKC0pMl2HYBrRPHAqyUc+g=
|
||||
github.com/Microsoft/go-winio v0.6.4/go.mod h1:1xl5JXG4ze/m74GH6Dc4Mwxfc3VL5siRVQjIAlHH07U=
|
||||
github.com/Microsoft/hcsshim v0.15.0-rc.4 h1:aZFX4LH0S20Lgjq0wG61StIClj7im4yzrxIClkaR8Z8=
|
||||
github.com/Microsoft/hcsshim v0.15.0-rc.4/go.mod h1:BA9CBztgu4h/6Jsvo1O1M4qjWw09PoYpaEYgexPE578=
|
||||
github.com/Microsoft/hcsshim v0.15.0 h1:AWiHGf0+9f7cDcfhuU3zXORCAPmIOWzTYfHhcrMcx3U=
|
||||
github.com/Microsoft/hcsshim v0.15.0/go.mod h1:eStXUkML1Fmkgmwr6XHbYkBC1j7p2FA8ZtA9dyRMbts=
|
||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
@@ -63,8 +63,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go-v2 v1.47.5 h1:5yA+A+BiXbDq1ZLbYwPViwhEGDMdR0MEhL8Jh2LnLS4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.47.5/go.mod h1:iNIkogrRvFwm+1OQIUOP2lPBtlXZboOIUWiCa+pHEMw=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 h1:GPRlPwz40I2B2VrBEASOA3Bi77NyeqejNLkifosX0rs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20/go.mod h1:g7PNzKcsOKWb4fkSRBA7BZVAS6Y8IcxzN+nRohhQ1Q8=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.25 h1:MW0Iql1SOTgQPj8mCxrU/eH/K/AIDWi08ItOsXaWRLU=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.25/go.mod h1:+ucVYmHYB3JqVueY0E1RHahdXAnKQbIHqybSbvOUbOM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.33.13 h1:u52x6ISQOx172tpwM0VRxhJzEsTJYpMcEYUGM2A1wrw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.33.13/go.mod h1:/ixtz6XPHb42zekjQ0LqD9XjTpTysNWhIGwufY1OzI4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.20.13 h1:j4z41OtspmQ3ftQycaDunJ26b69a9SHfaSVUPqgRv8k=
|
||||
@@ -212,56 +212,56 @@ github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/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.25.5 h1:xPYEvTb90o1y0epuiOPAoG4QqahjP3cdp5xNlHeKJRI=
|
||||
github.com/go-openapi/analysis v0.25.5/go.mod h1:d3UGtQC5uq5Kqqqis2VH09Km/v3vwsWrYkbp4gdm+Rc=
|
||||
github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I=
|
||||
github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||
github.com/go-openapi/loads v0.25.0 h1:74Bc2snfaVlsHzwdQj/3gsA9XJz3daXTJVs+4ZaK7jI=
|
||||
github.com/go-openapi/loads v0.25.0/go.mod h1:JFBw4SIB9+PTIFHDfcXuSSy5h6aWzjtUCrPYyx3qWU8=
|
||||
github.com/go-openapi/runtime v0.33.0 h1:Dd3Oj2ig+WH8ckK95l0Wn2V8a4bH/UqWPRZVT0vc8yU=
|
||||
github.com/go-openapi/runtime v0.33.0/go.mod h1:+rsupH3+TFKqmFysqkmgBOTxpVJV8eV+j9myvvea2Xw=
|
||||
github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog=
|
||||
github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs=
|
||||
github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w=
|
||||
github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0=
|
||||
github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM=
|
||||
github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc=
|
||||
github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw=
|
||||
github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg=
|
||||
github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q=
|
||||
github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
|
||||
github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=
|
||||
github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k=
|
||||
github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU=
|
||||
github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||
github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU=
|
||||
github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k=
|
||||
github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM=
|
||||
github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
|
||||
github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k=
|
||||
github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
|
||||
github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU=
|
||||
github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
github.com/go-openapi/validate v0.26.1 h1:pZSbvtRO8G2R2FpWTYRn3w8LrsNwbtaVhP2dWiBa0Us=
|
||||
github.com/go-openapi/validate v0.26.1/go.mod h1:B8UMgXiQiwwQWIbmuROlwJZDPGlikPuh7iHV1vPX9Oo=
|
||||
github.com/go-openapi/analysis v1.0.0 h1:sNvbAGCJqUTqIAodr9IVqJMmuZas3YS9ms1dGK9yiJ4=
|
||||
github.com/go-openapi/analysis v1.0.0/go.mod h1:NhYjJ57fnE+bcE7UwrJyMkhWA3Dfz7TdiBfTVAnos4Y=
|
||||
github.com/go-openapi/errors v0.22.9 h1:HI9+SyVYiRzyeBQGv0CbWbQa+u2S0nny7G7AhY+UMiM=
|
||||
github.com/go-openapi/errors v0.22.9/go.mod h1:R73q66sXP2smzAGgFOI4nqtusRK2xN5pYCjsij+KX58=
|
||||
github.com/go-openapi/jsonpointer v1.0.1 h1:2KxywRmNwJkT/FMBa3iRNHEaAxSJvjqoufQZy3au1Mg=
|
||||
github.com/go-openapi/jsonpointer v1.0.1/go.mod h1:wI7ZYsFmbIi9nBXOZqgDaS/bqOchRGZjqxFli7FBYxY=
|
||||
github.com/go-openapi/jsonreference v1.0.1 h1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38=
|
||||
github.com/go-openapi/jsonreference v1.0.1/go.mod h1:dYplQXa6p5lXprLcJ8LE2iU7vNpXsAHDQ5ZAgL+Qx3A=
|
||||
github.com/go-openapi/loads v0.25.2 h1:+uNsDlRQfYtZTrh+3pdwampcAqZVPuBJW0IA82aZHII=
|
||||
github.com/go-openapi/loads v0.25.2/go.mod h1:RXsfJEQGGNv4uw8u/KdmF8Vh8OR4fTfs9ZO4QrzNohA=
|
||||
github.com/go-openapi/runtime v0.33.2 h1:HSxskMs0WmpCdQvBWVxHt2t2mXMwn8DDav3VtVidwig=
|
||||
github.com/go-openapi/runtime v0.33.2/go.mod h1:NQpSLiIsEAIpZDEs6xUrhjm6ZiQh85X8bHKlqLPgNP8=
|
||||
github.com/go-openapi/runtime/server-middleware v0.33.2 h1:BVFjAaW4Jh/kZ4QSsgDrGzLuSuaKzSiyvappUFKxgUk=
|
||||
github.com/go-openapi/runtime/server-middleware v0.33.2/go.mod h1:E3mWY61/UgBJ5EUmKh+h6A1oQQdD1U4raowVMmPmQug=
|
||||
github.com/go-openapi/spec v1.0.0 h1:JtB/GHOj+eetjse6YvxqLze88oEekl/4uPBethvzRrA=
|
||||
github.com/go-openapi/spec v1.0.0/go.mod h1:boj1PRhqS0x5jylgcNp9BRWxenphrh7vVNYZ90IRCoo=
|
||||
github.com/go-openapi/strfmt v0.27.2 h1:SG32SlbwNy92s0KJiVxt2joJeFdqIYHvwrA0OU6HqzQ=
|
||||
github.com/go-openapi/strfmt v0.27.2/go.mod h1:M4CKsMO0Fb8qR10+1Ra75wCKNNquy+Vj+4LWZrhTo2E=
|
||||
github.com/go-openapi/swag v0.29.2 h1:9n8frkcsuQRA0INU31VbxNxt1zBaHxrHYGEf5BUjFi4=
|
||||
github.com/go-openapi/swag v0.29.2/go.mod h1:RkCiX1gCVXWgLOavDXE3ALJrQ/VO2ziiIzOUs3z10xo=
|
||||
github.com/go-openapi/swag/cmdutils v0.29.2 h1:cXEzX/nWCODon251f+1HgL75/R4XXML5IZxgLCWMoro=
|
||||
github.com/go-openapi/swag/cmdutils v0.29.2/go.mod h1:XziaSVzYqkDvpWXzASfWajw2YvJrLscq610O2XiyqJw=
|
||||
github.com/go-openapi/swag/conv v0.29.2 h1:8c9shoB8l0QRSR6ymq1llHdBWDqpIgJfjTGHx3Vuhm0=
|
||||
github.com/go-openapi/swag/conv v0.29.2/go.mod h1:AZS0YigTNf8qNtD6WqJz/N4RUNmpr76SyjFGkq4+p6Q=
|
||||
github.com/go-openapi/swag/fileutils v0.29.2 h1:mdUL+Vw5ah1fO1AvFCoHeIyv7YZsCz2MN3KNqd/lLVI=
|
||||
github.com/go-openapi/swag/fileutils v0.29.2/go.mod h1:7QKmmodjAebaq61lGVJa8ldLdGeMF4DSgYv8tXOGGo0=
|
||||
github.com/go-openapi/swag/jsonutils v0.29.2 h1:uZNSD2/rJDYAfvsLYkykTzXuyxM3QpDKV9jhRHrMu6k=
|
||||
github.com/go-openapi/swag/jsonutils v0.29.2/go.mod h1:ONTdNvj3Y+IXRzfa17E+Rgt2ns/qiSOYjIo2K7v2yDs=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2 h1:w+Fd6EBOMGlC74GYGHqCLGyb3Bpams8TtNrgM/SG4jo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.2/go.mod h1:HC2egPORFzbj/gf05Zrfgz8Mgplx3oW7hgGi7pjZVhM=
|
||||
github.com/go-openapi/swag/loading v0.29.2 h1:QU1ry24e6r6Shoxe380Nx0X5iKkpRO+hJffsyJ2dVlY=
|
||||
github.com/go-openapi/swag/loading v0.29.2/go.mod h1:DqilDjuiJKETecsS4TRopWG1acUDsfqN39ZmQhPu6uI=
|
||||
github.com/go-openapi/swag/mangling v0.29.2 h1:ltdo3K0tTP4P6zWhVVWDU9D37/YYBuMJRdZxiS2Dtog=
|
||||
github.com/go-openapi/swag/mangling v0.29.2/go.mod h1:RjDi4TnItAenS3cgCXSpIl1kKbjG7il6pnmQVl/8bp8=
|
||||
github.com/go-openapi/swag/netutils v0.29.2 h1:dBli6jyUa93sDS/XYWShJzf4+nnVfFKfvY9SLUaUzB8=
|
||||
github.com/go-openapi/swag/netutils v0.29.2/go.mod h1:1pM6Xg/Um8E1eps3umWl8Hme3ByWXOtHsvFC0CULHUE=
|
||||
github.com/go-openapi/swag/pools v0.29.2 h1:PlGoDRF8WtSyXkedZZkpW3f9wDhFf3u8KtaBcmgmh8c=
|
||||
github.com/go-openapi/swag/pools v0.29.2/go.mod h1:V3lhxVT4qDYRqc2MRSSbLsTYIYV/2HlHafhyEkmzLIc=
|
||||
github.com/go-openapi/swag/stringutils v0.29.2 h1:lcnBxwAaysT3bMUVBfn9V/PkfVqkurpqufKU6rTUMGk=
|
||||
github.com/go-openapi/swag/stringutils v0.29.2/go.mod h1:i9cdh7sGaa0nm3CqIvH5IM5h2QClk1wns2ktKeILvRI=
|
||||
github.com/go-openapi/swag/typeutils v0.29.2 h1:O7aVvkTs3pXwikgtrPLenigEMdQFgONKRooQA9pgf2I=
|
||||
github.com/go-openapi/swag/typeutils v0.29.2/go.mod h1:7+GDG+uz9Ke+eVt4rClZg7wklSDp8hT/mKNh/pC26TE=
|
||||
github.com/go-openapi/swag/yamlutils v0.29.2 h1:IFKFFeDnuIwzfsuWRQU+rI8iL3g4XyAYjV/RlnlxPLM=
|
||||
github.com/go-openapi/swag/yamlutils v0.29.2/go.mod h1:7MGqtcrK73sxQ4ceiyIf8qiXS/s09JLMdR5esK5euYQ=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.7.0 h1:wPW6YRgx3+SID1yUy/Xwa17L8kFEaEKod2VRbJDZNUs=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.7.0/go.mod h1:mI1M88etYbc3PhgHsWQK2kwvNwW5aGFqMPbmib+SGIs=
|
||||
github.com/go-openapi/testify/v2 v2.8.0 h1:19QDx5b57p8KjO2E4sgs/woL4Akp+FGLRlVm2xn0Rho=
|
||||
github.com/go-openapi/testify/v2 v2.8.0/go.mod h1:4gUN8jC+RqOE2qjW/vDmfyTvl5BCht4qL7oca0odrak=
|
||||
github.com/go-openapi/validate v1.0.0 h1:dFsYCLVUQUL6Vi2lQSexgwmCXDuHe7eWRDhQxkE+xYA=
|
||||
github.com/go-openapi/validate v1.0.0/go.mod h1:wwXGRqMQzOZ7PCqBcgNk+DD9+Cacnxv7we5T0M/eA3Y=
|
||||
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-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
@@ -284,8 +284,8 @@ github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc
|
||||
github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU=
|
||||
github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE=
|
||||
github.com/google/go-containerregistry v0.22.0 h1:eGbCiPeYxAH/7WLLq6zTBALP0tUIFsoyRauhxXDJ53I=
|
||||
github.com/google/go-containerregistry v0.22.0/go.mod h1:bJR35SK8XgisYmhg/FMQ/5RK0S/XrOAqLBV5/LR2XE0=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg=
|
||||
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
@@ -303,8 +303,8 @@ github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU
|
||||
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 h1:Bd7KaOxzULLxtZ/K5s1aLbWhR0+5RToO65TXHsf3bqQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0/go.mod h1:nN7ts3dFXKtCZWc//yfkpcQNKJABg16/uDVAZpLDalo=
|
||||
github.com/hanwen/go-fuse/v2 v2.11.0 h1:CGVkJh9gRz0pTRMADNcqdFl3ec/5QbE/Vx1Gl7ESozM=
|
||||
github.com/hanwen/go-fuse/v2 v2.11.0/go.mod h1:aU7NkGYZUmuJrZapoI3mEcNve7PZTySUOLBuch/vR6U=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
@@ -398,8 +398,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
|
||||
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
|
||||
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/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec=
|
||||
github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY=
|
||||
github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk=
|
||||
github.com/onsi/gomega v1.38.1 h1:FaLA8GlcpXDwsb7m0h2A9ew2aTk3vnZMlzFgg5tz/pk=
|
||||
@@ -428,16 +428,16 @@ github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDj
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
|
||||
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
|
||||
github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0=
|
||||
github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk=
|
||||
github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=
|
||||
github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM=
|
||||
github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8=
|
||||
github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk=
|
||||
github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4=
|
||||
github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk=
|
||||
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
|
||||
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
|
||||
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||
github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=
|
||||
github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
|
||||
@@ -539,40 +539,42 @@ go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
|
||||
go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 h1:B2h3uqicet1CT2N5TOFhS+Gq++9i0/CLmaxvhmhtP5s=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0/go.mod h1:dylvB+ZiiwMvsDij9O84Uy7SijLgHMX4mbkncds+4Sw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.71.0 h1:oFNJW32h2SXnET7XXstgT7pVh4vN+jW+GfiIaBguIZE=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.71.0/go.mod h1:+H3sPOFwag14eMHTPMElZtV0e4YfVZ/85KgrKUCB5FI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0/go.mod h1:Ef8SuTh59BT7+ofpDxN9z+yOlc4t2GjLmKDgYNJL/NU=
|
||||
go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
|
||||
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0 h1:Tq+E65HaNbZTeqcLw6QUhsV+/EIWdkfUH2CQzcrlE5M=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0/go.mod h1:A+EVhDakAj1waJBPhmTszZaSr08ww8MRRKy1nB1rqck=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.72.0 h1:jva1c3z2ZFEQ5sTvnLG2tBALNehO+QvdXKQii6eKRoo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.72.0/go.mod h1:3pjFS5EnOVfjvYO6/NMdtJFHFQR+8uiGtrZ965nZP4w=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0 h1:LxwW/9ctSCv+QkE/cLR7M91ZIkXNMqJtEMi1vCw9U8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.72.0/go.mod h1:tOsftB4SslBwwErVEPaenU2RpThXWPIU8DoJHEC4dyw=
|
||||
go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM=
|
||||
go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0 h1:qkDYCAFiZXLcs1L4aY+tP2wguQ4kURANqHOQMA2et2s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0/go.mod h1:tkipS4DRzmpAmvg+Gw4++O1IdDq6TVDnvnYU6cmbQVs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0 h1:AP23h/mFgb/lc7tdck1Kfn9qxsM8TAeNPCU5C3pzaps=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0/go.mod h1:K4EqCe1b4kGk5WR690ntg9LaBfsPoV32FwthbyoptuA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0 h1:w53CDeOA/Kurp7yRsegSr6pbbr759dOvJ+yNmWM6Hxs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0/go.mod h1:BOmGMCbAtvcJiSJ+hLuhgPLdDbimnraSl8irz3iY8sY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0/go.mod h1:zDSEzoEqsOrgBeGvH66KRgxh90VonFyJqBHA0Pk3+rM=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.68.0 h1:QOf2IftqQwITVRJpnn0M7M9ZCbgWfxz4P7i9C9yc2N4=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.68.0/go.mod h1:bgSvqu2TWGXiz7yr5UTMfObH8oqxJWHTnubQ3ef9BO4=
|
||||
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
|
||||
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
|
||||
go.opentelemetry.io/otel/metric/x v0.68.0 h1:TA/cBT23D3MnxYPwHL7YFOdYGdx0A0v+s7Mzotpd1dU=
|
||||
go.opentelemetry.io/otel/metric/x v0.68.0/go.mod h1:agudOmvWhwUTjgibWDzxD2PoWYnpw5Ht5jISYOD2Hd4=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4=
|
||||
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
|
||||
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.47.0 h1:LaLVGJtIzHcvHjVs2OftmKcc+M9mdOqS8vZ1fBD1Yes=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.47.0/go.mod h1:vF6H5sNDuQBJWgSX00iZgbIqk2MZZoVMH0ar5t5bOpw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.47.0 h1:7kszRN4Ze5GeAkUnBjBSUIunVBMA+mZIG04auueqguM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.47.0/go.mod h1:CLzakYy8jOdkBfcveCWYkeKJxujcF4xPhh2Z7IfE98U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSdDdqt5h7Zw9YWmRlWRhI0jd54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxOpYPmMNUtOWhdb+nC1WELIgVf8z9higURrbzYU4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 h1:aXZXsZ012wOgVkqaiQv+Z/d8V0xKjmg70F6m4CE94lc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0/go.mod h1:ziu1gUIJhAaxM3EV1e3Ralk5AAyL1UOYz3lJJ4VSI38=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.69.0 h1:LQVBTGnHvzrfGJpDoCSVTY7eaLZArvaTSq5l84Skxew=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.69.0/go.mod h1:moif0nlgj/M16NRT8lDZVt8MC52+EOAJfR8EKRJ8LyU=
|
||||
go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI=
|
||||
go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw=
|
||||
go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY=
|
||||
go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU=
|
||||
go.opentelemetry.io/otel/metric/x v0.69.0 h1:DjRLr15H83v+hCW7JA9NoJvOkYTtmq5YoDRbe9deYpM=
|
||||
go.opentelemetry.io/otel/metric/x v0.69.0/go.mod h1:uVvsMPMFFyj/HUQfrUnH3JjnOQ1dwFDorgFLRBasM0k=
|
||||
go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I=
|
||||
go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw=
|
||||
go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM=
|
||||
go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0=
|
||||
go.opentelemetry.io/proto/otlp v1.11.1 h1:SCrPqH9NE5CmBrOqpjNCiT0X5mGwMyGERXdogfR7Yjw=
|
||||
go.opentelemetry.io/proto/otlp v1.11.1/go.mod h1:wq8W4aSf6bOy8RZyESOeQFr9Pu7wydWX/TY4sgvrTPg=
|
||||
go.step.sm/crypto v0.77.7 h1:6azC+pD678Vjju8yXnMDHCZJ+HzFaEmL3sCryiezTIA=
|
||||
go.step.sm/crypto v0.77.7/go.mod h1:OW/2sEHwTtDKq70PvSQ5B0JGy/CrLyDKOiVy3YvZMTQ=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
@@ -585,18 +587,18 @@ go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
|
||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
go.yaml.in/yaml/v3 v3.0.6 h1:zcDfvjXEqTQmpjniUBLc91xYJMB2c4QOFxEF/LCEckM=
|
||||
go.yaml.in/yaml/v3 v3.0.6/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
||||
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
||||
golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q=
|
||||
golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU=
|
||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E=
|
||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
||||
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
|
||||
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
|
||||
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
||||
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
||||
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
|
||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -619,12 +621,12 @@ google.golang.org/api v0.283.0 h1:0lkp8u0MPwJVHqRL+nJlMAoZVVzbmiXmFHXMOTmSPik=
|
||||
google.golang.org/api v0.283.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 h1:izFU9hz7aeLI/Mi1J0991ae+xcwRLr7hTqWnB/9aIIU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5/go.mod h1:3LhxRw4YYkf+ylAfgaY9JlVLFKhokkCV8duhLLe7+t0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
|
||||
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
|
||||
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941 h1:DS/w3N8rsfrwxwgGSj6WhMsPo7aNQGmDx74J81zd0NQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20261012220534-4341edf3f941/go.mod h1:AuGAdi9KnXss7xbLxIKhCvk+Ro/SxpET/iS45DNYN+M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941 h1:QG6hS4zFplRZvOQedzei4t2pT/zkQc3VR1u+S9/rcAw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
|
||||
google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw=
|
||||
google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+17
-3
@@ -194,16 +194,30 @@ func (r *legacyLayerReader) reset() {
|
||||
|
||||
func findBackupStreamSize(r io.Reader) (int64, error) {
|
||||
br := winio.NewBackupStreamReader(r)
|
||||
var size int64
|
||||
for {
|
||||
hdr, err := br.Next()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = nil
|
||||
// With no inline data, sparse blocks provide the logical size.
|
||||
return size, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
if hdr.Id == winio.BackupData {
|
||||
return hdr.Size, nil
|
||||
switch hdr.Id {
|
||||
case winio.BackupData:
|
||||
// Non-sparse files and sparse files with inline data carry their
|
||||
// logical size in BackupData.
|
||||
if hdr.Size > 0 || hdr.Attributes&winio.StreamSparseAttributes == 0 {
|
||||
return hdr.Size, nil
|
||||
}
|
||||
// A sparse BackupData stream without inline data is followed by
|
||||
// sparse blocks that describe its logical size.
|
||||
case winio.BackupSparseBlock:
|
||||
// The terminal zero-length block has an offset at logical EOF.
|
||||
if end := hdr.Offset + hdr.Size; end > size {
|
||||
size = end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+14
@@ -154,6 +154,20 @@ func CimGetVerificationInformation(blockCimPath string, isSealed *uint32, hashSi
|
||||
)(blockCimPath, isSealed, hashSize, signatureSize, fixedHeaderSize, hash, signature)
|
||||
}
|
||||
|
||||
func CimGetVerificationInformation2(blockCimPath string, flags types.CimGetVerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *types.CimSignatureType, hashAlgorithm *types.CimHashAlgorithm) (hr error) {
|
||||
return pickSupported(
|
||||
cimwriter.CimGetVerificationInformation2,
|
||||
cimfs.CimGetVerificationInformation2,
|
||||
)(blockCimPath, flags, isSealed, hashSize, signatureOffset, signatureSize, fixedHeaderSize, hash, signature, signatureType, hashAlgorithm)
|
||||
}
|
||||
|
||||
func CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *types.CimFsCimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
|
||||
return pickSupported(
|
||||
cimwriter.CimQueryBlockInfo,
|
||||
cimfs.CimQueryBlockInfo,
|
||||
)(blockCimPath, cimInfoBufferSize, cimInfoBuffer, requiredBufferSize, cimCount)
|
||||
}
|
||||
|
||||
func CimMountVerifiedImage(imagePath string, fsName string, flags uint32, volumeID *guid.GUID, hashSize uint16, hash *byte) error {
|
||||
return cimfs.CimMountVerifiedImage(imagePath, fsName, flags, volumeID, hashSize, hash)
|
||||
}
|
||||
|
||||
+7
-1
@@ -19,6 +19,10 @@ type FsHandle = types.FsHandle
|
||||
type StreamHandle = types.StreamHandle
|
||||
type FileMetadata = types.CimFsFileMetadata
|
||||
type ImagePath = types.CimFsImagePath
|
||||
type CimInfo = types.CimFsCimInfo
|
||||
type VerificationInfoFlags = types.CimGetVerificationInfoFlags
|
||||
type SignatureType = types.CimSignatureType
|
||||
type HashAlgorithm = types.CimHashAlgorithm
|
||||
|
||||
//sys CimMountImage(imagePath string, fsName string, flags uint32, volumeID *GUID) (hr error) = cimfs.CimMountImage?
|
||||
//sys CimDismountImage(volumeID *GUID) (hr error) = cimfs.CimDismountImage?
|
||||
@@ -29,7 +33,7 @@ type ImagePath = types.CimFsImagePath
|
||||
//sys CimCommitImage(cimFSHandle FsHandle) (hr error) = cimfs.CimCommitImage?
|
||||
|
||||
//sys CimCreateFile(cimFSHandle FsHandle, path string, file *FileMetadata, cimStreamHandle *StreamHandle) (hr error) = cimfs.CimCreateFile?
|
||||
//sys CimCloseStream(cimStreamHandle StreamHandle) (hr error) = cimfs.CimCloseStream?
|
||||
//sys CimCloseStream(cimStreamHandle StreamHandle) = cimfs.CimCloseStream?
|
||||
//sys CimWriteStream(cimStreamHandle StreamHandle, buffer uintptr, bufferSize uint32) (hr error) = cimfs.CimWriteStream?
|
||||
//sys CimDeletePath(cimFSHandle FsHandle, path string) (hr error) = cimfs.CimDeletePath?
|
||||
//sys CimCreateHardLink(cimFSHandle FsHandle, newPath string, oldPath string) (hr error) = cimfs.CimCreateHardLink?
|
||||
@@ -41,6 +45,8 @@ type ImagePath = types.CimFsImagePath
|
||||
//sys CimCreateMergeLink(cimFSHandle FsHandle, newPath string, oldPath string) (hr error) = cimfs.CimCreateMergeLink?
|
||||
//sys CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) = cimfs.CimSealImage?
|
||||
//sys CimGetVerificationInformation(blockCimPath string, isSealed *uint32, hashSize *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte) (hr error) = cimfs.CimGetVerificationInformation?
|
||||
//sys CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) = cimfs.CimGetVerificationInformation2?
|
||||
//sys CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) = cimfs.CimQueryBlockInfo?
|
||||
//sys CimMountVerifiedImage(imagePath string, fsName string, flags uint32, volumeID *GUID, hashSize uint16, hash *byte) (hr error) = cimfs.CimMountVerifiedImage?
|
||||
//sys CimMergeMountVerifiedImage(numCimPaths uint32, backingImagePaths *ImagePath, flags uint32, volumeID *GUID, hashSize uint16, hash *byte) (hr error) = cimfs.CimMergeMountVerifiedImage?
|
||||
|
||||
|
||||
+77
-37
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
@@ -39,27 +35,29 @@ func errnoErr(e syscall.Errno) error {
|
||||
var (
|
||||
modcimfs = windows.NewLazySystemDLL("cimfs.dll")
|
||||
|
||||
procCimAddFsToMergedImage = modcimfs.NewProc("CimAddFsToMergedImage")
|
||||
procCimAddFsToMergedImage2 = modcimfs.NewProc("CimAddFsToMergedImage2")
|
||||
procCimCloseImage = modcimfs.NewProc("CimCloseImage")
|
||||
procCimCloseStream = modcimfs.NewProc("CimCloseStream")
|
||||
procCimCommitImage = modcimfs.NewProc("CimCommitImage")
|
||||
procCimCreateAlternateStream = modcimfs.NewProc("CimCreateAlternateStream")
|
||||
procCimCreateFile = modcimfs.NewProc("CimCreateFile")
|
||||
procCimCreateHardLink = modcimfs.NewProc("CimCreateHardLink")
|
||||
procCimCreateImage = modcimfs.NewProc("CimCreateImage")
|
||||
procCimCreateImage2 = modcimfs.NewProc("CimCreateImage2")
|
||||
procCimCreateMergeLink = modcimfs.NewProc("CimCreateMergeLink")
|
||||
procCimDeletePath = modcimfs.NewProc("CimDeletePath")
|
||||
procCimDismountImage = modcimfs.NewProc("CimDismountImage")
|
||||
procCimGetVerificationInformation = modcimfs.NewProc("CimGetVerificationInformation")
|
||||
procCimMergeMountImage = modcimfs.NewProc("CimMergeMountImage")
|
||||
procCimMergeMountVerifiedImage = modcimfs.NewProc("CimMergeMountVerifiedImage")
|
||||
procCimMountImage = modcimfs.NewProc("CimMountImage")
|
||||
procCimMountVerifiedImage = modcimfs.NewProc("CimMountVerifiedImage")
|
||||
procCimSealImage = modcimfs.NewProc("CimSealImage")
|
||||
procCimTombstoneFile = modcimfs.NewProc("CimTombstoneFile")
|
||||
procCimWriteStream = modcimfs.NewProc("CimWriteStream")
|
||||
procCimAddFsToMergedImage = modcimfs.NewProc("CimAddFsToMergedImage")
|
||||
procCimAddFsToMergedImage2 = modcimfs.NewProc("CimAddFsToMergedImage2")
|
||||
procCimCloseImage = modcimfs.NewProc("CimCloseImage")
|
||||
procCimCloseStream = modcimfs.NewProc("CimCloseStream")
|
||||
procCimCommitImage = modcimfs.NewProc("CimCommitImage")
|
||||
procCimCreateAlternateStream = modcimfs.NewProc("CimCreateAlternateStream")
|
||||
procCimCreateFile = modcimfs.NewProc("CimCreateFile")
|
||||
procCimCreateHardLink = modcimfs.NewProc("CimCreateHardLink")
|
||||
procCimCreateImage = modcimfs.NewProc("CimCreateImage")
|
||||
procCimCreateImage2 = modcimfs.NewProc("CimCreateImage2")
|
||||
procCimCreateMergeLink = modcimfs.NewProc("CimCreateMergeLink")
|
||||
procCimDeletePath = modcimfs.NewProc("CimDeletePath")
|
||||
procCimDismountImage = modcimfs.NewProc("CimDismountImage")
|
||||
procCimGetVerificationInformation = modcimfs.NewProc("CimGetVerificationInformation")
|
||||
procCimGetVerificationInformation2 = modcimfs.NewProc("CimGetVerificationInformation2")
|
||||
procCimMergeMountImage = modcimfs.NewProc("CimMergeMountImage")
|
||||
procCimMergeMountVerifiedImage = modcimfs.NewProc("CimMergeMountVerifiedImage")
|
||||
procCimMountImage = modcimfs.NewProc("CimMountImage")
|
||||
procCimMountVerifiedImage = modcimfs.NewProc("CimMountVerifiedImage")
|
||||
procCimQueryBlockInfo = modcimfs.NewProc("CimQueryBlockInfo")
|
||||
procCimSealImage = modcimfs.NewProc("CimSealImage")
|
||||
procCimTombstoneFile = modcimfs.NewProc("CimTombstoneFile")
|
||||
procCimWriteStream = modcimfs.NewProc("CimWriteStream")
|
||||
)
|
||||
|
||||
func CimAddFsToMergedImage(cimFSHandle FsHandle, path string) (hr error) {
|
||||
@@ -119,18 +117,12 @@ func CimCloseImage(cimFSHandle FsHandle) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func CimCloseStream(cimStreamHandle StreamHandle) (hr error) {
|
||||
hr = procCimCloseStream.Find()
|
||||
if hr != nil {
|
||||
func CimCloseStream(cimStreamHandle StreamHandle) (err error) {
|
||||
err = procCimCloseStream.Find()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -366,6 +358,30 @@ func _CimGetVerificationInformation(blockCimPath *uint16, isSealed *uint32, hash
|
||||
return
|
||||
}
|
||||
|
||||
func CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _CimGetVerificationInformation2(_p0, flags, isSealed, hashSize, signatureOffset, signatureSize, fixedHeaderSize, hash, signature, signatureType, hashAlgorithm)
|
||||
}
|
||||
|
||||
func _CimGetVerificationInformation2(blockCimPath *uint16, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
|
||||
hr = procCimGetVerificationInformation2.Find()
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.SyscallN(procCimGetVerificationInformation2.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(flags), uintptr(unsafe.Pointer(isSealed)), uintptr(unsafe.Pointer(hashSize)), uintptr(unsafe.Pointer(signatureOffset)), uintptr(unsafe.Pointer(signatureSize)), uintptr(unsafe.Pointer(fixedHeaderSize)), uintptr(unsafe.Pointer(hash)), uintptr(unsafe.Pointer(signature)), uintptr(unsafe.Pointer(signatureType)), uintptr(unsafe.Pointer(hashAlgorithm)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CimMergeMountImage(numCimPaths uint32, backingImagePaths *ImagePath, flags uint32, volumeID *GUID) (hr error) {
|
||||
hr = procCimMergeMountImage.Find()
|
||||
if hr != nil {
|
||||
@@ -454,6 +470,30 @@ func _CimMountVerifiedImage(imagePath *uint16, fsName *uint16, flags uint32, vol
|
||||
return
|
||||
}
|
||||
|
||||
func CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _CimQueryBlockInfo(_p0, cimInfoBufferSize, cimInfoBuffer, requiredBufferSize, cimCount)
|
||||
}
|
||||
|
||||
func _CimQueryBlockInfo(blockCimPath *uint16, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
|
||||
hr = procCimQueryBlockInfo.Find()
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.SyscallN(procCimQueryBlockInfo.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(cimInfoBufferSize), uintptr(unsafe.Pointer(cimInfoBuffer)), uintptr(unsafe.Pointer(requiredBufferSize)), uintptr(unsafe.Pointer(cimCount)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
|
||||
|
||||
+7
-1
@@ -17,6 +17,10 @@ type FsHandle = types.FsHandle
|
||||
type StreamHandle = types.StreamHandle
|
||||
type FileMetadata = types.CimFsFileMetadata
|
||||
type ImagePath = types.CimFsImagePath
|
||||
type CimInfo = types.CimFsCimInfo
|
||||
type VerificationInfoFlags = types.CimGetVerificationInfoFlags
|
||||
type SignatureType = types.CimSignatureType
|
||||
type HashAlgorithm = types.CimHashAlgorithm
|
||||
|
||||
//sys CimCreateImage(imagePath string, oldFSName *uint16, newFSName *uint16, cimFSHandle *FsHandle) (hr error) = cimwriter.CimCreateImage?
|
||||
//sys CimCreateImage2(imagePath string, flags uint32, oldFSName *uint16, newFSName *uint16, cimFSHandle *FsHandle) (hr error) = cimwriter.CimCreateImage2?
|
||||
@@ -24,7 +28,7 @@ type ImagePath = types.CimFsImagePath
|
||||
//sys CimCommitImage(cimFSHandle FsHandle) (hr error) = cimwriter.CimCommitImage?
|
||||
|
||||
//sys CimCreateFile(cimFSHandle FsHandle, path string, file *FileMetadata, cimStreamHandle *StreamHandle) (hr error) = cimwriter.CimCreateFile?
|
||||
//sys CimCloseStream(cimStreamHandle StreamHandle) (hr error) = cimwriter.CimCloseStream?
|
||||
//sys CimCloseStream(cimStreamHandle StreamHandle) = cimwriter.CimCloseStream?
|
||||
//sys CimWriteStream(cimStreamHandle StreamHandle, buffer uintptr, bufferSize uint32) (hr error) = cimwriter.CimWriteStream?
|
||||
//sys CimDeletePath(cimFSHandle FsHandle, path string) (hr error) = cimwriter.CimDeletePath?
|
||||
//sys CimCreateHardLink(cimFSHandle FsHandle, newPath string, oldPath string) (hr error) = cimwriter.CimCreateHardLink?
|
||||
@@ -37,6 +41,8 @@ type ImagePath = types.CimFsImagePath
|
||||
//sys CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) = cimwriter.CimSealImage?
|
||||
|
||||
//sys CimGetVerificationInformation(blockCimPath string, isSealed *uint32, hashSize *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte) (hr error) = cimwriter.CimGetVerificationInformation?
|
||||
//sys CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) = cimwriter.CimGetVerificationInformation2?
|
||||
//sys CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) = cimwriter.CimQueryBlockInfo?
|
||||
|
||||
var load = sync.OnceValue(func() error {
|
||||
// Pre-load the DLL with a restricted search path (System32 + application directory only)
|
||||
|
||||
Generated
Vendored
+74
-32
@@ -7,18 +7,16 @@ package cimwriter
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -28,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
@@ -37,22 +35,24 @@ func errnoErr(e syscall.Errno) error {
|
||||
var (
|
||||
modcimwriter = syscall.NewLazyDLL("cimwriter.dll")
|
||||
|
||||
procCimAddFsToMergedImage = modcimwriter.NewProc("CimAddFsToMergedImage")
|
||||
procCimAddFsToMergedImage2 = modcimwriter.NewProc("CimAddFsToMergedImage2")
|
||||
procCimCloseImage = modcimwriter.NewProc("CimCloseImage")
|
||||
procCimCloseStream = modcimwriter.NewProc("CimCloseStream")
|
||||
procCimCommitImage = modcimwriter.NewProc("CimCommitImage")
|
||||
procCimCreateAlternateStream = modcimwriter.NewProc("CimCreateAlternateStream")
|
||||
procCimCreateFile = modcimwriter.NewProc("CimCreateFile")
|
||||
procCimCreateHardLink = modcimwriter.NewProc("CimCreateHardLink")
|
||||
procCimCreateImage = modcimwriter.NewProc("CimCreateImage")
|
||||
procCimCreateImage2 = modcimwriter.NewProc("CimCreateImage2")
|
||||
procCimCreateMergeLink = modcimwriter.NewProc("CimCreateMergeLink")
|
||||
procCimDeletePath = modcimwriter.NewProc("CimDeletePath")
|
||||
procCimGetVerificationInformation = modcimwriter.NewProc("CimGetVerificationInformation")
|
||||
procCimSealImage = modcimwriter.NewProc("CimSealImage")
|
||||
procCimTombstoneFile = modcimwriter.NewProc("CimTombstoneFile")
|
||||
procCimWriteStream = modcimwriter.NewProc("CimWriteStream")
|
||||
procCimAddFsToMergedImage = modcimwriter.NewProc("CimAddFsToMergedImage")
|
||||
procCimAddFsToMergedImage2 = modcimwriter.NewProc("CimAddFsToMergedImage2")
|
||||
procCimCloseImage = modcimwriter.NewProc("CimCloseImage")
|
||||
procCimCloseStream = modcimwriter.NewProc("CimCloseStream")
|
||||
procCimCommitImage = modcimwriter.NewProc("CimCommitImage")
|
||||
procCimCreateAlternateStream = modcimwriter.NewProc("CimCreateAlternateStream")
|
||||
procCimCreateFile = modcimwriter.NewProc("CimCreateFile")
|
||||
procCimCreateHardLink = modcimwriter.NewProc("CimCreateHardLink")
|
||||
procCimCreateImage = modcimwriter.NewProc("CimCreateImage")
|
||||
procCimCreateImage2 = modcimwriter.NewProc("CimCreateImage2")
|
||||
procCimCreateMergeLink = modcimwriter.NewProc("CimCreateMergeLink")
|
||||
procCimDeletePath = modcimwriter.NewProc("CimDeletePath")
|
||||
procCimGetVerificationInformation = modcimwriter.NewProc("CimGetVerificationInformation")
|
||||
procCimGetVerificationInformation2 = modcimwriter.NewProc("CimGetVerificationInformation2")
|
||||
procCimQueryBlockInfo = modcimwriter.NewProc("CimQueryBlockInfo")
|
||||
procCimSealImage = modcimwriter.NewProc("CimSealImage")
|
||||
procCimTombstoneFile = modcimwriter.NewProc("CimTombstoneFile")
|
||||
procCimWriteStream = modcimwriter.NewProc("CimWriteStream")
|
||||
)
|
||||
|
||||
func CimAddFsToMergedImage(cimFSHandle FsHandle, path string) (hr error) {
|
||||
@@ -112,18 +112,12 @@ func CimCloseImage(cimFSHandle FsHandle) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func CimCloseStream(cimStreamHandle StreamHandle) (hr error) {
|
||||
hr = procCimCloseStream.Find()
|
||||
if hr != nil {
|
||||
func CimCloseStream(cimStreamHandle StreamHandle) (err error) {
|
||||
err = procCimCloseStream.Find()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
syscall.SyscallN(procCimCloseStream.Addr(), uintptr(cimStreamHandle))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -344,6 +338,54 @@ func _CimGetVerificationInformation(blockCimPath *uint16, isSealed *uint32, hash
|
||||
return
|
||||
}
|
||||
|
||||
func CimGetVerificationInformation2(blockCimPath string, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _CimGetVerificationInformation2(_p0, flags, isSealed, hashSize, signatureOffset, signatureSize, fixedHeaderSize, hash, signature, signatureType, hashAlgorithm)
|
||||
}
|
||||
|
||||
func _CimGetVerificationInformation2(blockCimPath *uint16, flags VerificationInfoFlags, isSealed *uint32, hashSize *uint64, signatureOffset *uint64, signatureSize *uint64, fixedHeaderSize *uint64, hash *byte, signature *byte, signatureType *SignatureType, hashAlgorithm *HashAlgorithm) (hr error) {
|
||||
hr = procCimGetVerificationInformation2.Find()
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.SyscallN(procCimGetVerificationInformation2.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(flags), uintptr(unsafe.Pointer(isSealed)), uintptr(unsafe.Pointer(hashSize)), uintptr(unsafe.Pointer(signatureOffset)), uintptr(unsafe.Pointer(signatureSize)), uintptr(unsafe.Pointer(fixedHeaderSize)), uintptr(unsafe.Pointer(hash)), uintptr(unsafe.Pointer(signature)), uintptr(unsafe.Pointer(signatureType)), uintptr(unsafe.Pointer(hashAlgorithm)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CimQueryBlockInfo(blockCimPath string, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
return _CimQueryBlockInfo(_p0, cimInfoBufferSize, cimInfoBuffer, requiredBufferSize, cimCount)
|
||||
}
|
||||
|
||||
func _CimQueryBlockInfo(blockCimPath *uint16, cimInfoBufferSize uint32, cimInfoBuffer *CimInfo, requiredBufferSize *uint32, cimCount *uint32) (hr error) {
|
||||
hr = procCimQueryBlockInfo.Find()
|
||||
if hr != nil {
|
||||
return
|
||||
}
|
||||
r0, _, _ := syscall.SyscallN(procCimQueryBlockInfo.Addr(), uintptr(unsafe.Pointer(blockCimPath)), uintptr(cimInfoBufferSize), uintptr(unsafe.Pointer(cimInfoBuffer)), uintptr(unsafe.Pointer(requiredBufferSize)), uintptr(unsafe.Pointer(cimCount)))
|
||||
if int32(r0) < 0 {
|
||||
if r0&0x1fff0000 == 0x00070000 {
|
||||
r0 &= 0xffff
|
||||
}
|
||||
hr = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CimSealImage(blockCimPath string, hashSize *uint64, fixedHeaderSize *uint64, hash *byte) (hr error) {
|
||||
var _p0 *uint16
|
||||
_p0, hr = syscall.UTF16PtrFromString(blockCimPath)
|
||||
|
||||
+27
@@ -11,6 +11,27 @@ import (
|
||||
type FsHandle uintptr
|
||||
type StreamHandle uintptr
|
||||
|
||||
type CimGetVerificationInfoFlags uint32
|
||||
|
||||
const (
|
||||
CimGetVerificationInfoNone CimGetVerificationInfoFlags = 0
|
||||
CimGetVerificationInfoFromSkeletonCim CimGetVerificationInfoFlags = 1
|
||||
)
|
||||
|
||||
type CimSignatureType uint32
|
||||
|
||||
const (
|
||||
CimSignatureNone CimSignatureType = 0
|
||||
CimSignaturePKCS7 CimSignatureType = 1
|
||||
)
|
||||
|
||||
type CimHashAlgorithm uint32
|
||||
|
||||
const (
|
||||
CimHashAlgorithmSHA256 CimHashAlgorithm = 0
|
||||
CimHashAlgorithmSHA512 CimHashAlgorithm = 1
|
||||
)
|
||||
|
||||
type CimFsFileMetadata struct {
|
||||
Attributes uint32
|
||||
FileSize int64
|
||||
@@ -34,3 +55,9 @@ type CimFsImagePath struct {
|
||||
ImageDir *uint16
|
||||
ImageName *uint16
|
||||
}
|
||||
|
||||
type CimFsCimInfo struct {
|
||||
NextEntryOffset uint32
|
||||
CimNameLength uint16
|
||||
CimName [1]uint16
|
||||
}
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+2
-6
@@ -15,12 +15,8 @@ var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
errERROR_IO_PENDING error = windows.ERROR_IO_PENDING
|
||||
errERROR_EINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
@@ -30,7 +26,7 @@ func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return errERROR_EINVAL
|
||||
case errnoERROR_IO_PENDING:
|
||||
case windows.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
return e
|
||||
|
||||
+20
@@ -1,3 +1,23 @@
|
||||
# v1.7.25 (2026-10-27)
|
||||
|
||||
* **Dependency Update**: Update to smithy-go v1.28.7.
|
||||
|
||||
# v1.7.24 (2026-10-22)
|
||||
|
||||
* **Bug Fix**: Update smithy-go to v1.28.6
|
||||
|
||||
# v1.7.23 (2026-10-21)
|
||||
|
||||
* **Bug Fix**: Enforce event stream header key value length limit instead of truncating
|
||||
|
||||
# v1.7.22 (2026-10-09.2)
|
||||
|
||||
* **Dependency Update**: Update to smithy-go v1.28.5.
|
||||
|
||||
# v1.7.21 (2026-10-08)
|
||||
|
||||
* **Dependency Update**: Update to smithy-go v1.28.4.
|
||||
|
||||
# v1.7.20 (2026-08-26)
|
||||
|
||||
* **Dependency Update**: Update to smithy-go v1.28.0.
|
||||
|
||||
+7
@@ -141,6 +141,13 @@ func encodePrelude(w io.Writer, crc hash.Hash32, headersLen, payloadLen uint32)
|
||||
// stream format. Returns an error if a header fails to encode.
|
||||
func EncodeHeaders(w io.Writer, headers Headers) error {
|
||||
for _, h := range headers {
|
||||
if len(h.Name) > maxHeaderNameLen {
|
||||
return LengthError{
|
||||
Part: "header name",
|
||||
Want: maxHeaderNameLen, Have: len(h.Name),
|
||||
Value: h.Name,
|
||||
}
|
||||
}
|
||||
hn := headerName{
|
||||
Len: uint8(len(h.Name)),
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -3,4 +3,4 @@
|
||||
package eventstream
|
||||
|
||||
// goModuleVersion is the tagged release for this module
|
||||
const goModuleVersion = "1.7.20"
|
||||
const goModuleVersion = "1.7.25"
|
||||
+5
@@ -7,3 +7,8 @@ coverage:
|
||||
patch:
|
||||
default:
|
||||
target: 80%
|
||||
|
||||
ignore:
|
||||
- "testdata"
|
||||
- "internal/antest"
|
||||
- "internal/testintegration"
|
||||
+1
@@ -10,6 +10,7 @@ linters:
|
||||
- gomodguard
|
||||
- gomodguard_v2
|
||||
- exhaustruct
|
||||
- exhaustruct_v5
|
||||
- nlreturn
|
||||
- nonamedreturns
|
||||
- noinlineerr
|
||||
|
||||
+17
-16
@@ -4,31 +4,32 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 22 | 273 |
|
||||
| 23 | 298 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @fredbi | 133 | <https://github.com/go-openapi/analysis/commits?author=fredbi> |
|
||||
| @fredbi | 149 | <https://github.com/go-openapi/analysis/commits?author=fredbi> |
|
||||
| @casualjim | 91 | <https://github.com/go-openapi/analysis/commits?author=casualjim> |
|
||||
| @amoudani | 9 | <https://github.com/go-openapi/analysis/commits?author=amoudani> |
|
||||
| @keramix | 9 | <https://github.com/go-openapi/analysis/commits?author=keramix> |
|
||||
| @youyuanwu | 8 | <https://github.com/go-openapi/analysis/commits?author=youyuanwu> |
|
||||
| @wjase | 7 | <https://github.com/go-openapi/analysis/commits?author=wjase> |
|
||||
| @kul-amr | 3 | <https://github.com/go-openapi/analysis/commits?author=kul-amr> |
|
||||
| @schafle | 3 | <https://github.com/go-openapi/analysis/commits?author=schafle> |
|
||||
| @msample | 3 | <https://github.com/go-openapi/analysis/commits?author=msample> |
|
||||
| @mbohlool | 2 | <https://github.com/go-openapi/analysis/commits?author=mbohlool> |
|
||||
| @schafle | 3 | <https://github.com/go-openapi/analysis/commits?author=schafle> |
|
||||
| @kul-amr | 3 | <https://github.com/go-openapi/analysis/commits?author=kul-amr> |
|
||||
| @zmay2030 | 2 | <https://github.com/go-openapi/analysis/commits?author=zmay2030> |
|
||||
| @ujjwalsh | 1 | <https://github.com/go-openapi/analysis/commits?author=ujjwalsh> |
|
||||
| @itengfei | 1 | <https://github.com/go-openapi/analysis/commits?author=itengfei> |
|
||||
| @nrnrk | 1 | <https://github.com/go-openapi/analysis/commits?author=nrnrk> |
|
||||
| @cuishuang | 1 | <https://github.com/go-openapi/analysis/commits?author=cuishuang> |
|
||||
| @tklauser | 1 | <https://github.com/go-openapi/analysis/commits?author=tklauser> |
|
||||
| @Shimizu1111 | 1 | <https://github.com/go-openapi/analysis/commits?author=Shimizu1111> |
|
||||
| @thaJeztah | 1 | <https://github.com/go-openapi/analysis/commits?author=thaJeztah> |
|
||||
| @knweiss | 1 | <https://github.com/go-openapi/analysis/commits?author=knweiss> |
|
||||
| @guillemj | 1 | <https://github.com/go-openapi/analysis/commits?author=guillemj> |
|
||||
| @gregmarr | 1 | <https://github.com/go-openapi/analysis/commits?author=gregmarr> |
|
||||
| @danielfbm | 1 | <https://github.com/go-openapi/analysis/commits?author=danielfbm> |
|
||||
| @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> |
|
||||
| @thaJeztah | 1 | <https://github.com/go-openapi/analysis/commits?author=thaJeztah> |
|
||||
| @Shimizu1111 | 1 | <https://github.com/go-openapi/analysis/commits?author=Shimizu1111> |
|
||||
| @tklauser | 1 | <https://github.com/go-openapi/analysis/commits?author=tklauser> |
|
||||
| @cuishuang | 1 | <https://github.com/go-openapi/analysis/commits?author=cuishuang> |
|
||||
| @nrnrk | 1 | <https://github.com/go-openapi/analysis/commits?author=nrnrk> |
|
||||
| @itengfei | 1 | <https://github.com/go-openapi/analysis/commits?author=itengfei> |
|
||||
| @ujjwalsh | 1 | <https://github.com/go-openapi/analysis/commits?author=ujjwalsh> |
|
||||
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github-community-projects/contributors)_
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -102,8 +102,6 @@ Maintainers can cut a new release by either:
|
||||
[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 -->
|
||||
|
||||
+74
@@ -30,6 +30,15 @@ type referenceAnalysis struct {
|
||||
parameterItems map[string]spec.Ref
|
||||
allRefs map[string]spec.Ref
|
||||
pathItems map[string]spec.Ref
|
||||
|
||||
// unmappedRefs holds the $ref found under keywords the Swagger 2.0 model does not map, which
|
||||
// land in [spec.Schema.ExtraProps] as raw JSON: propertyNames, contains, if/then/else, $defs.
|
||||
//
|
||||
// They are kept apart from allRefs and schemas on purpose. Flatten needs them to import their
|
||||
// target and rewrite the pointer; every other consumer of this analysis - AllRefs,
|
||||
// AllReferences, AllDefinitionReferences and what go-swagger builds on them - addresses schemas
|
||||
// through the model, and a key naming a raw JSON node is of no use there.
|
||||
unmappedRefs map[string]spec.Ref
|
||||
}
|
||||
|
||||
func (r *referenceAnalysis) addRef(key string, ref spec.Ref) {
|
||||
@@ -48,6 +57,11 @@ func (r *referenceAnalysis) addItemsRef(key string, items *spec.Items, location
|
||||
}
|
||||
}
|
||||
|
||||
// addUnmappedRef records a $ref held by a keyword the model does not map.
|
||||
func (r *referenceAnalysis) addUnmappedRef(key string, ref spec.Ref) {
|
||||
r.unmappedRefs["#"+key] = ref
|
||||
}
|
||||
|
||||
func (r *referenceAnalysis) addSchemaRef(key string, ref SchemaRef) {
|
||||
r.schemas["#"+key] = ref.Schema.Ref
|
||||
r.addRef(key, ref.Schema.Ref)
|
||||
@@ -585,6 +599,20 @@ func (s *Spec) AllRefs() (result []spec.Ref) {
|
||||
return
|
||||
}
|
||||
|
||||
// AllRefsByLocation returns all the references found in the document, keyed by
|
||||
// where each one is declared.
|
||||
//
|
||||
// Keys are local JSON references into the analyzed document, with tokens
|
||||
// escaped as per RFC 6901, e.g. "#/paths/~1pets/get/responses/200/schema".
|
||||
//
|
||||
// Unlike [Spec.AllRefs], the result is not deduplicated: the same reference
|
||||
// declared in several places appears under each of its locations.
|
||||
//
|
||||
// The map is cloned to avoid accidental changes.
|
||||
func (s *Spec) AllRefsByLocation() map[string]spec.Ref {
|
||||
return cloneRefMap(s.references.allRefs)
|
||||
}
|
||||
|
||||
// ParameterPatterns returns all the patterns found in parameters
|
||||
// the map is cloned to avoid accidental changes.
|
||||
func (s *Spec) ParameterPatterns() map[string]string {
|
||||
@@ -725,6 +753,7 @@ func (s *Spec) reset() {
|
||||
s.references.headerItems = make(map[string]spec.Ref, allocLargeMap)
|
||||
s.references.parameterItems = make(map[string]spec.Ref, allocLargeMap)
|
||||
s.references.allRefs = make(map[string]spec.Ref, allocLargeMap)
|
||||
s.references.unmappedRefs = make(map[string]spec.Ref, allocSmallMap)
|
||||
s.patterns.parameters = make(map[string]string, allocLargeMap)
|
||||
s.patterns.headers = make(map[string]string, allocLargeMap)
|
||||
s.patterns.items = make(map[string]string, allocLargeMap)
|
||||
@@ -958,6 +987,42 @@ func (s *Spec) analyzeResponse(prefix string, k int, res spec.Response) {
|
||||
}
|
||||
}
|
||||
|
||||
// analyzeUnmapped records the $ref held by the keywords of a schema that the Swagger 2.0 model
|
||||
// does not map, which json.Unmarshal leaves in ExtraProps as raw JSON.
|
||||
//
|
||||
// The keys it produces address the node holding the $ref, so "#/definitions/deep/propertyNames"
|
||||
// or "#/definitions/deep/if/anyOf/0". [replace.UpdateRef] writes to them through the same
|
||||
// jsonpointer call every other key goes through.
|
||||
func (s *Spec) analyzeUnmapped(prefix string, extra map[string]any) {
|
||||
for key := range extra {
|
||||
s.analyzeUnmappedNode(slashpath.Join(prefix, jsonpointer.Escape(key)), extra[key])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Spec) analyzeUnmappedNode(refURI string, node any) {
|
||||
switch value := node.(type) {
|
||||
case map[string]any:
|
||||
if raw, ok := value["$ref"].(string); ok {
|
||||
ref, err := spec.NewRef(raw)
|
||||
if err != nil {
|
||||
return // a string under a "$ref" key is not necessarily a reference
|
||||
}
|
||||
|
||||
s.references.addUnmappedRef(refURI, ref)
|
||||
|
||||
return // a $ref makes its siblings irrelevant
|
||||
}
|
||||
|
||||
for key := range value {
|
||||
s.analyzeUnmappedNode(slashpath.Join(refURI, jsonpointer.Escape(key)), value[key])
|
||||
}
|
||||
case []any:
|
||||
for i := range value {
|
||||
s.analyzeUnmappedNode(slashpath.Join(refURI, strconv.Itoa(i)), value[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Spec) analyzeSchema(name string, schema *spec.Schema, prefix string) {
|
||||
refURI := slashpath.Join(prefix, jsonpointer.Escape(name))
|
||||
schRef := SchemaRef{
|
||||
@@ -981,6 +1046,8 @@ func (s *Spec) analyzeSchema(name string, schema *spec.Schema, prefix string) {
|
||||
s.enums.addSchemaEnum(refURI, schema.Enum)
|
||||
}
|
||||
|
||||
s.analyzeUnmapped(refURI, schema.ExtraProps)
|
||||
|
||||
for k, v := range schema.Definitions {
|
||||
s.analyzeSchema(k, &v, slashpath.Join(refURI, "definitions"))
|
||||
}
|
||||
@@ -1053,6 +1120,13 @@ func cloneStringMap(source map[string]string) map[string]string {
|
||||
return res
|
||||
}
|
||||
|
||||
func cloneRefMap(source map[string]spec.Ref) map[string]spec.Ref {
|
||||
res := make(map[string]spec.Ref, len(source))
|
||||
maps.Copy(res, source)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func cloneEnumMap(source map[string][]any) map[string][]any {
|
||||
res := make(map[string][]any, len(source))
|
||||
maps.Copy(res, source)
|
||||
|
||||
+33
-14
@@ -5,6 +5,7 @@ package analysis
|
||||
|
||||
import (
|
||||
"log"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -179,18 +180,20 @@ func normalizeRef(opts *FlattenOpts) error {
|
||||
debugLog("normalizeRef")
|
||||
|
||||
altered := false
|
||||
for k, w := range opts.Spec.references.allRefs {
|
||||
if !strings.HasPrefix(w.String(), opts.BasePath+definitionsPath) { // may be a mix of / and \, depending on OS
|
||||
continue
|
||||
}
|
||||
for _, refs := range []map[string]spec.Ref{opts.Spec.references.allRefs, opts.Spec.references.unmappedRefs} {
|
||||
for k, w := range refs {
|
||||
if !strings.HasPrefix(w.String(), opts.BasePath+definitionsPath) { // may be a mix of / and \, depending on OS
|
||||
continue
|
||||
}
|
||||
|
||||
altered = true
|
||||
debugLog("stripping absolute path for: %s", w.String())
|
||||
altered = true
|
||||
debugLog("stripping absolute path for: %s", w.String())
|
||||
|
||||
// strip the base path from definition
|
||||
if err := replace.UpdateRef(opts.Swagger(), k,
|
||||
spec.MustCreateRef(path.Join(definitionsPath, path.Base(w.String())))); err != nil {
|
||||
return err
|
||||
// strip the base path from definition
|
||||
if err := replace.UpdateRef(opts.Swagger(), k,
|
||||
spec.MustCreateRef(path.Join(definitionsPath, path.Base(w.String())))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +279,10 @@ func removeUnusedSinglePass(opts *FlattenOpts) (hasRemoved bool) {
|
||||
delete(expected, k)
|
||||
}
|
||||
|
||||
for _, ref := range opts.Spec.references.unmappedRefs {
|
||||
delete(expected, ref.String())
|
||||
}
|
||||
|
||||
for k := range expected {
|
||||
hasRemoved = true
|
||||
debugLog("removing unused definition %s", path.Base(k))
|
||||
@@ -327,9 +334,11 @@ func importNewRef(entry sortref.RefRevIdx, refStr string, opts *FlattenOpts) err
|
||||
partialAnalyzer.analyzeSchema("", sch, "/")
|
||||
|
||||
// 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)
|
||||
for _, refs := range []map[string]spec.Ref{partialAnalyzer.references.allRefs, partialAnalyzer.references.unmappedRefs} {
|
||||
for key, ref := range refs {
|
||||
if err := replace.UpdateRef(sch, key, spec.MustCreateRef(normalize.RebaseRef(entry.Ref.String(), ref.String()))); err != nil {
|
||||
return ErrRewriteRef(key, entry.Ref.String(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,10 +383,20 @@ func importNewRef(entry sortref.RefRevIdx, refStr string, opts *FlattenOpts) err
|
||||
// At every iteration, new remotes may be found when digging deeper: they are rebased to the current schema before being imported.
|
||||
//
|
||||
// This returns true when no more remote references can be found.
|
||||
// importableRefs returns every $ref that flatten has to bring into the root document: the ones the
|
||||
// model maps, and the ones held by keywords it does not.
|
||||
func importableRefs(sp *Spec) map[string]spec.Ref {
|
||||
refs := make(map[string]spec.Ref, len(sp.references.schemas)+len(sp.references.unmappedRefs))
|
||||
maps.Copy(refs, sp.references.schemas)
|
||||
maps.Copy(refs, sp.references.unmappedRefs)
|
||||
|
||||
return refs
|
||||
}
|
||||
|
||||
func importExternalReferences(opts *FlattenOpts) (bool, error) {
|
||||
debugLog("importExternalReferences")
|
||||
|
||||
groupedRefs := sortref.ReverseIndex(opts.Spec.references.schemas, opts.BasePath)
|
||||
groupedRefs := sortref.ReverseIndex(importableRefs(opts.Spec), opts.BasePath)
|
||||
sortedRefStr := make([]string, 0, len(groupedRefs))
|
||||
if opts.flattenContext == nil {
|
||||
opts.flattenContext = newContext()
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
go 1.25.0
|
||||
go 1.26.0
|
||||
|
||||
use (
|
||||
.
|
||||
|
||||
+12
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
const (
|
||||
definitionsPath = "#/definitions"
|
||||
jsonRef = "$ref"
|
||||
allocMediumMap = 64
|
||||
)
|
||||
|
||||
@@ -217,6 +218,17 @@ func UpdateRef(sp any, key string, ref spec.Ref) error {
|
||||
switch refable := value.(type) {
|
||||
case *spec.Schema:
|
||||
refable.Ref = ref
|
||||
case map[string]any:
|
||||
// a keyword the Swagger 2.0 model does not map: the node is raw JSON, and the map it
|
||||
// holds is the one in the document - writing to it reaches the document
|
||||
refable[jsonRef] = ref.String()
|
||||
case *any:
|
||||
raw, ok := (*refable).(map[string]any)
|
||||
if !ok {
|
||||
return ErrNoSchemaWithRef(key, value)
|
||||
}
|
||||
|
||||
raw[jsonRef] = ref.String()
|
||||
case *spec.SchemaOrArray:
|
||||
if refable.Schema != nil {
|
||||
refable.Schema.Ref = ref
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# revive configuration used by codefactor.io.
|
||||
#
|
||||
# This is redundant with .golangci.yml, since revive is part of golangci-lint,
|
||||
# but we need to configure it to remain consistent across reporting tools.
|
||||
|
||||
ignore-generated-header = false
|
||||
severity = "warning"
|
||||
confidence = 0.8
|
||||
error-code = 0
|
||||
warning-code = 0
|
||||
enable-default-rules = true
|
||||
|
||||
[rule.exported]
|
||||
disabled = true
|
||||
[rule.cyclomatic]
|
||||
arguments = [25]
|
||||
[rule.cognitive-complexity]
|
||||
arguments = [25]
|
||||
+2
-2
@@ -4,12 +4,12 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 13 | 115 |
|
||||
| 13 | 121 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @casualjim | 58 | <https://github.com/go-openapi/errors/commits?author=casualjim> |
|
||||
| @fredbi | 41 | <https://github.com/go-openapi/errors/commits?author=fredbi> |
|
||||
| @fredbi | 47 | <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> |
|
||||
|
||||
+8
-12
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -18,12 +18,9 @@ Shared errors and error interface used throughout the various libraries found in
|
||||
|
||||
* **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.
|
||||
@@ -69,9 +66,9 @@ 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)
|
||||
* [Contributing guidelines][contributing-doc-site]
|
||||
* [Maintainers documentation][maintainers-doc-site]
|
||||
* [Code style][style-doc-site]
|
||||
|
||||
## Cutting a new release
|
||||
|
||||
@@ -95,16 +92,11 @@ Maintainers can cut a new release by either:
|
||||
[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/FfnFYaC3k5
|
||||
|
||||
@@ -116,3 +108,7 @@ Maintainers can cut a new release by either:
|
||||
[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
|
||||
<!-- Organization docs -->
|
||||
[contributing-doc-site]: https://go-openapi.github.io/doc-site/contributing/contributing/index.html
|
||||
[maintainers-doc-site]: https://go-openapi.github.io/doc-site/maintainers/index.html
|
||||
[style-doc-site]: https://go-openapi.github.io/doc-site/contributing/style/index.html
|
||||
+2
-2
@@ -4,11 +4,11 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 13 | 132 |
|
||||
| 13 | 136 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @fredbi | 83 | <https://github.com/go-openapi/jsonpointer/commits?author=fredbi> |
|
||||
| @fredbi | 87 | <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> |
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -152,8 +152,6 @@ Maintainers can cut a new release by either:
|
||||
[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 -->
|
||||
|
||||
+1
-1
@@ -284,7 +284,7 @@ func (p *Pointer) set(node, data any, nameProvider NameProvider) (any, error) {
|
||||
// new child reference (e.g. a slice header returned by an "-" append) into its parent on the way
|
||||
// back up.
|
||||
//
|
||||
// Returning the (possibly new) node at each level is what makes append work at any depth without
|
||||
// Returning the (possibly new) node at each level makes append work at any depth without
|
||||
// requiring the caller to pass a pointer to the containing slice: the new slice header propagates
|
||||
// up and each parent rebinds it via the appropriate kind-specific setter.
|
||||
func (p *Pointer) setAt(node any, tokens []string, data any, nameProvider NameProvider) (any, error) {
|
||||
|
||||
+2
-2
@@ -4,11 +4,11 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 9 | 83 |
|
||||
| 9 | 84 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @fredbi | 46 | <https://github.com/go-openapi/jsonreference/commits?author=fredbi> |
|
||||
| @fredbi | 47 | <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> |
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -97,8 +97,6 @@ Maintainers can cut a new release by either:
|
||||
[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 -->
|
||||
|
||||
+59
-20
@@ -5,19 +5,12 @@ package internal
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHTTPPort = ":80"
|
||||
defaultHTTPSPort = ":443"
|
||||
)
|
||||
|
||||
// Regular expressions used by the normalizations.
|
||||
var (
|
||||
rxPort = regexp.MustCompile(`(:\d+)/?$`)
|
||||
rxDupSlashes = regexp.MustCompile(`/{2,}`)
|
||||
defaultHTTPPort = "80"
|
||||
defaultHTTPSPort = "443"
|
||||
)
|
||||
|
||||
// NormalizeURL will normalize the specified URL
|
||||
@@ -55,20 +48,66 @@ func lowercaseHost(u *url.URL) {
|
||||
}
|
||||
}
|
||||
|
||||
// removeDefaultPort drops :80 from an http URL and :443 from an https one.
|
||||
//
|
||||
// The port stays when dropping it would leave an authority url.Parse no longer accepts, so the
|
||||
// shortened host is parsed before being kept. url.Parse reads "https://:a:443" as the host ":a"
|
||||
// on port 443, and ":a" on its own is an invalid port, so "https://:a" no longer parses.
|
||||
//
|
||||
// A degenerate authority can spell a default port twice - url.Parse reads "http://:80:80" as the
|
||||
// host ":80" on port 80 - so removal repeats until nothing more comes off. Each pass shortens the
|
||||
// host, so the loop ends, and normalizing the result again changes nothing.
|
||||
func removeDefaultPort(u *url.URL) {
|
||||
if len(u.Host) > 0 {
|
||||
scheme := strings.ToLower(u.Scheme)
|
||||
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
|
||||
if (scheme == "http" && val == defaultHTTPPort) || (scheme == "https" && val == defaultHTTPSPort) {
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
})
|
||||
for {
|
||||
port := u.Port()
|
||||
if port == "" || port != defaultPortForScheme(strings.ToLower(u.Scheme)) {
|
||||
return
|
||||
}
|
||||
|
||||
host := strings.TrimSuffix(u.Host, ":"+port)
|
||||
if _, err := url.Parse("//" + host); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
u.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
func removeDuplicateSlashes(u *url.URL) {
|
||||
if len(u.Path) > 0 {
|
||||
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
|
||||
func defaultPortForScheme(scheme string) string {
|
||||
switch scheme {
|
||||
case "http":
|
||||
return defaultHTTPPort
|
||||
case "https":
|
||||
return defaultHTTPSPort
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// removeDuplicateSlashes collapses every run of slashes in the path to a single one, however
|
||||
// long the run is: "/a//b///c" becomes "/a/b/c".
|
||||
//
|
||||
// A path holding no "//" is left as it is, which is the common case and costs one scan and no
|
||||
// allocation.
|
||||
func removeDuplicateSlashes(u *url.URL) {
|
||||
const doubleSlash = "//"
|
||||
|
||||
start := strings.Index(u.Path, doubleSlash)
|
||||
if start < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var collapsed strings.Builder
|
||||
collapsed.Grow(len(u.Path))
|
||||
collapsed.WriteString(u.Path[:start+1]) // everything up to and including the first slash of the run
|
||||
|
||||
for i := start + 1; i < len(u.Path); i++ {
|
||||
c := u.Path[i]
|
||||
if c == '/' && u.Path[i-1] == '/' {
|
||||
continue
|
||||
}
|
||||
collapsed.WriteByte(c)
|
||||
}
|
||||
|
||||
u.Path = collapsed.String()
|
||||
}
|
||||
+2
-2
@@ -4,11 +4,11 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 14 | 136 |
|
||||
| 14 | 139 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @fredbi | 58 | <https://github.com/go-openapi/loads/commits?author=fredbi> |
|
||||
| @fredbi | 61 | <https://github.com/go-openapi/loads/commits?author=fredbi> |
|
||||
| @casualjim | 48 | <https://github.com/go-openapi/loads/commits?author=casualjim> |
|
||||
| @youyuanwu | 6 | <https://github.com/go-openapi/loads/commits?author=youyuanwu> |
|
||||
| @vburenin | 4 | <https://github.com/go-openapi/loads/commits?author=vburenin> |
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -127,8 +127,6 @@ Maintainers can cut a new release by either:
|
||||
[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 -->
|
||||
|
||||
+20
-1
@@ -3,6 +3,11 @@
|
||||
|
||||
package loads
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type loaderError string
|
||||
|
||||
func (e loaderError) Error() string {
|
||||
@@ -11,7 +16,7 @@ func (e loaderError) Error() string {
|
||||
|
||||
const (
|
||||
// ErrLoads is an error returned by the loads package.
|
||||
ErrLoads loaderError = "loaderrs error"
|
||||
ErrLoads loaderError = "cannot load spec"
|
||||
|
||||
// ErrNoLoader indicates that no configured loader matched the input.
|
||||
ErrNoLoader loaderError = "no loader matched"
|
||||
@@ -20,3 +25,17 @@ const (
|
||||
// to a non-public address (loopback, private, link-local, or unspecified).
|
||||
ErrForbiddenAddress loaderError = "blocked dial to a non-public address"
|
||||
)
|
||||
|
||||
// errLoads marks err as an error from this package, so callers may test it with
|
||||
// [errors.Is] against [ErrLoads].
|
||||
//
|
||||
// The cause is reported on a single line, after the sentinel. An error that already
|
||||
// carries the sentinel is returned unchanged: loaders are chained, and reporting
|
||||
// "cannot load spec" once per link in the chain would tell the caller nothing.
|
||||
func errLoads(err error) error {
|
||||
if err == nil || errors.Is(err, ErrLoads) {
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %w", ErrLoads, err)
|
||||
}
|
||||
+3
-4
@@ -5,7 +5,6 @@ package loads
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"slices"
|
||||
|
||||
@@ -162,7 +161,7 @@ func (l *loader) WithNext(next *loader) *loader {
|
||||
func (l *loader) Load(path string) (json.RawMessage, error) {
|
||||
_, erp := url.Parse(path)
|
||||
if erp != nil {
|
||||
return nil, errors.Join(erp, ErrLoads)
|
||||
return nil, errLoads(erp)
|
||||
}
|
||||
|
||||
var lastErr error = ErrNoLoader // default error if no match was found
|
||||
@@ -180,7 +179,7 @@ func (l *loader) Load(path string) (json.RawMessage, error) {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
return nil, errors.Join(lastErr, ErrLoads)
|
||||
return nil, errLoads(lastErr)
|
||||
}
|
||||
|
||||
func (l *loader) clone() *loader {
|
||||
@@ -202,7 +201,7 @@ func (l *loader) clone() *loader {
|
||||
func JSONDoc(path string, opts ...loading.Option) (json.RawMessage, error) {
|
||||
data, err := loading.LoadFromFileOrHTTP(path, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.Join(err, ErrLoads)
|
||||
return nil, errLoads(err)
|
||||
}
|
||||
return json.RawMessage(data), nil
|
||||
}
|
||||
|
||||
+14
-18
@@ -5,22 +5,16 @@ package loads
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/swag/jsonutils"
|
||||
"github.com/go-openapi/swag/yamlutils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(map[string]any{})
|
||||
gob.Register([]any{})
|
||||
}
|
||||
|
||||
// Document represents a swagger spec document.
|
||||
type Document struct {
|
||||
// specAnalyzer
|
||||
@@ -110,7 +104,7 @@ func Analyzed(data json.RawMessage, version string, options ...LoaderOption) (*D
|
||||
version = "2.0"
|
||||
}
|
||||
if version != "2.0" {
|
||||
return nil, fmt.Errorf("spec version %q is not supported: %w", version, ErrLoads)
|
||||
return nil, fmt.Errorf("%w: spec version %q is not supported", ErrLoads, version)
|
||||
}
|
||||
|
||||
raw, err := trimData(data) // trim blanks, then convert yaml docs into json
|
||||
@@ -120,12 +114,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, errLoads(err)
|
||||
}
|
||||
|
||||
origsqspec, err := cloneSpec(swspec)
|
||||
if err != nil {
|
||||
return nil, errors.Join(err, ErrLoads)
|
||||
return nil, errLoads(err)
|
||||
}
|
||||
|
||||
d := &Document{
|
||||
@@ -153,12 +147,12 @@ func trimData(in json.RawMessage) (json.RawMessage, error) {
|
||||
// assume yaml doc: convert it to json
|
||||
yml, err := yamlutils.BytesToYAMLDoc(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("analyzed: %w: %w", err, ErrLoads)
|
||||
return nil, fmt.Errorf("analyzed: %w", errLoads(err))
|
||||
}
|
||||
|
||||
d, err := yamlutils.YAMLToJSON(yml)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("analyzed: %w: %w", err, ErrLoads)
|
||||
return nil, fmt.Errorf("analyzed: %w", errLoads(err))
|
||||
}
|
||||
|
||||
return d, nil
|
||||
@@ -277,14 +271,16 @@ func (d *Document) SpecFilePath() string {
|
||||
return d.specFilePath
|
||||
}
|
||||
|
||||
// cloneSpec deep-copies a specification through its JSON representation.
|
||||
//
|
||||
// It used to go through gob, which omits a field holding the zero value for its type and
|
||||
// flattens a pointer to what it points at, so an optional number that was present and zero came
|
||||
// back nil: "minimum": 0, "maximum": 0, "maxLength": 0, "maxItems": 0 and "maxProperties": 0 were
|
||||
// dropped from the copy, at any depth and with no error. That copy is what OrigSpec returns and
|
||||
// what ResetDefinitions writes back over the live definitions.
|
||||
func cloneSpec(src *spec.Swagger) (*spec.Swagger, error) {
|
||||
var b bytes.Buffer
|
||||
if err := gob.NewEncoder(&b).Encode(src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dst spec.Swagger
|
||||
if err := gob.NewDecoder(&b).Decode(&dst); err != nil {
|
||||
if err := jsonutils.FromDynamicJSON(src, &dst); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -5,6 +5,7 @@ linters:
|
||||
- depguard
|
||||
- err113 # disabled temporarily: there are just too many issues to address
|
||||
- exhaustruct
|
||||
- exhaustruct_v5
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
|
||||
+7
-5
@@ -4,21 +4,21 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 71 | 570 |
|
||||
| 73 | 581 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @casualjim | 268 | <https://github.com/go-openapi/runtime/commits?author=casualjim> |
|
||||
| @fredbi | 144 | <https://github.com/go-openapi/runtime/commits?author=fredbi> |
|
||||
| @fredbi | 152 | <https://github.com/go-openapi/runtime/commits?author=fredbi> |
|
||||
| @youyuanwu | 19 | <https://github.com/go-openapi/runtime/commits?author=youyuanwu> |
|
||||
| @josephwoodward | 13 | <https://github.com/go-openapi/runtime/commits?author=josephwoodward> |
|
||||
| @kenjones-cisco | 12 | <https://github.com/go-openapi/runtime/commits?author=kenjones-cisco> |
|
||||
| @GlenDC | 7 | <https://github.com/go-openapi/runtime/commits?author=GlenDC> |
|
||||
| @moenning | 6 | <https://github.com/go-openapi/runtime/commits?author=moenning> |
|
||||
| @mstoykov | 6 | <https://github.com/go-openapi/runtime/commits?author=mstoykov> |
|
||||
| @moenning | 6 | <https://github.com/go-openapi/runtime/commits?author=moenning> |
|
||||
| @elakito | 6 | <https://github.com/go-openapi/runtime/commits?author=elakito> |
|
||||
| @Copilot | 5 | <https://github.com/go-openapi/runtime/commits?author=Copilot> |
|
||||
| @ifraixedes | 5 | <https://github.com/go-openapi/runtime/commits?author=ifraixedes> |
|
||||
| @Copilot | 4 | <https://github.com/go-openapi/runtime/commits?author=Copilot> |
|
||||
| @zeitlinger | 4 | <https://github.com/go-openapi/runtime/commits?author=zeitlinger> |
|
||||
| @jkawamoto | 3 | <https://github.com/go-openapi/runtime/commits?author=jkawamoto> |
|
||||
| @stoyanr | 3 | <https://github.com/go-openapi/runtime/commits?author=stoyanr> |
|
||||
@@ -40,12 +40,13 @@
|
||||
| @tschaub | 1 | <https://github.com/go-openapi/runtime/commits?author=tschaub> |
|
||||
| @pytlesk4 | 1 | <https://github.com/go-openapi/runtime/commits?author=pytlesk4> |
|
||||
| @tgraf | 1 | <https://github.com/go-openapi/runtime/commits?author=tgraf> |
|
||||
| @delthas | 1 | <https://github.com/go-openapi/runtime/commits?author=delthas> |
|
||||
| @seanprince | 1 | <https://github.com/go-openapi/runtime/commits?author=seanprince> |
|
||||
| @rodriguise | 1 | <https://github.com/go-openapi/runtime/commits?author=rodriguise> |
|
||||
| @petrkotas | 1 | <https://github.com/go-openapi/runtime/commits?author=petrkotas> |
|
||||
| @fpawel | 1 | <https://github.com/go-openapi/runtime/commits?author=fpawel> |
|
||||
| @maxatome | 1 | <https://github.com/go-openapi/runtime/commits?author=maxatome> |
|
||||
| @maxkarelov | 1 | <https://github.com/go-openapi/runtime/commits?author=maxkarelov> |
|
||||
| @tooolbox | 1 | <https://github.com/go-openapi/runtime/commits?author=tooolbox> |
|
||||
| @akutz | 1 | <https://github.com/go-openapi/runtime/commits?author=akutz> |
|
||||
| @yabberyabber | 1 | <https://github.com/go-openapi/runtime/commits?author=yabberyabber> |
|
||||
| @elv-gilles | 1 | <https://github.com/go-openapi/runtime/commits?author=elv-gilles> |
|
||||
@@ -79,5 +80,6 @@
|
||||
| @KuaaMU | 1 | <https://github.com/go-openapi/runtime/commits?author=KuaaMU> |
|
||||
| @germanhs | 1 | <https://github.com/go-openapi/runtime/commits?author=germanhs> |
|
||||
| @pracucci | 1 | <https://github.com/go-openapi/runtime/commits?author=pracucci> |
|
||||
| @tooolbox | 1 | <https://github.com/go-openapi/runtime/commits?author=tooolbox> |
|
||||
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github-community-projects/contributors)_
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url]
|
||||
<!-- Badges: documentation & support -->
|
||||
<!-- Badges: others & stats -->
|
||||
[![Doc][doc-badge]][doc-url] [![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]
|
||||
@@ -123,8 +123,6 @@ Maintainers can cut a new release by either:
|
||||
[release-badge]: https://badge.fury.io/gh/go-openapi%2Fruntime.svg
|
||||
[release-url]: https://badge.fury.io/gh/go-openapi%2Fruntime
|
||||
<!-- Badges: code quality -->
|
||||
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/runtime
|
||||
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/runtime
|
||||
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/runtime
|
||||
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/runtime
|
||||
<!-- Badges: documentation & support -->
|
||||
|
||||
+6
-1
@@ -35,6 +35,7 @@ func (k *keepAliveTransport) RoundTrip(r *http.Request) (*http.Response, error)
|
||||
type drainingReadCloser struct {
|
||||
rdr io.ReadCloser
|
||||
seenEOF atomic.Uint32
|
||||
discard io.Writer
|
||||
}
|
||||
|
||||
func (d *drainingReadCloser) Read(p []byte) (n int, err error) {
|
||||
@@ -51,7 +52,11 @@ func (d *drainingReadCloser) Close() error {
|
||||
// If the reader side (a HTTP server) is misbehaving, it still may send
|
||||
// some bytes, but the closer ignores them to keep the underling
|
||||
// connection open.
|
||||
_, _ = io.Copy(io.Discard, d.rdr)
|
||||
discard := d.discard
|
||||
if discard == nil {
|
||||
discard = io.Discard
|
||||
}
|
||||
_, _ = io.Copy(discard, d.rdr)
|
||||
}
|
||||
return d.rdr.Close()
|
||||
}
|
||||
+1
-1
@@ -5,4 +5,4 @@ use (
|
||||
./server-middleware
|
||||
)
|
||||
|
||||
go 1.25.0
|
||||
go 1.26.0
|
||||
+12
-1
@@ -342,6 +342,10 @@ func (s *MultipartFormStream) Drain() error {
|
||||
// particular, a net/http server request body may discard a limited amount of
|
||||
// unread data to allow connection reuse, so Close is not guaranteed to return
|
||||
// immediately. Call Drain when trailing form fields must be collected.
|
||||
//
|
||||
// Close returns the error reported by the request body, except io.EOF: a
|
||||
// net/http server body reports io.EOF once it discards that unread remainder,
|
||||
// which tells us the body ended, not that closing it failed.
|
||||
func (s *MultipartFormStream) Close() error {
|
||||
if s == nil || s.closed {
|
||||
return nil
|
||||
@@ -357,7 +361,14 @@ func (s *MultipartFormStream) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.request.Body.Close()
|
||||
// Since go1.27.0, net/http (*body).Close returns io.EOF when it discards
|
||||
// the unread remainder of the request itself. Reaching the end of the body
|
||||
// is not a close failure.
|
||||
if err := s.request.Body.Close(); err != nil && !stderrors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func supportsMultipartFormStream(method string) bool {
|
||||
|
||||
+20
-7
@@ -197,18 +197,14 @@ func Parse(s string) (MediaType, error) {
|
||||
if plus := strings.LastIndexByte(mt.Subtype, '+'); plus >= 0 && plus < len(mt.Subtype)-1 {
|
||||
mt.Suffix = mt.Subtype[plus+1:]
|
||||
}
|
||||
|
||||
if q, ok := params["q"]; ok {
|
||||
if qf, perr := strconv.ParseFloat(q, 64); perr == nil {
|
||||
if qf < 0 {
|
||||
qf = 0
|
||||
}
|
||||
if qf > 1 {
|
||||
qf = 1
|
||||
}
|
||||
if qf, isFloat := boundedQ(q); isFloat {
|
||||
mt.Q = qf
|
||||
}
|
||||
delete(params, "q")
|
||||
}
|
||||
|
||||
if len(params) > 0 {
|
||||
mt.Params = params
|
||||
}
|
||||
@@ -267,6 +263,23 @@ func (m MediaType) Specificity() int {
|
||||
return SpecificityExactWithParams
|
||||
}
|
||||
|
||||
func boundedQ(q string) (float64, bool) {
|
||||
qf, err := strconv.ParseFloat(q, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if qf < 0 {
|
||||
qf = 0
|
||||
}
|
||||
|
||||
if qf > 1 {
|
||||
qf = 1
|
||||
}
|
||||
|
||||
return qf, true
|
||||
}
|
||||
|
||||
// typeAgrees reports whether two top-level types match, allowing "*" on
|
||||
// either side. A type of "*" without a "*" subtype is rejected per RFC
|
||||
// 7231 §5.3.2 ("*/sub" is not valid), but Parse never produces such a
|
||||
|
||||
Generated
Vendored
+7
-1
@@ -300,7 +300,13 @@ func expectQuality(s string) (q float64, rest string) {
|
||||
n = n*10 + int(b) - '0'
|
||||
d *= 10
|
||||
}
|
||||
return q + float64(n)/float64(d), s[i:]
|
||||
result := q + float64(n)/float64(d)
|
||||
// RFC 7231 §5.3.1: qvalue is in [0, 1]. Inputs like "1.1"
|
||||
// would otherwise yield > 1; reject as malformed.
|
||||
if result > 1 {
|
||||
return -1, s[i:]
|
||||
}
|
||||
return result, s[i:]
|
||||
}
|
||||
|
||||
func expectTokenOrQuoted(s string) (value string, rest string) {
|
||||
|
||||
+10
@@ -89,6 +89,16 @@ func WithMatchSuffix(enable bool) Option {
|
||||
//
|
||||
// Encoding tokens have no parameters, so this function is unaffected by
|
||||
// the v0.30 parameter-honouring change to [ContentType].
|
||||
//
|
||||
// Deprecated: ContentEncoding negotiation is not used by the components
|
||||
// of this project.
|
||||
//
|
||||
// This historical addition has never been associated with proper
|
||||
// compression middleware and is thus half a feature.
|
||||
// The runtime does not ship compression.
|
||||
// Use github.com/CAFxX/httpcompression or github.com/klauspost/compress/gzhttp
|
||||
// at the http.Handler level, or github.com/klauspost/compress/* for client
|
||||
// transport wrapping. See docs/examples/middleware for a recipe.
|
||||
func ContentEncoding(r *http.Request, offers []string) string {
|
||||
bestOffer := "identity"
|
||||
bestQ := -1.0
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ linters:
|
||||
- gomodguard
|
||||
- gomodguard_v2
|
||||
- exhaustruct
|
||||
- exhaustruct_v5
|
||||
- nlreturn
|
||||
- nonamedreturns
|
||||
- noinlineerr
|
||||
|
||||
+3
-2
@@ -4,12 +4,12 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 38 | 403 |
|
||||
| 39 | 439 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @casualjim | 191 | <https://github.com/go-openapi/spec/commits?author=casualjim> |
|
||||
| @fredbi | 101 | <https://github.com/go-openapi/spec/commits?author=fredbi> |
|
||||
| @fredbi | 136 | <https://github.com/go-openapi/spec/commits?author=fredbi> |
|
||||
| @pytlesk4 | 26 | <https://github.com/go-openapi/spec/commits?author=pytlesk4> |
|
||||
| @kul-amr | 10 | <https://github.com/go-openapi/spec/commits?author=kul-amr> |
|
||||
| @keramix | 10 | <https://github.com/go-openapi/spec/commits?author=keramix> |
|
||||
@@ -46,5 +46,6 @@
|
||||
| @sttts | 1 | <https://github.com/go-openapi/spec/commits?author=sttts> |
|
||||
| @ChandanChainani | 1 | <https://github.com/go-openapi/spec/commits?author=ChandanChainani> |
|
||||
| @bvwells | 1 | <https://github.com/go-openapi/spec/commits?author=bvwells> |
|
||||
| @amoudani | 1 | <https://github.com/go-openapi/spec/commits?author=amoudani> |
|
||||
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github-community-projects/contributors)_
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -120,8 +120,6 @@ Maintainers can cut a new release by either:
|
||||
[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fspec.svg
|
||||
[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fspec
|
||||
<!-- Badges: code quality -->
|
||||
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/spec
|
||||
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/spec
|
||||
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/spec
|
||||
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/spec
|
||||
<!-- Badges: documentation & support -->
|
||||
|
||||
+110
-10
@@ -4,8 +4,12 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/go-openapi/swag/loading"
|
||||
)
|
||||
@@ -109,7 +113,8 @@ func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
|
||||
specBasePath := options.RelativeBase
|
||||
|
||||
if !options.SkipSchemas {
|
||||
for key, definition := range spec.Definitions {
|
||||
for key := range sortedKeys(spec.Definitions) {
|
||||
definition := spec.Definitions[key]
|
||||
parentRefs := make([]string, 0, smallPrealloc)
|
||||
parentRefs = append(parentRefs, "#/definitions/"+key)
|
||||
|
||||
@@ -123,7 +128,7 @@ func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
for key := range spec.Parameters {
|
||||
for key := range sortedKeys(spec.Parameters) {
|
||||
parameter := spec.Parameters[key]
|
||||
if err := expandParameterOrResponse(¶meter, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
@@ -131,7 +136,7 @@ func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
|
||||
spec.Parameters[key] = parameter
|
||||
}
|
||||
|
||||
for key := range spec.Responses {
|
||||
for key := range sortedKeys(spec.Responses) {
|
||||
response := spec.Responses[key]
|
||||
if err := expandParameterOrResponse(&response, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
@@ -140,7 +145,7 @@ func ExpandSpec(spec *Swagger, options *ExpandOptions) error {
|
||||
}
|
||||
|
||||
if spec.Paths != nil {
|
||||
for key := range spec.Paths.Paths {
|
||||
for key := range sortedKeys(spec.Paths.Paths) {
|
||||
pth := spec.Paths.Paths[key]
|
||||
if err := expandPathItem(&pth, resolver, specBasePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
@@ -278,6 +283,38 @@ func expandItems(target Schema, parentRefs []string, resolver *schemaLoader, bas
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// sortedKeys walks the keys of a map in a fixed order.
|
||||
//
|
||||
// Expansion inlines the first branch that reaches a cycle and leaves a $ref on the others, so
|
||||
// the order the walk visits siblings in decides which node ends up holding the $ref. Ranging a
|
||||
// map straight gives that decision to Go's map iteration, and the same document then expands
|
||||
// differently from one run to the next - see go-openapi/spec#93.
|
||||
//
|
||||
// A map of fewer than two keys has only one order, so it is yielded without sorting: schemata
|
||||
// with a single property or definition are most of what a walk of a large document visits, and
|
||||
// the slice this would otherwise allocate is paid at every node.
|
||||
func sortedKeys[K cmp.Ordered, V any](m map[K]V) iter.Seq[K] {
|
||||
const alreadyOrdered = 2 // a map of fewer keys than this has only one order
|
||||
|
||||
return func(yield func(K) bool) {
|
||||
if len(m) < alreadyOrdered {
|
||||
for key := range m {
|
||||
yield(key)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range slices.Sorted(maps.Keys(m)) {
|
||||
if !yield(key) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocognit,gocyclo,cyclop // complex but well-tested $ref expansion logic; refactoring deferred to dedicated PR
|
||||
func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||
if err := resolver.context.countNode(); err != nil {
|
||||
@@ -312,7 +349,9 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
for k := range target.Definitions {
|
||||
rebaseExtraRefs(target.ExtraProps, resolver, basePath)
|
||||
|
||||
for k := range sortedKeys(target.Definitions) {
|
||||
tt, err := expandSchema(target.Definitions[k], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
@@ -370,7 +409,7 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba
|
||||
}
|
||||
}
|
||||
|
||||
for k := range target.Properties {
|
||||
for k := range sortedKeys(target.Properties) {
|
||||
t, err := expandSchema(target.Properties[k], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
@@ -390,7 +429,7 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba
|
||||
}
|
||||
}
|
||||
|
||||
for k := range target.PatternProperties {
|
||||
for k := range sortedKeys(target.PatternProperties) {
|
||||
t, err := expandSchema(target.PatternProperties[k], parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
return &target, err
|
||||
@@ -400,7 +439,7 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba
|
||||
}
|
||||
}
|
||||
|
||||
for k := range target.Dependencies {
|
||||
for k := range sortedKeys(target.Dependencies) {
|
||||
if target.Dependencies[k].Schema != nil {
|
||||
t, err := expandSchema(*target.Dependencies[k].Schema, parentRefs, resolver, basePath)
|
||||
if resolver.shouldStopOnError(err) {
|
||||
@@ -424,6 +463,67 @@ func expandSchema(target Schema, parentRefs []string, resolver *schemaLoader, ba
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// rebaseExtraRefs rewrites the $ref held by keywords this model does not map, so that
|
||||
// they still point at their target once the schema is inlined into another document.
|
||||
//
|
||||
// expandSchema walks the fields of [Schema] and stops there. A keyword the Swagger 2.0
|
||||
// model predates - propertyNames, contains, if/then/else, $defs - lands in ExtraProps as
|
||||
// raw JSON, and a $ref inside it is copied into the root verbatim: "#/definitions/leaf"
|
||||
// then names a definition of the root document instead of the one it came from.
|
||||
//
|
||||
// Rebasing makes the pointer correct. It does not expand it, and it does not make the
|
||||
// expanded document self-contained: a $ref that came from another document keeps pointing
|
||||
// there.
|
||||
func rebaseExtraRefs(extra map[string]any, resolver *schemaLoader, basePath string) {
|
||||
for key := range extra {
|
||||
rebaseRawRefs(extra[key], resolver, basePath)
|
||||
}
|
||||
}
|
||||
|
||||
// rebaseRawRefs walks raw JSON and rebases every "$ref" string value it finds.
|
||||
func rebaseRawRefs(node any, resolver *schemaLoader, basePath string) {
|
||||
switch value := node.(type) {
|
||||
case map[string]any:
|
||||
for key := range value {
|
||||
if key == jsonRef {
|
||||
if ref, ok := value[key].(string); ok {
|
||||
if rebased, ok := rebaseRawRef(ref, resolver, basePath); ok {
|
||||
value[key] = rebased
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
rebaseRawRefs(value[key], resolver, basePath)
|
||||
}
|
||||
case []any:
|
||||
for i := range value {
|
||||
rebaseRawRefs(value[i], resolver, basePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rebaseRawRef resolves a $ref against basePath, then spells it relative to the document
|
||||
// being expanded, like the SkipSchemas branch of [expandSchema] does for a mapped $ref.
|
||||
//
|
||||
// It reports false when the $ref is empty or does not parse: an unmapped keyword may hold
|
||||
// any JSON, and a string under a "$ref" key is not necessarily a reference.
|
||||
func rebaseRawRef(ref string, resolver *schemaLoader, basePath string) (string, bool) {
|
||||
if ref == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
rebased, err := NewRef(normalizeURI(ref, basePath))
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
denormalized := denormalizeRef(&rebased, resolver.context.basePath, resolver.context.rootID)
|
||||
|
||||
return denormalized.String(), true
|
||||
}
|
||||
|
||||
func expandSchemaRef(target Schema, parentRefs []string, resolver *schemaLoader, basePath string) (*Schema, error) {
|
||||
// if a Ref is found, all sibling fields are skipped
|
||||
// Ref also changes the resolution scope of children expandSchema
|
||||
@@ -483,7 +583,7 @@ func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string)
|
||||
|
||||
pathItem.Ref = Ref{}
|
||||
for i := range pathItem.Parameters {
|
||||
if err := expandParameterOrResponse(&(pathItem.Parameters[i]), resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
if err := expandParameterOrResponse(&pathItem.Parameters[i], resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -528,7 +628,7 @@ func expandOperation(op *Operation, resolver *schemaLoader, basePath string) err
|
||||
return err
|
||||
}
|
||||
|
||||
for code := range responses.StatusCodeResponses {
|
||||
for code := range sortedKeys(responses.StatusCodeResponses) {
|
||||
response := responses.StatusCodeResponses[code]
|
||||
if err := expandParameterOrResponse(&response, resolver, basePath); resolver.shouldStopOnError(err) {
|
||||
return err
|
||||
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package spec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
// Optional numbers and gob.
|
||||
//
|
||||
// gob omits any struct field that holds the zero value for its type, and flattens a pointer to
|
||||
// the value it points at. An optional number that is present and zero - "minimum": 0, which the
|
||||
// JSON Schema meta-schema spells for every positiveInteger, or "maximum": 0 on a parameter -
|
||||
// therefore travels as the zero value, is omitted, and comes back as a nil pointer. The bound is
|
||||
// dropped and nothing reports it.
|
||||
//
|
||||
// [Schema], [Parameter], [Header] and [Items] are the types that carry those numbers, so each
|
||||
// sends which of them were present-and-zero alongside the struct, and puts the zero back on the
|
||||
// way in. The encoders sit on the outer types rather than on CommonValidations or SchemaProps
|
||||
// because a method on an embedded type is promoted to the type embedding it: gob would then call
|
||||
// it for the whole value and drop everything the embedded type does not hold. [Swagger] and
|
||||
// [Operation] already carry their own encoders for the same reason.
|
||||
|
||||
// zeroBounds records which optional numbers of a value were present and zero.
|
||||
type zeroBounds uint16
|
||||
|
||||
const (
|
||||
zeroMaximum zeroBounds = 1 << iota
|
||||
zeroMinimum
|
||||
zeroMaxLength
|
||||
zeroMinLength
|
||||
zeroMaxItems
|
||||
zeroMinItems
|
||||
zeroMultipleOf
|
||||
zeroMaxProperties
|
||||
zeroMinProperties
|
||||
)
|
||||
|
||||
func floatBound(value *float64, bit zeroBounds) zeroBounds {
|
||||
if value != nil && *value == 0 {
|
||||
return bit
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func intBound(value *int64, bit zeroBounds) zeroBounds {
|
||||
if value != nil && *value == 0 {
|
||||
return bit
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func restoreFloat(value **float64, bounds, bit zeroBounds) {
|
||||
if bounds&bit != 0 && *value == nil {
|
||||
*value = new(float64)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreInt(value **int64, bounds, bit zeroBounds) {
|
||||
if bounds&bit != 0 && *value == nil {
|
||||
*value = new(int64)
|
||||
}
|
||||
}
|
||||
|
||||
func commonBounds(v CommonValidations) zeroBounds {
|
||||
return floatBound(v.Maximum, zeroMaximum) |
|
||||
floatBound(v.Minimum, zeroMinimum) |
|
||||
floatBound(v.MultipleOf, zeroMultipleOf) |
|
||||
intBound(v.MaxLength, zeroMaxLength) |
|
||||
intBound(v.MinLength, zeroMinLength) |
|
||||
intBound(v.MaxItems, zeroMaxItems) |
|
||||
intBound(v.MinItems, zeroMinItems)
|
||||
}
|
||||
|
||||
func restoreCommon(v *CommonValidations, bounds zeroBounds) {
|
||||
restoreFloat(&v.Maximum, bounds, zeroMaximum)
|
||||
restoreFloat(&v.Minimum, bounds, zeroMinimum)
|
||||
restoreFloat(&v.MultipleOf, bounds, zeroMultipleOf)
|
||||
restoreInt(&v.MaxLength, bounds, zeroMaxLength)
|
||||
restoreInt(&v.MinLength, bounds, zeroMinLength)
|
||||
restoreInt(&v.MaxItems, bounds, zeroMaxItems)
|
||||
restoreInt(&v.MinItems, bounds, zeroMinItems)
|
||||
}
|
||||
|
||||
// GobEncode provides a gob encoder for Schema that keeps its zero-valued bounds.
|
||||
func (s Schema) GobEncode() ([]byte, error) {
|
||||
type plain Schema
|
||||
|
||||
p := s.SchemaProps
|
||||
bounds := floatBound(p.Maximum, zeroMaximum) |
|
||||
floatBound(p.Minimum, zeroMinimum) |
|
||||
floatBound(p.MultipleOf, zeroMultipleOf) |
|
||||
intBound(p.MaxLength, zeroMaxLength) |
|
||||
intBound(p.MinLength, zeroMinLength) |
|
||||
intBound(p.MaxItems, zeroMaxItems) |
|
||||
intBound(p.MinItems, zeroMinItems) |
|
||||
intBound(p.MaxProperties, zeroMaxProperties) |
|
||||
intBound(p.MinProperties, zeroMinProperties)
|
||||
|
||||
var b bytes.Buffer
|
||||
err := gob.NewEncoder(&b).Encode(struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}{Plain: plain(s), Bounds: bounds})
|
||||
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a gob decoder for Schema that keeps its zero-valued bounds.
|
||||
func (s *Schema) GobDecode(b []byte) error {
|
||||
type plain Schema
|
||||
|
||||
var raw struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}
|
||||
if err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = Schema(raw.Plain)
|
||||
restoreFloat(&s.Maximum, raw.Bounds, zeroMaximum)
|
||||
restoreFloat(&s.Minimum, raw.Bounds, zeroMinimum)
|
||||
restoreFloat(&s.MultipleOf, raw.Bounds, zeroMultipleOf)
|
||||
restoreInt(&s.MaxLength, raw.Bounds, zeroMaxLength)
|
||||
restoreInt(&s.MinLength, raw.Bounds, zeroMinLength)
|
||||
restoreInt(&s.MaxItems, raw.Bounds, zeroMaxItems)
|
||||
restoreInt(&s.MinItems, raw.Bounds, zeroMinItems)
|
||||
restoreInt(&s.MaxProperties, raw.Bounds, zeroMaxProperties)
|
||||
restoreInt(&s.MinProperties, raw.Bounds, zeroMinProperties)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GobEncode provides a gob encoder for Parameter that keeps its zero-valued bounds.
|
||||
func (p Parameter) GobEncode() ([]byte, error) {
|
||||
type plain Parameter
|
||||
|
||||
var b bytes.Buffer
|
||||
err := gob.NewEncoder(&b).Encode(struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}{Plain: plain(p), Bounds: commonBounds(p.CommonValidations)})
|
||||
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a gob decoder for Parameter that keeps its zero-valued bounds.
|
||||
func (p *Parameter) GobDecode(b []byte) error {
|
||||
type plain Parameter
|
||||
|
||||
var raw struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}
|
||||
if err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*p = Parameter(raw.Plain)
|
||||
restoreCommon(&p.CommonValidations, raw.Bounds)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GobEncode provides a gob encoder for Header that keeps its zero-valued bounds.
|
||||
func (h Header) GobEncode() ([]byte, error) {
|
||||
type plain Header
|
||||
|
||||
var b bytes.Buffer
|
||||
err := gob.NewEncoder(&b).Encode(struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}{Plain: plain(h), Bounds: commonBounds(h.CommonValidations)})
|
||||
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a gob decoder for Header that keeps its zero-valued bounds.
|
||||
func (h *Header) GobDecode(b []byte) error {
|
||||
type plain Header
|
||||
|
||||
var raw struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}
|
||||
if err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*h = Header(raw.Plain)
|
||||
restoreCommon(&h.CommonValidations, raw.Bounds)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GobEncode provides a gob encoder for Items that keeps its zero-valued bounds.
|
||||
func (i Items) GobEncode() ([]byte, error) {
|
||||
type plain Items
|
||||
|
||||
var b bytes.Buffer
|
||||
err := gob.NewEncoder(&b).Encode(struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}{Plain: plain(i), Bounds: commonBounds(i.CommonValidations)})
|
||||
|
||||
return b.Bytes(), err
|
||||
}
|
||||
|
||||
// GobDecode provides a gob decoder for Items that keeps its zero-valued bounds.
|
||||
func (i *Items) GobDecode(b []byte) error {
|
||||
type plain Items
|
||||
|
||||
var raw struct {
|
||||
Plain plain
|
||||
Bounds zeroBounds
|
||||
}
|
||||
if err := gob.NewDecoder(bytes.NewBuffer(b)).Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*i = Items(raw.Plain)
|
||||
restoreCommon(&i.CommonValidations, raw.Bounds)
|
||||
|
||||
return nil
|
||||
}
|
||||
+139
-19
@@ -41,10 +41,11 @@ func normalizeURI(refPath, base string) string {
|
||||
if refURL.Path == "." {
|
||||
refURL.Path = ""
|
||||
}
|
||||
forgetSpelling(refURL)
|
||||
|
||||
r := MustCreateRef(refURL.String())
|
||||
if r.IsCanonical() {
|
||||
return refURL.String()
|
||||
return r.String()
|
||||
}
|
||||
|
||||
baseURL, _ := parseURL(base)
|
||||
@@ -56,6 +57,14 @@ func normalizeURI(refPath, base string) string {
|
||||
// copying fragment from ref to base
|
||||
baseURL.Fragment = refURL.Fragment
|
||||
|
||||
if baseURL.Scheme != "" && baseURL.Path != "" && !path.IsAbs(baseURL.Path) {
|
||||
// a base that carries no path of its own (e.g. "smb://host") leaves the join relative.
|
||||
// Anchor it, because a relative path under a scheme does not survive rendering: it is
|
||||
// either promoted to a host component, which is not even guaranteed to be a legal one
|
||||
// ("a://some file.json" no longer parses), or read back as absolute anyway.
|
||||
baseURL.Path = path.Join("/", baseURL.Path)
|
||||
}
|
||||
|
||||
return baseURL.String()
|
||||
}
|
||||
|
||||
@@ -82,23 +91,45 @@ func denormalizeRef(ref *Ref, originalRelativeBase, id string) Ref {
|
||||
}
|
||||
|
||||
if id != "" {
|
||||
idBaseURL, err := parseURL(id)
|
||||
if err == nil { // if the schema id is not usable as a URI, ignore it
|
||||
if ref, ok := rebase(ref, idBaseURL, true); ok { // rebase, but keep references to root unchanged (do not want $ref: "")
|
||||
if _, err := parseURL(id); err == nil { // if the schema id is not usable as a URI, ignore it
|
||||
// rebase, but keep references to root unchanged (do not want $ref: "")
|
||||
if ref, ok := rebase(ref, canonicalURL(id), true); ok {
|
||||
// $ref relative to the ID of the schema in the root document
|
||||
return ref
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
originalRelativeBaseURL, _ := parseURL(originalRelativeBase)
|
||||
|
||||
r, _ := rebase(ref, originalRelativeBaseURL, false)
|
||||
r, _ := rebase(ref, canonicalURL(originalRelativeBase), false)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func rebase(ref *Ref, v *url.URL, notEqual bool) (Ref, bool) {
|
||||
// canonicalURL parses a URI the way a Ref does, so that rebase compares authorities that are
|
||||
// spelled alike.
|
||||
//
|
||||
// Turning a URI into a Ref lower-cases the host and drops a default port, whereas the normalizer
|
||||
// keeps the spelling it was handed: comparing the two spellings directly makes a $ref look like
|
||||
// it belongs to another host, and leaves it absolute when it should have been rebased.
|
||||
func canonicalURL(in string) *url.URL {
|
||||
if ref, err := NewRef(in); err == nil {
|
||||
return ref.GetURL()
|
||||
}
|
||||
|
||||
if u, err := parseURL(in); err == nil {
|
||||
return u
|
||||
}
|
||||
|
||||
return &url.URL{}
|
||||
}
|
||||
|
||||
// rebase expresses a $ref relative to v, which is either the URI of the base document or
|
||||
// the "id" that anchors the root schema.
|
||||
//
|
||||
// The two differ in what v stands for. An "id" anchors a namespace, so a $ref below it is
|
||||
// expressed relative to the id itself. A base is a document, so a $ref is expressed relative
|
||||
// to the folder that holds it, and only a $ref to that very document collapses to an empty $ref.
|
||||
func rebase(ref *Ref, v *url.URL, isID bool) (Ref, bool) {
|
||||
var newBase url.URL
|
||||
|
||||
u := ref.GetURL()
|
||||
@@ -118,26 +149,78 @@ func rebase(ref *Ref, v *url.URL, notEqual bool) (Ref, bool) {
|
||||
|
||||
newBase.Fragment = u.Fragment
|
||||
|
||||
if after, ok := strings.CutPrefix(u.Path, docPath); ok {
|
||||
newBase.Path = after
|
||||
} else {
|
||||
newBase.Path = strings.TrimPrefix(u.Path, v.Path)
|
||||
}
|
||||
switch {
|
||||
case isID:
|
||||
if after, ok := cutPathPrefix(u.Path, docPath); ok {
|
||||
newBase.Path = after
|
||||
} else {
|
||||
newBase.Path = strings.TrimPrefix(u.Path, v.Path)
|
||||
}
|
||||
|
||||
if notEqual && newBase.Path == "" && newBase.Fragment == "" {
|
||||
// do not want rebasing to end up in an empty $ref
|
||||
return *ref, false
|
||||
if newBase.Path == "" && newBase.Fragment == "" {
|
||||
// do not want rebasing to end up in an empty $ref
|
||||
return *ref, false
|
||||
}
|
||||
|
||||
case u.Path == docPath:
|
||||
// the $ref points to the base document itself
|
||||
newBase.Path = ""
|
||||
|
||||
default:
|
||||
newBase.Path = strings.TrimPrefix(u.Path, v.Path)
|
||||
|
||||
if newBase.Path == "" {
|
||||
// the $ref points to the folder that holds the base, not to a document:
|
||||
// a $ref with no path of its own would denote the base document instead
|
||||
return *ref, false
|
||||
}
|
||||
}
|
||||
|
||||
if path.IsAbs(newBase.Path) {
|
||||
// whenever we end up with an absolute path, specify the scheme and host
|
||||
// whenever we end up with an absolute path, we render a whole URI
|
||||
newBase.Scheme = v.Scheme
|
||||
newBase.Host = v.Host
|
||||
newBase.User = u.User
|
||||
newBase.RawQuery = u.RawQuery
|
||||
newBase.ForceQuery = u.ForceQuery
|
||||
} else if u.RawQuery != v.RawQuery || u.ForceQuery != v.ForceQuery || !sameUserinfo(u.User, v.User) {
|
||||
// a relative $ref carries neither credentials nor query of its own: normalizing one
|
||||
// applies those of the base. Leave the $ref absolute rather than have it resolve
|
||||
// against a different query or different credentials.
|
||||
return *ref, false
|
||||
}
|
||||
|
||||
return MustCreateRef(newBase.String()), true
|
||||
}
|
||||
|
||||
// sameUserinfo compares the userinfo components of two URIs.
|
||||
//
|
||||
// An absent userinfo differs from an empty one: "file:///x" and "file://@/x" do not render alike.
|
||||
func sameUserinfo(a, b *url.Userinfo) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == b
|
||||
}
|
||||
|
||||
return a.String() == b.String()
|
||||
}
|
||||
|
||||
// cutPathPrefix is strings.CutPrefix, cutting on path separators only.
|
||||
//
|
||||
// "/base/spec.json" is a prefix of "/base/spec.json/x" but not of "/base/spec.json.orig":
|
||||
// cutting the latter would leave ".orig" to be rebased against the wrong folder.
|
||||
func cutPathPrefix(pth, prefix string) (string, bool) {
|
||||
after, ok := strings.CutPrefix(pth, prefix)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if after == "" || strings.HasPrefix(after, "/") || strings.HasSuffix(prefix, "/") {
|
||||
return after, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// normalizeRef canonicalize a Ref, using a canonical relativeBase as its absolute anchor.
|
||||
func normalizeRef(ref *Ref, relativeBase string) *Ref {
|
||||
r := MustCreateRef(normalizeURI(ref.String(), relativeBase))
|
||||
@@ -170,11 +253,12 @@ func normalizeBase(in string) string {
|
||||
if u.Path == "." { // empty after Clean()
|
||||
u.Path = ""
|
||||
}
|
||||
forgetSpelling(u)
|
||||
|
||||
if u.Scheme != "" {
|
||||
if path.IsAbs(u.Path) || u.Scheme != fileScheme {
|
||||
// this is absolute or explicitly not a local file: we're good
|
||||
return u.String()
|
||||
return canonicalString(u)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,5 +271,41 @@ func normalizeBase(in string) string {
|
||||
u.Scheme = fileScheme
|
||||
u.Path = absPath(u.Path) // platform-dependent
|
||||
u.RawQuery = "" // any query component is irrelevant for a base
|
||||
return u.String()
|
||||
return canonicalString(u)
|
||||
}
|
||||
|
||||
// forgetSpelling drops the escaped spelling url.Parse recorded, so that rendering the URL
|
||||
// derives it from Path and Fragment again.
|
||||
//
|
||||
// url.URL.String returns RawPath whenever it still decodes to Path, and the normalizer rewrites
|
||||
// Path - path.Clean, then a join onto the base. A RawPath describing the path as it was written
|
||||
// no longer describes the path we now hold, and rendering it can produce a URI that does not
|
||||
// parse: normalizeBase("%2F") used to return "file://%2F", where the escaped form lost the
|
||||
// leading slash that Path carries and what is left reads as an authority.
|
||||
func forgetSpelling(u *url.URL) {
|
||||
u.RawPath = ""
|
||||
u.RawFragment = ""
|
||||
}
|
||||
|
||||
// canonicalString renders a URL the way jsonreference does.
|
||||
//
|
||||
// The normalizer and jsonreference each canonicalize a URI, and they used to disagree: the
|
||||
// normalizer kept the spelling it was handed while a Ref lower-cases the host, drops a default
|
||||
// port and re-escapes path and fragment. Everything the expander stores - cache keys, the
|
||||
// parentRefs chain, a $ref written back into the document - goes through both, so the two must
|
||||
// agree or one document splits into two spellings of itself.
|
||||
//
|
||||
// A URI that jsonreference cannot parse is returned as it stands, rather than panicking through
|
||||
// MustCreateRef. Such a URI is a defect in the normalizer, and the fuzz target reports it as one.
|
||||
func canonicalString(u *url.URL) string {
|
||||
rendered := u.String()
|
||||
|
||||
r, err := NewRef(rendered)
|
||||
if err != nil {
|
||||
specLogger.Printf("warning: the normalizer produced an unparseable URI %q: %v", rendered, err)
|
||||
|
||||
return rendered
|
||||
}
|
||||
|
||||
return r.String()
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// -build windows
|
||||
//go:build windows
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
+3
-1
@@ -403,7 +403,9 @@ func (s *SchemaOrArray) ContainsType(name string) bool {
|
||||
|
||||
// MarshalJSON converts this schema object or array into JSON structure.
|
||||
func (s SchemaOrArray) MarshalJSON() ([]byte, error) {
|
||||
if len(s.Schemas) > 0 {
|
||||
if s.Schemas != nil {
|
||||
// an empty array is still the array form: marshalling it as the single form
|
||||
// would yield "null", which is not a schema
|
||||
return json.Marshal(s.Schemas)
|
||||
}
|
||||
return json.Marshal(s.Schema)
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ linters:
|
||||
- gomodguard_v2
|
||||
- godox
|
||||
- exhaustruct
|
||||
- exhaustruct_v5
|
||||
- nlreturn
|
||||
- nonamedreturns
|
||||
- noinlineerr
|
||||
|
||||
+2
-2
@@ -4,12 +4,12 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 41 | 243 |
|
||||
| 41 | 254 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @casualjim | 88 | <https://github.com/go-openapi/strfmt/commits?author=casualjim> |
|
||||
| @fredbi | 74 | <https://github.com/go-openapi/strfmt/commits?author=fredbi> |
|
||||
| @fredbi | 85 | <https://github.com/go-openapi/strfmt/commits?author=fredbi> |
|
||||
| @youyuanwu | 13 | <https://github.com/go-openapi/strfmt/commits?author=youyuanwu> |
|
||||
| @jlambatl | 9 | <https://github.com/go-openapi/strfmt/commits?author=jlambatl> |
|
||||
| @GlenDC | 5 | <https://github.com/go-openapi/strfmt/commits?author=GlenDC> |
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -213,8 +213,6 @@ Maintainers can cut a new release by either:
|
||||
[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fstrfmt.svg
|
||||
[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fstrfmt
|
||||
<!-- Badges: code quality -->
|
||||
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/strfmt
|
||||
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/strfmt
|
||||
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/strfmt
|
||||
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/strfmt
|
||||
<!-- Badges: documentation & support -->
|
||||
|
||||
+2
-1
@@ -119,6 +119,7 @@ func IsHostname(str string) bool {
|
||||
}
|
||||
|
||||
// IDNA check
|
||||
str, _ = strings.CutSuffix(str, ".") // tolerates trailing ".", whereas IDNA ValidateLabel rule does not
|
||||
res, err := idnaHostChecker.ToASCII(strings.ToLower(str))
|
||||
if err != nil || res == "" {
|
||||
return false
|
||||
@@ -1976,7 +1977,7 @@ func isISBN(str string, version int) bool {
|
||||
for i = range isbnVersion13 - 1 {
|
||||
checksum += factor[i%2] * int32(sanitized[i]-'0')
|
||||
}
|
||||
return (int32(sanitized[isbnVersion13-1]-'0'))-((decimalBase-(checksum%decimalBase))%decimalBase) == 0
|
||||
return int32(sanitized[isbnVersion13-1]-'0')-((decimalBase-(checksum%decimalBase))%decimalBase) == 0
|
||||
default:
|
||||
return isISBN(str, isbnVersion10) || isISBN(str, isbnVersion13)
|
||||
}
|
||||
|
||||
+3
-3
@@ -122,7 +122,7 @@ func (f *defaultFormats) Add(name string, strfmt Format, validator Validator) bo
|
||||
nme := f.normalizeName(name)
|
||||
|
||||
tpe := reflect.TypeOf(strfmt)
|
||||
if tpe.Kind() == reflect.Ptr {
|
||||
if tpe.Kind() == reflect.Pointer {
|
||||
tpe = tpe.Elem()
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func (f *defaultFormats) DelByFormat(strfmt Format) bool {
|
||||
defer f.Unlock()
|
||||
|
||||
tpe := reflect.TypeOf(strfmt)
|
||||
if tpe.Kind() == reflect.Ptr {
|
||||
if tpe.Kind() == reflect.Pointer {
|
||||
tpe = tpe.Elem()
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ func (f *defaultFormats) ContainsFormat(strfmt Format) bool {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
tpe := reflect.TypeOf(strfmt)
|
||||
if tpe.Kind() == reflect.Ptr {
|
||||
if tpe.Kind() == reflect.Pointer {
|
||||
tpe = tpe.Elem()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@ use (
|
||||
./internal/testintegration
|
||||
)
|
||||
|
||||
go 1.25.0
|
||||
go 1.26.0
|
||||
+2
-1
@@ -7,6 +7,7 @@ linters:
|
||||
- errchkjson
|
||||
- errorlint
|
||||
- exhaustruct
|
||||
- exhaustruct_v5
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
@@ -48,7 +49,7 @@ linters:
|
||||
min-len: 2
|
||||
min-occurrences: 3
|
||||
gocyclo:
|
||||
min-complexity: 45
|
||||
min-complexity: 25
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
|
||||
+2
-2
@@ -4,11 +4,11 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 24 | 251 |
|
||||
| 24 | 272 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @fredbi | 121 | <https://github.com/go-openapi/swag/commits?author=fredbi> |
|
||||
| @fredbi | 142 | <https://github.com/go-openapi/swag/commits?author=fredbi> |
|
||||
| @casualjim | 98 | <https://github.com/go-openapi/swag/commits?author=casualjim> |
|
||||
| @alexandear | 4 | <https://github.com/go-openapi/swag/commits?author=alexandear> |
|
||||
| @orisano | 3 | <https://github.com/go-openapi/swag/commits?author=orisano> |
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -197,8 +197,6 @@ Maintainers can cut a new release by either:
|
||||
[gomod-badge]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fswag.svg
|
||||
[gomod-url]: https://badge.fury.io/go/github.com%2Fgo-openapi%2Fswag
|
||||
<!-- Badges: code quality -->
|
||||
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/swag
|
||||
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/swag
|
||||
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/swag
|
||||
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/swag
|
||||
<!-- Badges: documentation & support -->
|
||||
|
||||
+10
-4
@@ -3,8 +3,14 @@
|
||||
|
||||
// Package fileutils exposes utilities to deal with files and paths.
|
||||
//
|
||||
// Currently, there is:
|
||||
// - [File] to represent an abstraction of an uploaded file.
|
||||
// For instance, this is used by [github.com/go-openapi/runtime.File].
|
||||
// - path search utilities (e.g. finding packages in the GO search path)
|
||||
// It provides:
|
||||
//
|
||||
// - [File], an abstraction of an uploaded file.
|
||||
// It is used by [github.com/go-openapi/runtime.File].
|
||||
// - Implementations of [fs.FS]: [OsFS] and [GlobOsFS] wrap the os package,
|
||||
// [MapFS] serves files held in memory, [OverlayFS] stacks file systems on top of one another,
|
||||
// [OpaqueFS] lets a layer claim a directory for itself, and [FileReaderFS] adds a ReadFile
|
||||
// method to any [fs.FS].
|
||||
// - [MustSub], to re-root a file system inline when the directory is a constant of the program.
|
||||
// - path search utilities, to locate a package in the go search path.
|
||||
package fileutils
|
||||
+7
-2
@@ -6,17 +6,22 @@ package fileutils
|
||||
import "mime/multipart"
|
||||
|
||||
// File represents an uploaded file.
|
||||
//
|
||||
// Data holds the payload, and Header the multipart metadata.
|
||||
// File implements [io.ReadCloser] by delegating both methods to Data.
|
||||
//
|
||||
// The zero File is not usable: [File.Read] and [File.Close] both panic when Data is nil.
|
||||
type File struct {
|
||||
Data multipart.File
|
||||
Header *multipart.FileHeader
|
||||
}
|
||||
|
||||
// Read bytes from the file
|
||||
// Read reads bytes from the payload.
|
||||
func (f *File) Read(p []byte) (n int, err error) {
|
||||
return f.Data.Read(p)
|
||||
}
|
||||
|
||||
// Close the file
|
||||
// Close closes the payload.
|
||||
func (f *File) Close() error {
|
||||
return f.Data.Close()
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// OsFS exposes package os features as an [fs.FS], without having to use [os.Root].
|
||||
//
|
||||
// Existing alternatives from the standard library are [os.DirFS], which requires a base directory,
|
||||
// and [os.Root.FS], which requires a root.
|
||||
// [OsFS] is intended to be used when none of these alternatives are workable,
|
||||
// that is when the caller does not know which root it should run in.
|
||||
//
|
||||
// Names are passed to the os package unchanged,
|
||||
// so [OsFS] accepts absolute and relative paths, which a conforming [fs.FS] rejects.
|
||||
// It offers no containment: every file that the process may read is reachable.
|
||||
//
|
||||
// [OsFS] implements [fs.FS], [fs.ReadFileFS] and [fs.ReadDirFS].
|
||||
type OsFS struct {
|
||||
}
|
||||
|
||||
// NewReadOnlyOsFS builds an [OsFS], a read-only view of the os file system.
|
||||
func NewReadOnlyOsFS() *OsFS {
|
||||
return &OsFS{}
|
||||
}
|
||||
|
||||
// Open opens the named file for reading.
|
||||
func (f *OsFS) Open(name string) (fs.File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
// ReadFile reads the named file and returns its content.
|
||||
func (f *OsFS) ReadFile(name string) ([]byte, error) {
|
||||
return os.ReadFile(name)
|
||||
}
|
||||
|
||||
// ReadDir reads the named directory and returns its entries sorted by file name.
|
||||
func (f *OsFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
return os.ReadDir(name)
|
||||
}
|
||||
|
||||
// FileReaderFS makes a [fs.FS] into a [fs.ReadFileFS], with a [FileReaderFS.ReadFile] method.
|
||||
type FileReaderFS struct {
|
||||
fs.FS
|
||||
}
|
||||
|
||||
// NewFileReaderFS transforms a [fs.FS] into a [fs.ReadFileFS].
|
||||
func NewFileReaderFS(base fs.FS) *FileReaderFS {
|
||||
return &FileReaderFS{
|
||||
FS: base,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFile reads the named file from the base file system and returns its content.
|
||||
func (f *FileReaderFS) ReadFile(name string) ([]byte, error) {
|
||||
return fs.ReadFile(f.FS, name)
|
||||
}
|
||||
|
||||
// GlobOsFS is an [OsFS] that also implements [fs.GlobFS], with a [GlobOsFS.Glob] method.
|
||||
type GlobOsFS struct {
|
||||
*OsFS
|
||||
}
|
||||
|
||||
// NewGlobOsFS is like [NewReadOnlyOsFS], augmented to match the [fs.GlobFS] interface.
|
||||
func NewGlobOsFS() *GlobOsFS {
|
||||
return &GlobOsFS{
|
||||
OsFS: NewReadOnlyOsFS(),
|
||||
}
|
||||
}
|
||||
|
||||
// Glob returns the names matching pattern, sorted in lexical order.
|
||||
//
|
||||
// It returns a nil slice and no error when nothing matches,
|
||||
// and [path.ErrBadPattern] when the pattern is malformed.
|
||||
func (f *GlobOsFS) Glob(pattern string) ([]string, error) {
|
||||
return fs.Glob(f.OsFS, pattern)
|
||||
}
|
||||
|
||||
// MustSub re-roots a file system at one of its directories, and panics when it cannot.
|
||||
//
|
||||
// It is [fs.Sub] for the cases where the directory is a constant of the program, such as a folder
|
||||
// of an [embed.FS] assembled at initialization time: there, a failure means the program is wrong,
|
||||
// not that its input is.
|
||||
//
|
||||
// Use [fs.Sub] itself whenever the directory comes from the outside, such as a flag,
|
||||
// a configuration file or a request, so that an invalid one is reported rather than fatal.
|
||||
//
|
||||
// It is meant to be composed inline:
|
||||
//
|
||||
// assets := NewOverlayFS(
|
||||
// MustSub(embedded, "templates"),
|
||||
// MustSub(embedded, "templates/contrib/mine"),
|
||||
// )
|
||||
func MustSub(fsys fs.FS, dir string) fs.FS {
|
||||
subFS, err := fs.Sub(fsys, dir)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("fileutils.MustSub: cannot re-root at %q: %w", dir, err))
|
||||
}
|
||||
|
||||
return subFS
|
||||
}
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Default modes of the entries of a [MapFS].
|
||||
//
|
||||
// A [MapFS] is read-only, so its files and directories are readable and never writable.
|
||||
const (
|
||||
// DefaultFileMode is the mode reported by a file with no [MapFile.Mode] of its own.
|
||||
DefaultFileMode fs.FileMode = 0o444
|
||||
|
||||
// DefaultDirMode is the mode reported by the directories of a [MapFS].
|
||||
DefaultDirMode fs.FileMode = fs.ModeDir | 0o555
|
||||
)
|
||||
|
||||
var (
|
||||
// errNameIsFileAndDir is reported when the same name is held as a file and as a parent directory.
|
||||
errNameIsFileAndDir = errors.New("name is held both as a file and as a directory")
|
||||
|
||||
// errNotDir is reported when a regular file is listed as a directory.
|
||||
errNotDir = errors.New("not a directory")
|
||||
)
|
||||
|
||||
// MapFile is a file held by a [MapFS].
|
||||
//
|
||||
// Only [MapFile.Data] is required. [NewMapFS] fills the remaining fields with presets when they
|
||||
// are left to their zero value.
|
||||
type MapFile struct {
|
||||
// Data is the content of the file.
|
||||
Data []byte
|
||||
|
||||
// Mode is the file mode reported by [fs.FileInfo.Mode]. Zero means [DefaultFileMode].
|
||||
Mode fs.FileMode
|
||||
|
||||
// ModTime is the modification time reported by [fs.FileInfo.ModTime].
|
||||
//
|
||||
// The zero value is left as is, so that a [MapFS] built from the same input twice
|
||||
// reports the same metadata.
|
||||
ModTime time.Time
|
||||
|
||||
// Sys is the opaque value reported by [fs.FileInfo.Sys].
|
||||
Sys any
|
||||
}
|
||||
|
||||
// MapFS is a read-only in-memory [fs.FS], built from a map of file names to content.
|
||||
//
|
||||
// It is intended for the cases where the files to serve are held in memory rather than on disk:
|
||||
// an overlay assembled from raw bytes, assets that a configuration provides, or a fixture in a test.
|
||||
//
|
||||
// Names are slash-separated paths, as accepted by [fs.ValidPath]. [NewMapFS] cleans them,
|
||||
// so a name may be given with a leading "./" or "/", or with redundant elements.
|
||||
// A name that remains invalid once cleaned, such as one climbing above the root, is reported
|
||||
// as an error rather than dropped.
|
||||
//
|
||||
// Separators are never translated, so that the same input yields the same file system on every
|
||||
// platform: a caller holding os paths converts them with [path/filepath.ToSlash] beforehand.
|
||||
//
|
||||
// Directories are implied by the names of the files, and are indexed once, when the file system
|
||||
// is built: a name holds a file, and every one of its parents holds a directory.
|
||||
// The root "." always exists, even when the file system holds no file at all.
|
||||
//
|
||||
// [MapFS] implements [fs.FS], [fs.ReadFileFS], [fs.StatFS] and [fs.ReadDirFS], but not [fs.GlobFS]:
|
||||
// [fs.Glob] resolves against it all the same, through [MapFS.ReadDir].
|
||||
type MapFS struct {
|
||||
files map[string]MapFile
|
||||
dirs map[string][]fs.DirEntry
|
||||
}
|
||||
|
||||
// NewMapFS builds an in-memory file system from a map of file names to content.
|
||||
//
|
||||
// Names are normalized, and reported as an error when they remain invalid, or when the same name
|
||||
// is held both as a file and as the parent directory of another one.
|
||||
// A [MapFile] left with a zero [MapFile.Mode] reports [DefaultFileMode].
|
||||
//
|
||||
// The map is copied, so adding or removing an entry afterwards leaves the file system alone.
|
||||
// The contents are not: a caller that writes to a [MapFile.Data] slice it still holds changes
|
||||
// what the file system serves. Hand over a slice nothing else keeps, or copy it first.
|
||||
func NewMapFS(files map[string]MapFile) (*MapFS, error) {
|
||||
normalized := make(map[string]MapFile, len(files))
|
||||
|
||||
for name, file := range files {
|
||||
clean, err := normalizeMapName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, isDuplicate := normalized[clean]; isDuplicate {
|
||||
return nil, &fs.PathError{Op: "newmapfs", Path: clean, Err: fs.ErrExist}
|
||||
}
|
||||
|
||||
if file.Mode == 0 {
|
||||
file.Mode = DefaultFileMode
|
||||
}
|
||||
|
||||
normalized[clean] = file
|
||||
}
|
||||
|
||||
dirs, err := indexMapDirs(normalized)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MapFS{
|
||||
files: normalized,
|
||||
dirs: dirs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FromRawMap builds the files of a [MapFS] from raw contents, leaving every metadata field to its preset.
|
||||
//
|
||||
// It is the shortest way to a [MapFS] when all the caller holds is bytes:
|
||||
//
|
||||
// mapFS, err := NewMapFS(FromRawMap(map[string][]byte{
|
||||
// "folder/file1": raw1,
|
||||
// "folder/file2": raw2,
|
||||
// }))
|
||||
func FromRawMap(raw map[string][]byte) map[string]MapFile {
|
||||
files := make(map[string]MapFile, len(raw))
|
||||
for name, data := range raw {
|
||||
files[name] = MapFile{Data: data}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
// Open opens the named file or directory.
|
||||
//
|
||||
// Opening a directory yields a [fs.ReadDirFile] reporting the same entries as [MapFS.ReadDir].
|
||||
func (f *MapFS) Open(name string) (fs.File, error) {
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
|
||||
}
|
||||
|
||||
if file, isFile := f.files[name]; isFile {
|
||||
return &openMapFile{
|
||||
info: mapFileInfo{name: path.Base(name), file: file},
|
||||
reader: bytes.NewReader(file.Data),
|
||||
}, nil
|
||||
}
|
||||
|
||||
entries, isDir := f.dirs[name]
|
||||
if !isDir {
|
||||
return nil, notFound("open", name)
|
||||
}
|
||||
|
||||
return &openMapDir{info: mapDirInfo{name: path.Base(name)}, entries: entries}, nil
|
||||
}
|
||||
|
||||
// ReadFile reads the named file and returns a copy of its content.
|
||||
func (f *MapFS) ReadFile(name string) ([]byte, error) {
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{Op: "read", Path: name, Err: fs.ErrInvalid}
|
||||
}
|
||||
|
||||
file, isFile := f.files[name]
|
||||
if !isFile {
|
||||
if _, isDir := f.dirs[name]; isDir {
|
||||
return nil, &fs.PathError{Op: "read", Path: name, Err: errIsDir}
|
||||
}
|
||||
|
||||
return nil, notFound("read", name)
|
||||
}
|
||||
|
||||
// a caller mutating the result would otherwise mutate what the file system serves
|
||||
return slices.Clone(file.Data), nil
|
||||
}
|
||||
|
||||
// Stat returns the [fs.FileInfo] of the named file or directory.
|
||||
func (f *MapFS) Stat(name string) (fs.FileInfo, error) {
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid}
|
||||
}
|
||||
|
||||
if file, isFile := f.files[name]; isFile {
|
||||
return mapFileInfo{name: path.Base(name), file: file}, nil
|
||||
}
|
||||
|
||||
if _, isDir := f.dirs[name]; isDir {
|
||||
return mapDirInfo{name: path.Base(name)}, nil
|
||||
}
|
||||
|
||||
return nil, notFound("stat", name)
|
||||
}
|
||||
|
||||
// ReadDir lists the named directory, with its entries sorted by file name.
|
||||
func (f *MapFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
if !fs.ValidPath(name) {
|
||||
return nil, &fs.PathError{Op: "readdir", Path: name, Err: fs.ErrInvalid}
|
||||
}
|
||||
|
||||
entries, isDir := f.dirs[name]
|
||||
if !isDir {
|
||||
if _, isFile := f.files[name]; isFile {
|
||||
return nil, &fs.PathError{Op: "readdir", Path: name, Err: errNotDir}
|
||||
}
|
||||
|
||||
return nil, notFound("readdir", name)
|
||||
}
|
||||
|
||||
// the index is shared by every caller, so it must not escape
|
||||
return slices.Clone(entries), nil
|
||||
}
|
||||
|
||||
// normalizeMapName turns the name of a file into the cleaned form that [fs.ValidPath] accepts.
|
||||
//
|
||||
// Separators are left alone: an [fs.FS] name is slash-separated by definition, and translating
|
||||
// them here would resolve the same input differently depending on the platform.
|
||||
func normalizeMapName(name string) (string, error) {
|
||||
clean := path.Clean(strings.TrimPrefix(name, "/"))
|
||||
|
||||
if !fs.ValidPath(clean) || clean == "." {
|
||||
return "", &fs.PathError{Op: "newmapfs", Path: name, Err: fs.ErrInvalid}
|
||||
}
|
||||
|
||||
return clean, nil
|
||||
}
|
||||
|
||||
// indexMapDirs builds the directory index of a [MapFS], once, from the names of its files.
|
||||
//
|
||||
// Every parent of a file name holds a directory, up to the root, which always exists.
|
||||
func indexMapDirs(files map[string]MapFile) (map[string][]fs.DirEntry, error) {
|
||||
children := map[string]map[string]bool{".": {}} // directory -> child base name -> is a directory
|
||||
|
||||
for name := range files {
|
||||
dir := path.Dir(name)
|
||||
addMapChild(children, dir, path.Base(name), false)
|
||||
|
||||
// every ancestor of the file holds a directory
|
||||
for dir != "." {
|
||||
parent := path.Dir(dir)
|
||||
addMapChild(children, parent, path.Base(dir), true)
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
dirs := make(map[string][]fs.DirEntry, len(children))
|
||||
for dir, names := range children {
|
||||
if _, isFile := files[dir]; isFile {
|
||||
return nil, &fs.PathError{Op: "newmapfs", Path: dir, Err: errNameIsFileAndDir}
|
||||
}
|
||||
|
||||
entries := make([]fs.DirEntry, 0, len(names))
|
||||
for base, isDir := range names {
|
||||
if isDir {
|
||||
entries = append(entries, mapDirInfo{name: base})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
entries = append(entries, mapFileInfo{name: base, file: files[path.Join(dir, base)]})
|
||||
}
|
||||
|
||||
slices.SortFunc(entries, func(a, b fs.DirEntry) int {
|
||||
return strings.Compare(a.Name(), b.Name())
|
||||
})
|
||||
dirs[dir] = entries
|
||||
}
|
||||
|
||||
return dirs, nil
|
||||
}
|
||||
|
||||
// addMapChild records that a directory holds an entry.
|
||||
func addMapChild(children map[string]map[string]bool, dir, base string, isDir bool) {
|
||||
entries, exists := children[dir]
|
||||
if !exists {
|
||||
entries = make(map[string]bool)
|
||||
children[dir] = entries
|
||||
}
|
||||
|
||||
entries[base] = isDir
|
||||
}
|
||||
|
||||
// mapFileInfo reports the metadata of a file of a [MapFS]. It is both a [fs.FileInfo] and a [fs.DirEntry].
|
||||
type mapFileInfo struct {
|
||||
name string
|
||||
file MapFile
|
||||
}
|
||||
|
||||
func (i mapFileInfo) Name() string { return i.name }
|
||||
func (i mapFileInfo) Size() int64 { return int64(len(i.file.Data)) }
|
||||
func (i mapFileInfo) Mode() fs.FileMode { return i.file.Mode }
|
||||
func (i mapFileInfo) Type() fs.FileMode { return i.file.Mode.Type() }
|
||||
func (i mapFileInfo) ModTime() time.Time { return i.file.ModTime }
|
||||
func (i mapFileInfo) IsDir() bool { return false }
|
||||
func (i mapFileInfo) Sys() any { return i.file.Sys }
|
||||
func (i mapFileInfo) Info() (fs.FileInfo, error) { return i, nil }
|
||||
|
||||
// mapDirInfo reports the metadata of a directory of a [MapFS]. It is both a [fs.FileInfo] and a [fs.DirEntry].
|
||||
//
|
||||
// Directories are implied by the names of the files, so they carry no metadata of their own.
|
||||
type mapDirInfo struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (i mapDirInfo) Name() string { return i.name }
|
||||
func (i mapDirInfo) Size() int64 { return 0 }
|
||||
func (i mapDirInfo) Mode() fs.FileMode { return DefaultDirMode }
|
||||
func (i mapDirInfo) Type() fs.FileMode { return fs.ModeDir }
|
||||
func (i mapDirInfo) ModTime() time.Time { return time.Time{} }
|
||||
func (i mapDirInfo) IsDir() bool { return true }
|
||||
func (i mapDirInfo) Sys() any { return nil }
|
||||
func (i mapDirInfo) Info() (fs.FileInfo, error) { return i, nil }
|
||||
|
||||
// openMapFile is the [fs.File] returned when opening a file of a [MapFS].
|
||||
type openMapFile struct {
|
||||
info mapFileInfo
|
||||
reader *bytes.Reader
|
||||
}
|
||||
|
||||
func (f *openMapFile) Stat() (fs.FileInfo, error) { return f.info, nil }
|
||||
|
||||
func (f *openMapFile) Close() error { return nil }
|
||||
|
||||
func (f *openMapFile) Read(p []byte) (int, error) { return f.reader.Read(p) }
|
||||
|
||||
func (f *openMapFile) Seek(offset int64, whence int) (int64, error) {
|
||||
return f.reader.Seek(offset, whence)
|
||||
}
|
||||
|
||||
// openMapDir is the [fs.ReadDirFile] returned when opening a directory of a [MapFS].
|
||||
type openMapDir struct {
|
||||
info mapDirInfo
|
||||
entries []fs.DirEntry
|
||||
offset int
|
||||
}
|
||||
|
||||
func (d *openMapDir) Stat() (fs.FileInfo, error) { return d.info, nil }
|
||||
|
||||
func (d *openMapDir) Close() error { return nil }
|
||||
|
||||
func (d *openMapDir) Read([]byte) (int, error) {
|
||||
return 0, &fs.PathError{Op: "read", Path: d.info.name, Err: errIsDir}
|
||||
}
|
||||
|
||||
// ReadDir returns the next n entries, or all the remaining ones when n is not positive.
|
||||
func (d *openMapDir) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
remaining := len(d.entries) - d.offset
|
||||
if n <= 0 {
|
||||
entries := slices.Clone(d.entries[d.offset:])
|
||||
d.offset = len(d.entries)
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
if remaining == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
n = min(n, remaining)
|
||||
entries := slices.Clone(d.entries[d.offset : d.offset+n])
|
||||
d.offset += n
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"path"
|
||||
)
|
||||
|
||||
// OpaqueDirFS is a file system that entirely owns some of its directories.
|
||||
//
|
||||
// [OverlayFS] neither merges an opaque directory with the layers below it,
|
||||
// nor resolves any name under it against them:
|
||||
// what the owning layer holds replaces what they hold under the same name.
|
||||
//
|
||||
// [OpaqueFS] is the implementation provided by this package.
|
||||
type OpaqueDirFS interface {
|
||||
fs.FS
|
||||
|
||||
// IsOpaqueDir reports whether a directory was declared as entirely owned by this file system.
|
||||
//
|
||||
// It answers from the declarations given to [NewOpaqueFS], and does not check that the file
|
||||
// system holds the directory: a name declared opaque reports true either way.
|
||||
// [OverlayFS] consults it only for a layer that holds the directory.
|
||||
IsOpaqueDir(name string) bool
|
||||
}
|
||||
|
||||
// OpaqueDirsAll declares that a file system owns every directory it holds, except its root.
|
||||
//
|
||||
// It is the only pattern recognized by [NewOpaqueFS]: directories are otherwise matched
|
||||
// by their exact name.
|
||||
const OpaqueDirsAll = "*"
|
||||
|
||||
// OpaqueFS makes a [fs.FS] into an [OpaqueDirFS], by declaring the directories that it owns.
|
||||
type OpaqueFS struct {
|
||||
fs.FS
|
||||
|
||||
opaqueDirs map[string]struct{}
|
||||
}
|
||||
|
||||
// NewOpaqueFS declares the directories that a file system entirely owns,
|
||||
// so that they shadow the layers below when it is stacked in an [OverlayFS].
|
||||
//
|
||||
// Directories are slash-separated paths, as accepted by [fs.ValidPath], and are cleaned.
|
||||
// Declaring "." makes the whole file system opaque, down from its root.
|
||||
// Declaring [OpaqueDirsAll] makes every directory opaque but the root,
|
||||
// so that the root still merges and the layers below keep contributing what they hold beside it.
|
||||
//
|
||||
// Declaring a directory that this file system does not hold changes nothing once it is stacked.
|
||||
// [OverlayFS] skips a layer that does not hold the directory before it consults opacity,
|
||||
// so the layers below keep resolving that directory and everything under it.
|
||||
func NewOpaqueFS(base fs.FS, dirs ...string) *OpaqueFS {
|
||||
opaqueDirs := make(map[string]struct{}, len(dirs))
|
||||
for _, dir := range dirs {
|
||||
opaqueDirs[path.Clean(dir)] = struct{}{}
|
||||
}
|
||||
|
||||
return &OpaqueFS{
|
||||
FS: base,
|
||||
opaqueDirs: opaqueDirs,
|
||||
}
|
||||
}
|
||||
|
||||
// IsOpaqueDir reports whether a directory was declared as entirely owned by this file system.
|
||||
//
|
||||
// It answers from the declarations given to [NewOpaqueFS], and does not check that the file
|
||||
// system holds the directory: a name declared opaque reports true either way.
|
||||
// [OverlayFS] consults it only for a layer that holds the directory.
|
||||
func (f *OpaqueFS) IsOpaqueDir(name string) bool {
|
||||
if _, isOpaque := f.opaqueDirs[name]; isOpaque {
|
||||
return true
|
||||
}
|
||||
|
||||
if name == "." {
|
||||
// the root is owned only when it is declared explicitly
|
||||
return false
|
||||
}
|
||||
|
||||
_, ownsAll := f.opaqueDirs[OpaqueDirsAll]
|
||||
|
||||
return ownsAll
|
||||
}
|
||||
|
||||
// ReadFile reads a file from the wrapped file system.
|
||||
func (f *OpaqueFS) ReadFile(name string) ([]byte, error) {
|
||||
return fs.ReadFile(f.FS, name)
|
||||
}
|
||||
|
||||
// Stat returns the [fs.FileInfo] of a name in the wrapped file system.
|
||||
func (f *OpaqueFS) Stat(name string) (fs.FileInfo, error) {
|
||||
return fs.Stat(f.FS, name)
|
||||
}
|
||||
|
||||
// ReadDir lists a directory of the wrapped file system.
|
||||
func (f *OpaqueFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
return fs.ReadDir(f.FS, name)
|
||||
}
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// OverlayFS is a read-only [fs.FS] that stacks overlays on top of a base file system.
|
||||
//
|
||||
// A name is resolved in the topmost layer that holds it, then down to the base.
|
||||
// See [NewOverlayFS] for the order in which layers are stacked.
|
||||
// When the name is absent from all layers, every method returns a [fs.PathError] that reports
|
||||
// the name and matches [fs.ErrNotExist].
|
||||
//
|
||||
// [OverlayFS] implements [fs.FS], [fs.ReadFileFS], [fs.StatFS] and [fs.ReadDirFS], but not [fs.GlobFS].
|
||||
// A directory returns an error when the resolved layer does not support reading directories.
|
||||
//
|
||||
// Directories are merged: a directory reports the union of the entries held by every layer,
|
||||
// and the topmost layer wins whenever the same name is held by several of them.
|
||||
// A layer may claim a directory for itself with [NewOpaqueFS], which stops the merge
|
||||
// and hides everything the lower layers hold under that directory.
|
||||
type OverlayFS struct {
|
||||
layers []fs.FS
|
||||
}
|
||||
|
||||
// NewOverlayFS builds an overlay file system from a base file system and a list of overlays.
|
||||
//
|
||||
// Overlays are stacked in the order in which they are provided:
|
||||
// the last one sits on top and is resolved first, then the preceding ones in reverse order.
|
||||
// The base is always resolved last.
|
||||
//
|
||||
// An empty list of overlays yields a file system that resolves against the base alone.
|
||||
func NewOverlayFS(base fs.FS, overlays ...fs.FS) *OverlayFS {
|
||||
layers := make([]fs.FS, 0, len(overlays)+1)
|
||||
for _, overlay := range slices.Backward(overlays) {
|
||||
layers = append(layers, overlay)
|
||||
}
|
||||
layers = append(layers, base)
|
||||
|
||||
return &OverlayFS{
|
||||
layers: layers,
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens a file, resolving layers from the topmost overlay down to the base.
|
||||
//
|
||||
// Opening a directory yields a [fs.ReadDirFile] that reports the same entries as
|
||||
// [OverlayFS.ReadDir], so that a merged directory reads alike either way.
|
||||
func (f *OverlayFS) Open(name string) (fs.File, error) {
|
||||
file, err := f.openInLayers(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// the entries of a directory come from every layer, not from the one that resolved it
|
||||
_ = file.Close()
|
||||
|
||||
entries, err := f.readMergedDir(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mergedDir{name: name, info: info, entries: entries}, nil
|
||||
}
|
||||
|
||||
// ReadFile reads a file, resolving layers from the topmost overlay down to the base.
|
||||
func (f *OverlayFS) ReadFile(name string) ([]byte, error) {
|
||||
layer, err := f.findInLayers("open", name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fs.ReadFile(layer, name)
|
||||
}
|
||||
|
||||
// Stat returns the [fs.FileInfo] of a file, resolving layers from the topmost overlay down to the base.
|
||||
func (f *OverlayFS) Stat(name string) (fs.FileInfo, error) {
|
||||
layer, err := f.findInLayers("stat", name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fs.Stat(layer, name)
|
||||
}
|
||||
|
||||
// ReadDir lists a directory, resolving layers from the topmost overlay down to the base.
|
||||
//
|
||||
// The entries of every layer holding the directory are merged, sorted by file name,
|
||||
// and a name held by several layers is reported by the topmost of them.
|
||||
// The merge stops at the topmost layer that owns the directory, as declared by [NewOpaqueFS].
|
||||
func (f *OverlayFS) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
return f.readMergedDir(name)
|
||||
}
|
||||
|
||||
// readMergedDir collects the entries of every layer that holds a directory.
|
||||
//
|
||||
// A name that is not a directory in some layer shadows the layers below it,
|
||||
// just like a regular file does.
|
||||
func (f *OverlayFS) readMergedDir(name string) ([]fs.DirEntry, error) {
|
||||
var (
|
||||
merged []fs.DirEntry
|
||||
found bool
|
||||
)
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for _, layer := range f.layers {
|
||||
info, err := fs.Stat(layer, name)
|
||||
if err != nil {
|
||||
if !isNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ownsSubtreeOf(layer, name) {
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
if found {
|
||||
// a directory held by an upper layer shadows this entry
|
||||
break
|
||||
}
|
||||
|
||||
// let the layer report why the name cannot be listed
|
||||
return fs.ReadDir(layer, name)
|
||||
}
|
||||
|
||||
entries, err := fs.ReadDir(layer, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found = true
|
||||
for _, entry := range entries {
|
||||
if _, isShadowed := seen[entry.Name()]; isShadowed {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[entry.Name()] = struct{}{}
|
||||
merged = append(merged, entry)
|
||||
}
|
||||
|
||||
if declaresOpaqueDir(layer, name) || ownsSubtreeOf(layer, name) {
|
||||
// this layer owns the directory, or one of its parents:
|
||||
// the layers below it contribute nothing
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, notFound("readdir", name)
|
||||
}
|
||||
|
||||
slices.SortFunc(merged, func(a, b fs.DirEntry) int {
|
||||
return strings.Compare(a.Name(), b.Name())
|
||||
})
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
// openInLayers opens a name in the topmost layer that holds it.
|
||||
func (f *OverlayFS) openInLayers(name string) (fs.File, error) {
|
||||
for _, layer := range f.layers {
|
||||
file, err := layer.Open(name)
|
||||
if err == nil {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
if !isNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ownsSubtreeOf(layer, name) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, notFound("open", name)
|
||||
}
|
||||
|
||||
// findInLayers returns the topmost layer that holds a name.
|
||||
//
|
||||
// op names the operation reported by the [fs.PathError] raised when no layer holds the name.
|
||||
func (f *OverlayFS) findInLayers(op, name string) (fs.FS, error) {
|
||||
for _, layer := range f.layers {
|
||||
err := probeInLayer(layer, name)
|
||||
if err == nil {
|
||||
return layer, nil
|
||||
}
|
||||
|
||||
if !isNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ownsSubtreeOf(layer, name) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, notFound(op, name)
|
||||
}
|
||||
|
||||
// probeInLayer reports the error raised by a layer when looking a name up.
|
||||
//
|
||||
// A layer implementing [fs.StatFS] is probed with Stat.
|
||||
// Otherwise the name is opened, then closed again.
|
||||
func probeInLayer(layer fs.FS, name string) error {
|
||||
if statFS, supportsStat := layer.(fs.StatFS); supportsStat {
|
||||
_, err := statFS.Stat(name)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := layer.Open(name)
|
||||
if err == nil {
|
||||
_ = file.Close()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// declaresOpaqueDir tells whether a layer marks a directory as entirely owned.
|
||||
func declaresOpaqueDir(layer fs.FS, name string) bool {
|
||||
opaque, isOpaqueFS := layer.(OpaqueDirFS)
|
||||
|
||||
return isOpaqueFS && opaque.IsOpaqueDir(name)
|
||||
}
|
||||
|
||||
// ownsDir tells whether a layer marks a directory as entirely owned, and holds it.
|
||||
func ownsDir(layer fs.FS, name string) bool {
|
||||
if !declaresOpaqueDir(layer, name) {
|
||||
return false
|
||||
}
|
||||
|
||||
info, err := fs.Stat(layer, name)
|
||||
|
||||
return err == nil && info.IsDir()
|
||||
}
|
||||
|
||||
// ownsSubtreeOf tells whether a layer owns one of the parent directories of a name.
|
||||
//
|
||||
// The layers below such a layer hold nothing that is reachable under that name.
|
||||
func ownsSubtreeOf(layer fs.FS, name string) bool {
|
||||
if _, isOpaqueFS := layer.(OpaqueDirFS); !isOpaqueFS {
|
||||
return false
|
||||
}
|
||||
|
||||
for dir := path.Dir(name); ; {
|
||||
if ownsDir(layer, dir) {
|
||||
return true
|
||||
}
|
||||
|
||||
// "." and "/" are their own parent, so this is where the walk up ends
|
||||
parent := path.Dir(dir)
|
||||
if parent == dir {
|
||||
return false
|
||||
}
|
||||
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
// errIsDir is reported when a merged directory is read as a regular file.
|
||||
var errIsDir = errors.New("is a directory")
|
||||
|
||||
// mergedDir is the [fs.ReadDirFile] returned when opening a directory with merged layers.
|
||||
//
|
||||
// Its [fs.FileInfo] is the one of the topmost layer holding the directory,
|
||||
// while its entries come from all the layers holding it.
|
||||
type mergedDir struct {
|
||||
name string
|
||||
info fs.FileInfo
|
||||
entries []fs.DirEntry
|
||||
offset int
|
||||
}
|
||||
|
||||
func (d *mergedDir) Stat() (fs.FileInfo, error) { return d.info, nil }
|
||||
|
||||
func (d *mergedDir) Close() error { return nil }
|
||||
|
||||
func (d *mergedDir) Read([]byte) (int, error) {
|
||||
return 0, &fs.PathError{Op: "read", Path: d.name, Err: errIsDir}
|
||||
}
|
||||
|
||||
// ReadDir returns the next n entries, or all the remaining ones when n is not positive.
|
||||
func (d *mergedDir) ReadDir(n int) ([]fs.DirEntry, error) {
|
||||
remaining := len(d.entries) - d.offset
|
||||
if n <= 0 {
|
||||
entries := d.entries[d.offset:]
|
||||
d.offset = len(d.entries)
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
if remaining == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
n = min(n, remaining)
|
||||
entries := d.entries[d.offset : d.offset+n]
|
||||
d.offset += n
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// notFound builds the error reported when a name is absent from all layers.
|
||||
//
|
||||
// It mirrors what the os package raises for a missing file,
|
||||
// so that a caller may retrieve the name from the error alone.
|
||||
func notFound(op, name string) error {
|
||||
return &fs.PathError{Op: op, Path: name, Err: fs.ErrNotExist}
|
||||
}
|
||||
|
||||
// isNotFound tells whether an error raised by a layer means that the name is absent from that layer.
|
||||
//
|
||||
// The whole error chain is inspected, so a layer that wraps its errors resolves like any other,
|
||||
// and so does a layer that is itself an [OverlayFS].
|
||||
//
|
||||
// [syscall.ENOTDIR] is matched explicitly, because a path traversing a regular file
|
||||
// does not report [fs.ErrNotExist] on all platforms.
|
||||
// Any other error stops the resolution and is reported to the caller.
|
||||
func isNotFound(err error) bool {
|
||||
return errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR)
|
||||
}
|
||||
+16
-4
@@ -10,10 +10,17 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GOPATHKey represents the env key for gopath
|
||||
// GOPATHKey is the name of the environment variable that holds the go search path.
|
||||
const GOPATHKey = "GOPATH"
|
||||
|
||||
// FindInSearchPath finds a package in a provided lists of paths
|
||||
// FindInSearchPath finds a package in a list of search paths.
|
||||
//
|
||||
// searchPath lists directories separated by the OS path separator,
|
||||
// in the form accepted by [filepath.SplitList].
|
||||
// Each directory is probed for a src/pkg subdirectory.
|
||||
//
|
||||
// It returns the first match, with symlinks resolved,
|
||||
// or an empty string when the package is not found in any of the directories.
|
||||
func FindInSearchPath(searchPath, pkg string) string {
|
||||
pathsList := filepath.SplitList(searchPath)
|
||||
for _, path := range pathsList {
|
||||
@@ -26,7 +33,9 @@ func FindInSearchPath(searchPath, pkg string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindInGoSearchPath finds a package in the $GOPATH:$GOROOT
|
||||
// FindInGoSearchPath finds a package in $GOPATH and $GOROOT.
|
||||
//
|
||||
// It returns an empty string when the package is not found.
|
||||
//
|
||||
// Deprecated: this function is no longer relevant with modern go.
|
||||
// It uses [runtime.GOROOT] under the hood, which is deprecated as of go1.24.
|
||||
@@ -34,7 +43,10 @@ func FindInGoSearchPath(pkg string) string {
|
||||
return FindInSearchPath(FullGoSearchPath(), pkg)
|
||||
}
|
||||
|
||||
// FullGoSearchPath gets the search paths for finding packages
|
||||
// FullGoSearchPath returns the search paths in which a package may be found.
|
||||
//
|
||||
// It joins $GOPATH, which defaults to $HOME/go when unset, with [runtime.GOROOT].
|
||||
// The two are separated by a colon, so the result is not usable on windows.
|
||||
//
|
||||
// Deprecated: this function is no longer relevant with modern go.
|
||||
// It uses [runtime.GOROOT] under the hood, which is deprecated as of go1.24.
|
||||
|
||||
+1
-1
@@ -18,4 +18,4 @@ use (
|
||||
./yamlutils
|
||||
)
|
||||
|
||||
go 1.25.0
|
||||
go 1.26.0
|
||||
+5
@@ -10,6 +10,11 @@ package json
|
||||
// It mirrors the limit enforced by the standard library's [encoding/json] decoder
|
||||
// (see encoding/json's internal maxNestingDepth), which this adapter would otherwise
|
||||
// not benefit from since it drives [encoding/json.Decoder.Token] directly.
|
||||
//
|
||||
// With encoding/json v2 (GOEXPERIMENT=jsonv2, the default from go1.27), Token applies
|
||||
// that same limit itself and reports "exceeded max depth" before this counter fires.
|
||||
// Set a lower limit with [WithMaxNestingDepth] to be rejected earlier than the stdlib;
|
||||
// a higher one does not lift the stdlib's own 10,000-level ceiling on the decode path.
|
||||
const defaultMaxNestingDepth = 10000
|
||||
|
||||
// Option selects options for the stdlib adapter.
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# revive configuration used by codefactor.io.
|
||||
#
|
||||
# This is redundant with .golangci.yml, since revive is part of golangci-lint,
|
||||
# but we need to configure it to remain consistent across reporting tools.
|
||||
|
||||
ignore-generated-header = false
|
||||
severity = "warning"
|
||||
confidence = 0.8
|
||||
error-code = 0
|
||||
warning-code = 0
|
||||
enable-default-rules = true
|
||||
|
||||
[rule.exported]
|
||||
disabled = true
|
||||
[rule.cyclomatic]
|
||||
arguments = [25]
|
||||
[rule.cognitive-complexity]
|
||||
arguments = [25]
|
||||
+1
-1
@@ -248,7 +248,7 @@ func yamlScalar(node *yaml.Node) (any, error) {
|
||||
|
||||
func yamlStringScalarC(node *yaml.Node) (string, error) {
|
||||
if node.Kind != yaml.ScalarNode {
|
||||
return "", fmt.Errorf("expecting a string scalar but got %q: %w", node.Kind, ErrYAML)
|
||||
return "", fmt.Errorf("expecting a string scalar but got node.Kind(%d): %w", node.Kind, ErrYAML)
|
||||
}
|
||||
switch node.LongTag() {
|
||||
case yamlStringScalar, yamlIntScalar, yamlFloatScalar:
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
.idea
|
||||
.env
|
||||
.mcp.json
|
||||
.worktrees
|
||||
+3
@@ -7,7 +7,10 @@ linters:
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- godox
|
||||
- gomodguard
|
||||
- gomodguard_v2
|
||||
- exhaustruct
|
||||
- exhaustruct_v5
|
||||
- ireturn
|
||||
- nlreturn
|
||||
- nestif
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
# Benchmark
|
||||
|
||||
Validating the Kubernetes Swagger API
|
||||
|
||||
## v0.22.6: 60,000,000 allocs
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/validate
|
||||
cpu: AMD Ryzen 7 5800X 8-Core Processor
|
||||
Benchmark_KubernetesSpec/validating_kubernetes_API-16 1 8549863982 ns/op 7067424936 B/op 59583275 allocs/op
|
||||
```
|
||||
|
||||
## After refact PR: minor but noticeable improvements: 25,000,000 allocs
|
||||
|
||||
```
|
||||
go test -bench Spec
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/validate
|
||||
cpu: AMD Ryzen 7 5800X 8-Core Processor
|
||||
Benchmark_KubernetesSpec/validating_kubernetes_API-16 1 4064535557 ns/op 3379715592 B/op 25320330 allocs/op
|
||||
```
|
||||
|
||||
## After reduce GC pressure PR: 17,000,000 allocs
|
||||
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/go-openapi/validate
|
||||
cpu: AMD Ryzen 7 5800X 8-Core Processor
|
||||
Benchmark_KubernetesSpec/validating_kubernetes_API-16 1 3758414145 ns/op 2593881496 B/op 17111373 allocs/op
|
||||
```
|
||||
+3
-2
@@ -4,12 +4,12 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 31 | 310 |
|
||||
| 32 | 351 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @casualjim | 169 | <https://github.com/go-openapi/validate/commits?author=casualjim> |
|
||||
| @fredbi | 73 | <https://github.com/go-openapi/validate/commits?author=fredbi> |
|
||||
| @fredbi | 113 | <https://github.com/go-openapi/validate/commits?author=fredbi> |
|
||||
| @sttts | 11 | <https://github.com/go-openapi/validate/commits?author=sttts> |
|
||||
| @youyuanwu | 9 | <https://github.com/go-openapi/validate/commits?author=youyuanwu> |
|
||||
| @keramix | 8 | <https://github.com/go-openapi/validate/commits?author=keramix> |
|
||||
@@ -32,6 +32,7 @@
|
||||
| @liggitt | 1 | <https://github.com/go-openapi/validate/commits?author=liggitt> |
|
||||
| @ilyakaznacheev | 1 | <https://github.com/go-openapi/validate/commits?author=ilyakaznacheev> |
|
||||
| @hypnoglow | 1 | <https://github.com/go-openapi/validate/commits?author=hypnoglow> |
|
||||
| @HNO3Miracle | 1 | <https://github.com/go-openapi/validate/commits?author=HNO3Miracle> |
|
||||
| @gautierdelorme | 1 | <https://github.com/go-openapi/validate/commits?author=gautierdelorme> |
|
||||
| @flavioribeiro | 1 | <https://github.com/go-openapi/validate/commits?author=flavioribeiro> |
|
||||
| @pheepi | 1 | <https://github.com/go-openapi/validate/commits?author=pheepi> |
|
||||
|
||||
+1
-3
@@ -5,7 +5,7 @@
|
||||
<!-- 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]
|
||||
[![Release][release-badge]][release-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]
|
||||
@@ -98,8 +98,6 @@ Maintainers can cut a new release by either:
|
||||
[release-badge]: https://badge.fury.io/gh/go-openapi%2Fvalidate.svg
|
||||
[release-url]: https://badge.fury.io/gh/go-openapi%2Fvalidate
|
||||
<!-- Badges: code quality -->
|
||||
[gocard-badge]: https://goreportcard.com/badge/github.com/go-openapi/validate
|
||||
[gocard-url]: https://goreportcard.com/report/github.com/go-openapi/validate
|
||||
[codefactor-badge]: https://img.shields.io/codefactor/grade/github/go-openapi/validate
|
||||
[codefactor-url]: https://www.codefactor.io/repository/github/go-openapi/validate
|
||||
<!-- Badges: documentation & support -->
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
// validateCollectionFormats warns about a collectionFormat that has no effect.
|
||||
//
|
||||
// collectionFormat says how to join the members of an array into one value on the wire — csv, ssv,
|
||||
// tsv, pipes, or multi for a parameter repeated once per member:
|
||||
//
|
||||
// parameters:
|
||||
// - name: tags
|
||||
// in: query
|
||||
// type: array
|
||||
// items: { type: string }
|
||||
// collectionFormat: pipes
|
||||
//
|
||||
// There is nothing to join when the type is not array, so a collectionFormat written on a string or
|
||||
// an integer does nothing. Swagger 2.0 says the member "determines the format of the array if type
|
||||
// array is used" and stops there — it never forbids writing it elsewhere, so this is a warning and
|
||||
// the specification stays valid.
|
||||
//
|
||||
// The meta-schema already covers the rest of the collectionFormat rules, and covers them at every
|
||||
// location: the value must be one of csv, ssv, tsv or pipes, widened with multi for a query or
|
||||
// formData parameter, where repeating the parameter is possible. A body parameter cannot carry the
|
||||
// member at all. None of that needs a rule here.
|
||||
//
|
||||
// Only a parameter, a header and an items carry a collectionFormat — [spec.SimpleSchema] holds it,
|
||||
// and [spec.Schema] has no such member. So this walks what the operations of the document declare,
|
||||
// the way [SpecValidator.validateItems] does, and never looks at a schema. A JSON schema validated
|
||||
// on its own is untouched by this rule.
|
||||
func (s *SpecValidator) validateCollectionFormats() *Result {
|
||||
res := validatorPools.results.Borrow()
|
||||
|
||||
operations := s.analyzer.Operations()
|
||||
for _, method := range sortedKeys(operations) {
|
||||
byPath := operations[method]
|
||||
for _, path := range sortedKeys(byPath) {
|
||||
op := byPath[path]
|
||||
|
||||
for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
|
||||
if param.In == swaggerBody {
|
||||
// a body parameter describes itself with a schema, and the meta-schema
|
||||
// forbids it a collectionFormat outright
|
||||
continue
|
||||
}
|
||||
|
||||
at := s.parameterPath(path, method, param.In, param.Name)
|
||||
in := fmt.Sprintf("parameter %q", param.Name)
|
||||
checkCollectionFormat(at, param.CollectionFormat, param.Type, in, res)
|
||||
checkItemsCollectionFormats(at, param.Items, in, res)
|
||||
}
|
||||
|
||||
for _, response := range responsesOf(op) {
|
||||
at := responsePath(path, method, response.code)
|
||||
for _, name := range sortedKeys(response.resp.Headers) {
|
||||
header := response.resp.Headers[name]
|
||||
headerAt := at.children(swaggerHeaders, name)
|
||||
in := fmt.Sprintf("header %q", name)
|
||||
checkCollectionFormat(headerAt, header.CollectionFormat, header.Type, in, res)
|
||||
checkItemsCollectionFormats(headerAt, header.Items, in, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// codedResponse is a response together with the code the operation files it under, "default"
|
||||
// included.
|
||||
type codedResponse struct {
|
||||
code string
|
||||
resp spec.Response
|
||||
}
|
||||
|
||||
// responsesOf lists the responses an operation declares, in a settled order: the default response
|
||||
// first, then the status codes in ascending order.
|
||||
func responsesOf(op *spec.Operation) []codedResponse {
|
||||
if op == nil || op.Responses == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var responses []codedResponse
|
||||
if op.Responses.Default != nil {
|
||||
responses = append(responses, codedResponse{code: jsonDefault, resp: *op.Responses.Default})
|
||||
}
|
||||
|
||||
for _, code := range sortedKeys(op.Responses.StatusCodeResponses) {
|
||||
responses = append(responses, codedResponse{
|
||||
code: strconv.Itoa(code),
|
||||
resp: op.Responses.StatusCodeResponses[code],
|
||||
})
|
||||
}
|
||||
|
||||
return responses
|
||||
}
|
||||
|
||||
// checkItemsCollectionFormats checks the items of a parameter or header, then the items of those
|
||||
// items, as deep as the document nests them.
|
||||
//
|
||||
// Every level is named the same way in a message: an array of arrays that writes a pointless
|
||||
// collectionFormat twice is one thing to fix, and the deeper location is reported only when the
|
||||
// shallower one is sound.
|
||||
func checkItemsCollectionFormats(at pathSegments, items *spec.Items, in string, res *Result) {
|
||||
for items != nil {
|
||||
at = at.child(jsonItems)
|
||||
checkCollectionFormat(at, items.CollectionFormat, items.Type, "items of "+in, res)
|
||||
items = items.Items
|
||||
}
|
||||
}
|
||||
|
||||
// checkCollectionFormat warns when a collectionFormat is written on something that is not an array.
|
||||
//
|
||||
// A missing collectionFormat has nothing to answer for, and neither has a missing type: a document
|
||||
// that leaves the type out is already reported by the meta-schema, and guessing what it meant here
|
||||
// would only add noise.
|
||||
func checkCollectionFormat(at pathSegments, collectionFormat, typ, in string, res *Result) {
|
||||
if collectionFormat == "" || typ == "" || typ == arrayType {
|
||||
return
|
||||
}
|
||||
|
||||
res.addWarningsAt(at.child(swaggerCollectionFormat), collectionFormatIgnoredMsg(collectionFormat, in, typ))
|
||||
}
|
||||
+72
-74
@@ -4,9 +4,6 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
@@ -20,7 +17,7 @@ type defaultValidator struct {
|
||||
|
||||
// Validate validates the default values declared in the swagger spec.
|
||||
func (d *defaultValidator) Validate() *Result {
|
||||
errs := pools.poolOfResults.BorrowResult() // will redeem when merged
|
||||
errs := validatorPools.results.Borrow() // will redeem when merged
|
||||
|
||||
if d == nil || d.SpecValidator == nil {
|
||||
return errs
|
||||
@@ -44,28 +41,16 @@ func (d *defaultValidator) resetVisited() {
|
||||
}
|
||||
}
|
||||
|
||||
func isVisited(path string, visitedSchemas map[string]struct{}) bool {
|
||||
_, found := visitedSchemas[path]
|
||||
func isVisited(path pathSegments, visitedSchemas map[string]struct{}) bool {
|
||||
_, found := visitedSchemas[path.pointer()]
|
||||
if found {
|
||||
return true
|
||||
}
|
||||
|
||||
// search for overlapping paths
|
||||
var (
|
||||
parent string
|
||||
suffix string
|
||||
)
|
||||
const backtrackFromEnd = 2
|
||||
for i := len(path) - backtrackFromEnd; i >= 0; i-- {
|
||||
r := path[i]
|
||||
if r != '.' {
|
||||
continue
|
||||
}
|
||||
|
||||
parent = path[0:i]
|
||||
suffix = path[i+1:]
|
||||
|
||||
if strings.HasSuffix(parent, suffix) {
|
||||
// search for overlapping paths: a trailing run of tokens that already
|
||||
// appears at the end of what leads to it means we are going in circles.
|
||||
for i := 1; i < len(path); i++ {
|
||||
if path[:i].hasSuffix(path[i:]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -74,12 +59,12 @@ func isVisited(path string, visitedSchemas map[string]struct{}) bool {
|
||||
}
|
||||
|
||||
// beingVisited asserts a schema is being visited.
|
||||
func (d *defaultValidator) beingVisited(path string) {
|
||||
d.visitedSchemas[path] = struct{}{}
|
||||
func (d *defaultValidator) beingVisited(path pathSegments) {
|
||||
d.visitedSchemas[path.pointer()] = struct{}{}
|
||||
}
|
||||
|
||||
// isVisited tells if a path has already been visited.
|
||||
func (d *defaultValidator) isVisited(path string) bool {
|
||||
func (d *defaultValidator) isVisited(path pathSegments) bool {
|
||||
return isVisited(path, d.visitedSchemas)
|
||||
}
|
||||
|
||||
@@ -88,15 +73,18 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
|
||||
// every default value that is specified must validate against the schema for that property
|
||||
// headers, items, parameters, schema
|
||||
|
||||
res := pools.poolOfResults.BorrowResult() // will redeem when merged
|
||||
res := validatorPools.results.Borrow() // will redeem when merged
|
||||
s := d.SpecValidator
|
||||
|
||||
for method, pathItem := range s.expandedAnalyzer().Operations() {
|
||||
for path, op := range pathItem {
|
||||
operations := s.expandedAnalyzer().Operations()
|
||||
for _, method := range sortedKeys(operations) {
|
||||
pathItem := operations[method]
|
||||
for _, path := range sortedKeys(pathItem) {
|
||||
op := pathItem[path]
|
||||
// parameters
|
||||
for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
|
||||
if param.Default != nil && param.Required {
|
||||
res.AddWarnings(requiredHasDefaultMsg(param.Name, param.In))
|
||||
res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), requiredHasDefaultMsg(param.Name, param.In))
|
||||
}
|
||||
|
||||
// reset explored schemas to get depth-first recursive-proof exploration
|
||||
@@ -107,33 +95,34 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
|
||||
if param.Default != nil && param.Schema == nil {
|
||||
// check param default value is valid
|
||||
red := newParamValidator(¶m, s.KnownFormats, d.schemaOptions).Validate(param.Default) //#nosec
|
||||
red.relocate(s.parameterPath(path, method, param.In, param.Name).child(jsonDefault))
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively follows Items and Schemas
|
||||
if param.Items != nil {
|
||||
red := d.validateDefaultValueItemsAgainstSchema(param.Name, param.In, ¶m, param.Items) //#nosec
|
||||
red := d.validateDefaultValueItemsAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, ¶m, param.Items) //#nosec
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddErrors(defaultValueItemsDoesNotValidateMsg(param.Name, param.In))
|
||||
res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueItemsDoesNotValidateMsg(param.Name, param.In))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
if param.Schema != nil {
|
||||
// Validate default value against schema
|
||||
red := d.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema)
|
||||
red := d.validateDefaultValueSchemaAgainstSchema(s.parameterPath(path, method, param.In, param.Name).structuralChild(jsonSchema), param.In, param.Schema)
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.addErrorsAt(s.parameterPath(path, method, param.In, param.Name), defaultValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,68 +130,74 @@ func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result {
|
||||
if op.Responses != nil {
|
||||
if op.Responses.Default != nil {
|
||||
// Same constraint on default Response
|
||||
res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, 0, op.ID))
|
||||
res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, method, 0, op.ID))
|
||||
}
|
||||
// Same constraint on regular Responses
|
||||
if op.Responses.StatusCodeResponses != nil { // Safeguard
|
||||
for code, r := range op.Responses.StatusCodeResponses {
|
||||
res.Merge(d.validateDefaultInResponse(&r, "response", path, code, op.ID)) //#nosec
|
||||
for _, code := range sortedKeys(op.Responses.StatusCodeResponses) {
|
||||
r := op.Responses.StatusCodeResponses[code]
|
||||
res.Merge(d.validateDefaultInResponse(&r, "response", path, method, code, op.ID))
|
||||
}
|
||||
}
|
||||
} else if op.ID != "" {
|
||||
// Empty op.ID means there is no meaningful operation: no need to report a specific message
|
||||
res.AddErrors(noValidResponseMsg(op.ID))
|
||||
res.addErrorsAt(operationPath(path, method), noValidResponseMsg(op.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.spec.Spec().Definitions != nil { // Safeguard
|
||||
// reset explored schemas to get depth-first recursive-proof exploration
|
||||
d.resetVisited()
|
||||
for nm, sch := range s.spec.Spec().Definitions {
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema("definitions."+nm, "body", &sch)) //#nosec
|
||||
definitions := s.spec.Spec().Definitions
|
||||
for _, nm := range sortedKeys(definitions) {
|
||||
sch := definitions[nm]
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(newPathSegments(swaggerDefinitions, nm), "body", &sch))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result {
|
||||
func (d *defaultValidator) validateDefaultInResponse(
|
||||
resp *spec.Response, responseType, path, method string, responseCode int, operationID string,
|
||||
) *Result {
|
||||
s := d.SpecValidator
|
||||
|
||||
response, res := responseHelp.expandResponseRef(resp, path, s)
|
||||
responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
|
||||
response, res := responseHelp.expandResponseRef(resp, path, responsePath(path, method, responseCodeAsStr), s)
|
||||
if !res.IsValid() {
|
||||
return res
|
||||
}
|
||||
|
||||
responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
|
||||
|
||||
if response.Headers != nil { // Safeguard
|
||||
for nm, h := range response.Headers {
|
||||
for _, nm := range sortedKeys(response.Headers) {
|
||||
h := response.Headers[nm]
|
||||
// reset explored schemas to get depth-first recursive-proof exploration
|
||||
d.resetVisited()
|
||||
|
||||
if h.Default != nil {
|
||||
red := newHeaderValidator(nm, &h, s.KnownFormats, d.schemaOptions).Validate(h.Default) //#nosec
|
||||
red.relocate(responseHeaderPath(path, method, responseCodeAsStr, nm).child(jsonDefault))
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddErrors(defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
// Headers have inline definition, like params
|
||||
if h.Items != nil {
|
||||
red := d.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items) //#nosec
|
||||
red := d.validateDefaultValueItemsAgainstSchema(responseHeaderPath(path, method, responseCodeAsStr, nm), "header", &h, h.Items) //#nosec
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddErrors(defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := compileRegexp(h.Pattern); err != nil {
|
||||
res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
|
||||
res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
|
||||
}
|
||||
|
||||
// Headers don't have schema
|
||||
@@ -212,62 +207,65 @@ func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, respon
|
||||
// reset explored schemas to get depth-first recursive-proof exploration
|
||||
d.resetVisited()
|
||||
|
||||
red := d.validateDefaultValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema)
|
||||
red := d.validateDefaultValueSchemaAgainstSchema(
|
||||
responsePath(path, method, responseCodeAsStr).structuralChild(jsonSchema), "response", response.Schema)
|
||||
if red.HasErrorsOrWarnings() {
|
||||
// Additional message to make sure the context of the error is not lost
|
||||
res.AddErrors(defaultValueInDoesNotValidateMsg(operationID, responseName))
|
||||
res.addErrorsAt(responsePath(path, method, responseCodeAsStr), defaultValueInDoesNotValidateMsg(operationID, responseName))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
|
||||
func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path pathSegments, in string, schema *spec.Schema) *Result {
|
||||
if schema == nil || d.isVisited(path) {
|
||||
// Avoids recursing if we are already done with that check
|
||||
return nil
|
||||
}
|
||||
d.beingVisited(path)
|
||||
res := pools.poolOfResults.BorrowResult()
|
||||
res := validatorPools.results.Borrow()
|
||||
s := d.SpecValidator
|
||||
|
||||
if schema.Default != nil {
|
||||
res.Merge(
|
||||
newSchemaValidator(schema, s.spec.Spec(), path+".default", s.KnownFormats, d.schemaOptions).Validate(schema.Default),
|
||||
newSchemaValidator(schema, s.spec.Spec(), path.child(jsonDefault), s.KnownFormats, d.schemaOptions).Validate(schema.Default),
|
||||
)
|
||||
}
|
||||
if schema.Items != nil {
|
||||
if schema.Items.Schema != nil {
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".items.default", in, schema.Items.Schema))
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonItems), in, schema.Items.Schema))
|
||||
}
|
||||
// Multiple schemas in items
|
||||
if schema.Items.Schemas != nil { // Safeguard
|
||||
for i, sch := range schema.Items.Schemas {
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].default", path, i), in, &sch)) //#nosec
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonItems).item(i), in, &sch)) //#nosec
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := compileRegexp(schema.Pattern); err != nil {
|
||||
res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern))
|
||||
res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, schema.Pattern))
|
||||
}
|
||||
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
|
||||
// NOTE: we keep validating values, even though additionalItems is not supported by Swagger 2.0 (and 3.0 as well)
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalItems", in, schema.AdditionalItems.Schema))
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAdditionalItems), in, schema.AdditionalItems.Schema))
|
||||
}
|
||||
for propName, prop := range schema.Properties {
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
|
||||
for _, propName := range sortedKeys(schema.Properties) {
|
||||
prop := schema.Properties[propName]
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.structuralChild(jsonProperties).child(propName), in, &prop))
|
||||
}
|
||||
for propName, prop := range schema.PatternProperties {
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
|
||||
for _, propName := range sortedKeys(schema.PatternProperties) {
|
||||
prop := schema.PatternProperties[propName]
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.structuralChild(jsonPatternProperties).child(propName), in, &prop))
|
||||
}
|
||||
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".additionalProperties", in, schema.AdditionalProperties.Schema))
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAdditionalProperties), in, schema.AdditionalProperties.Schema))
|
||||
}
|
||||
if schema.AllOf != nil {
|
||||
for i, aoSch := range schema.AllOf {
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) //#nosec
|
||||
res.Merge(d.validateDefaultValueSchemaAgainstSchema(path.child(jsonAllOf).item(i), in, &aoSch)) //#nosec
|
||||
}
|
||||
}
|
||||
return res
|
||||
@@ -275,8 +273,8 @@ func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in stri
|
||||
|
||||
// NOTE: Temporary duplicated code. Need to refactor with examples
|
||||
|
||||
func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in string, root any, items *spec.Items) *Result {
|
||||
res := pools.poolOfResults.BorrowResult()
|
||||
func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path pathSegments, in string, root any, items *spec.Items) *Result {
|
||||
res := validatorPools.results.Borrow()
|
||||
s := d.SpecValidator
|
||||
if items != nil {
|
||||
if items.Default != nil {
|
||||
@@ -285,10 +283,10 @@ func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in strin
|
||||
)
|
||||
}
|
||||
if items.Items != nil {
|
||||
res.Merge(d.validateDefaultValueItemsAgainstSchema(path+"[0].default", in, root, items.Items))
|
||||
res.Merge(d.validateDefaultValueItemsAgainstSchema(path.item(0), in, root, items.Items))
|
||||
}
|
||||
if _, err := compileRegexp(items.Pattern); err != nil {
|
||||
res.AddErrors(invalidPatternInMsg(path, in, items.Pattern))
|
||||
res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, items.Pattern))
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
// validateDiscriminators checks the discriminator of every definition, and of every schema a
|
||||
// definition holds inline.
|
||||
//
|
||||
// A discriminator names the property that tells subtypes apart:
|
||||
//
|
||||
// Pet:
|
||||
// discriminator: petType
|
||||
// required: [ petType ]
|
||||
// properties:
|
||||
// petType: { type: string }
|
||||
//
|
||||
// Swagger 2.0 asks two things of that property: the schema must define it, and must list it as
|
||||
// required. Both matter for the same reason — an instance carries its subtype in that property,
|
||||
// so a subtype cannot be resolved from an instance that has nowhere to put the value, or that is
|
||||
// free to leave it out.
|
||||
//
|
||||
// The JSON meta-schema types discriminator as a plain string and never compares it against
|
||||
// properties or required, so it can express neither check.
|
||||
//
|
||||
// A property contributed by an allOf member counts as defined, and one that member requires counts
|
||||
// as required: [SpecValidator.declaresProperty] already reads a composed definition that way for
|
||||
// the required rule, and a discriminator resolves against the instance the whole composition
|
||||
// describes.
|
||||
//
|
||||
// The third clause of the rule — the value must name this schema or one that inherits it —
|
||||
// constrains the data, not the document, so it belongs to schema validation rather than here.
|
||||
func (s *SpecValidator) validateDiscriminators() *Result {
|
||||
res := validatorPools.results.Borrow()
|
||||
definitions := s.spec.Spec().Definitions
|
||||
|
||||
for _, name := range sortedKeys(definitions) {
|
||||
schema := definitions[name]
|
||||
s.walkDiscriminators(newPathSegments(swaggerDefinitions, name), &schema, res)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// walkDiscriminators checks the discriminator of a schema, then of every schema it holds inline.
|
||||
//
|
||||
// A schema written as a $ref is left alone: it is checked where it is defined, and following it
|
||||
// here would report the same fault twice and, for a recursive definition, would not terminate.
|
||||
// This mirrors [SpecValidator.walkRequired].
|
||||
func (s *SpecValidator) walkDiscriminators(at pathSegments, v *spec.Schema, res *Result) {
|
||||
if v == nil || v.Ref.String() != "" {
|
||||
return
|
||||
}
|
||||
|
||||
s.checkDiscriminator(at, v, res)
|
||||
|
||||
for _, name := range sortedKeys(v.Properties) {
|
||||
held := v.Properties[name]
|
||||
s.walkDiscriminators(at.structuralChild(jsonProperties).child(name), &held, res)
|
||||
}
|
||||
|
||||
for _, pattern := range sortedKeys(v.PatternProperties) {
|
||||
held := v.PatternProperties[pattern]
|
||||
s.walkDiscriminators(at.structuralChild(jsonPatternProperties).child(pattern), &held, res)
|
||||
}
|
||||
|
||||
if v.Items != nil {
|
||||
if v.Items.Schema != nil {
|
||||
s.walkDiscriminators(at.child(jsonItems), v.Items.Schema, res)
|
||||
}
|
||||
for i := range v.Items.Schemas {
|
||||
s.walkDiscriminators(at.child(jsonItems).item(i), &v.Items.Schemas[i], res)
|
||||
}
|
||||
}
|
||||
|
||||
if v.AdditionalProperties != nil && v.AdditionalProperties.Schema != nil {
|
||||
s.walkDiscriminators(at.child(jsonAdditionalProperties), v.AdditionalProperties.Schema, res)
|
||||
}
|
||||
|
||||
for _, composition := range []struct {
|
||||
keyword string
|
||||
members []spec.Schema
|
||||
}{
|
||||
{jsonAllOf, v.AllOf},
|
||||
{jsonAnyOf, v.AnyOf},
|
||||
{jsonOneOf, v.OneOf},
|
||||
} {
|
||||
for i := range composition.members {
|
||||
s.walkDiscriminators(at.child(composition.keyword).item(i), &composition.members[i], res)
|
||||
}
|
||||
}
|
||||
|
||||
if v.Not != nil {
|
||||
s.walkDiscriminators(at.child(jsonNot), v.Not, res)
|
||||
}
|
||||
}
|
||||
|
||||
// checkDiscriminator checks the discriminator a single schema declares. A schema without one has
|
||||
// nothing to answer for.
|
||||
//
|
||||
// Both findings are reported against the discriminator itself, which is the entry a reader has to
|
||||
// go and amend, and both are reported when they apply: a discriminator naming a property that is
|
||||
// neither defined nor required is two separate slips to fix.
|
||||
func (s *SpecValidator) checkDiscriminator(at pathSegments, v *spec.Schema, res *Result) {
|
||||
if v.Discriminator == "" {
|
||||
return
|
||||
}
|
||||
|
||||
of := identify(at).name
|
||||
discriminatorAt := at.child(jsonDiscriminator)
|
||||
|
||||
if _, declared := s.declaresProperty(v, v.Discriminator, maxCompositionHops); !declared {
|
||||
res.addErrorsAt(discriminatorAt, discriminatorNotDefinedMsg(v.Discriminator, of))
|
||||
}
|
||||
|
||||
if !s.requiresProperty(v, v.Discriminator, maxCompositionHops) {
|
||||
res.addErrorsAt(discriminatorAt, discriminatorNotRequiredMsg(v.Discriminator, of))
|
||||
}
|
||||
}
|
||||
|
||||
// requiresProperty reports whether a schema, or any schema composed into it by allOf, lists the
|
||||
// named property as required.
|
||||
//
|
||||
// It is the required-list counterpart of [SpecValidator.declaresProperty], and follows allOf the
|
||||
// same way, including the local $ref an allOf member may be written as.
|
||||
func (s *SpecValidator) requiresProperty(v *spec.Schema, name string, hops int) bool {
|
||||
if v == nil || hops <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if slices.Contains(v.Required, name) {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := range v.AllOf {
|
||||
member := &v.AllOf[i]
|
||||
if member.Ref.String() != "" {
|
||||
resolved, err := s.resolveRef(&member.Ref)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
member = resolved
|
||||
}
|
||||
|
||||
if s.requiresProperty(member, name, hops-1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+15
-2
@@ -12,6 +12,11 @@
|
||||
// Validates a spec document (from JSON or YAML) against the JSON schema for swagger,
|
||||
// then checks a number of extra rules that can't be expressed in JSON schema.
|
||||
//
|
||||
// The lists below hold the extra rules only. The meta-schema already settles a great deal on its
|
||||
// own, and where it does, no rule is repeated here: collectionFormat, say, must be one of csv, ssv,
|
||||
// tsv or pipes, widened with multi for a query or formData parameter, and a body parameter may not
|
||||
// carry one at all — all of that comes out of the meta-schema, at every location it applies to.
|
||||
//
|
||||
// Entry points:
|
||||
//
|
||||
// - Spec()
|
||||
@@ -25,6 +30,9 @@
|
||||
// [x] path uniqueness: each api path should be non-verbatim (account for path param names) unique per method. Validation can be laxed by disabling StrictPathParamUniqueness.
|
||||
// [x] each security reference should contain only unique scopes
|
||||
// [x] each security scope in a security definition should be unique
|
||||
// [x] a discriminator must name a property the schema defines and lists as required
|
||||
// [x] each security requirement must name a scheme declared in securityDefinitions
|
||||
// [x] only an oauth2 security requirement may list scopes: every other scheme type must list none
|
||||
// [x] parameters in path must be unique
|
||||
// [x] each path parameter must correspond to a parameter placeholder and vice versa
|
||||
// [x] each referenceable definition must have references
|
||||
@@ -47,6 +55,8 @@
|
||||
// [x] unsupported validation of examples on non-JSON media types
|
||||
// [x] examples in response without schema
|
||||
// [x] readOnly properties should not be required
|
||||
// [x] an oauth2 security requirement names a scope its security scheme does not declare
|
||||
// [x] collectionFormat is written on a parameter, header or items whose type is not array
|
||||
//
|
||||
// # Validating a schema
|
||||
//
|
||||
@@ -69,8 +79,11 @@
|
||||
// [ ] errors and warnings are not reported with key/line number in spec
|
||||
// [ ] default values and examples on responses only support application/json producer type
|
||||
// [ ] invalid numeric constraints (such as Minimum, etc..) are not checked except for default and example values
|
||||
// [ ] rules for collectionFormat are not implemented
|
||||
// [ ] no validation rule for polymorphism support (discriminator) [not done here]
|
||||
// [ ] valid js ECMA regexp not supported by Go regexp engine are considered invalid
|
||||
// [ ] arbitrary large numbers are not supported: max is math.MaxFloat64
|
||||
//
|
||||
// Left out by design, rather than pending: a discriminator value is not checked against the schema
|
||||
// names it may take. That clause of the swagger rule constrains an instance rather than the
|
||||
// document, so there is nothing in a specification for [SpecValidator] to read, and checking it
|
||||
// would mean resolving polymorphic payloads, which this package does not do.
|
||||
package validate
|
||||
+73
-58
@@ -4,8 +4,6 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
@@ -24,7 +22,7 @@ type exampleValidator struct {
|
||||
// - individual property
|
||||
// - responses
|
||||
func (ex *exampleValidator) Validate() *Result {
|
||||
errs := pools.poolOfResults.BorrowResult()
|
||||
errs := validatorPools.results.Borrow()
|
||||
|
||||
if ex == nil || ex.SpecValidator == nil {
|
||||
return errs
|
||||
@@ -50,12 +48,12 @@ func (ex *exampleValidator) resetVisited() {
|
||||
}
|
||||
|
||||
// beingVisited asserts a schema is being visited.
|
||||
func (ex *exampleValidator) beingVisited(path string) {
|
||||
ex.visitedSchemas[path] = struct{}{}
|
||||
func (ex *exampleValidator) beingVisited(path pathSegments) {
|
||||
ex.visitedSchemas[path.pointer()] = struct{}{}
|
||||
}
|
||||
|
||||
// isVisited tells if a path has already been visited.
|
||||
func (ex *exampleValidator) isVisited(path string) bool {
|
||||
func (ex *exampleValidator) isVisited(path pathSegments) bool {
|
||||
return isVisited(path, ex.visitedSchemas)
|
||||
}
|
||||
|
||||
@@ -65,11 +63,14 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result {
|
||||
// in: schemas, properties, object, items
|
||||
// not in: headers, parameters without schema
|
||||
|
||||
res := pools.poolOfResults.BorrowResult()
|
||||
res := validatorPools.results.Borrow()
|
||||
s := ex.SpecValidator
|
||||
|
||||
for method, pathItem := range s.expandedAnalyzer().Operations() {
|
||||
for path, op := range pathItem {
|
||||
operations := s.expandedAnalyzer().Operations()
|
||||
for _, method := range sortedKeys(operations) {
|
||||
pathItem := operations[method]
|
||||
for _, path := range sortedKeys(pathItem) {
|
||||
op := pathItem[path]
|
||||
// parameters
|
||||
for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) {
|
||||
|
||||
@@ -84,33 +85,34 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result {
|
||||
if param.Example != nil && param.Schema == nil {
|
||||
// check param default value is valid
|
||||
red := newParamValidator(¶m, s.KnownFormats, ex.schemaOptions).Validate(param.Example) //#nosec
|
||||
red.relocate(s.parameterPath(path, method, param.In, param.Name).child(swaggerExample))
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.MergeAsWarnings(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively follows Items and Schemas
|
||||
if param.Items != nil {
|
||||
red := ex.validateExampleValueItemsAgainstSchema(param.Name, param.In, ¶m, param.Items) //#nosec
|
||||
red := ex.validateExampleValueItemsAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, ¶m, param.Items) //#nosec
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddWarnings(exampleValueItemsDoesNotValidateMsg(param.Name, param.In))
|
||||
res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueItemsDoesNotValidateMsg(param.Name, param.In))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
if param.Schema != nil {
|
||||
// Validate example value against schema
|
||||
red := ex.validateExampleValueSchemaAgainstSchema(param.Name, param.In, param.Schema)
|
||||
red := ex.validateExampleValueSchemaAgainstSchema(s.parameterPath(path, method, param.In, param.Name).structuralChild(jsonSchema), param.In, param.Schema)
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddWarnings(exampleValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.addWarningsAt(s.parameterPath(path, method, param.In, param.Name), exampleValueDoesNotValidateMsg(param.Name, param.In))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,68 +120,74 @@ func (ex *exampleValidator) validateExampleValueValidAgainstSchema() *Result {
|
||||
if op.Responses != nil {
|
||||
if op.Responses.Default != nil {
|
||||
// Same constraint on default Response
|
||||
res.Merge(ex.validateExampleInResponse(op.Responses.Default, jsonDefault, path, 0, op.ID))
|
||||
res.Merge(ex.validateExampleInResponse(op.Responses.Default, jsonDefault, path, method, 0, op.ID))
|
||||
}
|
||||
// Same constraint on regular Responses
|
||||
if op.Responses.StatusCodeResponses != nil { // Safeguard
|
||||
for code, r := range op.Responses.StatusCodeResponses {
|
||||
res.Merge(ex.validateExampleInResponse(&r, "response", path, code, op.ID)) //#nosec
|
||||
for _, code := range sortedKeys(op.Responses.StatusCodeResponses) {
|
||||
r := op.Responses.StatusCodeResponses[code]
|
||||
res.Merge(ex.validateExampleInResponse(&r, "response", path, method, code, op.ID))
|
||||
}
|
||||
}
|
||||
} else if op.ID != "" {
|
||||
// Empty op.ID means there is no meaningful operation: no need to report a specific message
|
||||
res.AddErrors(noValidResponseMsg(op.ID))
|
||||
res.addErrorsAt(operationPath(path, method), noValidResponseMsg(op.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.spec.Spec().Definitions != nil { // Safeguard
|
||||
// reset explored schemas to get depth-first recursive-proof exploration
|
||||
ex.resetVisited()
|
||||
for nm, sch := range s.spec.Spec().Definitions {
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema("definitions."+nm, "body", &sch)) //#nosec
|
||||
definitions := s.spec.Spec().Definitions
|
||||
for _, nm := range sortedKeys(definitions) {
|
||||
sch := definitions[nm]
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(newPathSegments(swaggerDefinitions, nm), "body", &sch))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ex *exampleValidator) validateExampleInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result {
|
||||
func (ex *exampleValidator) validateExampleInResponse(
|
||||
resp *spec.Response, responseType, path, method string, responseCode int, operationID string,
|
||||
) *Result {
|
||||
s := ex.SpecValidator
|
||||
|
||||
response, res := responseHelp.expandResponseRef(resp, path, s)
|
||||
responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
|
||||
response, res := responseHelp.expandResponseRef(resp, path, responsePath(path, method, responseCodeAsStr), s)
|
||||
if !res.IsValid() { // Safeguard
|
||||
return res
|
||||
}
|
||||
|
||||
responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode)
|
||||
|
||||
if response.Headers != nil { // Safeguard
|
||||
for nm, h := range response.Headers {
|
||||
for _, nm := range sortedKeys(response.Headers) {
|
||||
h := response.Headers[nm]
|
||||
// reset explored schemas to get depth-first recursive-proof exploration
|
||||
ex.resetVisited()
|
||||
|
||||
if h.Example != nil {
|
||||
red := newHeaderValidator(nm, &h, s.KnownFormats, ex.schemaOptions).Validate(h.Example) //#nosec
|
||||
red.relocate(responseHeaderPath(path, method, responseCodeAsStr, nm).child(swaggerExample))
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddWarnings(exampleValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.addWarningsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), exampleValueHeaderDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.MergeAsWarnings(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
// Headers have inline definition, like params
|
||||
if h.Items != nil {
|
||||
red := ex.validateExampleValueItemsAgainstSchema(nm, "header", &h, h.Items) //#nosec
|
||||
red := ex.validateExampleValueItemsAgainstSchema(responseHeaderPath(path, method, responseCodeAsStr, nm), "header", &h, h.Items) //#nosec
|
||||
if red.HasErrorsOrWarnings() {
|
||||
res.AddWarnings(exampleValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.addWarningsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), exampleValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName))
|
||||
res.MergeAsWarnings(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := compileRegexp(h.Pattern); err != nil {
|
||||
res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
|
||||
res.addErrorsAt(responseHeaderPath(path, method, responseCodeAsStr, nm), invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err))
|
||||
}
|
||||
|
||||
// Headers don't have schema
|
||||
@@ -189,77 +197,84 @@ func (ex *exampleValidator) validateExampleInResponse(resp *spec.Response, respo
|
||||
// reset explored schemas to get depth-first recursive-proof exploration
|
||||
ex.resetVisited()
|
||||
|
||||
red := ex.validateExampleValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema)
|
||||
red := ex.validateExampleValueSchemaAgainstSchema(
|
||||
responsePath(path, method, responseCodeAsStr).structuralChild(jsonSchema), "response", response.Schema)
|
||||
if red.HasErrorsOrWarnings() {
|
||||
// Additional message to make sure the context of the error is not lost
|
||||
res.AddWarnings(exampleValueInDoesNotValidateMsg(operationID, responseName))
|
||||
res.addWarningsAt(responsePath(path, method, responseCodeAsStr), exampleValueInDoesNotValidateMsg(operationID, responseName))
|
||||
res.Merge(red)
|
||||
} else if red.wantsRedeemOnMerge {
|
||||
pools.poolOfResults.RedeemResult(red)
|
||||
redeemResult(red)
|
||||
}
|
||||
}
|
||||
|
||||
if response.Examples != nil {
|
||||
if response.Schema != nil {
|
||||
if example, ok := response.Examples["application/json"]; ok {
|
||||
if example, ok := response.Examples[jsonMimeApplicationJSON]; ok {
|
||||
exampleAt := responsePath(path, method, responseCodeAsStr).
|
||||
child(swaggerExamples).
|
||||
structuralChild(jsonMimeApplicationJSON)
|
||||
res.MergeAsWarnings(
|
||||
newSchemaValidator(response.Schema, s.spec.Spec(), path+".examples", s.KnownFormats, s.schemaOptions).Validate(example),
|
||||
newSchemaValidator(response.Schema, s.spec.Spec(),
|
||||
exampleAt, s.KnownFormats, s.schemaOptions).Validate(example),
|
||||
)
|
||||
} else {
|
||||
// Proposal for enhancement: validate other media types too
|
||||
res.AddWarnings(examplesMimeNotSupportedMsg(operationID, responseName))
|
||||
res.addWarningsAt(responsePath(path, method, responseCodeAsStr).child(swaggerExamples), examplesMimeNotSupportedMsg(operationID, responseName))
|
||||
}
|
||||
} else {
|
||||
res.AddWarnings(examplesWithoutSchemaMsg(operationID, responseName))
|
||||
res.addWarningsAt(responsePath(path, method, responseCodeAsStr).child(swaggerExamples), examplesWithoutSchemaMsg(operationID, responseName))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result {
|
||||
func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path pathSegments, in string, schema *spec.Schema) *Result {
|
||||
if schema == nil || ex.isVisited(path) {
|
||||
// Avoids recursing if we are already done with that check
|
||||
return nil
|
||||
}
|
||||
ex.beingVisited(path)
|
||||
s := ex.SpecValidator
|
||||
res := pools.poolOfResults.BorrowResult()
|
||||
res := validatorPools.results.Borrow()
|
||||
|
||||
if schema.Example != nil {
|
||||
res.MergeAsWarnings(
|
||||
newSchemaValidator(schema, s.spec.Spec(), path+".example", s.KnownFormats, ex.schemaOptions).Validate(schema.Example),
|
||||
newSchemaValidator(schema, s.spec.Spec(), path.child(swaggerExample), s.KnownFormats, ex.schemaOptions).Validate(schema.Example),
|
||||
)
|
||||
}
|
||||
if schema.Items != nil {
|
||||
if schema.Items.Schema != nil {
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".items.example", in, schema.Items.Schema))
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonItems), in, schema.Items.Schema))
|
||||
}
|
||||
// Multiple schemas in items
|
||||
if schema.Items.Schemas != nil { // Safeguard
|
||||
for i, sch := range schema.Items.Schemas {
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].example", path, i), in, &sch)) //#nosec
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonItems).item(i), in, &sch)) //#nosec
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := compileRegexp(schema.Pattern); err != nil {
|
||||
res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern))
|
||||
res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, schema.Pattern))
|
||||
}
|
||||
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
|
||||
// NOTE: we keep validating values, even though additionalItems is unsupported in Swagger 2.0 (and 3.0 as well)
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".additionalItems", in, schema.AdditionalItems.Schema))
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonAdditionalItems), in, schema.AdditionalItems.Schema))
|
||||
}
|
||||
for propName, prop := range schema.Properties {
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
|
||||
for _, propName := range sortedKeys(schema.Properties) {
|
||||
prop := schema.Properties[propName]
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.structuralChild(jsonProperties).child(propName), in, &prop))
|
||||
}
|
||||
for propName, prop := range schema.PatternProperties {
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec
|
||||
for _, propName := range sortedKeys(schema.PatternProperties) {
|
||||
prop := schema.PatternProperties[propName]
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.structuralChild(jsonPatternProperties).child(propName), in, &prop))
|
||||
}
|
||||
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path+".additionalProperties", in, schema.AdditionalProperties.Schema))
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonAdditionalProperties), in, schema.AdditionalProperties.Schema))
|
||||
}
|
||||
if schema.AllOf != nil {
|
||||
for i, aoSch := range schema.AllOf {
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) //#nosec
|
||||
res.Merge(ex.validateExampleValueSchemaAgainstSchema(path.child(jsonAllOf).item(i), in, &aoSch)) //#nosec
|
||||
}
|
||||
}
|
||||
return res
|
||||
@@ -268,8 +283,8 @@ func (ex *exampleValidator) validateExampleValueSchemaAgainstSchema(path, in str
|
||||
// NOTE: Temporary duplicated code. Need to refactor with examples
|
||||
//
|
||||
|
||||
func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path, in string, root any, items *spec.Items) *Result {
|
||||
res := pools.poolOfResults.BorrowResult()
|
||||
func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path pathSegments, in string, root any, items *spec.Items) *Result {
|
||||
res := validatorPools.results.Borrow()
|
||||
s := ex.SpecValidator
|
||||
if items != nil {
|
||||
if items.Example != nil {
|
||||
@@ -278,10 +293,10 @@ func (ex *exampleValidator) validateExampleValueItemsAgainstSchema(path, in stri
|
||||
)
|
||||
}
|
||||
if items.Items != nil {
|
||||
res.Merge(ex.validateExampleValueItemsAgainstSchema(path+"[0].example", in, root, items.Items))
|
||||
res.Merge(ex.validateExampleValueItemsAgainstSchema(path.item(0), in, root, items.Items))
|
||||
}
|
||||
if _, err := compileRegexp(items.Pattern); err != nil {
|
||||
res.AddErrors(invalidPatternInMsg(path, in, items.Pattern))
|
||||
res.addErrorsAt(path, invalidPatternInMsg(path.dotted(), in, items.Pattern))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -11,21 +11,21 @@ import (
|
||||
)
|
||||
|
||||
type formatValidator struct {
|
||||
Path string
|
||||
Path pathSegments
|
||||
In string
|
||||
Format string
|
||||
KnownFormats strfmt.Registry
|
||||
Options *SchemaValidatorOptions
|
||||
}
|
||||
|
||||
func newFormatValidator(path, in, format string, formats strfmt.Registry, opts *SchemaValidatorOptions) *formatValidator {
|
||||
func newFormatValidator(path pathSegments, in, format string, formats strfmt.Registry, opts *SchemaValidatorOptions) *formatValidator {
|
||||
if opts == nil {
|
||||
opts = new(SchemaValidatorOptions)
|
||||
}
|
||||
|
||||
var f *formatValidator
|
||||
if opts.recycleValidators {
|
||||
f = pools.poolOfFormatValidators.BorrowValidator()
|
||||
f = validatorPools.formatValidators.Borrow()
|
||||
} else {
|
||||
f = new(formatValidator)
|
||||
}
|
||||
@@ -39,10 +39,6 @@ func newFormatValidator(path, in, format string, formats strfmt.Registry, opts *
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *formatValidator) SetPath(path string) {
|
||||
f.Path = path
|
||||
}
|
||||
|
||||
func (f *formatValidator) Applies(source any, kind reflect.Kind) bool {
|
||||
if source == nil || f.KnownFormats == nil {
|
||||
return false
|
||||
@@ -71,7 +67,7 @@ func (f *formatValidator) Validate(val any) *Result {
|
||||
|
||||
var result *Result
|
||||
if f.Options.recycleResult {
|
||||
result = pools.poolOfResults.BorrowResult()
|
||||
result = validatorPools.results.Borrow()
|
||||
} else {
|
||||
result = new(Result)
|
||||
}
|
||||
@@ -81,13 +77,17 @@ func (f *formatValidator) Validate(val any) *Result {
|
||||
return result
|
||||
}
|
||||
|
||||
if err := FormatOf(f.Path, f.In, f.Format, str, f.KnownFormats); err != nil {
|
||||
result.AddErrors(err)
|
||||
if err := FormatOf(f.Path.dotted(), f.In, f.Format, str, f.KnownFormats); err != nil {
|
||||
result.addErrorsAt(f.Path, err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *formatValidator) setPath(path pathSegments) {
|
||||
f.Path = path
|
||||
}
|
||||
|
||||
func (f *formatValidator) redeem() {
|
||||
pools.poolOfFormatValidators.RedeemValidator(f)
|
||||
validatorPools.formatValidators.Redeem(f)
|
||||
}
|
||||
+113
-26
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
@@ -33,13 +34,84 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
jsonProperties = "properties"
|
||||
jsonItems = "items"
|
||||
jsonType = "type"
|
||||
// jsonSchema = "schema".
|
||||
jsonDefault = "default"
|
||||
jsonProperties = "properties"
|
||||
jsonPatternProperties = "patternProperties"
|
||||
jsonItems = "items"
|
||||
jsonType = "type"
|
||||
jsonSchema = "schema"
|
||||
jsonRequired = "required"
|
||||
jsonRef = "$ref"
|
||||
jsonDefault = "default"
|
||||
jsonDiscriminator = "discriminator"
|
||||
|
||||
jsonAllOf = "allOf"
|
||||
jsonAnyOf = "anyOf"
|
||||
jsonOneOf = "oneOf"
|
||||
jsonNot = "not"
|
||||
jsonAdditionalItems = "additionalItems"
|
||||
jsonAdditionalProperties = "additionalProperties"
|
||||
|
||||
swaggerPaths = "paths"
|
||||
swaggerDefinitions = "definitions"
|
||||
swaggerResponses = "responses"
|
||||
swaggerParameters = "parameters"
|
||||
swaggerHeaders = "headers"
|
||||
swaggerOperationID = "operationId"
|
||||
swaggerSecurity = "security"
|
||||
swaggerCollectionFormat = "collectionFormat"
|
||||
|
||||
// securitySchemeOAuth2 is the only security scheme type whose requirements carry scopes.
|
||||
securitySchemeOAuth2 = "oauth2"
|
||||
|
||||
jsonMimeApplicationJSON = "application/json"
|
||||
)
|
||||
|
||||
// operationPath locates an operation in the spec document.
|
||||
func operationPath(path, method string) pathSegments {
|
||||
return newPathSegments(swaggerPaths, path, methodToken(method))
|
||||
}
|
||||
|
||||
// parameterPath locates a parameter of an operation in the spec document.
|
||||
func (s *SpecValidator) parameterPath(path, method, in, name string) pathSegments {
|
||||
return s.paramLocations.at(path, method, in, name)
|
||||
}
|
||||
|
||||
// responsePath locates a response of an operation in the spec document.
|
||||
func responsePath(path, method, responseCode string) pathSegments {
|
||||
return operationPath(path, method).children(swaggerResponses, responseCode)
|
||||
}
|
||||
|
||||
// responseHeaderPath locates a header declared by a response.
|
||||
func responseHeaderPath(path, method, responseCode, header string) pathSegments {
|
||||
return responsePath(path, method, responseCode).children(swaggerHeaders, header)
|
||||
}
|
||||
|
||||
// methodToken normalizes an HTTP method into the key under which the operation
|
||||
// is found in the document: the analyzer hands them over in upper case, but a
|
||||
// path item spells them in lower case.
|
||||
func methodToken(method string) string {
|
||||
return strings.ToLower(method)
|
||||
}
|
||||
|
||||
// localRefPath turns a local JSON reference such as "#/definitions/Pet" into
|
||||
// the location of what it points to.
|
||||
//
|
||||
// It yields the document root for anything that does not address a local
|
||||
// fragment, a remote reference in particular.
|
||||
func localRefPath(ref string) pathSegments {
|
||||
rest, isLocal := strings.CutPrefix(ref, "#/")
|
||||
if !isLocal {
|
||||
return rootPath()
|
||||
}
|
||||
|
||||
tokens := strings.Split(rest, "/")
|
||||
for i, token := range tokens {
|
||||
tokens[i] = jsonpointer.Unescape(token)
|
||||
}
|
||||
|
||||
return newPathSegments(tokens...)
|
||||
}
|
||||
|
||||
const (
|
||||
stringFormatDate = "date"
|
||||
stringFormatDateTime = "date-time"
|
||||
@@ -91,24 +163,33 @@ type errorHelper struct {
|
||||
}
|
||||
|
||||
func (h *errorHelper) sErr(err errors.Error, recycle bool) *Result {
|
||||
// Builds a Result from standard errors.Error
|
||||
return h.sErrAt(nil, err, recycle)
|
||||
}
|
||||
|
||||
// sErrAt builds a Result from a standard errors.Error reported at a known location.
|
||||
func (h *errorHelper) sErrAt(at pathSegments, err errors.Error, recycle bool) *Result {
|
||||
var result *Result
|
||||
if recycle {
|
||||
result = pools.poolOfResults.BorrowResult()
|
||||
result = validatorPools.results.Borrow()
|
||||
} else {
|
||||
result = new(Result)
|
||||
}
|
||||
result.Errors = []error{err}
|
||||
result.addErrorsAt(at, err)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *errorHelper) addPointerError(res *Result, err error, ref string, fromPath string) *Result {
|
||||
// Provides more context on error messages
|
||||
// reported by the jsoinpointer package by altering the passed Result
|
||||
return h.addPointerErrorAt(res, nil, err, ref, fromPath)
|
||||
}
|
||||
|
||||
// addPointerErrorAt provides more context on error messages reported by the
|
||||
// jsonpointer package, by altering the passed Result.
|
||||
func (h *errorHelper) addPointerErrorAt(res *Result, at pathSegments, err error, ref string, fromPath string) *Result {
|
||||
if err != nil {
|
||||
res.AddErrors(cannotResolveRefMsg(fromPath, ref, err))
|
||||
res.addErrorsAt(at, cannotResolveRefMsg(fromPath, ref, err))
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -222,15 +303,19 @@ func (h *paramHelper) safeExpandedParamsFor(path, method, operationID string, re
|
||||
// remove params with invalid expansion from Slice
|
||||
operation.Parameters = resolvedParams
|
||||
|
||||
for _, ppr := range s.expandedAnalyzer().SafeParamsFor(method, path,
|
||||
// the analyzer keys parameters by name and location: walk those keys in
|
||||
// order, so that findings about an operation's parameters come out the
|
||||
// same way on every run
|
||||
safeParams := s.expandedAnalyzer().SafeParamsFor(method, path,
|
||||
func(_ spec.Parameter, err error) bool {
|
||||
// since params have already been expanded, there are few causes for error
|
||||
res.AddErrors(someParametersBrokenMsg(path, method, operationID))
|
||||
res.addErrorsAt(operationPath(path, method), someParametersBrokenMsg(path, method, operationID))
|
||||
// original error from analyzer
|
||||
res.AddErrors(err)
|
||||
res.addErrorsAt(operationPath(path, method), err)
|
||||
return true
|
||||
}) {
|
||||
params = append(params, ppr)
|
||||
})
|
||||
for _, k := range sortedKeys(safeParams) {
|
||||
params = append(params, safeParams[k])
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -249,14 +334,16 @@ func (h *paramHelper) resolveParam(path, method, operationID string, param *spec
|
||||
if err != nil { // Safeguard
|
||||
// NOTE: we may enter here when the whole parameter is an unresolved $ref
|
||||
refPath := strings.Join([]string{"\"" + path + "\"", method}, ".")
|
||||
errorHelp.addPointerError(res, err, param.Ref.String(), refPath)
|
||||
errorHelp.addPointerErrorAt(res, s.parameterPath(path, method, param.In, param.Name), err, param.Ref.String(), refPath)
|
||||
return nil, res
|
||||
}
|
||||
res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, isRef))
|
||||
res.Merge(h.checkExpandedParam(param, param.Name, param.In, operationID, s.parameterPath(path, method, param.In, param.Name), isRef))
|
||||
return param, res
|
||||
}
|
||||
|
||||
func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation string, isRef bool) *Result {
|
||||
func (h *paramHelper) checkExpandedParam(
|
||||
pr *spec.Parameter, path, in, operation string, at pathSegments, isRef bool,
|
||||
) *Result {
|
||||
// Secure parameter structure after $ref resolution
|
||||
res := new(Result)
|
||||
simpleZero := spec.SimpleSchema{}
|
||||
@@ -267,17 +354,17 @@ func (h *paramHelper) checkExpandedParam(pr *spec.Parameter, path, in, operation
|
||||
// Most likely, a $ref with a sibling is an unwanted situation: in itself this is a warning...
|
||||
// but we detect it because of the following error:
|
||||
// schema took over Parameter for an unexplained reason
|
||||
res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
|
||||
res.addWarningsAt(at, refShouldNotHaveSiblingsMsg(path, operation))
|
||||
}
|
||||
res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
|
||||
res.addErrorsAt(at, invalidParameterDefinitionMsg(path, in, operation))
|
||||
case pr.In != swaggerBody && pr.Schema != nil:
|
||||
if isRef {
|
||||
res.AddWarnings(refShouldNotHaveSiblingsMsg(path, operation))
|
||||
res.addWarningsAt(at, refShouldNotHaveSiblingsMsg(path, operation))
|
||||
}
|
||||
res.AddErrors(invalidParameterDefinitionAsSchemaMsg(path, in, operation))
|
||||
res.addErrorsAt(at, invalidParameterDefinitionAsSchemaMsg(path, in, operation))
|
||||
case (pr.In == swaggerBody && pr.Schema == nil) || (pr.In != swaggerBody && pr.SimpleSchema == simpleZero):
|
||||
// Other unexpected mishaps
|
||||
res.AddErrors(invalidParameterDefinitionMsg(path, in, operation))
|
||||
res.addErrorsAt(at, invalidParameterDefinitionMsg(path, in, operation))
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -288,7 +375,7 @@ type responseHelper struct {
|
||||
|
||||
func (r *responseHelper) expandResponseRef(
|
||||
response *spec.Response,
|
||||
path string, s *SpecValidator,
|
||||
path string, at pathSegments, s *SpecValidator,
|
||||
) (*spec.Response, *Result) {
|
||||
// Ensure response is expanded
|
||||
var err error
|
||||
@@ -301,7 +388,7 @@ func (r *responseHelper) expandResponseRef(
|
||||
}
|
||||
if err != nil { // Safeguard
|
||||
// NOTE: we may enter here when the whole response is an unresolved $ref.
|
||||
errorHelp.addPointerError(res, err, response.Ref.String(), path)
|
||||
errorHelp.addPointerErrorAt(res, at, err, response.Ref.String(), path)
|
||||
return nil, res
|
||||
}
|
||||
|
||||
|
||||
+53
-47
@@ -4,7 +4,6 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
@@ -14,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type objectValidator struct {
|
||||
Path string
|
||||
Path pathSegments
|
||||
In string
|
||||
MaxProperties *int64
|
||||
MinProperties *int64
|
||||
@@ -25,10 +24,9 @@ type objectValidator struct {
|
||||
Root any
|
||||
KnownFormats strfmt.Registry
|
||||
Options *SchemaValidatorOptions
|
||||
splitPath []string
|
||||
}
|
||||
|
||||
func newObjectValidator(path, in string,
|
||||
func newObjectValidator(path pathSegments, in string,
|
||||
maxProperties, minProperties *int64, required []string, properties spec.SchemaProperties,
|
||||
additionalProperties *spec.SchemaOrBool, patternProperties spec.SchemaProperties,
|
||||
root any, formats strfmt.Registry, opts *SchemaValidatorOptions,
|
||||
@@ -39,7 +37,7 @@ func newObjectValidator(path, in string,
|
||||
|
||||
var v *objectValidator
|
||||
if opts.recycleValidators {
|
||||
v = pools.poolOfObjectValidators.BorrowValidator()
|
||||
v = validatorPools.objectValidators.Borrow()
|
||||
} else {
|
||||
v = new(objectValidator)
|
||||
}
|
||||
@@ -55,7 +53,6 @@ func newObjectValidator(path, in string,
|
||||
v.Root = root
|
||||
v.KnownFormats = formats
|
||||
v.Options = opts
|
||||
v.splitPath = strings.Split(v.Path, ".")
|
||||
|
||||
return v
|
||||
}
|
||||
@@ -72,21 +69,21 @@ func (o *objectValidator) Validate(data any) *Result {
|
||||
var ok bool
|
||||
val, ok = data.(map[string]any)
|
||||
if !ok {
|
||||
return errorHelp.sErr(invalidObjectMsg(o.Path, o.In), o.Options.recycleResult)
|
||||
return errorHelp.sErrAt(o.Path, invalidObjectMsg(o.Path.dotted(), o.In), o.Options.recycleResult)
|
||||
}
|
||||
}
|
||||
numKeys := int64(len(val))
|
||||
|
||||
if o.MinProperties != nil && numKeys < *o.MinProperties {
|
||||
return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties), o.Options.recycleResult)
|
||||
return errorHelp.sErrAt(o.Path, errors.TooFewProperties(o.Path.dotted(), o.In, *o.MinProperties), o.Options.recycleResult)
|
||||
}
|
||||
if o.MaxProperties != nil && numKeys > *o.MaxProperties {
|
||||
return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties), o.Options.recycleResult)
|
||||
return errorHelp.sErrAt(o.Path, errors.TooManyProperties(o.Path.dotted(), o.In, *o.MaxProperties), o.Options.recycleResult)
|
||||
}
|
||||
|
||||
var res *Result
|
||||
if o.Options.recycleResult {
|
||||
res = pools.poolOfResults.BorrowResult()
|
||||
res = validatorPools.results.Borrow()
|
||||
} else {
|
||||
res = new(Result)
|
||||
}
|
||||
@@ -106,7 +103,8 @@ func (o *objectValidator) Validate(data any) *Result {
|
||||
|
||||
// Check patternProperties
|
||||
// NOTE: it looks like we have done that twice in many cases
|
||||
for key, value := range val {
|
||||
for _, key := range sortedKeys(val) {
|
||||
value := val[key]
|
||||
_, regularProperty := o.Properties[key]
|
||||
matched, _, patterns := o.validatePatternProperty(key, value, res) // applies to regular properties as well
|
||||
if regularProperty || !matched {
|
||||
@@ -115,7 +113,7 @@ func (o *objectValidator) Validate(data any) *Result {
|
||||
|
||||
for _, pName := range patterns {
|
||||
if v, ok := o.PatternProperties[pName]; ok {
|
||||
r := newSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value)
|
||||
r := newSchemaValidator(&v, o.Root, o.Path.child(key), o.KnownFormats, o.Options).Validate(value)
|
||||
res.mergeForField(data.(map[string]any), key, r) //nolint:forcetypeassert // data is always map[string]any at this point
|
||||
}
|
||||
}
|
||||
@@ -124,11 +122,6 @@ func (o *objectValidator) Validate(data any) *Result {
|
||||
return res
|
||||
}
|
||||
|
||||
func (o *objectValidator) SetPath(path string) {
|
||||
o.Path = path
|
||||
o.splitPath = strings.Split(path, ".")
|
||||
}
|
||||
|
||||
func (o *objectValidator) Applies(source any, kind reflect.Kind) bool {
|
||||
// NOTE: this should also work for structs
|
||||
// there is a problem in the type validator where it will be unhappy about null values
|
||||
@@ -137,19 +130,29 @@ func (o *objectValidator) Applies(source any, kind reflect.Kind) bool {
|
||||
return isSchema && (kind == reflect.Map || kind == reflect.Struct)
|
||||
}
|
||||
|
||||
// The three predicates below tell what kind of content the validated object
|
||||
// is, so that schema-only checks are not run against plain data.
|
||||
//
|
||||
// Array indices are trimmed first: an element of an example is example data
|
||||
// just as much as the example itself.
|
||||
|
||||
func (o *objectValidator) isProperties() bool {
|
||||
p := o.splitPath
|
||||
return len(p) > 1 && p[len(p)-1] == jsonProperties && p[len(p)-2] != jsonProperties
|
||||
p := o.Path.trimIndexes()
|
||||
|
||||
return p.last() == jsonProperties && p.beforeLast() != jsonProperties
|
||||
}
|
||||
|
||||
func (o *objectValidator) isDefault() bool {
|
||||
p := o.splitPath
|
||||
return len(p) > 1 && p[len(p)-1] == jsonDefault && p[len(p)-2] != jsonDefault
|
||||
p := o.Path.trimIndexes()
|
||||
|
||||
return p.last() == jsonDefault && p.beforeLast() != jsonDefault
|
||||
}
|
||||
|
||||
func (o *objectValidator) isExample() bool {
|
||||
p := o.splitPath
|
||||
return len(p) > 1 && (p[len(p)-1] == swaggerExample || p[len(p)-1] == swaggerExamples) && p[len(p)-2] != swaggerExample
|
||||
p := o.Path.trimIndexes()
|
||||
last := p.last()
|
||||
|
||||
return (last == swaggerExample || last == swaggerExamples) && p.beforeLast() != swaggerExample
|
||||
}
|
||||
|
||||
func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]any) {
|
||||
@@ -174,7 +177,7 @@ func (o *objectValidator) checkArrayMustHaveItems(res *Result, val map[string]an
|
||||
return
|
||||
}
|
||||
|
||||
res.AddErrors(errors.Required(jsonItems, o.Path, item))
|
||||
res.addErrorsAt(o.Path, errors.Required(jsonItems, o.Path.dotted(), item))
|
||||
}
|
||||
|
||||
func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]any) {
|
||||
@@ -194,11 +197,11 @@ func (o *objectValidator) checkItemsMustBeTypeArray(res *Result, val map[string]
|
||||
t, typeFound := val[jsonType]
|
||||
if !typeFound {
|
||||
// there is no type
|
||||
res.AddErrors(errors.Required(jsonType, o.Path, t))
|
||||
res.addErrorsAt(o.Path, errors.Required(jsonType, o.Path.dotted(), t))
|
||||
}
|
||||
|
||||
if tpe, isString := t.(string); !isString || tpe != arrayType {
|
||||
res.AddErrors(errors.InvalidType(o.Path, o.In, arrayType, nil))
|
||||
res.addErrorsAt(o.Path, errors.InvalidType(o.Path.dotted(), o.In, arrayType, nil))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +215,7 @@ func (o *objectValidator) precheck(res *Result, val map[string]any) {
|
||||
}
|
||||
|
||||
func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res *Result) {
|
||||
for k := range val {
|
||||
for _, k := range sortedKeys(val) {
|
||||
if k == "$schema" || k == "id" {
|
||||
// special properties "$schema" and "id" are ignored
|
||||
continue
|
||||
@@ -238,7 +241,7 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res
|
||||
continue
|
||||
}
|
||||
|
||||
res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k))
|
||||
res.addErrorsAt(o.Path.child(k), errors.PropertyNotAllowed(o.Path.dotted(), o.In, k))
|
||||
|
||||
// BUG(fredbi): This section should move to a part dedicated to spec validation as
|
||||
// it will conflict with regular schemas where a property "headers" is defined.
|
||||
@@ -261,7 +264,8 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res
|
||||
continue
|
||||
}
|
||||
|
||||
for headerKey, headerBody := range headers {
|
||||
for _, headerKey := range sortedKeys(headers) {
|
||||
headerBody := headers[headerKey]
|
||||
if headerBody == nil {
|
||||
continue
|
||||
}
|
||||
@@ -282,7 +286,7 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res
|
||||
}
|
||||
|
||||
msg := strings.Join([]string{", one may not use $ref=\":", refString, "\""}, "")
|
||||
res.AddErrors(refNotAllowedInHeaderMsg(o.Path, headerKey, msg))
|
||||
res.addErrorsAt(o.Path, refNotAllowedInHeaderMsg(o.Path.dotted(), headerKey, msg))
|
||||
/*
|
||||
case "$ref":
|
||||
if val[k] != nil {
|
||||
@@ -294,7 +298,8 @@ func (o *objectValidator) validateNoAdditionalProperties(val map[string]any, res
|
||||
}
|
||||
|
||||
func (o *objectValidator) validateAdditionalProperties(val map[string]any, res *Result) {
|
||||
for key, value := range val {
|
||||
for _, key := range sortedKeys(val) {
|
||||
value := val[key]
|
||||
_, regularProperty := o.Properties[key]
|
||||
if regularProperty {
|
||||
continue
|
||||
@@ -315,7 +320,7 @@ func (o *objectValidator) validateAdditionalProperties(val map[string]any, res *
|
||||
|
||||
// Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
|
||||
// AdditionalProperties as Schema
|
||||
r := newSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options).Validate(value)
|
||||
r := newSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path.child(key), o.KnownFormats, o.Options).Validate(value)
|
||||
res.mergeForField(val, key, r)
|
||||
}
|
||||
// Valid cases: additionalProperties: true or undefined
|
||||
@@ -326,19 +331,14 @@ func (o *objectValidator) validatePropertiesSchema(val map[string]any, res *Resu
|
||||
|
||||
// Property types:
|
||||
// - regular Property
|
||||
pSchema := pools.poolOfSchemas.BorrowSchema() // recycle a spec.Schema object which lifespan extends only to the validation of properties
|
||||
pSchema := validatorPools.schemas.Borrow() // recycle a spec.Schema object which lifespan extends only to the validation of properties
|
||||
defer func() {
|
||||
pools.poolOfSchemas.RedeemSchema(pSchema)
|
||||
validatorPools.schemas.Redeem(pSchema)
|
||||
}()
|
||||
|
||||
for pName := range o.Properties {
|
||||
for _, pName := range sortedKeys(o.Properties) {
|
||||
*pSchema = o.Properties[pName]
|
||||
var rName string
|
||||
if o.Path == "" {
|
||||
rName = pName
|
||||
} else {
|
||||
rName = o.Path + "." + pName
|
||||
}
|
||||
rName := o.Path.child(pName)
|
||||
|
||||
// Recursively validates each property against its schema
|
||||
v, ok := val[pName]
|
||||
@@ -374,7 +374,9 @@ func (o *objectValidator) validatePropertiesSchema(val map[string]any, res *Resu
|
||||
continue
|
||||
}
|
||||
|
||||
res.AddErrors(errors.Required(fmt.Sprintf("%s.%s", o.Path, k), o.In, v))
|
||||
// located on the object that lacks the property: the property itself
|
||||
// has no node to point at, and the object has to be amended
|
||||
res.addErrorsAt(o.Path, errors.Required(o.Path.child(k).dotted(), o.In, v))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,12 +390,12 @@ func (o *objectValidator) validatePatternProperty(key string, value any, result
|
||||
succeededOnce := false
|
||||
patterns := make([]string, 0, len(o.PatternProperties))
|
||||
|
||||
schema := pools.poolOfSchemas.BorrowSchema()
|
||||
schema := validatorPools.schemas.Borrow()
|
||||
defer func() {
|
||||
pools.poolOfSchemas.RedeemSchema(schema)
|
||||
validatorPools.schemas.Redeem(schema)
|
||||
}()
|
||||
|
||||
for k := range o.PatternProperties {
|
||||
for _, k := range sortedKeys(o.PatternProperties) {
|
||||
re, err := compileRegexp(k)
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -407,7 +409,7 @@ func (o *objectValidator) validatePatternProperty(key string, value any, result
|
||||
*schema = o.PatternProperties[k]
|
||||
patterns = append(patterns, k)
|
||||
matched = true
|
||||
validator := newSchemaValidator(schema, o.Root, fmt.Sprintf("%s.%s", o.Path, key), o.KnownFormats, o.Options)
|
||||
validator := newSchemaValidator(schema, o.Root, o.Path.child(key), o.KnownFormats, o.Options)
|
||||
|
||||
res := validator.Validate(value)
|
||||
result.Merge(res)
|
||||
@@ -416,6 +418,10 @@ func (o *objectValidator) validatePatternProperty(key string, value any, result
|
||||
return matched, succeededOnce, patterns
|
||||
}
|
||||
|
||||
func (o *objectValidator) setPath(path pathSegments) {
|
||||
o.Path = path
|
||||
}
|
||||
|
||||
func (o *objectValidator) redeem() {
|
||||
pools.poolOfObjectValidators.RedeemValidator(o)
|
||||
validatorPools.objectValidators.Redeem(o)
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
)
|
||||
|
||||
// paramLocations tells where an operation declares a parameter.
|
||||
//
|
||||
// Parameters are held in an array, so a name is not how a document addresses
|
||||
// one: only its index is. The index is not something a validator can work out
|
||||
// from an expanded parameter either, because expansion merges the parameters
|
||||
// an operation declares with those its path item declares, and resolves the
|
||||
// ones written as a $ref. So the unexpanded document is indexed once, and
|
||||
// looked up by what a validator does know: the operation, and the name and
|
||||
// location of the parameter it is reporting on.
|
||||
type paramLocations map[paramKey]pathSegments
|
||||
|
||||
// paramKey identifies a parameter the way the swagger specification does:
|
||||
// a name is unique only within an "in".
|
||||
type paramKey struct {
|
||||
path string
|
||||
method string
|
||||
in string
|
||||
name string
|
||||
}
|
||||
|
||||
// newParamLocations indexes the parameters declared by an unexpanded document.
|
||||
func newParamLocations(sp *spec.Swagger) paramLocations {
|
||||
locations := make(paramLocations)
|
||||
if sp == nil || sp.Paths == nil {
|
||||
return locations
|
||||
}
|
||||
|
||||
for path, pathItem := range sp.Paths.Paths {
|
||||
at := newPathSegments(swaggerPaths, path)
|
||||
|
||||
// parameters declared by the path item are shared by all its
|
||||
// operations: recorded once, without a method
|
||||
locations.collect(sp, paramKey{path: path}, at, pathItem.Parameters)
|
||||
|
||||
for method, op := range operationsOf(&pathItem) { //#nosec
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
locations.collect(sp, paramKey{path: path, method: method}, at.child(method), op.Parameters)
|
||||
}
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
// at returns where an operation declares a parameter.
|
||||
//
|
||||
// A parameter the operation does not declare itself may come from its path
|
||||
// item. When neither knows it, which happens for a parameter too broken to be
|
||||
// identified, the pointer stops on the array holding it and the name is kept
|
||||
// for the message alone: an index no one could work out would be a guess.
|
||||
func (l paramLocations) at(path, method, in, name string) pathSegments {
|
||||
if found, isDeclared := l[paramKey{path: path, method: methodToken(method), in: in, name: name}]; isDeclared {
|
||||
return found
|
||||
}
|
||||
|
||||
if found, isDeclared := l[paramKey{path: path, in: in, name: name}]; isDeclared {
|
||||
return found
|
||||
}
|
||||
|
||||
return operationPath(path, method).child(swaggerParameters).cosmeticChild(name)
|
||||
}
|
||||
|
||||
func (l paramLocations) collect(sp *spec.Swagger, key paramKey, at pathSegments, params []spec.Parameter) {
|
||||
for i := range params {
|
||||
name, in, ok := parameterIdentity(sp, ¶ms[i])
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
key.name = name
|
||||
key.in = in
|
||||
l[key] = at.child(swaggerParameters).childAs(strconv.Itoa(i), name)
|
||||
}
|
||||
}
|
||||
|
||||
// parameterIdentity names a parameter as declared, resolving the one indirection
|
||||
// a document may put in the way: an entry written as a local $ref.
|
||||
func parameterIdentity(sp *spec.Swagger, param *spec.Parameter) (name, in string, ok bool) {
|
||||
if param.Ref.String() == "" {
|
||||
return param.Name, param.In, param.Name != ""
|
||||
}
|
||||
|
||||
shared := localRefPath(param.Ref.String())
|
||||
const sharedParameterDepth = 2
|
||||
if len(shared) != sharedParameterDepth || shared.beforeLast() != swaggerParameters {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
declared, isDeclared := sp.Parameters[shared.last()]
|
||||
if !isDeclared {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
return declared.Name, declared.In, declared.Name != ""
|
||||
}
|
||||
|
||||
// operationsOf yields the operations of a path item, keyed as the document
|
||||
// spells them.
|
||||
func operationsOf(pathItem *spec.PathItem) map[string]*spec.Operation {
|
||||
return map[string]*spec.Operation{
|
||||
"get": pathItem.Get,
|
||||
"put": pathItem.Put,
|
||||
"post": pathItem.Post,
|
||||
"delete": pathItem.Delete,
|
||||
"options": pathItem.Options,
|
||||
"head": pathItem.Head,
|
||||
"patch": pathItem.Patch,
|
||||
}
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
)
|
||||
|
||||
// pathSegments is the location of a validated value inside a document,
|
||||
// held as an ordered list of unescaped JSON pointer reference tokens.
|
||||
//
|
||||
// Validators build a location by appending tokens as they descend into
|
||||
// properties and array items, then render it only when they report an error.
|
||||
// Keeping the tokens apart until then makes it possible to produce a
|
||||
// valid [RFC 6901] JSON pointer: a token is escaped when it is rendered, and
|
||||
// the separator can never be confused with a token that contains one.
|
||||
//
|
||||
// The zero value is the location of the document root.
|
||||
//
|
||||
// [RFC 6901]: https://datatracker.ietf.org/doc/html/rfc6901
|
||||
type pathSegments []pathToken
|
||||
|
||||
// pathToken is one step of a location.
|
||||
//
|
||||
// A token addresses a member the way the document does, which is not always
|
||||
// the way a reader recognizes it: an operation addresses its parameters by
|
||||
// index, while a message is far more useful naming them. When the two differ,
|
||||
// display carries the readable form and only the message uses it.
|
||||
type pathToken struct {
|
||||
token string
|
||||
display string
|
||||
|
||||
// structural marks a token a document needs to address the value, but
|
||||
// that messages have never shown: a "properties" between a schema and one
|
||||
// of its members, say. It is part of the pointer and absent from the
|
||||
// dotted form.
|
||||
structural bool
|
||||
|
||||
// cosmetic is the converse: a token messages name but that the document
|
||||
// does not address, such as a parameter too broken to be found by name in
|
||||
// the array holding it. It is part of the dotted form and absent from the
|
||||
// pointer, which then stops at the deepest node the document does contain.
|
||||
cosmetic bool
|
||||
}
|
||||
|
||||
// readable renders a token the way a message should spell it.
|
||||
func (t pathToken) readable() string {
|
||||
if t.display != "" {
|
||||
return t.display
|
||||
}
|
||||
|
||||
return t.token
|
||||
}
|
||||
|
||||
// newPathSegments builds a location from a list of unescaped tokens.
|
||||
func newPathSegments(tokens ...string) pathSegments {
|
||||
if len(tokens) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
segments := make(pathSegments, len(tokens))
|
||||
for i, token := range tokens {
|
||||
segments[i] = pathToken{token: token}
|
||||
}
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
// rootPath is the location of the document root.
|
||||
func rootPath() pathSegments { return nil }
|
||||
|
||||
// String implements [fmt.Stringer] with the legacy dotted notation, so that a
|
||||
// location interpolated into a message reads as it always has.
|
||||
func (p pathSegments) String() string { return p.dotted() }
|
||||
|
||||
// child returns the location of a named member of the value at p.
|
||||
//
|
||||
// The receiver is never modified: sibling children may be derived from the
|
||||
// same parent without aliasing one another.
|
||||
func (p pathSegments) child(token string) pathSegments {
|
||||
return p.appendToken(pathToken{token: token})
|
||||
}
|
||||
|
||||
// childAs returns the location of a member the document addresses as token,
|
||||
// which messages should spell as display instead.
|
||||
func (p pathSegments) childAs(token, display string) pathSegments {
|
||||
return p.appendToken(pathToken{token: token, display: display})
|
||||
}
|
||||
|
||||
// structuralChild returns the location of a member a document addresses but
|
||||
// messages do not name.
|
||||
func (p pathSegments) structuralChild(token string) pathSegments {
|
||||
return p.appendToken(pathToken{token: token, structural: true})
|
||||
}
|
||||
|
||||
// cosmeticChild returns a location that messages spell as a member named token,
|
||||
// while the pointer stays on p.
|
||||
func (p pathSegments) cosmeticChild(token string) pathSegments {
|
||||
return p.appendToken(pathToken{token: token, cosmetic: true})
|
||||
}
|
||||
|
||||
func (p pathSegments) appendToken(token pathToken) pathSegments {
|
||||
child := make(pathSegments, len(p)+1)
|
||||
copy(child, p)
|
||||
child[len(p)] = p.inherit(token)
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
// inherit passes down what a parent token says about addressability: nothing
|
||||
// below a token the document does not address is addressable either, so the
|
||||
// pointer has to stop at the same place.
|
||||
func (p pathSegments) inherit(token pathToken) pathToken {
|
||||
if len(p) > 0 && p[len(p)-1].cosmetic {
|
||||
token.cosmetic = true
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// children returns the location of a chain of named members below p.
|
||||
func (p pathSegments) children(tokens ...string) pathSegments {
|
||||
child := make(pathSegments, len(p)+len(tokens))
|
||||
copy(child, p)
|
||||
for i, token := range tokens {
|
||||
child[len(p)+i] = child[:len(p)+i].inherit(pathToken{token: token})
|
||||
}
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
// item returns the location of the index'th element of the array at p.
|
||||
func (p pathSegments) item(index int) pathSegments {
|
||||
return p.child(strconv.Itoa(index))
|
||||
}
|
||||
|
||||
// isEmpty tells if p locates the document root.
|
||||
func (p pathSegments) isEmpty() bool { return len(p) == 0 }
|
||||
|
||||
// last returns the trailing meaningful token, or an empty string at the
|
||||
// document root.
|
||||
//
|
||||
// Structural tokens are skipped: they say how a document addresses the value,
|
||||
// not what the value is, and the callers here are asking the latter.
|
||||
func (p pathSegments) last() string {
|
||||
if token, ok := p.meaningfulAt(0); ok {
|
||||
return token
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// beforeLast returns the meaningful token before the trailing one, or an empty
|
||||
// string when p holds fewer than two of them.
|
||||
func (p pathSegments) beforeLast() string {
|
||||
if token, ok := p.meaningfulAt(1); ok {
|
||||
return token
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// meaningfulAt returns the nth token from the end, counting only the tokens a
|
||||
// message would show.
|
||||
func (p pathSegments) meaningfulAt(n int) (string, bool) {
|
||||
seen := 0
|
||||
for _, token := range slices.Backward(p) {
|
||||
if token.structural {
|
||||
continue
|
||||
}
|
||||
|
||||
if seen == n {
|
||||
return token.token, true
|
||||
}
|
||||
|
||||
seen++
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// trimIndexes returns p without its trailing array index tokens.
|
||||
//
|
||||
// It answers "what is this value inside of", disregarding how deep into an
|
||||
// array it sits: the items of an example are still an example.
|
||||
func (p pathSegments) trimIndexes() pathSegments {
|
||||
end := len(p)
|
||||
for end > 0 && isIndexToken(p[end-1].token) {
|
||||
end--
|
||||
}
|
||||
|
||||
return p[:end]
|
||||
}
|
||||
|
||||
// isIndexToken tells if a token addresses an array element rather than a member.
|
||||
func isIndexToken(token string) bool {
|
||||
if token == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range token {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// hasSuffix tells if p ends with the given sequence of tokens.
|
||||
func (p pathSegments) hasSuffix(suffix pathSegments) bool {
|
||||
if len(suffix) > len(p) {
|
||||
return false
|
||||
}
|
||||
|
||||
offset := len(p) - len(suffix)
|
||||
for i, token := range suffix {
|
||||
if p[offset+i].token != token.token {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// dotted renders the location in the legacy dot-separated notation, e.g.
|
||||
// "definitions.Pet.friends.0.name".
|
||||
//
|
||||
// Tokens are emitted verbatim: a token containing a dot is indistinguishable
|
||||
// from a separator. This notation is kept because it is what surfaces as the
|
||||
// name of a validation error, and API consumers of go-swagger servers see it.
|
||||
// Use [pathSegments.pointer] whenever the location needs to be unambiguous.
|
||||
func (p pathSegments) dotted() string {
|
||||
readable := make([]string, 0, len(p))
|
||||
for _, token := range p {
|
||||
if token.structural {
|
||||
continue
|
||||
}
|
||||
|
||||
readable = append(readable, token.readable())
|
||||
}
|
||||
|
||||
return strings.Join(readable, ".")
|
||||
}
|
||||
|
||||
// pointer renders the location as an RFC 6901 JSON pointer, e.g.
|
||||
// "/definitions/Pet/friends/0/name". The document root renders as "".
|
||||
func (p pathSegments) pointer() string {
|
||||
if len(p) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var w strings.Builder
|
||||
for _, token := range p {
|
||||
if token.cosmetic {
|
||||
continue
|
||||
}
|
||||
|
||||
w.WriteByte('/')
|
||||
w.WriteString(jsonpointer.Escape(token.token))
|
||||
}
|
||||
|
||||
return w.String()
|
||||
}
|
||||
+64
-346
@@ -1,369 +1,87 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !validatedebug
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/swag/pools"
|
||||
)
|
||||
|
||||
var pools allPools
|
||||
// validatorPools recycles the objects allocated while validating.
|
||||
//
|
||||
// Validation allocates a validator per schema node and a result per check, so
|
||||
// the same handful of types are built and thrown away constantly. Recycling
|
||||
// them keeps validating a large specification affordable.
|
||||
//
|
||||
// Build with the "poolsdebug" tag to have every borrow and redeem tracked:
|
||||
// misuse then panics where it happens rather than corrupting a pool, and
|
||||
// [pools.AssertNoLeaks] reports what was borrowed and never given back.
|
||||
var validatorPools allPools
|
||||
|
||||
func init() {
|
||||
resetPools()
|
||||
}
|
||||
|
||||
// resetPools builds a fresh set of pools.
|
||||
//
|
||||
// Recycling an object twice leaves a pool holding it twice, and it would then
|
||||
// be handed to two borrowers at once. A test that provokes such misuse has to
|
||||
// start the next one from clean pools.
|
||||
func resetPools() {
|
||||
// NOTE: for testing purpose, we might want to reset pools after calling Validate twice.
|
||||
// The pool is corrupted in that case: calling Put twice inserts a duplicate in the pool
|
||||
// and further calls to Get are mishandled.
|
||||
|
||||
pools = allPools{
|
||||
poolOfSchemaValidators: schemaValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &SchemaValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfObjectValidators: objectValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &objectValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfSliceValidators: sliceValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &schemaSliceValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfItemsValidators: itemsValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &itemsValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfBasicCommonValidators: basicCommonValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &basicCommonValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfHeaderValidators: headerValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &HeaderValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfParamValidators: paramValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &ParamValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfBasicSliceValidators: basicSliceValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &basicSliceValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfNumberValidators: numberValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &numberValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfStringValidators: stringValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &stringValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfSchemaPropsValidators: schemaPropsValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &schemaPropsValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfFormatValidators: formatValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &formatValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfTypeValidators: typeValidatorsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &typeValidator{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfSchemas: schemasPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &spec.Schema{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
poolOfResults: resultsPool{
|
||||
Pool: &sync.Pool{
|
||||
New: func() any {
|
||||
s := &Result{}
|
||||
|
||||
return s
|
||||
},
|
||||
},
|
||||
},
|
||||
validatorPools = allPools{
|
||||
schemaValidators: pools.New[SchemaValidator](),
|
||||
objectValidators: pools.New[objectValidator](),
|
||||
sliceValidators: pools.New[schemaSliceValidator](),
|
||||
itemsValidators: pools.New[itemsValidator](),
|
||||
basicCommonValidators: pools.New[basicCommonValidator](),
|
||||
headerValidators: pools.New[HeaderValidator](),
|
||||
paramValidators: pools.New[ParamValidator](),
|
||||
basicSliceValidators: pools.New[basicSliceValidator](),
|
||||
numberValidators: pools.New[numberValidator](),
|
||||
stringValidators: pools.New[stringValidator](),
|
||||
schemaPropsValidators: pools.New[schemaPropsValidator](),
|
||||
formatValidators: pools.New[formatValidator](),
|
||||
typeValidators: pools.New[typeValidator](),
|
||||
schemas: pools.New[spec.Schema](),
|
||||
results: pools.New[Result](),
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
allPools struct {
|
||||
// memory pools for all validator objects.
|
||||
//
|
||||
// Each pool can be borrowed from and redeemed to.
|
||||
poolOfSchemaValidators schemaValidatorsPool
|
||||
poolOfObjectValidators objectValidatorsPool
|
||||
poolOfSliceValidators sliceValidatorsPool
|
||||
poolOfItemsValidators itemsValidatorsPool
|
||||
poolOfBasicCommonValidators basicCommonValidatorsPool
|
||||
poolOfHeaderValidators headerValidatorsPool
|
||||
poolOfParamValidators paramValidatorsPool
|
||||
poolOfBasicSliceValidators basicSliceValidatorsPool
|
||||
poolOfNumberValidators numberValidatorsPool
|
||||
poolOfStringValidators stringValidatorsPool
|
||||
poolOfSchemaPropsValidators schemaPropsValidatorsPool
|
||||
poolOfFormatValidators formatValidatorsPool
|
||||
poolOfTypeValidators typeValidatorsPool
|
||||
poolOfSchemas schemasPool
|
||||
poolOfResults resultsPool
|
||||
}
|
||||
|
||||
schemaValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
objectValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
sliceValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
itemsValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
basicCommonValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
headerValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
paramValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
basicSliceValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
numberValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
stringValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
schemaPropsValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
formatValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
typeValidatorsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
schemasPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
|
||||
resultsPool struct {
|
||||
*sync.Pool
|
||||
}
|
||||
)
|
||||
|
||||
func (p schemaValidatorsPool) BorrowValidator() *SchemaValidator {
|
||||
return p.Get().(*SchemaValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
// allPools is the set of pools shared by the validators of this package.
|
||||
type allPools struct {
|
||||
schemaValidators *pools.Pool[SchemaValidator]
|
||||
objectValidators *pools.Pool[objectValidator]
|
||||
sliceValidators *pools.Pool[schemaSliceValidator]
|
||||
itemsValidators *pools.Pool[itemsValidator]
|
||||
basicCommonValidators *pools.Pool[basicCommonValidator]
|
||||
headerValidators *pools.Pool[HeaderValidator]
|
||||
paramValidators *pools.Pool[ParamValidator]
|
||||
basicSliceValidators *pools.Pool[basicSliceValidator]
|
||||
numberValidators *pools.Pool[numberValidator]
|
||||
stringValidators *pools.Pool[stringValidator]
|
||||
schemaPropsValidators *pools.Pool[schemaPropsValidator]
|
||||
formatValidators *pools.Pool[formatValidator]
|
||||
typeValidators *pools.Pool[typeValidator]
|
||||
schemas *pools.Pool[spec.Schema]
|
||||
results *pools.Pool[Result]
|
||||
}
|
||||
|
||||
func (p schemaValidatorsPool) RedeemValidator(s *SchemaValidator) {
|
||||
// NOTE: s might be nil. In that case, Put is a noop.
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p objectValidatorsPool) BorrowValidator() *objectValidator {
|
||||
return p.Get().(*objectValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p objectValidatorsPool) RedeemValidator(s *objectValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p sliceValidatorsPool) BorrowValidator() *schemaSliceValidator {
|
||||
return p.Get().(*schemaSliceValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p sliceValidatorsPool) RedeemValidator(s *schemaSliceValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p itemsValidatorsPool) BorrowValidator() *itemsValidator {
|
||||
return p.Get().(*itemsValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p itemsValidatorsPool) RedeemValidator(s *itemsValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p basicCommonValidatorsPool) BorrowValidator() *basicCommonValidator {
|
||||
return p.Get().(*basicCommonValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p basicCommonValidatorsPool) RedeemValidator(s *basicCommonValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p headerValidatorsPool) BorrowValidator() *HeaderValidator {
|
||||
return p.Get().(*HeaderValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p headerValidatorsPool) RedeemValidator(s *HeaderValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p paramValidatorsPool) BorrowValidator() *ParamValidator {
|
||||
return p.Get().(*ParamValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p paramValidatorsPool) RedeemValidator(s *ParamValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p basicSliceValidatorsPool) BorrowValidator() *basicSliceValidator {
|
||||
return p.Get().(*basicSliceValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p basicSliceValidatorsPool) RedeemValidator(s *basicSliceValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p numberValidatorsPool) BorrowValidator() *numberValidator {
|
||||
return p.Get().(*numberValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p numberValidatorsPool) RedeemValidator(s *numberValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p stringValidatorsPool) BorrowValidator() *stringValidator {
|
||||
return p.Get().(*stringValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p stringValidatorsPool) RedeemValidator(s *stringValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p schemaPropsValidatorsPool) BorrowValidator() *schemaPropsValidator {
|
||||
return p.Get().(*schemaPropsValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p schemaPropsValidatorsPool) RedeemValidator(s *schemaPropsValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p formatValidatorsPool) BorrowValidator() *formatValidator {
|
||||
return p.Get().(*formatValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p formatValidatorsPool) RedeemValidator(s *formatValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p typeValidatorsPool) BorrowValidator() *typeValidator {
|
||||
return p.Get().(*typeValidator) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p typeValidatorsPool) RedeemValidator(s *typeValidator) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p schemasPool) BorrowSchema() *spec.Schema {
|
||||
return p.Get().(*spec.Schema) //nolint:forcetypeassert // pool New always returns this type
|
||||
}
|
||||
|
||||
func (p schemasPool) RedeemSchema(s *spec.Schema) {
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func (p resultsPool) BorrowResult() *Result {
|
||||
return p.Get().(*Result).cleared() //nolint:forcetypeassert // pool New always returns *Result
|
||||
}
|
||||
|
||||
func (p resultsPool) RedeemResult(s *Result) {
|
||||
if s == emptyResult {
|
||||
// redeemResult returns a result to the pool.
|
||||
//
|
||||
// emptyResult is a shared value that was never borrowed, so it is not the
|
||||
// pool's to take back: handing it over would be reported as a foreign redeem,
|
||||
// rightly.
|
||||
//
|
||||
// Results are borrowed straight from the pool rather than through a helper,
|
||||
// so that the instrumented build attributes a leak to the code that borrowed it.
|
||||
//
|
||||
// This wrapper costs that attribution on redeem, where a double redeem still
|
||||
// names the offending call site in the panic it raises.
|
||||
func redeemResult(r *Result) {
|
||||
if r == emptyResult {
|
||||
return
|
||||
}
|
||||
p.Put(s)
|
||||
|
||||
validatorPools.results.Redeem(r)
|
||||
}
|
||||
-1015
File diff suppressed because it is too large.
Load diff
+49
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/analysis"
|
||||
)
|
||||
|
||||
// refLocations tells where a $ref value is declared in a document.
|
||||
//
|
||||
// The analyzer indexes references the other way around, by the location each
|
||||
// was found at, so the index is inverted here. Only declarations are indexed:
|
||||
// a "$ref" member sitting in an example or in a default value is data, and the
|
||||
// analyzer never walks into it.
|
||||
type refLocations map[string]pathSegments
|
||||
|
||||
// newRefLocations inverts the analyzer's reference index.
|
||||
//
|
||||
// A reference declared in several places keeps the smallest declaration, so
|
||||
// that the answer does not depend on map iteration order.
|
||||
func newRefLocations(analyzer *analysis.Spec) refLocations {
|
||||
declarations := make(map[string]string)
|
||||
for location, ref := range analyzer.AllRefsByLocation() {
|
||||
value := ref.String()
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if known, isKnown := declarations[value]; isKnown && known <= location {
|
||||
continue
|
||||
}
|
||||
|
||||
declarations[value] = location
|
||||
}
|
||||
|
||||
locations := make(refLocations, len(declarations))
|
||||
for value, location := range declarations {
|
||||
locations[value] = localRefPath(location)
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
// at returns where a reference is declared, or the document root when the
|
||||
// reference is not one the analyzer indexed.
|
||||
func (l refLocations) at(ref string) pathSegments {
|
||||
return l[ref]
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
)
|
||||
|
||||
// maxRefHops bounds how many $ref a pointer may be followed through, so that a
|
||||
// document referring to itself cannot spin here.
|
||||
const maxRefHops = 10
|
||||
|
||||
// refRedirects maps the location of a $ref to the location it points at, for
|
||||
// the local references of a document.
|
||||
//
|
||||
// Checks walk the expanded document, so a finding below a $ref comes out with a
|
||||
// pointer that descends into a node the authored document does not contain: a
|
||||
// bare "$ref" member has nothing under it. Following the reference turns such a
|
||||
// pointer back into one the document addresses.
|
||||
type refRedirects map[string]string
|
||||
|
||||
func newRefRedirects(analyzer *analysis.Spec) refRedirects {
|
||||
redirects := make(refRedirects)
|
||||
for location, ref := range analyzer.AllRefsByLocation() {
|
||||
target := ref.String()
|
||||
if !strings.HasPrefix(target, "#/") {
|
||||
// only a local reference has a location in this document
|
||||
continue
|
||||
}
|
||||
|
||||
redirects[strings.TrimPrefix(location, "#")] = strings.TrimPrefix(target, "#")
|
||||
}
|
||||
|
||||
return redirects
|
||||
}
|
||||
|
||||
// through rewrites a pointer that descends below a $ref.
|
||||
//
|
||||
// A pointer that stops at the $ref itself is left alone: that node exists, and
|
||||
// it is where a reader has to go to amend the reference.
|
||||
func (r refRedirects) through(pointer string) string {
|
||||
if len(r) == 0 {
|
||||
return pointer
|
||||
}
|
||||
|
||||
for range maxRefHops {
|
||||
prefix, rest, ok := r.crossing(pointer)
|
||||
if !ok {
|
||||
return pointer
|
||||
}
|
||||
|
||||
pointer = prefix + rest
|
||||
}
|
||||
|
||||
return pointer
|
||||
}
|
||||
|
||||
// crossing finds the longest prefix of pointer that holds a $ref, and returns
|
||||
// the location that reference points at together with what is left below it.
|
||||
func (r refRedirects) crossing(pointer string) (target, rest string, ok bool) {
|
||||
for at := strings.LastIndex(pointer, "/"); at > 0; at = strings.LastIndex(pointer[:at], "/") {
|
||||
if target, isRef := r[pointer[:at]]; isRef {
|
||||
return target, pointer[at:], true
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", false
|
||||
}
|
||||
Loaded 100 of 354 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user