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:
Tonis Tiigi committed 2026-11-04 00:44:46 -08:00
1 parent a2b271198f
commit 7281e97ffb
354 files changed
+12340 -5796

No files matched your search

+52 -51
View File
@@ -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
+114 -112
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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?
@@ -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)
@@ -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)
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
@@ -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.
@@ -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)),
}
@@ -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
View File
@@ -7,3 +7,8 @@ coverage:
patch:
default:
target: 80%
ignore:
- "testdata"
- "internal/antest"
- "internal/testintegration"
+1
View File
@@ -10,6 +10,7 @@ linters:
- gomodguard
- gomodguard_v2
- exhaustruct
- exhaustruct_v5
- nlreturn
- nonamedreturns
- noinlineerr
+17 -16
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,4 @@
go 1.25.0
go 1.26.0
use (
.
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -5,4 +5,4 @@ use (
./server-middleware
)
go 1.25.0
go 1.26.0
+12 -1
View File
@@ -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 {
@@ -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
@@ -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) {
@@ -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
View File
@@ -9,6 +9,7 @@ linters:
- gomodguard
- gomodguard_v2
- exhaustruct
- exhaustruct_v5
- nlreturn
- nonamedreturns
- noinlineerr
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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(&parameter, 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -10,6 +10,7 @@ linters:
- gomodguard_v2
- godox
- exhaustruct
- exhaustruct_v5
- nlreturn
- nonamedreturns
- noinlineerr
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -4,4 +4,4 @@ use (
./internal/testintegration
)
go 1.25.0
go 1.26.0
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -18,4 +18,4 @@ use (
./yamlutils
)
go 1.25.0
go 1.26.0
@@ -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
View File
@@ -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
View File
@@ -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:
+1
View File
@@ -3,3 +3,4 @@
.idea
.env
.mcp.json
.worktrees
+3
View File
@@ -7,7 +7,10 @@ linters:
- gochecknoglobals
- gochecknoinits
- godox
- gomodguard
- gomodguard_v2
- exhaustruct
- exhaustruct_v5
- ireturn
- nlreturn
- nestif
-34
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(&param, 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, &param, param.Items) //#nosec
red := d.validateDefaultValueItemsAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, &param, 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
View File
@@ -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
View File
@@ -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
View File
@@ -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(&param, 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, &param, param.Items) //#nosec
red := ex.validateExampleValueItemsAgainstSchema(s.parameterPath(path, method, param.In, param.Name), param.In, &param, 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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, &params[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
View File
@@ -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
View File
@@ -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)
}
File diff suppressed because it is too large. Load diff
+49
View File
@@ -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
View File
@@ -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