[chore] update dependencies (#4848)
- ~~github.com/go-swagger/go-swagger: v0.33.2 -> v0.34.0~~ ignoring for now - github.com/jackc/pgx/v5: v5.9.2 -> v5.10.0 - github.com/minio/minio-go/v7: v7.1.0 -> v7.2.0 - github.com/mitchellh/mapstructure: v1.5.0 -> v1.5.0 - github.com/ncruces/go-sqlite3: v0.34.2 -> v0.34.4 - github.com/tetratelabs/wazero: v1.11.0 -> v1.12.0 - go.opentelemetry.io/contrib/exporters/autoexport: v0.68.0 -> v0.69.0 - go.opentelemetry.io/contrib/instrumentation/runtime: v0.68.0 -> v0.69.0 - golang.org/x/image: v0.40.0 -> v0.41.0 - codeberg.org/gruf/go-ffmpreg: v0.6.19 -> v0.6.20 Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4848
This commit is contained in:
@@ -22,7 +22,7 @@ require (
|
||||
codeberg.org/gruf/go-errors/v2 v2.3.4
|
||||
codeberg.org/gruf/go-fastcopy v1.1.3
|
||||
codeberg.org/gruf/go-fastpath/v2 v2.0.1
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.19
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.20
|
||||
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf
|
||||
codeberg.org/gruf/go-kv/v2 v2.0.10
|
||||
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
|
||||
@@ -51,14 +51,14 @@ require (
|
||||
github.com/gorilla/feeds v1.2.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/cronexpr v1.1.3
|
||||
github.com/jackc/pgx/v5 v5.9.2
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/k3a/html2text v1.4.0
|
||||
github.com/klauspost/compress v1.18.6
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/minio/minio-go/v7 v7.1.0
|
||||
github.com/minio/minio-go/v7 v7.2.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/ncruces/go-sqlite3 v0.34.2
|
||||
github.com/ncruces/go-sqlite3 v0.34.4
|
||||
github.com/oklog/ulid/v2 v2.1.1
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
@@ -70,7 +70,7 @@ require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tdewolff/minify/v2 v2.24.13
|
||||
github.com/temoto/robotstxt v1.1.2
|
||||
github.com/tetratelabs/wazero v1.11.0
|
||||
github.com/tetratelabs/wazero v1.12.0
|
||||
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
github.com/uptrace/bun v1.2.18
|
||||
@@ -79,24 +79,24 @@ require (
|
||||
github.com/uptrace/bun/extra/bunotel v1.2.18
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.68.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0
|
||||
go.opentelemetry.io/otel/metric v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.69.0
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.69.0
|
||||
go.opentelemetry.io/otel v1.44.0
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.66.0
|
||||
go.opentelemetry.io/otel/metric v1.44.0
|
||||
go.opentelemetry.io/otel/sdk v1.44.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0
|
||||
go.opentelemetry.io/otel/trace v1.44.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/crypto v0.51.0
|
||||
golang.org/x/image v0.40.0
|
||||
golang.org/x/net v0.54.0
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/image v0.41.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/text v0.37.0
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.50.1
|
||||
modernc.org/sqlite v1.51.0
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
)
|
||||
|
||||
@@ -109,7 +109,7 @@ require (
|
||||
codeberg.org/gruf/go-xunsafe v0.0.0-20260211121553-1c99e9364fee // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.5.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
@@ -128,41 +128,41 @@ require (
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.24.3 // indirect
|
||||
github.com/go-openapi/analysis v0.25.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.7 // indirect
|
||||
github.com/go-openapi/inflect v0.21.5 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.5 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.23.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.5 // indirect
|
||||
github.com/go-openapi/loads v0.23.3 // indirect
|
||||
github.com/go-openapi/runtime v0.29.3 // indirect
|
||||
github.com/go-openapi/runtime v0.32.2 // indirect
|
||||
github.com/go-openapi/runtime/server-middleware v0.32.1 // indirect
|
||||
github.com/go-openapi/spec v0.22.4 // indirect
|
||||
github.com/go-openapi/strfmt v0.26.1 // indirect
|
||||
github.com/go-openapi/swag v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
|
||||
github.com/go-openapi/strfmt v0.26.2 // indirect
|
||||
github.com/go-openapi/swag v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/conv v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.26.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.26.0 // indirect
|
||||
github.com/go-openapi/validate v0.25.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
@@ -172,7 +172,7 @@ require (
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
@@ -194,11 +194,11 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-sqlite3-wasm/v2 v2.4.35301 // indirect
|
||||
github.com/ncruces/go-sqlite3-wasm/v2 v2.6.35302 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
@@ -213,9 +213,8 @@ require (
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.8.12 // indirect
|
||||
@@ -229,30 +228,31 @@ require (
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/zeebo/xxh3 v1.1.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.69.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.20.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.20.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/grpc v1.81.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/ini.v1 v1.67.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.72.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
|
||||
@@ -28,8 +28,8 @@ codeberg.org/gruf/go-fastcopy v1.1.3 h1:Jo9VTQjI6KYimlw25PPc7YLA3Xm+XMQhaHwKnM7x
|
||||
codeberg.org/gruf/go-fastcopy v1.1.3/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
|
||||
codeberg.org/gruf/go-fastpath/v2 v2.0.1 h1:smCq3mpYiPibH4Na3SEN2rtr5oPmrY+cVRQ8nFCznJ4=
|
||||
codeberg.org/gruf/go-fastpath/v2 v2.0.1/go.mod h1:ABH9P8jc/bUub/RW9yTI7i+Gm/Oqe6ZHcIGEumvAOKk=
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.19 h1:2dCx2fFDRSdOrfe2OI00pbpYPKaghqjuz7n8XRsuqjY=
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.19/go.mod h1:hE0Dmx3cjI3ZgCUVFNi3H+0JgIz6Us27arY1ML0AxqU=
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.20 h1:4Rc9yQLH1GTdMeiGPlMR1JUGNMJSjw6mRIxd+rxqDCo=
|
||||
codeberg.org/gruf/go-ffmpreg v0.6.20/go.mod h1:XM6eW8CSgAuZRKRynm5nDiSNedm0rav30IychqygOHE=
|
||||
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf h1:84s/ii8N6lYlskZjHH+DG6jyia8w2mXMZlRwFn8Gs3A=
|
||||
codeberg.org/gruf/go-iotools v0.0.0-20240710125620-934ae9c654cf/go.mod h1:zZAICsp5rY7+hxnws2V0ePrWxE0Z2Z/KXcN3p/RQCfk=
|
||||
codeberg.org/gruf/go-kv v1.6.5 h1:ttPf0NA8F79pDqBttSudPTVCZmGncumeNIxmeM9ztz0=
|
||||
@@ -74,8 +74,8 @@ github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43Dw
|
||||
github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0 h1:ocnzNKWN23T9nvHi6IfyrQjkIc0oJWv1B1pULsf9i3s=
|
||||
@@ -140,16 +140,16 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
|
||||
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
@@ -169,8 +169,6 @@ github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -178,54 +176,56 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/analysis v0.24.3 h1:a1hrvMr8X0Xt69KP5uVTu5jH62DscmDifrLzNglAayk=
|
||||
github.com/go-openapi/analysis v0.24.3/go.mod h1:Nc+dWJ/FxZbhSow5Yh3ozg5CLJioB+XXT6MdLvJUsUw=
|
||||
github.com/go-openapi/analysis v0.25.0 h1:EnjAq1yO8wEO9HbPmY8vLPEIkdZuuFhCAKBPvCB7bCs=
|
||||
github.com/go-openapi/analysis v0.25.0/go.mod h1:5WFTRE43WLkPG9r9OtlMfqkkvUTYLVVCIxLlEpyF8kE=
|
||||
github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=
|
||||
github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w=
|
||||
github.com/go-openapi/inflect v0.21.5 h1:M2RCq6PPS3YbIaL7CXosGL3BbzAcmfBAT0nC3YfesZA=
|
||||
github.com/go-openapi/inflect v0.21.5/go.mod h1:GypUyi6bU880NYurWaEH2CmH84zFDNd+EhhmzroHmB4=
|
||||
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
|
||||
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
|
||||
github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
|
||||
github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
|
||||
github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
|
||||
github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
|
||||
github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ=
|
||||
github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA=
|
||||
github.com/go-openapi/runtime v0.29.3 h1:h5twGaEqxtQg40ePiYm9vFFH1q06Czd7Ot6ufdK0w/Y=
|
||||
github.com/go-openapi/runtime v0.29.3/go.mod h1:8A1W0/L5eyNJvKciqZtvIVQvYO66NlB7INMSZ9bw/oI=
|
||||
github.com/go-openapi/runtime v0.32.2 h1:X9mZz716lFwYZ6bFV1BBnthNdHTy46zKM5Em4D1UISI=
|
||||
github.com/go-openapi/runtime v0.32.2/go.mod h1:IfM3cpgencPuwBp5Uo16i2IQaE74odL7Q4DCGovIQac=
|
||||
github.com/go-openapi/runtime/server-middleware v0.32.1 h1:HCtsghA5YuJEJwne3wQj8agMkEgg1iWbJjZw7Qdvu8g=
|
||||
github.com/go-openapi/runtime/server-middleware v0.32.1/go.mod h1:fYPep4GdTwg/XqZUjR40uIM/8C12Ba5M+MrGCiwpTHo=
|
||||
github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
|
||||
github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
|
||||
github.com/go-openapi/strfmt v0.26.1 h1:7zGCHji7zSYDC2tCXIusoxYQz/48jAf2q+sF6wXTG+c=
|
||||
github.com/go-openapi/strfmt v0.26.1/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y=
|
||||
github.com/go-openapi/swag v0.25.5 h1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=
|
||||
github.com/go-openapi/swag v0.25.5/go.mod h1:B3RT6l8q7X803JRxa2e59tHOiZlX1t8viplOcs9CwTA=
|
||||
github.com/go-openapi/swag/cmdutils v0.25.5 h1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=
|
||||
github.com/go-openapi/swag/cmdutils v0.25.5/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
|
||||
github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
|
||||
github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
|
||||
github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=
|
||||
github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
|
||||
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
|
||||
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
|
||||
github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=
|
||||
github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY=
|
||||
github.com/go-openapi/swag/netutils v0.25.5 h1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=
|
||||
github.com/go-openapi/swag/netutils v0.25.5/go.mod h1:lHbtmj4m57APG/8H7ZcMMSWzNqIQcu0RFiXrPUara14=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
|
||||
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
|
||||
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 h1:NZOrZmIb6PTv5LTFxr5/mKV/FjbUzGE7E6gLz7vFoOQ=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.1/go.mod h1:r7dwsujEHawapMsxA69i+XMGZrQ5tRauhLAjV/sxg3Q=
|
||||
github.com/go-openapi/testify/v2 v2.4.1 h1:zB34HDKj4tHwyUQHrUkpV0Q0iXQ6dUCOQtIqn8hE6Iw=
|
||||
github.com/go-openapi/testify/v2 v2.4.1/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-openapi/strfmt v0.26.2 h1:ysjheCh4i1rmFEo2LanhELDNucNzfWTZhUDKgWWPaFM=
|
||||
github.com/go-openapi/strfmt v0.26.2/go.mod h1:fXh1e449cyUn2NYuz+wb3wARBUdMl7qPEZwX00nqivY=
|
||||
github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI=
|
||||
github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0=
|
||||
github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU=
|
||||
github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
|
||||
github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I=
|
||||
github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE=
|
||||
github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU=
|
||||
github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc=
|
||||
github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=
|
||||
github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M=
|
||||
github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA=
|
||||
github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y=
|
||||
github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko=
|
||||
github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg=
|
||||
github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ=
|
||||
github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0=
|
||||
github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c=
|
||||
github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo=
|
||||
github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg=
|
||||
github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE=
|
||||
github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4=
|
||||
github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE=
|
||||
github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk=
|
||||
github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo=
|
||||
github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
github.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0=
|
||||
github.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@@ -244,8 +244,8 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY=
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
@@ -294,8 +294,8 @@ github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzq
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/hashicorp/cronexpr v1.1.3 h1:rl5IkxXN2m681EfivTlccqIryzYJSXRGRNa0xeG7NA4=
|
||||
github.com/hashicorp/cronexpr v1.1.3/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@@ -310,8 +310,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
||||
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -353,8 +353,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.1.0 h1:QEt5IStDpxgGjEdtOgpiZ5QhmSl3ax7qy61vi2SwHO8=
|
||||
github.com/minio/minio-go/v7 v7.1.0/go.mod h1:Dm7WS1AgLmBa0NcQD6SeJnJf+K/EUW3GR7Ks6olB3OA=
|
||||
github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs=
|
||||
github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
@@ -372,10 +372,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-sqlite3 v0.34.2 h1:+B50kRdn2BfMTSoRbkgnNaIolxIq1qS6lhcXyvNe230=
|
||||
github.com/ncruces/go-sqlite3 v0.34.2/go.mod h1:ZUqB9w9k4ACD7X5YeISBY05glvkgTur3dwhoDFGASK4=
|
||||
github.com/ncruces/go-sqlite3-wasm/v2 v2.4.35301 h1:xGFgiIf1SS4yTqyuW3cSR6hd9KRlUFzVloJ873AyrxU=
|
||||
github.com/ncruces/go-sqlite3-wasm/v2 v2.4.35301/go.mod h1:7dV8P4xml/vrgb/zKfJaZ5aas5el3VyBR28XkpBq5NM=
|
||||
github.com/ncruces/go-sqlite3 v0.34.4 h1:bp8jd1o2CMvjkrrp1lOtHDu1TdzExNWACt+piQeMwzg=
|
||||
github.com/ncruces/go-sqlite3 v0.34.4/go.mod h1:tOyhDWnzlrzflKIGw177imjuO4ZEbfOS1NJ67B1BanQ=
|
||||
github.com/ncruces/go-sqlite3-wasm/v2 v2.6.35302 h1:IZCiInPIp6OhOc1skMDGOBMwMQuE1TK+QqVe35vd/ro=
|
||||
github.com/ncruces/go-sqlite3-wasm/v2 v2.6.35302/go.mod h1:ELHF6yqC51E0DiitfabHXl/aKKouCihugbhNT5a+yEY=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
@@ -385,8 +385,8 @@ github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNs
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
|
||||
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -427,8 +427,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
@@ -441,8 +441,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
@@ -477,8 +475,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
|
||||
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
||||
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
||||
github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
|
||||
github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
|
||||
github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
|
||||
@@ -554,50 +552,52 @@ gitlab.com/NyaaaWhatsUpDoc/sqlite v1.50.1-concurrency-workaround h1:LzPBKbt330N8
|
||||
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.50.1-concurrency-workaround/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
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/bridges/prometheus v0.68.0 h1:w3zlHYETbDwXyWHZlyyR58ZC39XGi8rAhkBgUgJ9d5w=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0/go.mod h1:GR/mClR2nn7vE8RLwxKjoBNg+QtgdDhRzxVa93koy5o=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0 h1:0D3GFvELGIwQGfC6agLsbrEYSGWZTRTxIXxcQUqrOuk=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0/go.mod h1:DM2NV7Zb8CcGeVPt6glouY0FAiwZQ/iqgcWExhgWeN8=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.68.0 h1:jhVIQEprwUTV+KfzzliLidclhoTOoHTgdz96kAyR8mU=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.68.0/go.mod h1:4HsdbLUbernaTnA8CNaNE+1g026SciXb3juRYe3l8EY=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/BUNCEgfrmfboztcS6PhvSo88HKM=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU=
|
||||
go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
|
||||
go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.69.0 h1:saQoWg5845Q8TojpqeVStS7zGwVZ6bc5W2PJavTPiBM=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.69.0/go.mod h1:AAaS6xs5AyqMdR3Ir0nSWK+QudL2XM8Vbw5INzUxNc8=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.69.0 h1:R3jsCoTIzv0BiYNhW0axyswn/6SMJ8xL1OuGxvni1Kw=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.69.0/go.mod h1:m07gqyr2QhQxKOKb5vqKCCBtLH3uqlNYR7PU/FISXVU=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.69.0 h1:MtkMsuRo3zEXTTMALfyrszwCDZTkB6wolyPjbwFAdq0=
|
||||
go.opentelemetry.io/contrib/instrumentation/runtime v0.69.0/go.mod h1:FYTxnpsm+UPD0erZNq20GvnM8T2YQHiHtT2vokdpoac=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 h1:rydZ9sxbcFdm/oWrVyfLTjHIygMgv0bEeMd+3B/BvoM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0/go.mod h1:earQ25dooT0Hhspq59DZ8YCC50jWfOlFEeWoxy/P444=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 h1:owlhcJ3QO3X0YTDTCcDZ4V+6aVDkWbNmBoQ5NUp7Oww=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0/go.mod h1:MP4eemTiI9zC8fgg+DYynhYDYf3ba72S376TvP+Ye0Q=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUplec5dp06reu1zaXmOXdvqH398taqrDXqUl99jxSc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.66.0 h1:vkrK8PAznv2NKt2r+kdu252ccGzkEqLc2aSXbQIALYQ=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.66.0/go.mod h1:V/UB6D3vMF/UBOL5igAsAYnk1nG/bzYYTzvsB16cy7o=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0 h1:aZfdmtI6QU/DAPD4b7YZ5zuJgewxO1EW9miOZklqleU=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.20.0/go.mod h1:isNl10/Om5CBWu9jj8WOb2+tJLbCVXDgqwzCaJMnJ6w=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 h1:bl2S7Ubua0Nms+D/gAmznQTd4dxxMA93aKbcpKqiTCs=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0=
|
||||
go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs=
|
||||
go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
|
||||
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/log v0.20.0 h1:vM3xI7TQgKPiSghe6urZtAkyFY7SodrSpC83CffDFuY=
|
||||
go.opentelemetry.io/otel/sdk/log v0.20.0/go.mod h1:Knej2nmsTUzN79T2eeXdRsjjPcoxoq2pUyUHz9TFyyU=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.20.0 h1:OqdRZ1guyzamK3M6LlRsmGqRrjkHWw6WZOKKli5ELpg=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.20.0/go.mod h1:PuMIlm7zAt7c3z8zfOI5ox4iT1Z87We+PF6YoINux/M=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
@@ -618,17 +618,17 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
|
||||
golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
|
||||
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -644,8 +644,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -673,8 +673,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -704,22 +704,24 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss=
|
||||
gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0 h1:kcsiS+WsTKyIEPABJBJtoG0KkOS6yzvJ+/eZlhD79kk=
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
BIN
Binary file not shown.
+1
@@ -17,6 +17,7 @@ const CoreFeatures = api.CoreFeatureSIMD |
|
||||
api.CoreFeatureMutableGlobal |
|
||||
api.CoreFeatureReferenceTypes |
|
||||
api.CoreFeatureSignExtensionOps |
|
||||
experimental.CoreFeaturesExtendedConst |
|
||||
experimental.CoreFeaturesTailCall
|
||||
|
||||
// NewRuntime returns a new WebAssembly wazero.Runtime compatible with go-ffmpreg.
|
||||
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
_fuzz/
|
||||
_fuzz/
|
||||
.devcontainer/
|
||||
+37
-22
@@ -1,27 +1,42 @@
|
||||
run:
|
||||
deadline: 2m
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- misspell
|
||||
- govet
|
||||
- staticcheck
|
||||
- errcheck
|
||||
- unparam
|
||||
- ineffassign
|
||||
- nakedret
|
||||
- gocyclo
|
||||
- dupl
|
||||
- goimports
|
||||
- revive
|
||||
- errcheck
|
||||
- gocyclo
|
||||
- gosec
|
||||
- gosimple
|
||||
- typecheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- revive
|
||||
- staticcheck
|
||||
- unparam
|
||||
- unused
|
||||
|
||||
linters-settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
dupl:
|
||||
threshold: 600
|
||||
settings:
|
||||
dupl:
|
||||
threshold: 600
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- goimports
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
+58
-36
@@ -21,21 +21,43 @@ type Constraints struct {
|
||||
IncludePrerelease bool
|
||||
}
|
||||
|
||||
// MaxConstraintLen is the maximum allowed length of a constraint string.
|
||||
const MaxConstraintLen = 512
|
||||
|
||||
// MaxConstraintGroups is the maximum number of OR groups allowed in a
|
||||
// constraint string.
|
||||
const MaxConstraintGroups = 32
|
||||
|
||||
// ErrConstraintTooLong is returned when a constraint string exceeds the
|
||||
// maximum allowed length.
|
||||
var ErrConstraintTooLong = fmt.Errorf("constraint string is too long (max %d bytes)", MaxConstraintLen)
|
||||
|
||||
// ErrTooManyConstraintGroups is returned when a constraint string contains
|
||||
// too many OR groups.
|
||||
var ErrTooManyConstraintGroups = fmt.Errorf("too many constraint groups (max %d)", MaxConstraintGroups)
|
||||
|
||||
// NewConstraint returns a Constraints instance that a Version instance can
|
||||
// be checked against. If there is a parse error it will be returned.
|
||||
func NewConstraint(c string) (*Constraints, error) {
|
||||
|
||||
if len(c) > MaxConstraintLen {
|
||||
return nil, ErrConstraintTooLong
|
||||
}
|
||||
|
||||
// Rewrite - ranges into a comparison operation.
|
||||
c = rewriteRange(c)
|
||||
|
||||
ors := strings.Split(c, "||")
|
||||
if len(ors) > MaxConstraintGroups {
|
||||
return nil, ErrTooManyConstraintGroups
|
||||
}
|
||||
lenors := len(ors)
|
||||
or := make([][]*constraint, lenors)
|
||||
hasPre := make([]bool, lenors)
|
||||
for k, v := range ors {
|
||||
// Validate the segment
|
||||
if !validConstraintRegex.MatchString(v) {
|
||||
return nil, fmt.Errorf("improper constraint: %s", v)
|
||||
return nil, fmt.Errorf("improper constraint: %q", v)
|
||||
}
|
||||
|
||||
cs := findConstraintRegex.FindAllString(v, -1)
|
||||
@@ -104,9 +126,9 @@ func (cs Constraints) Validate(v *Version) (bool, []error) {
|
||||
for _, c := range o {
|
||||
// Before running the check handle the case there the version is
|
||||
// a prerelease and the check is not searching for prereleases.
|
||||
if !(cs.IncludePrerelease || cs.containsPre[i]) && v.pre != "" {
|
||||
if !cs.IncludePrerelease && !cs.containsPre[i] && v.pre != "" {
|
||||
if !prerelesase {
|
||||
em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
em := fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
e = append(e, em)
|
||||
prerelesase = true
|
||||
}
|
||||
@@ -258,7 +280,7 @@ func parseConstraint(c string) (*constraint, error) {
|
||||
if len(c) > 0 {
|
||||
m := constraintRegex.FindStringSubmatch(c)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("improper constraint: %s", c)
|
||||
return nil, fmt.Errorf("improper constraint: %q", c)
|
||||
}
|
||||
|
||||
cs := &constraint{
|
||||
@@ -325,7 +347,7 @@ func constraintNotEqual(v *Version, c *constraint, includePre bool) (bool, error
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
if c.dirty {
|
||||
@@ -335,7 +357,7 @@ func constraintNotEqual(v *Version, c *constraint, includePre bool) (bool, error
|
||||
if c.con.Minor() != v.Minor() && !c.minorDirty {
|
||||
return true, nil
|
||||
} else if c.minorDirty {
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is equal to %q", v, c.orig)
|
||||
} else if c.con.Patch() != v.Patch() && !c.patchDirty {
|
||||
return true, nil
|
||||
} else if c.patchDirty {
|
||||
@@ -345,15 +367,15 @@ func constraintNotEqual(v *Version, c *constraint, includePre bool) (bool, error
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is equal to %q", v, c.orig)
|
||||
}
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is equal to %q", v, c.orig)
|
||||
}
|
||||
}
|
||||
|
||||
eq := v.Equal(c.con)
|
||||
if eq {
|
||||
return false, fmt.Errorf("%s is equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is equal to %q", v, c.orig)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -364,7 +386,7 @@ func constraintGreaterThan(v *Version, c *constraint, includePre bool) (bool, er
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
var eq bool
|
||||
@@ -374,17 +396,17 @@ func constraintGreaterThan(v *Version, c *constraint, includePre bool) (bool, er
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than or equal to %q", v, c.orig)
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return true, nil
|
||||
} else if v.Major() < c.con.Major() {
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than or equal to %q", v, c.orig)
|
||||
} else if c.minorDirty {
|
||||
// This is a range case such as >11. When the version is something like
|
||||
// 11.1.0 is it not > 11. For that we would need 12 or higher
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than or equal to %q", v, c.orig)
|
||||
} else if c.patchDirty {
|
||||
// This is for ranges such as >11.1. A version of 11.1.1 is not greater
|
||||
// which one of 11.2.1 is greater
|
||||
@@ -392,7 +414,7 @@ func constraintGreaterThan(v *Version, c *constraint, includePre bool) (bool, er
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than or equal to %q", v, c.orig)
|
||||
}
|
||||
|
||||
// If we have gotten here we are not comparing pre-preleases and can use the
|
||||
@@ -401,21 +423,21 @@ func constraintGreaterThan(v *Version, c *constraint, includePre bool) (bool, er
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than or equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than or equal to %q", v, c.orig)
|
||||
}
|
||||
|
||||
func constraintLessThan(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
eq := v.Compare(c.con) < 0
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is greater than or equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is greater than or equal to %q", v, c.orig)
|
||||
}
|
||||
|
||||
func constraintGreaterThanEqual(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
@@ -423,21 +445,21 @@ func constraintGreaterThanEqual(v *Version, c *constraint, includePre bool) (boo
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
eq := v.Compare(c.con) >= 0
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than %q", v, c.orig)
|
||||
}
|
||||
|
||||
func constraintLessThanEqual(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
var eq bool
|
||||
@@ -447,13 +469,13 @@ func constraintLessThanEqual(v *Version, c *constraint, includePre bool) (bool,
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is greater than %q", v, c.orig)
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is greater than %q", v, c.orig)
|
||||
} else if v.Major() == c.con.Major() && v.Minor() > c.con.Minor() && !c.minorDirty {
|
||||
return false, fmt.Errorf("%s is greater than %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is greater than %q", v, c.orig)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -469,11 +491,11 @@ func constraintTilde(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
if v.LessThan(c.con) {
|
||||
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than %q", v, c.orig)
|
||||
}
|
||||
|
||||
// ~0.0.0 is a special case where all constraints are accepted. It's
|
||||
@@ -484,11 +506,11 @@ func constraintTilde(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
}
|
||||
|
||||
if v.Major() != c.con.Major() {
|
||||
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q does not have same major version as %q", v, c.orig)
|
||||
}
|
||||
|
||||
if v.Minor() != c.con.Minor() && !c.minorDirty {
|
||||
return false, fmt.Errorf("%s does not have same major and minor version as %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q does not have same major and minor version as %q", v, c.orig)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@@ -500,7 +522,7 @@ func constraintTildeOrEqual(v *Version, c *constraint, includePre bool) (bool, e
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
if c.dirty {
|
||||
@@ -512,7 +534,7 @@ func constraintTildeOrEqual(v *Version, c *constraint, includePre bool) (bool, e
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("%s is not equal to %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is not equal to %q", v, c.orig)
|
||||
}
|
||||
|
||||
// ^* --> (any)
|
||||
@@ -528,12 +550,12 @@ func constraintCaret(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
// The existence of prereleases is checked at the group level and passed in.
|
||||
// Exit early if the version has a prerelease but those are to be ignored.
|
||||
if v.Prerelease() != "" && !includePre {
|
||||
return false, fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
return false, fmt.Errorf("%q is a prerelease version and the constraint is only looking for release versions", v)
|
||||
}
|
||||
|
||||
// This less than handles prereleases
|
||||
if v.LessThan(c.con) {
|
||||
return false, fmt.Errorf("%s is less than %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q is less than %q", v, c.orig)
|
||||
}
|
||||
|
||||
var eq bool
|
||||
@@ -548,12 +570,12 @@ func constraintCaret(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q does not have same major version as %q", v, c.orig)
|
||||
}
|
||||
|
||||
// ^ when the major is 0 and minor > 0 is >=0.y.z < 0.y+1
|
||||
if c.con.Major() == 0 && v.Major() > 0 {
|
||||
return false, fmt.Errorf("%s does not have same major version as %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q does not have same major version as %q", v, c.orig)
|
||||
}
|
||||
// If the con Minor is > 0 it is not dirty
|
||||
if c.con.Minor() > 0 || c.patchDirty {
|
||||
@@ -561,11 +583,11 @@ func constraintCaret(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s does not have same minor version as %s. Expected minor versions to match when constraint major version is 0", v, c.orig)
|
||||
return false, fmt.Errorf("%q does not have same minor version as %q. Expected minor versions to match when constraint major version is 0", v, c.orig)
|
||||
}
|
||||
// ^ when the minor is 0 and minor > 0 is =0.0.z
|
||||
if c.con.Minor() == 0 && v.Minor() > 0 {
|
||||
return false, fmt.Errorf("%s does not have same minor version as %s", v, c.orig)
|
||||
return false, fmt.Errorf("%q does not have same minor version as %q", v, c.orig)
|
||||
}
|
||||
|
||||
// At this point the major is 0 and the minor is 0 and not dirty. The patch
|
||||
@@ -574,7 +596,7 @@ func constraintCaret(v *Version, c *constraint, includePre bool) (bool, error) {
|
||||
if eq {
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("%s does not equal %s. Expect version and constraint to equal when major and minor versions are 0", v, c.orig)
|
||||
return false, fmt.Errorf("%q does not equal %q. Expect version and constraint to equal when major and minor versions are 0", v, c.orig)
|
||||
}
|
||||
|
||||
func isX(x string) bool {
|
||||
|
||||
+38
-1
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -48,8 +49,16 @@ var (
|
||||
|
||||
// ErrInvalidPrerelease is returned when the pre-release is an invalid format
|
||||
ErrInvalidPrerelease = errors.New("invalid prerelease string")
|
||||
|
||||
// ErrVersionTooLong is returned when a version string exceeds the
|
||||
// maximum allowed length.
|
||||
ErrVersionTooLong = fmt.Errorf("version string is too long (max %d bytes)", MaxVersionLen)
|
||||
)
|
||||
|
||||
// MaxVersionLen is the maximum allowed length of a version string. This guards
|
||||
// against unbounded input causing excessive memory allocations during parsing.
|
||||
const MaxVersionLen = 256
|
||||
|
||||
// semVerRegex is the regular expression used to parse a semantic version.
|
||||
// This is not the official regex from the semver spec. It has been modified to allow for loose handling
|
||||
// where versions like 2.1 are detected.
|
||||
@@ -94,6 +103,10 @@ func StrictNewVersion(v string) (*Version, error) {
|
||||
return nil, ErrEmptyString
|
||||
}
|
||||
|
||||
if len(v) > MaxVersionLen {
|
||||
return nil, ErrVersionTooLong
|
||||
}
|
||||
|
||||
// Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build
|
||||
parts := strings.SplitN(v, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
@@ -161,6 +174,9 @@ func StrictNewVersion(v string) (*Version, error) {
|
||||
// attempts to convert it to SemVer. If you want to validate it was a strict
|
||||
// semantic version at parse time see StrictNewVersion().
|
||||
func NewVersion(v string) (*Version, error) {
|
||||
if len(v) > MaxVersionLen {
|
||||
return nil, ErrVersionTooLong
|
||||
}
|
||||
if CoerceNewVersion {
|
||||
return coerceNewVersion(v)
|
||||
}
|
||||
@@ -289,6 +305,8 @@ func coerceNewVersion(v string) (*Version, error) {
|
||||
|
||||
// New creates a new instance of Version with each of the parts passed in as
|
||||
// arguments instead of parsing a version string.
|
||||
// Note, New does not validate prerelease or metadata. Incorrect information can
|
||||
// be passed in.
|
||||
func New(major, minor, patch uint64, pre, metadata string) *Version {
|
||||
v := Version{
|
||||
major: major,
|
||||
@@ -301,6 +319,7 @@ func New(major, minor, patch uint64, pre, metadata string) *Version {
|
||||
|
||||
v.original = v.String()
|
||||
|
||||
// TODO: In the next semver major version validate the pre and metadata. Return error if there is one.
|
||||
return &v
|
||||
}
|
||||
|
||||
@@ -388,6 +407,9 @@ func (v Version) IncPatch() Version {
|
||||
} else {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
if v.patch == math.MaxUint64 {
|
||||
panic("patch version increment would overflow uint64")
|
||||
}
|
||||
vNext.patch = v.patch + 1
|
||||
}
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
@@ -404,6 +426,9 @@ func (v Version) IncMinor() Version {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
if v.minor == math.MaxUint64 {
|
||||
panic("minor version increment would overflow uint64")
|
||||
}
|
||||
vNext.minor = v.minor + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
@@ -421,6 +446,9 @@ func (v Version) IncMajor() Version {
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
vNext.minor = 0
|
||||
if v.major == math.MaxUint64 {
|
||||
panic("major version increment would overflow uint64")
|
||||
}
|
||||
vNext.major = v.major + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
@@ -568,7 +596,16 @@ func (v Version) MarshalText() ([]byte, error) {
|
||||
// Scan implements the SQL.Scanner interface.
|
||||
func (v *Version) Scan(value interface{}) error {
|
||||
var s string
|
||||
s, _ = value.(string)
|
||||
switch t := value.(type) {
|
||||
case string:
|
||||
s = t
|
||||
case []byte:
|
||||
s = string(t)
|
||||
case nil:
|
||||
return fmt.Errorf("cannot scan nil into Version")
|
||||
default:
|
||||
return fmt.Errorf("unsupported Scan type %T", value)
|
||||
}
|
||||
temp, err := NewVersion(s)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
freebsd_task:
|
||||
name: 'FreeBSD'
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-2
|
||||
install_script:
|
||||
- pkg update -f
|
||||
- pkg install -y go
|
||||
test_script:
|
||||
# run tests as user "cirrus" instead of root
|
||||
- pw useradd cirrus -m
|
||||
- chown -R cirrus:cirrus .
|
||||
- FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
||||
- sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
||||
- FSNOTIFY_DEBUG=1 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./...
|
||||
+49
@@ -1,5 +1,54 @@
|
||||
# Changelog
|
||||
|
||||
1.10.1 2026-05-04
|
||||
-----------------
|
||||
|
||||
### Changes and fixes
|
||||
|
||||
- inotify: don't remove sibling watches sharing a path prefix ([#754])
|
||||
|
||||
- inotify, windows: don't rename sibling watches sharing a path prefix
|
||||
([#755])
|
||||
|
||||
|
||||
[#754]: https://github.com/fsnotify/fsnotify/pull/754
|
||||
[#755]: https://github.com/fsnotify/fsnotify/pull/755
|
||||
|
||||
|
||||
1.10.0 2026-04-30
|
||||
-----------------
|
||||
This version of fsnotify needs Go 1.23.
|
||||
|
||||
### Changes and fixes
|
||||
|
||||
- inotify: improve initialization error message ([#731])
|
||||
|
||||
- inotify: send Rename event if recursive watch is renamed ([#696])
|
||||
|
||||
- inotify: avoid copying event buffers when reading names ([#741])
|
||||
|
||||
- kqueue: skip dangling symlinks (ENOENT) in watchDirectoryFiles, so a
|
||||
bad entry no longer aborts Watcher.Add for the whole directory ([#748])
|
||||
|
||||
- kqueue: drop watches directly in Close() to fix a file descriptor leak
|
||||
when recycling watchers ([#740])
|
||||
|
||||
- windows: fix nil pointer dereference in remWatch ([#736])
|
||||
|
||||
- windows: lock watch field updates against concurrent WatchList to fix
|
||||
a race introduced in v1.9.0 ([#709], [#749])
|
||||
|
||||
|
||||
[#696]: https://github.com/fsnotify/fsnotify/pull/696
|
||||
[#709]: https://github.com/fsnotify/fsnotify/pull/709
|
||||
[#731]: https://github.com/fsnotify/fsnotify/pull/731
|
||||
[#736]: https://github.com/fsnotify/fsnotify/pull/736
|
||||
[#740]: https://github.com/fsnotify/fsnotify/pull/740
|
||||
[#741]: https://github.com/fsnotify/fsnotify/pull/741
|
||||
[#748]: https://github.com/fsnotify/fsnotify/pull/748
|
||||
[#749]: https://github.com/fsnotify/fsnotify/pull/749
|
||||
|
||||
|
||||
1.9.0 2024-04-04
|
||||
----------------
|
||||
|
||||
|
||||
+2
@@ -77,6 +77,8 @@ End-of-line escapes with `\` are not supported.
|
||||
debug [yes/no] # Enable/disable FSNOTIFY_DEBUG (tests are run in
|
||||
parallel by default, so -parallel=1 is probably a good
|
||||
idea).
|
||||
state # Print internal state to stderr (exact output differs
|
||||
# per backend).
|
||||
print [any strings] # Print text to stdout; for debugging.
|
||||
|
||||
touch path
|
||||
|
||||
+44
-12
@@ -1,7 +1,7 @@
|
||||
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
||||
Windows, Linux, macOS, BSD, and illumos.
|
||||
|
||||
Go 1.17 or newer is required; the full documentation is at
|
||||
Go 1.23 or newer is required; the full documentation is at
|
||||
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
||||
|
||||
---
|
||||
@@ -12,7 +12,7 @@ Platform support:
|
||||
| :-------------------- | :--------- | :------------------------------------------------------------------------ |
|
||||
| inotify | Linux | Supported |
|
||||
| kqueue | BSD, macOS | Supported |
|
||||
| ReadDirectoryChangesW | Windows | Supported |
|
||||
| ReadDirectoryChangesW | Windows | Supported ([excluding `Chmod` operations][#487]) |
|
||||
| FEN | illumos | Supported |
|
||||
| fanotify | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||
| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] |
|
||||
@@ -22,6 +22,7 @@ Platform support:
|
||||
Linux and illumos should include Android and Solaris, but these are currently
|
||||
untested.
|
||||
|
||||
[#487]: https://github.com/fsnotify/fsnotify/issues/487
|
||||
[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
|
||||
[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
|
||||
|
||||
@@ -126,7 +127,7 @@ settings* until we have a native FSEvents implementation (see [#11]).
|
||||
### Watching a file doesn't work well
|
||||
Watching individual files (rather than directories) is generally not recommended
|
||||
as many programs (especially editors) update files atomically: it will write to
|
||||
a temporary file which is then moved to to destination, overwriting the original
|
||||
a temporary file which is then moved to a destination, overwriting the original
|
||||
(or some variant thereof). The watcher on the original file is now lost, as that
|
||||
no longer exists.
|
||||
|
||||
@@ -151,26 +152,57 @@ This is the event that inotify sends, so not much can be changed about this.
|
||||
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
|
||||
the number of watches per user, and `fs.inotify.max_user_instances` specifies
|
||||
the maximum number of inotify instances per user. Every Watcher you create is an
|
||||
"instance", and every path you add is a "watch".
|
||||
"instance", and every path you add is a "watch". Reaching the limit will result
|
||||
in a "no space left on device" or "too many open files" error.
|
||||
|
||||
These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
|
||||
`/proc/sys/fs/inotify/max_user_instances`
|
||||
`/proc/sys/fs/inotify/max_user_instances`. The default values differ per distro
|
||||
and available memory.
|
||||
|
||||
To increase them you can use `sysctl` or write the value to proc file:
|
||||
|
||||
# The default values on Linux 5.18
|
||||
sysctl fs.inotify.max_user_watches=124983
|
||||
sysctl fs.inotify.max_user_instances=128
|
||||
sysctl fs.inotify.max_user_watches=200000
|
||||
sysctl fs.inotify.max_user_instances=256
|
||||
|
||||
To make the changes persist on reboot edit `/etc/sysctl.conf` or
|
||||
`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
|
||||
distro's documentation):
|
||||
|
||||
fs.inotify.max_user_watches=124983
|
||||
fs.inotify.max_user_instances=128
|
||||
fs.inotify.max_user_watches=200000
|
||||
fs.inotify.max_user_instances=256
|
||||
|
||||
### Windows
|
||||
Recursive watching is not currently enabled through fsnotify's public API
|
||||
(see the FAQ "Are subdirectories watched?" above). The notes below
|
||||
describe Windows backend behavior observed when recursive watching is
|
||||
enabled internally (for example, in fsnotify's own tests). They are kept
|
||||
here as a reference for maintainers and contributors who encounter the
|
||||
behavior, since the recursive code path still exists in the backend.
|
||||
|
||||
When recursive watching is enabled and you watch a directory, you may
|
||||
receive a `Write` event for an intermediate directory whenever a child
|
||||
entry inside it is created, renamed, or removed. For example, with a
|
||||
recursive watch on `/a` and a new file `/a/b/c`, you will receive
|
||||
`Create /a/b/c` and may also receive `Write /a/b`.
|
||||
|
||||
This happens because, on NTFS-backed volumes, modifying the entries of a
|
||||
directory updates that directory's last-write time, and the Windows
|
||||
backend requests `FILE_NOTIFY_CHANGE_LAST_WRITE` to support `Write` events
|
||||
on files. The same `Write` filter therefore picks up the directory's
|
||||
metadata update.
|
||||
|
||||
kqueue has the same "directory `Write` = directory contents changed"
|
||||
semantics, so portable code that treats `Write` on a directory as
|
||||
"something inside it changed" works on Windows and BSD/macOS, but not on
|
||||
Linux (inotify uses `Write` only for file-content changes). If you only
|
||||
care about file content, filter out `Write` events whose path refers to a
|
||||
directory.
|
||||
|
||||
Whether the directory `Write` is actually delivered alongside the child
|
||||
events is not guaranteed: it depends on `ReadDirectoryChangesW` buffering,
|
||||
NTFS metadata update timing, and event coalescing, none of which fsnotify
|
||||
controls.
|
||||
|
||||
Reaching the limit will result in a "no space left on device" or "too many open
|
||||
files" error.
|
||||
|
||||
### kqueue (macOS, all BSD systems)
|
||||
kqueue requires opening a file descriptor for every file that's being watched;
|
||||
|
||||
+3
-1
@@ -158,7 +158,9 @@ func (w *fen) readEvents() {
|
||||
|
||||
pevents := make([]unix.PortEvent, 8)
|
||||
for {
|
||||
count, err := w.port.Get(pevents, 1, nil)
|
||||
count, err := internal.IgnoringEINTR(func() (int, error) {
|
||||
return w.port.Get(pevents, 1, nil)
|
||||
})
|
||||
if err != nil && err != unix.ETIME {
|
||||
// Interrupted system call (count should be 0) ignore and continue
|
||||
if errors.Is(err, unix.EINTR) && count == 0 {
|
||||
|
||||
+49
-35
@@ -55,10 +55,10 @@ type (
|
||||
path map[string]uint32 // pathname → wd
|
||||
}
|
||||
watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
path string // Watch path.
|
||||
recurse bool // Recursion with ./...?
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
path string // Watch path.
|
||||
watchFlags watchFlag
|
||||
}
|
||||
koekje struct {
|
||||
cookie uint32
|
||||
@@ -66,6 +66,9 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func (w watch) byUser() bool { return w.watchFlags&flagByUser != 0 }
|
||||
func (w watch) recurse() bool { return w.watchFlags&flagRecurse != 0 }
|
||||
|
||||
func newWatches() *watches {
|
||||
return &watches{
|
||||
wd: make(map[uint32]*watch),
|
||||
@@ -79,6 +82,13 @@ func (w *watches) len() int { return len(w.wd) }
|
||||
func (w *watches) add(ww *watch) { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd }
|
||||
func (w *watches) remove(watch *watch) { delete(w.path, watch.path); delete(w.wd, watch.wd) }
|
||||
|
||||
func isSameOrDescendantPath(path, root string) bool {
|
||||
if path == root {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(path, root+string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func (w *watches) removePath(path string) ([]uint32, error) {
|
||||
path, recurse := recursivePath(path)
|
||||
wd, ok := w.path[path]
|
||||
@@ -87,20 +97,20 @@ func (w *watches) removePath(path string) ([]uint32, error) {
|
||||
}
|
||||
|
||||
watch := w.wd[wd]
|
||||
if recurse && !watch.recurse {
|
||||
if recurse && !watch.recurse() {
|
||||
return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path)
|
||||
}
|
||||
|
||||
delete(w.path, path)
|
||||
delete(w.wd, wd)
|
||||
if !watch.recurse {
|
||||
if !watch.recurse() {
|
||||
return []uint32{wd}, nil
|
||||
}
|
||||
|
||||
wds := make([]uint32, 0, 8)
|
||||
wds = append(wds, wd)
|
||||
for p, rwd := range w.path {
|
||||
if strings.HasPrefix(p, path) {
|
||||
if isSameOrDescendantPath(p, path) {
|
||||
delete(w.path, p)
|
||||
delete(w.wd, rwd)
|
||||
wds = append(wds, rwd)
|
||||
@@ -139,7 +149,7 @@ func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
// I/O operations won't terminate on close.
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||
if fd == -1 {
|
||||
return nil, errno
|
||||
return nil, fmt.Errorf("couldn't initialize inotify: %w", errno)
|
||||
}
|
||||
|
||||
w := &inotify{
|
||||
@@ -188,11 +198,8 @@ func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
||||
return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
|
||||
}
|
||||
|
||||
add := func(path string, with withOpts, recurse bool) error {
|
||||
add := func(path string, with withOpts, wf watchFlag) error {
|
||||
var flags uint32
|
||||
if with.noFollow {
|
||||
flags |= unix.IN_DONT_FOLLOW
|
||||
}
|
||||
if with.op.Has(Create) {
|
||||
flags |= unix.IN_CREATE
|
||||
}
|
||||
@@ -220,7 +227,7 @@ func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
||||
if with.op.Has(xUnportableCloseRead) {
|
||||
flags |= unix.IN_CLOSE_NOWRITE
|
||||
}
|
||||
return w.register(path, flags, recurse)
|
||||
return w.register(path, flags, wf)
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
@@ -248,14 +255,18 @@ func (w *inotify) AddWith(path string, opts ...addOpt) error {
|
||||
w.sendEvent(Event{Name: root, Op: Create})
|
||||
}
|
||||
|
||||
return add(root, with, true)
|
||||
wf := flagRecurse
|
||||
if root == path {
|
||||
wf |= flagByUser
|
||||
}
|
||||
return add(root, with, wf)
|
||||
})
|
||||
}
|
||||
|
||||
return add(path, with, false)
|
||||
return add(path, with, 0)
|
||||
}
|
||||
|
||||
func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
||||
func (w *inotify) register(path string, flags uint32, wf watchFlag) error {
|
||||
return w.watches.updatePath(path, func(existing *watch) (*watch, error) {
|
||||
if existing != nil {
|
||||
flags |= existing.flags | unix.IN_MASK_ADD
|
||||
@@ -272,10 +283,10 @@ func (w *inotify) register(path string, flags uint32, recurse bool) error {
|
||||
|
||||
if existing == nil {
|
||||
return &watch{
|
||||
wd: uint32(wd),
|
||||
path: path,
|
||||
flags: flags,
|
||||
recurse: recurse,
|
||||
wd: uint32(wd),
|
||||
path: path,
|
||||
flags: flags,
|
||||
watchFlags: wf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -425,11 +436,7 @@ func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offs
|
||||
nameLen = uint32(inEvent.Len)
|
||||
)
|
||||
if nameLen > 0 {
|
||||
/// Point "bytes" at the first byte of the filename
|
||||
bb := *buf
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&bb[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
/// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\x00")
|
||||
name += "/" + inotifyEventName(buf, offset, nameLen)
|
||||
}
|
||||
|
||||
if debug {
|
||||
@@ -450,7 +457,9 @@ func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offs
|
||||
// We can't really update the state when a watched path is moved; only
|
||||
// IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove the watch.
|
||||
if inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
||||
if watch.recurse { // Do nothing
|
||||
// Watch is set up as part of recurse: do nothing as the move gets
|
||||
// registered from the parent directory.
|
||||
if watch.recurse() && !watch.byUser() {
|
||||
return Event{}, true
|
||||
}
|
||||
|
||||
@@ -460,6 +469,10 @@ func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offs
|
||||
return Event{}, false
|
||||
}
|
||||
}
|
||||
|
||||
if watch.recurse() {
|
||||
return Event{Name: watch.path, Op: Rename}, true
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip if we're watching both this path and the parent; the parent will
|
||||
@@ -473,11 +486,11 @@ func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offs
|
||||
|
||||
ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie)
|
||||
// Need to update watch path for recurse.
|
||||
if watch.recurse {
|
||||
if watch.recurse() {
|
||||
isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR
|
||||
/// New directory created: set up watch on it.
|
||||
if isDir && ev.Has(Create) {
|
||||
err := w.register(ev.Name, watch.flags, true)
|
||||
err := w.register(ev.Name, watch.flags, flagRecurse)
|
||||
if !w.sendError(err) {
|
||||
return Event{}, false
|
||||
}
|
||||
@@ -495,7 +508,7 @@ func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offs
|
||||
if k == watch.wd || ww.path == ev.Name {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ww.path, ev.renamedFrom) {
|
||||
if isSameOrDescendantPath(ww.path, ev.renamedFrom) {
|
||||
ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
|
||||
w.watches.wd[k] = ww
|
||||
}
|
||||
@@ -507,12 +520,13 @@ func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offs
|
||||
return ev, true
|
||||
}
|
||||
|
||||
func (w *inotify) isRecursive(path string) bool {
|
||||
ww := w.watches.byPath(path)
|
||||
if ww == nil { // path could be a file, so also check the Dir.
|
||||
ww = w.watches.byPath(filepath.Dir(path))
|
||||
func inotifyEventName(buf *[65536]byte, offset, nameLen uint32) string {
|
||||
start := int(offset + unix.SizeofInotifyEvent)
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[start]))[:nameLen:nameLen]
|
||||
for nameLen > 0 && bytes[nameLen-1] == 0 {
|
||||
nameLen--
|
||||
}
|
||||
return ww != nil && ww.recurse
|
||||
return string(bytes[:nameLen])
|
||||
}
|
||||
|
||||
func (w *inotify) newEvent(name string, mask, cookie uint32) Event {
|
||||
@@ -578,6 +592,6 @@ func (w *inotify) state() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
for wd, ww := range w.watches.wd {
|
||||
fmt.Fprintf(os.Stderr, "%4d: recurse=%t %q\n", wd, ww.recurse, ww.path)
|
||||
fmt.Fprintf(os.Stderr, "%4d: %q watchFlags=0x%x\n", wd, ww.path, ww.watchFlags)
|
||||
}
|
||||
}
|
||||
|
||||
+49
-19
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -245,9 +246,26 @@ func (w *kqueue) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Snapshot and drop all watches directly. w.Remove -> w.remove
|
||||
// short-circuits on isClosed() (which is already true after
|
||||
// w.shared.close() above), so calling Remove here in the happy path
|
||||
// leaked every watched directory + file descriptor. On macOS a
|
||||
// single directory watch opens an fd for every file in the dir, so
|
||||
// long-running processes that recreate watchers (hot-reload dev
|
||||
// servers, etc.) ran out of fds with EMFILE (#732).
|
||||
pathsToRemove := w.watches.listPaths(false)
|
||||
for _, name := range pathsToRemove {
|
||||
w.Remove(name)
|
||||
info, ok := w.watches.byPath(name)
|
||||
if !ok {
|
||||
// w.path has an entry for name but w.wd doesn't --
|
||||
// drop the stale lookup entry so the map state is
|
||||
// consistent after Close.
|
||||
w.watches.remove(0, name)
|
||||
continue
|
||||
}
|
||||
_ = w.register([]int{info.wd}, unix.EV_DELETE, 0)
|
||||
unix.Close(info.wd)
|
||||
w.watches.remove(info.wd, name)
|
||||
}
|
||||
|
||||
unix.Close(w.closepipe[1]) // Send "quit" message to readEvents
|
||||
@@ -376,19 +394,12 @@ func (w *kqueue) addWatch(name string, flags uint32, listDir bool) (string, erro
|
||||
}
|
||||
}
|
||||
|
||||
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
||||
// See #354, and Go issues 11180 and 39237.
|
||||
for {
|
||||
info.wd, err = unix.Open(name, openMode, 0)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, unix.EINTR) {
|
||||
continue
|
||||
}
|
||||
info.wd, err = internal.IgnoringEINTR(func() (int, error) {
|
||||
return unix.Open(name, openMode, 0)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
info.isDir = fi.IsDir()
|
||||
}
|
||||
|
||||
@@ -436,9 +447,10 @@ func (w *kqueue) readEvents() {
|
||||
|
||||
eventBuffer := make([]unix.Kevent_t, 10)
|
||||
for {
|
||||
kevents, err := w.read(eventBuffer)
|
||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||
if err != nil && err != unix.EINTR {
|
||||
kevents, err := internal.IgnoringEINTR(func() ([]unix.Kevent_t, error) {
|
||||
return w.read(eventBuffer)
|
||||
})
|
||||
if err != nil {
|
||||
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
||||
return
|
||||
}
|
||||
@@ -583,12 +595,14 @@ func (w *kqueue) watchDirectoryFiles(dirPath string) error {
|
||||
|
||||
cleanPath, err := w.internalWatch(path, fi)
|
||||
if err != nil {
|
||||
// No permission to read the file; that's not a problem: just skip.
|
||||
// But do add it to w.fileExists to prevent it from being picked up
|
||||
// as a "new" file later (it still shows up in the directory
|
||||
// No permission, or the entry resolved to a missing target
|
||||
// (e.g. a dangling symlink): not a problem, just skip. But
|
||||
// do mark it as seen to prevent it from being picked up as
|
||||
// a "new" file later (it still shows up in the directory
|
||||
// listing).
|
||||
switch {
|
||||
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
||||
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) ||
|
||||
errors.Is(err, os.ErrNotExist):
|
||||
cleanPath = filepath.Clean(path)
|
||||
default:
|
||||
return fmt.Errorf("%q: %w", path, err)
|
||||
@@ -703,3 +717,19 @@ func (w *kqueue) xSupports(op Op) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *kqueue) state() {
|
||||
w.watches.mu.Lock()
|
||||
defer w.watches.mu.Unlock()
|
||||
|
||||
all := make([]int, 0, len(w.watches.wd))
|
||||
for wd := range w.watches.wd {
|
||||
all = append(all, wd)
|
||||
}
|
||||
sort.Ints(all)
|
||||
|
||||
for _, wd := range all {
|
||||
ww := w.watches.wd[wd]
|
||||
fmt.Fprintf(os.Stderr, "%4d %q linkname=%q\n", wd, ww.name, ww.linkName)
|
||||
}
|
||||
}
|
||||
|
||||
+50
-28
@@ -11,7 +11,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -37,6 +36,13 @@ type readDirChangesW struct {
|
||||
|
||||
var defaultBufferSize = 50
|
||||
|
||||
func isSameOrDescendantPath(path, root string) bool {
|
||||
if path == root {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(path, root+string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func newBackend(ev chan Event, errs chan error) (backend, error) {
|
||||
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||
if err != nil {
|
||||
@@ -359,22 +365,26 @@ func (w *readDirChangesW) addWatch(pathname string, flags uint64, bufsize int) e
|
||||
} else {
|
||||
windows.CloseHandle(ino.handle)
|
||||
}
|
||||
w.mu.Lock()
|
||||
if pathname == dir {
|
||||
watchEntry.mask |= flags
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
err = w.startRead(watchEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
if pathname == dir {
|
||||
watchEntry.mask &= ^provisional
|
||||
} else {
|
||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||
}
|
||||
w.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -394,8 +404,13 @@ func (w *readDirChangesW) remWatch(pathname string) error {
|
||||
w.mu.Lock()
|
||||
watch := w.watches.get(ino)
|
||||
w.mu.Unlock()
|
||||
if watch == nil {
|
||||
windows.CloseHandle(ino.handle)
|
||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
||||
}
|
||||
|
||||
if recurse && !watch.recurse {
|
||||
windows.CloseHandle(ino.handle)
|
||||
return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname)
|
||||
}
|
||||
|
||||
@@ -403,16 +418,19 @@ func (w *readDirChangesW) remWatch(pathname string) error {
|
||||
if err != nil {
|
||||
w.sendError(os.NewSyscallError("CloseHandle", err))
|
||||
}
|
||||
if watch == nil {
|
||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
||||
}
|
||||
if pathname == dir {
|
||||
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
|
||||
w.mu.Lock()
|
||||
mask := watch.mask
|
||||
watch.mask = 0
|
||||
w.mu.Unlock()
|
||||
w.sendEvent(watch.path, "", mask&sysFSIGNORED)
|
||||
} else {
|
||||
name := filepath.Base(pathname)
|
||||
w.sendEvent(filepath.Join(watch.path, name), "", watch.names[name]&sysFSIGNORED)
|
||||
w.mu.Lock()
|
||||
mask := watch.names[name]
|
||||
delete(watch.names, name)
|
||||
w.mu.Unlock()
|
||||
w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED)
|
||||
}
|
||||
|
||||
return w.startRead(watch)
|
||||
@@ -420,17 +438,23 @@ func (w *readDirChangesW) remWatch(pathname string) error {
|
||||
|
||||
// Must run within the I/O thread.
|
||||
func (w *readDirChangesW) deleteWatch(watch *watch) {
|
||||
for name, mask := range watch.names {
|
||||
if mask&provisional == 0 {
|
||||
w.sendEvent(filepath.Join(watch.path, name), "", mask&sysFSIGNORED)
|
||||
// Snapshot+clear under the lock so concurrent WatchList() readers see a
|
||||
// consistent state. sendEvent must run outside the lock since it can
|
||||
// block on the user-facing Events channel.
|
||||
w.mu.Lock()
|
||||
names := watch.names
|
||||
watch.names = make(map[string]uint64)
|
||||
mask := watch.mask
|
||||
watch.mask = 0
|
||||
w.mu.Unlock()
|
||||
|
||||
for name, m := range names {
|
||||
if m&provisional == 0 {
|
||||
w.sendEvent(filepath.Join(watch.path, name), "", m&sysFSIGNORED)
|
||||
}
|
||||
delete(watch.names, name)
|
||||
}
|
||||
if watch.mask != 0 {
|
||||
if watch.mask&provisional == 0 {
|
||||
w.sendEvent(watch.path, "", watch.mask&sysFSIGNORED)
|
||||
}
|
||||
watch.mask = 0
|
||||
if mask != 0 && mask&provisional == 0 {
|
||||
w.sendEvent(watch.path, "", mask&sysFSIGNORED)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,9 +481,8 @@ func (w *readDirChangesW) startRead(watch *watch) error {
|
||||
}
|
||||
|
||||
// We need to pass the array, rather than the slice.
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
|
||||
rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
|
||||
(*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
|
||||
unsafe.SliceData(watch.buf), uint32(len(watch.buf)),
|
||||
watch.recurse, mask, nil, &watch.ov, 0)
|
||||
if rdErr != nil {
|
||||
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
|
||||
@@ -565,12 +588,7 @@ func (w *readDirChangesW) readEvents() {
|
||||
|
||||
// Create a buf that is the size of the path name
|
||||
size := int(raw.FileNameLength / 2)
|
||||
var buf []uint16
|
||||
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||
sh.Len = size
|
||||
sh.Cap = size
|
||||
buf := unsafe.Slice(&raw.FileName, size)
|
||||
name := windows.UTF16ToString(buf)
|
||||
fullname := filepath.Join(watch.path, name)
|
||||
|
||||
@@ -587,31 +605,35 @@ func (w *readDirChangesW) readEvents() {
|
||||
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
||||
watch.rename = name
|
||||
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
||||
// Update saved path of all sub-watches.
|
||||
// Update saved path of all sub-watches and rename the
|
||||
// names entry under the lock so WatchList() can't observe
|
||||
// a torn state.
|
||||
old := filepath.Join(watch.path, watch.rename)
|
||||
w.mu.Lock()
|
||||
for _, watchMap := range w.watches {
|
||||
for _, ww := range watchMap {
|
||||
if strings.HasPrefix(ww.path, old) {
|
||||
if isSameOrDescendantPath(ww.path, old) {
|
||||
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
||||
}
|
||||
}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
if watch.names[watch.rename] != 0 {
|
||||
watch.names[name] |= watch.names[watch.rename]
|
||||
delete(watch.names, watch.rename)
|
||||
mask = sysFSMOVESELF
|
||||
}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
w.sendEvent(fullname, "", watch.names[name]&mask)
|
||||
}
|
||||
if raw.Action == windows.FILE_ACTION_REMOVED {
|
||||
w.sendEvent(fullname, "", watch.names[name]&sysFSIGNORED)
|
||||
w.mu.Lock()
|
||||
ignored := watch.names[name] & sysFSIGNORED
|
||||
delete(watch.names, name)
|
||||
w.mu.Unlock()
|
||||
w.sendEvent(fullname, "", ignored)
|
||||
}
|
||||
|
||||
if watch.rename != "" && raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
||||
|
||||
+51
-21
@@ -51,26 +51,25 @@ import (
|
||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
||||
// create is an "instance", and every path you add is a "watch".
|
||||
// create is an "instance", and every path you add is a "watch". Reaching the
|
||||
// limit will result in a "no space left on device" or "too many open files"
|
||||
// error.
|
||||
//
|
||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
||||
// /proc/sys/fs/inotify/max_user_instances
|
||||
// /proc/sys/fs/inotify/max_user_instances. The default values differ per distro
|
||||
// and available memory.
|
||||
//
|
||||
// To increase them you can use sysctl or write the value to the /proc file:
|
||||
//
|
||||
// # Default values on Linux 5.18
|
||||
// sysctl fs.inotify.max_user_watches=124983
|
||||
// sysctl fs.inotify.max_user_instances=128
|
||||
// sysctl fs.inotify.max_user_watches=200000
|
||||
// sysctl fs.inotify.max_user_instances=256
|
||||
//
|
||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
||||
// your distro's documentation):
|
||||
//
|
||||
// fs.inotify.max_user_watches=124983
|
||||
// fs.inotify.max_user_instances=128
|
||||
//
|
||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
||||
// files" error.
|
||||
// fs.inotify.max_user_watches=200000
|
||||
// fs.inotify.max_user_instances=256
|
||||
//
|
||||
// # kqueue notes (macOS, BSD)
|
||||
//
|
||||
@@ -93,6 +92,28 @@ import (
|
||||
// Sometimes it will send events for all files, sometimes it will send no
|
||||
// events, and often only for some files.
|
||||
//
|
||||
// Recursive watching is not currently enabled through fsnotify's public
|
||||
// API; the recursive code path is gated and only exercised by fsnotify's
|
||||
// own tests. The note below describes backend behavior observed when
|
||||
// recursive watching is enabled internally, and is kept here as a
|
||||
// reference for maintainers and contributors who encounter it.
|
||||
//
|
||||
// When recursive watching is enabled and you watch a directory, you may
|
||||
// receive a Write event for an intermediate directory whenever a child
|
||||
// entry inside it is created, renamed, or removed. For example, with a
|
||||
// recursive watch on /a and a new file /a/b/c, you will receive
|
||||
// Create /a/b/c and may also receive Write /a/b.
|
||||
//
|
||||
// This happens because, on NTFS-backed volumes, modifying the entries of a
|
||||
// directory updates that directory's last-write time, and the Windows
|
||||
// backend requests FILE_NOTIFY_CHANGE_LAST_WRITE to support Write events
|
||||
// on files. The same Write filter therefore picks up the directory's
|
||||
// metadata update.
|
||||
//
|
||||
// Whether the directory Write is actually delivered alongside the child
|
||||
// events is not guaranteed; it depends on ReadDirectoryChangesW buffering,
|
||||
// NTFS metadata update timing, and event coalescing.
|
||||
//
|
||||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
||||
// value that is guaranteed to work with SMB filesystems. If you have many
|
||||
// events in quick succession this may not be enough, and you will have to use
|
||||
@@ -129,8 +150,12 @@ type Watcher struct {
|
||||
// want to wait until you've stopped receiving them
|
||||
// (see the dedup example in cmd/fsnotify).
|
||||
//
|
||||
// Some systems may send Write event for directories
|
||||
// when the directory content changes.
|
||||
// Some systems also send Write events for directories
|
||||
// when the directory contents change. This is the
|
||||
// case for kqueue, and on Windows for the directory
|
||||
// that contains a created, renamed, or removed child
|
||||
// entry. It does not happen on inotify. See the
|
||||
// per-platform notes on [Watcher].
|
||||
//
|
||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
||||
// when a file is removed (or more accurately, when a
|
||||
@@ -179,7 +204,9 @@ const (
|
||||
Create Op = 1 << iota
|
||||
|
||||
// The pathname was written to; this does *not* mean the write has finished,
|
||||
// and a write can be followed by more writes.
|
||||
// and a write can be followed by more writes. On Windows and kqueue, a
|
||||
// Write on a directory can also indicate that its contents changed; see
|
||||
// the per-platform notes on [Watcher].
|
||||
Write
|
||||
|
||||
// The path was removed; any watches on it will be removed. Some "remove"
|
||||
@@ -220,7 +247,7 @@ const (
|
||||
|
||||
// File opened for reading was closed.
|
||||
//
|
||||
// Only works on Linux and FreeBSD.
|
||||
// Only works on Linux.
|
||||
xUnportableCloseRead
|
||||
)
|
||||
|
||||
@@ -410,7 +437,6 @@ type (
|
||||
withOpts struct {
|
||||
bufsize int
|
||||
op Op
|
||||
noFollow bool
|
||||
sendCreate bool
|
||||
}
|
||||
)
|
||||
@@ -469,12 +495,6 @@ func withOps(op Op) addOpt {
|
||||
return func(opt *withOpts) { opt.op = op }
|
||||
}
|
||||
|
||||
// WithNoFollow disables following symlinks, so the symlinks themselves are
|
||||
// watched.
|
||||
func withNoFollow() addOpt {
|
||||
return func(opt *withOpts) { opt.noFollow = true }
|
||||
}
|
||||
|
||||
// "Internal" option for recursive watches on inotify.
|
||||
func withCreate() addOpt {
|
||||
return func(opt *withOpts) { opt.sendCreate = true }
|
||||
@@ -494,3 +514,13 @@ func recursivePath(path string) (string, bool) {
|
||||
}
|
||||
return path, false
|
||||
}
|
||||
|
||||
type watchFlag uint8
|
||||
|
||||
const (
|
||||
// Added by user with Add(), rather than an internal watch.
|
||||
flagByUser = watchFlag(0x01)
|
||||
// Part of recursive watch; as the top-level path added by the user or an
|
||||
// "internal" watch.
|
||||
flagRecurse = watchFlag(0x02)
|
||||
)
|
||||
|
||||
-19
@@ -15,25 +15,6 @@ var (
|
||||
|
||||
var maxfiles uint64
|
||||
|
||||
func SetRlimit() {
|
||||
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||
var l syscall.Rlimit
|
||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
if err == nil && l.Cur != l.Max {
|
||||
l.Cur = l.Max
|
||||
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
}
|
||||
maxfiles = l.Cur
|
||||
|
||||
if n, err := syscall.SysctlUint32("kern.maxfiles"); err == nil && uint64(n) < maxfiles {
|
||||
maxfiles = uint64(n)
|
||||
}
|
||||
|
||||
if n, err := syscall.SysctlUint32("kern.maxfilesperproc"); err == nil && uint64(n) < maxfiles {
|
||||
maxfiles = uint64(n)
|
||||
}
|
||||
}
|
||||
|
||||
func Maxfiles() uint64 { return maxfiles }
|
||||
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
|
||||
|
||||
-42
@@ -6,52 +6,10 @@ var names = []struct {
|
||||
n string
|
||||
m uint32
|
||||
}{
|
||||
{"NOTE_ABSOLUTE", unix.NOTE_ABSOLUTE},
|
||||
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||
{"NOTE_BACKGROUND", unix.NOTE_BACKGROUND},
|
||||
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||
{"NOTE_CRITICAL", unix.NOTE_CRITICAL},
|
||||
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||
{"NOTE_EXITSTATUS", unix.NOTE_EXITSTATUS},
|
||||
{"NOTE_EXIT_CSERROR", unix.NOTE_EXIT_CSERROR},
|
||||
{"NOTE_EXIT_DECRYPTFAIL", unix.NOTE_EXIT_DECRYPTFAIL},
|
||||
{"NOTE_EXIT_DETAIL", unix.NOTE_EXIT_DETAIL},
|
||||
{"NOTE_EXIT_DETAIL_MASK", unix.NOTE_EXIT_DETAIL_MASK},
|
||||
{"NOTE_EXIT_MEMORY", unix.NOTE_EXIT_MEMORY},
|
||||
{"NOTE_EXIT_REPARENTED", unix.NOTE_EXIT_REPARENTED},
|
||||
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||
{"NOTE_FORK", unix.NOTE_FORK},
|
||||
{"NOTE_FUNLOCK", unix.NOTE_FUNLOCK},
|
||||
{"NOTE_LEEWAY", unix.NOTE_LEEWAY},
|
||||
{"NOTE_LINK", unix.NOTE_LINK},
|
||||
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||
{"NOTE_MACHTIME", unix.NOTE_MACHTIME},
|
||||
{"NOTE_MACH_CONTINUOUS_TIME", unix.NOTE_MACH_CONTINUOUS_TIME},
|
||||
{"NOTE_NONE", unix.NOTE_NONE},
|
||||
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
|
||||
{"NOTE_OOB", unix.NOTE_OOB},
|
||||
//{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK}, -0x100000 (?!)
|
||||
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||
{"NOTE_REAP", unix.NOTE_REAP},
|
||||
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||
{"NOTE_SECONDS", unix.NOTE_SECONDS},
|
||||
{"NOTE_SIGNAL", unix.NOTE_SIGNAL},
|
||||
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||
{"NOTE_USECONDS", unix.NOTE_USECONDS},
|
||||
{"NOTE_VM_ERROR", unix.NOTE_VM_ERROR},
|
||||
{"NOTE_VM_PRESSURE", unix.NOTE_VM_PRESSURE},
|
||||
{"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", unix.NOTE_VM_PRESSURE_SUDDEN_TERMINATE},
|
||||
{"NOTE_VM_PRESSURE_TERMINATE", unix.NOTE_VM_PRESSURE_TERMINATE},
|
||||
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||
}
|
||||
|
||||
-18
@@ -7,27 +7,9 @@ var names = []struct {
|
||||
m uint32
|
||||
}{
|
||||
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||
{"NOTE_FORK", unix.NOTE_FORK},
|
||||
{"NOTE_LINK", unix.NOTE_LINK},
|
||||
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||
{"NOTE_OOB", unix.NOTE_OOB},
|
||||
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||
}
|
||||
|
||||
+6
-28
@@ -6,37 +6,15 @@ var names = []struct {
|
||||
n string
|
||||
m uint32
|
||||
}{
|
||||
{"NOTE_ABSTIME", unix.NOTE_ABSTIME},
|
||||
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||
{"NOTE_CLOSE", unix.NOTE_CLOSE},
|
||||
{"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE},
|
||||
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||
{"NOTE_FFAND", unix.NOTE_FFAND},
|
||||
{"NOTE_FFCOPY", unix.NOTE_FFCOPY},
|
||||
{"NOTE_FFCTRLMASK", unix.NOTE_FFCTRLMASK},
|
||||
{"NOTE_FFLAGSMASK", unix.NOTE_FFLAGSMASK},
|
||||
{"NOTE_FFNOP", unix.NOTE_FFNOP},
|
||||
{"NOTE_FFOR", unix.NOTE_FFOR},
|
||||
{"NOTE_FILE_POLL", unix.NOTE_FILE_POLL},
|
||||
{"NOTE_FORK", unix.NOTE_FORK},
|
||||
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||
{"NOTE_LINK", unix.NOTE_LINK},
|
||||
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||
{"NOTE_MSECONDS", unix.NOTE_MSECONDS},
|
||||
{"NOTE_NSECONDS", unix.NOTE_NSECONDS},
|
||||
{"NOTE_OPEN", unix.NOTE_OPEN},
|
||||
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||
{"NOTE_READ", unix.NOTE_READ},
|
||||
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||
{"NOTE_SECONDS", unix.NOTE_SECONDS},
|
||||
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||
{"NOTE_TRIGGER", unix.NOTE_TRIGGER},
|
||||
{"NOTE_USECONDS", unix.NOTE_USECONDS},
|
||||
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||
{"NOTE_OPEN", unix.NOTE_OPEN},
|
||||
{"NOTE_CLOSE", unix.NOTE_CLOSE},
|
||||
{"NOTE_CLOSE_WRITE", unix.NOTE_CLOSE_WRITE},
|
||||
{"NOTE_READ", unix.NOTE_READ},
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,6 +27,6 @@ func Debug(name string, kevent *unix.Kevent_t) {
|
||||
if unknown > 0 {
|
||||
l = append(l, fmt.Sprintf("0x%x", unknown))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-60s → %q\n",
|
||||
fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s %10d:%-20s → %q\n",
|
||||
time.Now().Format("15:04:05.000000000"), mask, strings.Join(l, " | "), name)
|
||||
}
|
||||
|
||||
-10
@@ -7,19 +7,9 @@ var names = []struct {
|
||||
m uint32
|
||||
}{
|
||||
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||
{"NOTE_FORK", unix.NOTE_FORK},
|
||||
{"NOTE_LINK", unix.NOTE_LINK},
|
||||
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||
}
|
||||
|
||||
-12
@@ -7,22 +7,10 @@ var names = []struct {
|
||||
m uint32
|
||||
}{
|
||||
{"NOTE_ATTRIB", unix.NOTE_ATTRIB},
|
||||
// {"NOTE_CHANGE", unix.NOTE_CHANGE}, // Not on 386?
|
||||
{"NOTE_CHILD", unix.NOTE_CHILD},
|
||||
{"NOTE_DELETE", unix.NOTE_DELETE},
|
||||
{"NOTE_EOF", unix.NOTE_EOF},
|
||||
{"NOTE_EXEC", unix.NOTE_EXEC},
|
||||
{"NOTE_EXIT", unix.NOTE_EXIT},
|
||||
{"NOTE_EXTEND", unix.NOTE_EXTEND},
|
||||
{"NOTE_FORK", unix.NOTE_FORK},
|
||||
{"NOTE_LINK", unix.NOTE_LINK},
|
||||
{"NOTE_LOWAT", unix.NOTE_LOWAT},
|
||||
{"NOTE_PCTRLMASK", unix.NOTE_PCTRLMASK},
|
||||
{"NOTE_PDATAMASK", unix.NOTE_PDATAMASK},
|
||||
{"NOTE_RENAME", unix.NOTE_RENAME},
|
||||
{"NOTE_REVOKE", unix.NOTE_REVOKE},
|
||||
{"NOTE_TRACK", unix.NOTE_TRACK},
|
||||
{"NOTE_TRACKERR", unix.NOTE_TRACKERR},
|
||||
{"NOTE_TRUNCATE", unix.NOTE_TRUNCATE},
|
||||
{"NOTE_WRITE", unix.NOTE_WRITE},
|
||||
}
|
||||
|
||||
-11
@@ -15,17 +15,6 @@ var (
|
||||
|
||||
var maxfiles uint64
|
||||
|
||||
func SetRlimit() {
|
||||
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||
var l syscall.Rlimit
|
||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
if err == nil && l.Cur != l.Max {
|
||||
l.Cur = l.Max
|
||||
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
}
|
||||
maxfiles = uint64(l.Cur)
|
||||
}
|
||||
|
||||
func Maxfiles() uint64 { return maxfiles }
|
||||
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, uint64(dev)) }
|
||||
|
||||
-11
@@ -15,17 +15,6 @@ var (
|
||||
|
||||
var maxfiles uint64
|
||||
|
||||
func SetRlimit() {
|
||||
// Go 1.19 will do this automatically: https://go-review.googlesource.com/c/go/+/393354/
|
||||
var l syscall.Rlimit
|
||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
if err == nil && l.Cur != l.Max {
|
||||
l.Cur = l.Max
|
||||
syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
|
||||
}
|
||||
maxfiles = uint64(l.Cur)
|
||||
}
|
||||
|
||||
func Maxfiles() uint64 { return maxfiles }
|
||||
func Mkfifo(path string, mode uint32) error { return unix.Mkfifo(path, mode) }
|
||||
func Mknod(path string, mode uint32, dev int) error { return unix.Mknod(path, mode, dev) }
|
||||
|
||||
+18
@@ -2,6 +2,24 @@
|
||||
|
||||
package internal
|
||||
|
||||
import "syscall"
|
||||
|
||||
func HasPrivilegesForSymlink() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IgnoringEINTR makes a function call and repeats it if it returns an
|
||||
// EINTR error. This appears to be required even though we install all
|
||||
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
||||
// Also #20400 and #36644 are issues in which a signal handler is
|
||||
// installed without setting SA_RESTART. None of these are the common case,
|
||||
// but there are enough of them that it seems that we can't avoid
|
||||
// an EINTR loop.
|
||||
func IgnoringEINTR[T any](fn func() (T, error)) (T, error) {
|
||||
for {
|
||||
v, err := fn()
|
||||
if err != syscall.EINTR {
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -14,7 +14,6 @@ var (
|
||||
ErrUnixEACCES = errors.New("dummy")
|
||||
)
|
||||
|
||||
func SetRlimit() {}
|
||||
func Maxfiles() uint64 { return 1<<64 - 1 }
|
||||
func Mkfifo(path string, mode uint32) error { return errors.New("no FIFOs on Windows") }
|
||||
func Mknod(path string, mode uint32, dev int) error { return errors.New("no device nodes on Windows") }
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
linters-settings:
|
||||
staticcheck:
|
||||
checks: [
|
||||
"all",
|
||||
"-SA1019" # There are valid use cases of strings.Title
|
||||
]
|
||||
nakedret:
|
||||
max-func-lines: 0 # Disallow any unnamed return statement
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
- nakedret
|
||||
- gofmt
|
||||
- rowserrcheck
|
||||
- unconvert
|
||||
- goimports
|
||||
- unparam
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
codecov:
|
||||
notify:
|
||||
after_n_builds: 2
|
||||
|
||||
coverage:
|
||||
status:
|
||||
patch:
|
||||
|
||||
@@ -3,4 +3,3 @@
|
||||
.idea
|
||||
.env
|
||||
.mcp.json
|
||||
.claude/
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ linters:
|
||||
- recvcheck
|
||||
- testpackage
|
||||
- thelper
|
||||
- tagliatelle
|
||||
- tparallel
|
||||
- varnamelen
|
||||
- whitespace
|
||||
|
||||
+3
-3
@@ -4,11 +4,11 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 15 | 202 |
|
||||
| 15 | 207 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @fredbi | 99 | <https://github.com/go-openapi/analysis/commits?author=fredbi> |
|
||||
| @fredbi | 104 | <https://github.com/go-openapi/analysis/commits?author=fredbi> |
|
||||
| @casualjim | 70 | <https://github.com/go-openapi/analysis/commits?author=casualjim> |
|
||||
| @keramix | 9 | <https://github.com/go-openapi/analysis/commits?author=keramix> |
|
||||
| @youyuanwu | 8 | <https://github.com/go-openapi/analysis/commits?author=youyuanwu> |
|
||||
@@ -24,4 +24,4 @@
|
||||
| @cuishuang | 1 | <https://github.com/go-openapi/analysis/commits?author=cuishuang> |
|
||||
| @ujjwalsh | 1 | <https://github.com/go-openapi/analysis/commits?author=ujjwalsh> |
|
||||
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github-community-projects/contributors)_
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@ Maintainers can cut a new release by either:
|
||||
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
|
||||
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
|
||||
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
|
||||
[discord-url]: https://discord.gg/twZ9BwT3
|
||||
[discord-url]: https://discord.gg/FfnFYaC3k5
|
||||
|
||||
<!-- Badges: license & compliance -->
|
||||
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
use (
|
||||
.
|
||||
|
||||
+18
@@ -1,4 +1,6 @@
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
@@ -8,22 +10,38 @@ github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
|
||||
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0=
|
||||
golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
|
||||
-181
@@ -1,181 +0,0 @@
|
||||
# git-cliff ~ configuration file
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
[changelog]
|
||||
header = """
|
||||
"""
|
||||
|
||||
footer = """
|
||||
|
||||
-----
|
||||
|
||||
**[{{ remote.github.repo }}]({{ self::remote_url() }}) license terms**
|
||||
|
||||
[![License][license-badge]][license-url]
|
||||
|
||||
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
|
||||
[license-url]: {{ self::remote_url() }}/?tab=Apache-2.0-1-ov-file#readme
|
||||
|
||||
{%- macro remote_url() -%}
|
||||
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||
{%- endmacro -%}
|
||||
"""
|
||||
|
||||
body = """
|
||||
{%- if version %}
|
||||
## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/tree/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{%- else %}
|
||||
## [unreleased]
|
||||
{%- endif %}
|
||||
{%- if message %}
|
||||
{%- raw %}\n{% endraw %}
|
||||
{{ message }}
|
||||
{%- raw %}\n{% endraw %}
|
||||
{%- endif %}
|
||||
{%- if version %}
|
||||
{%- if previous.version %}
|
||||
|
||||
**Full Changelog**: <{{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}>
|
||||
{%- endif %}
|
||||
{%- else %}
|
||||
{%- raw %}\n{% endraw %}
|
||||
{%- endif %}
|
||||
|
||||
{%- if statistics %}{% if statistics.commit_count %}
|
||||
{%- raw %}\n{% endraw %}
|
||||
{{ statistics.commit_count }} commits in this release.
|
||||
{%- raw %}\n{% endraw %}
|
||||
{%- endif %}{% endif %}
|
||||
-----
|
||||
|
||||
{%- for group, commits in commits | group_by(attribute="group") %}
|
||||
{%- raw %}\n{% endraw %}
|
||||
### {{ group | upper_first }}
|
||||
{%- raw %}\n{% endraw %}
|
||||
{%- for commit in commits %}
|
||||
{%- if commit.remote.pr_title %}
|
||||
{%- set commit_message = commit.remote.pr_title %}
|
||||
{%- else %}
|
||||
{%- set commit_message = commit.message %}
|
||||
{%- endif %}
|
||||
* {{ commit_message | split(pat="\n") | first | trim }}
|
||||
{%- if commit.remote.username %}
|
||||
{%- raw %} {% endraw %}by [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }})
|
||||
{%- endif %}
|
||||
{%- if commit.remote.pr_number %}
|
||||
{%- raw %} {% endraw %}in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }})
|
||||
{%- endif %}
|
||||
{%- raw %} {% endraw %}[...]({{ self::remote_url() }}/commit/{{ commit.id }})
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
|
||||
{%- if github %}
|
||||
{%- raw %}\n{% endraw -%}
|
||||
{%- set all_contributors = github.contributors | length %}
|
||||
{%- if github.contributors | filter(attribute="username", value="dependabot[bot]") | length < all_contributors %}
|
||||
-----
|
||||
|
||||
### People who contributed to this release
|
||||
{% endif %}
|
||||
{%- for contributor in github.contributors | filter(attribute="username") | sort(attribute="username") %}
|
||||
{%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %}
|
||||
* [@{{ contributor.username }}](https://github.com/{{ contributor.username }})
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
|
||||
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||
-----
|
||||
{%- raw %}\n{% endraw %}
|
||||
|
||||
### New Contributors
|
||||
{%- endif %}
|
||||
|
||||
{%- for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
|
||||
{%- if contributor.username != "dependabot[bot]" and contributor.username != "github-actions[bot]" %}
|
||||
* @{{ contributor.username }} made their first contribution
|
||||
{%- if contributor.pr_number %}
|
||||
in [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{%- endif %}
|
||||
|
||||
{%- raw %}\n{% endraw %}
|
||||
|
||||
{%- macro remote_url() -%}
|
||||
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
|
||||
{%- endmacro -%}
|
||||
"""
|
||||
# Remove leading and trailing whitespaces from the changelog's body.
|
||||
trim = true
|
||||
# Render body even when there are no releases to process.
|
||||
render_always = true
|
||||
# An array of regex based postprocessors to modify the changelog.
|
||||
postprocessors = [
|
||||
# Replace the placeholder <REPO> with a URL.
|
||||
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
|
||||
]
|
||||
# output file path
|
||||
# output = "test.md"
|
||||
|
||||
[git]
|
||||
# Parse commits according to the conventional commits specification.
|
||||
# See https://www.conventionalcommits.org
|
||||
conventional_commits = false
|
||||
# Exclude commits that do not match the conventional commits specification.
|
||||
filter_unconventional = false
|
||||
# Require all commits to be conventional.
|
||||
# Takes precedence over filter_unconventional.
|
||||
require_conventional = false
|
||||
# Split commits on newlines, treating each line as an individual commit.
|
||||
split_commits = false
|
||||
# An array of regex based parsers to modify commit messages prior to further processing.
|
||||
commit_preprocessors = [
|
||||
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
|
||||
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
|
||||
# Check spelling of the commit message using https://github.com/crate-ci/typos.
|
||||
# If the spelling is incorrect, it will be fixed automatically.
|
||||
#{ pattern = '.*', replace_command = 'typos --write-changes -' }
|
||||
]
|
||||
# Prevent commits that are breaking from being excluded by commit parsers.
|
||||
protect_breaking_commits = false
|
||||
# An array of regex based parsers for extracting data from the commit message.
|
||||
# Assigns commits to groups.
|
||||
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
|
||||
commit_parsers = [
|
||||
{ message = "^[Cc]hore\\([Rr]elease\\): prepare for", skip = true },
|
||||
{ message = "(^[Mm]erge)|([Mm]erge conflict)", skip = true },
|
||||
{ field = "author.name", pattern = "dependabot*", group = "<!-- 0A -->Updates" },
|
||||
{ message = "([Ss]ecurity)|([Vv]uln)", group = "<!-- 08 -->Security" },
|
||||
{ body = "(.*[Ss]ecurity)|([Vv]uln)", group = "<!-- 08 -->Security" },
|
||||
{ message = "([Cc]hore\\(lint\\))|(style)|(lint)|(codeql)|(golangci)", group = "<!-- 05 -->Code quality" },
|
||||
{ message = "(^[Dd]oc)|((?i)readme)|(badge)|(typo)|(documentation)", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "(^[Ff]eat)|(^[Ee]nhancement)", group = "<!-- 00 -->Implemented enhancements" },
|
||||
{ message = "(^ci)|(\\(ci\\))|(fixup\\s+ci)|(fix\\s+ci)|(license)|(example)", group = "<!-- 07 -->Miscellaneous tasks" },
|
||||
{ message = "^test", group = "<!-- 06 -->Testing" },
|
||||
{ message = "(^fix)|(panic)", group = "<!-- 01 -->Fixed bugs" },
|
||||
{ message = "(^refact)|(rework)", group = "<!-- 02 -->Refactor" },
|
||||
{ message = "(^[Pp]erf)|(performance)", group = "<!-- 04 -->Performance" },
|
||||
{ message = "(^[Cc]hore)", group = "<!-- 07 -->Miscellaneous tasks" },
|
||||
{ message = "^[Rr]evert", group = "<!-- 09 -->Reverted changes" },
|
||||
{ message = "(upgrade.*?go)|(go\\s+version)", group = "<!-- 0A -->Updates" },
|
||||
{ message = ".*", group = "<!-- 0B -->Other" },
|
||||
]
|
||||
# Exclude commits that are not matched by any commit parser.
|
||||
filter_commits = false
|
||||
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
|
||||
link_parsers = []
|
||||
# Include only the tags that belong to the current branch.
|
||||
use_branch_tags = false
|
||||
# Order releases topologically instead of chronologically.
|
||||
topo_order = false
|
||||
# Order releases topologically instead of chronologically.
|
||||
topo_order_commits = true
|
||||
# Order of commits in each group/release within the changelog.
|
||||
# Allowed values: newest, oldest
|
||||
sort_commits = "newest"
|
||||
# Process submodules commits
|
||||
recurse_submodules = false
|
||||
|
||||
#[remote.github]
|
||||
#owner = "go-openapi"
|
||||
@@ -3,4 +3,3 @@
|
||||
.idea
|
||||
.env
|
||||
.mcp.json
|
||||
.claude/
|
||||
|
||||
+4
-3
@@ -4,11 +4,11 @@
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 12 | 101 |
|
||||
| 13 | 111 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @fredbi | 54 | <https://github.com/go-openapi/jsonpointer/commits?author=fredbi> |
|
||||
| @fredbi | 63 | <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> |
|
||||
@@ -18,7 +18,8 @@
|
||||
| @ianlancetaylor | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=ianlancetaylor> |
|
||||
| @mfleader | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=mfleader> |
|
||||
| @Neo2308 | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=Neo2308> |
|
||||
| @alexandear | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=alexandear> |
|
||||
| @olivierlemasle | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=olivierlemasle> |
|
||||
| @testwill | 1 | <https://github.com/go-openapi/jsonpointer/commits?author=testwill> |
|
||||
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github/contributors)_
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github-community-projects/contributors)_
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ It ships with copies of other software which license terms are recalled below.
|
||||
|
||||
The original software was authored on 25-02-2013 by sigu-399 (https://github.com/sigu-399, sigu.399@gmail.com).
|
||||
|
||||
github.com/sigh-399/jsonpointer
|
||||
github.com/sigu-399/jsonpointer
|
||||
===========================
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
|
||||
|
||||
+31
-21
@@ -16,17 +16,25 @@ An implementation of JSON Pointer for golang, which supports go `struct`.
|
||||
|
||||
## Announcements
|
||||
|
||||
* **2025-12-19** : new community chat on discord
|
||||
* a new discord community channel is available to be notified of changes and support users
|
||||
* our venerable Slack channel remains open, and will be eventually discontinued on **2026-03-31**
|
||||
* **2026-04-15** : added support for trailing "-" for arrays (v0.23.0)
|
||||
* this brings full support of [RFC6901][RFC6901]
|
||||
* this is supported for types relying on the reflection-based implemented
|
||||
* API semantics remain essentially unaltered. Exception: `Pointer.Set(document any,value any) (document any, err error)`
|
||||
can only perform a best-effort to mutate the input document in place. In the case of adding elements to an array with a
|
||||
trailing "-", either pass a mutable array (`*[]T`) as the input document, or use the returned updated document instead.
|
||||
* types that implement the `JSONSetable` interface may not implement the mutation implied by the trailing "-"
|
||||
|
||||
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]
|
||||
* **2026-04-15** : added support for optional alternate JSON name providers
|
||||
* for struct support the defaults might not suit all situations: there are known limitations
|
||||
when it comes to handle untagged fields or embedded types.
|
||||
* the default name provider in use is not fully aligned with go JSON stdlib
|
||||
* exposed an option (or global setting) to change the provider that resolves a struct into json keys
|
||||
* the default behavior is not altered
|
||||
* a new alternate name provider is added (imported from `go-openapi/swag/jsonname`), aligned with JSON stdlib behavior
|
||||
|
||||
## Status
|
||||
|
||||
API is stable.
|
||||
API is stable and feature-complete.
|
||||
|
||||
## Import this library in your project
|
||||
|
||||
@@ -88,7 +96,7 @@ See <https://github.com/go-openapi/jsonpointer/releases>
|
||||
|
||||
<https://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07>
|
||||
|
||||
also known as [RFC6901](https://www.rfc-editor.org/rfc/rfc6901)
|
||||
also known as [RFC6901][RFC6901].
|
||||
|
||||
## Licensing
|
||||
|
||||
@@ -99,19 +107,19 @@ on top of which it has been built.
|
||||
|
||||
## Limitations
|
||||
|
||||
The 4.Evaluation part of the previous reference, starting with 'If the currently referenced value is a JSON array,
|
||||
the reference token MUST contain either...' is not implemented.
|
||||
|
||||
That is because our implementation of the JSON pointer only supports explicit references to array elements:
|
||||
the provision in the spec to resolve non-existent members as "the last element in the array",
|
||||
using the special trailing character "-" is not implemented.
|
||||
* [RFC6901][RFC6901] is now fully supported, including trailing "-" semantics for arrays (for `Set` operations).
|
||||
* Default behavior: JSON name detection in go `struct`s
|
||||
- Unlike go standard marshaling, untagged fields do not default to the go field name and are ignored.
|
||||
- anonymous fields are not traversed if untagged
|
||||
- the above limitations may be overcome by calling `UseGoNameProvider()` at initialization time.
|
||||
- alternatively, users may inject the desired custom behavior for naming fields as an option.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -142,11 +150,8 @@ Maintainers can cut a new release by either:
|
||||
<!-- Badges: documentation & support -->
|
||||
[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/jsonpointer
|
||||
[godoc-url]: http://pkg.go.dev/github.com/go-openapi/jsonpointer
|
||||
[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png
|
||||
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
|
||||
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
|
||||
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
|
||||
[discord-url]: https://discord.gg/twZ9BwT3
|
||||
[discord-url]: https://discord.gg/FfnFYaC3k5
|
||||
|
||||
<!-- Badges: license & compliance -->
|
||||
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
|
||||
@@ -156,3 +161,8 @@ Maintainers can cut a new release by either:
|
||||
[goversion-url]: https://github.com/go-openapi/jsonpointer/blob/master/go.mod
|
||||
[top-badge]: https://img.shields.io/github/languages/top/go-openapi/jsonpointer
|
||||
[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/jsonpointer/latest
|
||||
[RFC6901]: https://www.rfc-editor.org/rfc/rfc6901
|
||||
<!-- 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
|
||||
|
||||
+25
-1
@@ -16,12 +16,24 @@ const (
|
||||
ErrPointer pointerError = "JSON pointer error"
|
||||
|
||||
// ErrInvalidStart states that a JSON pointer must start with a separator ("/").
|
||||
ErrInvalidStart pointerError = `JSON pointer must be empty or start with a "` + pointerSeparator
|
||||
ErrInvalidStart pointerError = `JSON pointer must be empty or start with a "` + pointerSeparator + `"`
|
||||
|
||||
// ErrUnsupportedValueType indicates that a value of the wrong type is being set.
|
||||
ErrUnsupportedValueType pointerError = "only structs, pointers, maps and slices are supported for setting values"
|
||||
|
||||
// ErrDashToken indicates use of the RFC 6901 "-" reference token
|
||||
// in a context where it cannot be resolved.
|
||||
//
|
||||
// Per RFC 6901 §4 the "-" token refers to the (nonexistent) element
|
||||
// after the last array element. It may only be used as the terminal
|
||||
// token of a [Pointer.Set] against a slice, where it means "append".
|
||||
// Any other use (get, offset, intermediate traversal, non-slice target)
|
||||
// is an error condition that wraps this sentinel.
|
||||
ErrDashToken pointerError = `the "-" array token cannot be resolved here` //nolint:gosec // G101 false positive: this is a JSON Pointer reference token, not a credential.
|
||||
)
|
||||
|
||||
const dashToken = "-"
|
||||
|
||||
func errNoKey(key string) error {
|
||||
return fmt.Errorf("object has no key %q: %w", key, ErrPointer)
|
||||
}
|
||||
@@ -33,3 +45,15 @@ func errOutOfBounds(length, idx int) error {
|
||||
func errInvalidReference(token string) error {
|
||||
return fmt.Errorf("invalid token reference %q: %w", token, ErrPointer)
|
||||
}
|
||||
|
||||
func errDashOnGet() error {
|
||||
return fmt.Errorf("cannot resolve %q token on get: %w: %w", dashToken, ErrDashToken, ErrPointer)
|
||||
}
|
||||
|
||||
func errDashIntermediate() error {
|
||||
return fmt.Errorf("the %q token may only appear as the terminal token of a pointer: %w: %w", dashToken, ErrDashToken, ErrPointer)
|
||||
}
|
||||
|
||||
func errDashOnOffset() error {
|
||||
return fmt.Errorf("cannot compute offset for %q token (nonexistent element): %w: %w", dashToken, ErrDashToken, ErrPointer)
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonpointer
|
||||
|
||||
import "reflect"
|
||||
|
||||
// JSONPointable is an interface for structs to implement,
|
||||
// when they need to customize the json pointer process or want to avoid the use of reflection.
|
||||
type JSONPointable interface {
|
||||
// JSONLookup returns a value pointed at this (unescaped) key.
|
||||
JSONLookup(key string) (any, error)
|
||||
}
|
||||
|
||||
// JSONSetable is an interface for structs to implement,
|
||||
// when they need to customize the json pointer process or want to avoid the use of reflection.
|
||||
//
|
||||
// # Handling of the RFC 6901 "-" token
|
||||
//
|
||||
// When a type implementing JSONSetable is the terminal parent of a [Pointer.Set]
|
||||
// call, the library passes the raw reference token to JSONSet without
|
||||
// interpretation. In particular, the RFC 6901 "-" token (which conventionally
|
||||
// means "append" for arrays, per RFC 6902) is forwarded verbatim as the key
|
||||
// argument. Implementations that model an array-like container are expected
|
||||
// to give "-" the append semantics; implementations that do not should return
|
||||
// an error wrapping [ErrDashToken] (or [ErrPointer]) for clarity.
|
||||
//
|
||||
// Implementations are responsible for any in-place mutation: the library does
|
||||
// not attempt to rebind the result of JSONSet into a parent container.
|
||||
type JSONSetable interface {
|
||||
// JSONSet sets the value pointed at the (unescaped) key.
|
||||
//
|
||||
// The key may be the RFC 6901 "-" token when the pointer targets a
|
||||
// slice-like member; see the interface documentation for details.
|
||||
JSONSet(key string, value any) error
|
||||
}
|
||||
|
||||
// NameProvider knows how to resolve go struct fields into json names.
|
||||
//
|
||||
// The default provider is brought by [github.com/go-openapi/swag/jsonname.DefaultJSONNameProvider].
|
||||
type NameProvider interface {
|
||||
// GetGoName gets the go name for a json property name
|
||||
GetGoName(subject any, name string) (string, bool)
|
||||
|
||||
// GetGoNameForType gets the go name for a given type for a json property name
|
||||
GetGoNameForType(tpe reflect.Type, name string) (string, bool)
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package jsonpointer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-openapi/swag/jsonname"
|
||||
)
|
||||
|
||||
// Option to tune the behavior of a JSON [Pointer].
|
||||
type Option func(*options)
|
||||
|
||||
var (
|
||||
//nolint:gochecknoglobals // package level defaults are provided as a convenient, backward-compatible way to adopt options.
|
||||
defaultOptions = options{
|
||||
provider: jsonname.DefaultJSONNameProvider,
|
||||
}
|
||||
//nolint:gochecknoglobals // guards defaultOptions against concurrent SetDefaultNameProvider / read races (testing)
|
||||
defaultOptionsMu sync.RWMutex
|
||||
)
|
||||
|
||||
// SetDefaultNameProvider sets the [NameProvider] as a package-level default.
|
||||
//
|
||||
// By default, the default provider is [jsonname.DefaultJSONNameProvider].
|
||||
//
|
||||
// It is safe to call concurrently with [Pointer.Get], [Pointer.Set],
|
||||
// [GetForToken] and [SetForToken]. The typical usage is to call it once
|
||||
// at initialization time.
|
||||
//
|
||||
// A nil provider is ignored.
|
||||
func SetDefaultNameProvider(provider NameProvider) {
|
||||
if provider == nil {
|
||||
return
|
||||
}
|
||||
|
||||
defaultOptionsMu.Lock()
|
||||
defer defaultOptionsMu.Unlock()
|
||||
|
||||
defaultOptions.provider = provider
|
||||
}
|
||||
|
||||
// UseGoNameProvider sets the [NameProvider] as a package-level default
|
||||
// to the alternative provider [jsonname.GoNameProvider], that covers a few areas
|
||||
// not supported by the default name provider.
|
||||
//
|
||||
// This implementation supports untagged exported fields and embedded types in go struct.
|
||||
// It follows strictly the behavior of the JSON standard library regarding field naming conventions.
|
||||
//
|
||||
// It is safe to call concurrently with [Pointer.Get], [Pointer.Set],
|
||||
// [GetForToken] and [SetForToken]. The typical usage is to call it once
|
||||
// at initialization time.
|
||||
func UseGoNameProvider() {
|
||||
SetDefaultNameProvider(jsonname.NewGoNameProvider())
|
||||
}
|
||||
|
||||
// DefaultNameProvider returns the current package-level [NameProvider].
|
||||
func DefaultNameProvider() NameProvider { //nolint:ireturn // returning the interface is the point — callers pick their own implementation.
|
||||
defaultOptionsMu.RLock()
|
||||
defer defaultOptionsMu.RUnlock()
|
||||
|
||||
return defaultOptions.provider
|
||||
}
|
||||
|
||||
// WithNameProvider injects a custom [NameProvider] to resolve json names from go struct types.
|
||||
func WithNameProvider(provider NameProvider) Option {
|
||||
return func(o *options) {
|
||||
o.provider = provider
|
||||
}
|
||||
}
|
||||
|
||||
type options struct {
|
||||
provider NameProvider
|
||||
}
|
||||
|
||||
func optionsWithDefaults(opts []Option) options {
|
||||
var o options
|
||||
o.provider = DefaultNameProvider()
|
||||
|
||||
for _, apply := range opts {
|
||||
apply(&o)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
+273
-85
@@ -11,8 +11,6 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/swag/jsonname"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,20 +18,6 @@ const (
|
||||
pointerSeparator = `/`
|
||||
)
|
||||
|
||||
// JSONPointable is an interface for structs to implement,
|
||||
// when they need to customize the json pointer process or want to avoid the use of reflection.
|
||||
type JSONPointable interface {
|
||||
// JSONLookup returns a value pointed at this (unescaped) key.
|
||||
JSONLookup(key string) (any, error)
|
||||
}
|
||||
|
||||
// JSONSetable is an interface for structs to implement,
|
||||
// when they need to customize the json pointer process or want to avoid the use of reflection.
|
||||
type JSONSetable interface {
|
||||
// JSONSet sets the value pointed at the (unescaped) key.
|
||||
JSONSet(key string, value any) error
|
||||
}
|
||||
|
||||
// Pointer is a representation of a json pointer.
|
||||
//
|
||||
// Use [Pointer.Get] to retrieve a value or [Pointer.Set] to set a value.
|
||||
@@ -41,7 +25,7 @@ type JSONSetable interface {
|
||||
// It works with any go type interpreted as a JSON document, which means:
|
||||
//
|
||||
// - if a type implements [JSONPointable], its [JSONPointable.JSONLookup] method is used to resolve [Pointer.Get]
|
||||
// - if a type implements [JSONSetable], its [JSONPointable.JSONSet] method is used to resolve [Pointer.Set]
|
||||
// - if a type implements [JSONSetable], its [JSONSetable.JSONSet] method is used to resolve [Pointer.Set]
|
||||
// - a go map[K]V is interpreted as an object, with type K assignable to a string
|
||||
// - a go slice []T is interpreted as an array
|
||||
// - a go struct is interpreted as an object, with exported fields interpreted as keys
|
||||
@@ -71,16 +55,35 @@ func New(jsonPointerString string) (Pointer, error) {
|
||||
// Get uses the pointer to retrieve a value from a JSON document.
|
||||
//
|
||||
// It returns the value with its type as a [reflect.Kind] or an error.
|
||||
func (p *Pointer) Get(document any) (any, reflect.Kind, error) {
|
||||
return p.get(document, jsonname.DefaultJSONNameProvider)
|
||||
func (p *Pointer) Get(document any, opts ...Option) (any, reflect.Kind, error) {
|
||||
o := optionsWithDefaults(opts)
|
||||
|
||||
return p.get(document, o.provider)
|
||||
}
|
||||
|
||||
// Set uses the pointer to set a value from a data type
|
||||
// that represent a JSON document.
|
||||
//
|
||||
// It returns the updated document.
|
||||
func (p *Pointer) Set(document any, value any) (any, error) {
|
||||
return document, p.set(document, value, jsonname.DefaultJSONNameProvider)
|
||||
// # Mutation contract
|
||||
//
|
||||
// Set mutates the provided document in place whenever Go's type system allows
|
||||
// it: when document is a map, a pointer, or when the targeted value is reached
|
||||
// through an addressable ancestor (e.g. a struct field traversed via a pointer,
|
||||
// a slice element). Callers that rely on this in-place behavior may continue
|
||||
// to ignore the returned document.
|
||||
//
|
||||
// The returned document is only load-bearing when Set cannot mutate in place.
|
||||
// This happens in one specific case: appending to a top-level slice passed by
|
||||
// value (e.g. document of type []T rather than *[]T) via the RFC 6901 "-"
|
||||
// terminal token. reflect.Append produces a new slice header that the library
|
||||
// cannot rebind into the caller's variable; the updated document is returned
|
||||
// instead. Pass *[]T if you want in-place rebind for that case as well.
|
||||
//
|
||||
// See [ErrDashToken] for the semantics of the "-" token.
|
||||
func (p *Pointer) Set(document any, value any, opts ...Option) (any, error) {
|
||||
o := optionsWithDefaults(opts)
|
||||
|
||||
return p.set(document, value, o.provider)
|
||||
}
|
||||
|
||||
// DecodedTokens returns the decoded (unescaped) tokens of this JSON pointer.
|
||||
@@ -109,6 +112,46 @@ func (p *Pointer) String() string {
|
||||
return pointerSeparator + strings.Join(p.referenceTokens, pointerSeparator)
|
||||
}
|
||||
|
||||
// Offset returns the byte offset, in the raw JSON text of document, of the
|
||||
// location referenced by this pointer's terminal token.
|
||||
//
|
||||
// Unlike [Pointer.Get] and [Pointer.Set], which operate on a decoded Go value,
|
||||
// Offset operates directly on the textual JSON source. It drives an
|
||||
// [encoding/json.Decoder] over the string and stops at the terminal token,
|
||||
// returning the position at which the decoder was about to read that token.
|
||||
//
|
||||
// It is primarily intended for tooling that needs to map a pointer back to a
|
||||
// region of the original source: reporting line/column for validation or
|
||||
// parse diagnostics, extracting a sub-document by slicing the raw bytes, or
|
||||
// highlighting the referenced span in an editor.
|
||||
//
|
||||
// # Offset semantics
|
||||
//
|
||||
// The meaning of the returned offset depends on whether the terminal token
|
||||
// addresses an object property or an array element:
|
||||
//
|
||||
// - Object property: the offset points to the first byte of the key (its
|
||||
// opening quote character), not to the associated value. For example,
|
||||
// pointer "/foo/bar" against {"foo": {"bar": 21}} returns 9, the index of
|
||||
// the opening quote of "bar".
|
||||
// - Array element: the offset points to the first byte of the value at that
|
||||
// index. For example, pointer "/0/1" against [[1,2], [3,4]] returns 4,
|
||||
// the index of the digit 2.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// Offset returns an error in any of these cases:
|
||||
//
|
||||
// - document is not syntactically valid JSON;
|
||||
// - the structure of document does not match the pointer (e.g. traversing
|
||||
// into a scalar, or a token that is neither a valid key nor a valid
|
||||
// numeric index);
|
||||
// - a referenced key or index does not exist in document;
|
||||
// - the pointer's terminal token is the RFC 6901 "-" array token, which
|
||||
// designates a nonexistent element and therefore has no offset in the
|
||||
// source. The returned error wraps [ErrDashToken].
|
||||
//
|
||||
// All errors wrap [ErrPointer].
|
||||
func (p *Pointer) Offset(document string) (int64, error) {
|
||||
dec := json.NewDecoder(strings.NewReader(document))
|
||||
var offset int64
|
||||
@@ -137,7 +180,35 @@ func (p *Pointer) Offset(document string) (int64, error) {
|
||||
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
|
||||
}
|
||||
}
|
||||
return offset, nil
|
||||
return skipJSONSeparator(document, offset), nil
|
||||
}
|
||||
|
||||
// skipJSONSeparator advances offset past trailing JSON whitespace and at most
|
||||
// one value separator (comma) in document, so the result points at the first
|
||||
// byte of the next JSON token.
|
||||
//
|
||||
// The streaming decoder's InputOffset sits right after the most recently
|
||||
// consumed token, which between values is the comma (or whitespace) — not
|
||||
// the following token. Normalizing here keeps Offset's contract uniform:
|
||||
// for both object keys and array elements, and regardless of position within
|
||||
// the parent container, the returned offset always points at the first byte
|
||||
// of the addressed token.
|
||||
func skipJSONSeparator(document string, offset int64) int64 {
|
||||
n := int64(len(document))
|
||||
for offset < n && isJSONWhitespace(document[offset]) {
|
||||
offset++
|
||||
}
|
||||
if offset < n && document[offset] == ',' {
|
||||
offset++
|
||||
}
|
||||
for offset < n && isJSONWhitespace(document[offset]) {
|
||||
offset++
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
func isJSONWhitespace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
|
||||
}
|
||||
|
||||
// "Constructor", parses the given string JSON pointer.
|
||||
@@ -157,9 +228,9 @@ func (p *Pointer) parse(jsonPointerString string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, reflect.Kind, error) {
|
||||
func (p *Pointer) get(node any, nameProvider NameProvider) (any, reflect.Kind, error) {
|
||||
if nameProvider == nil {
|
||||
nameProvider = jsonname.DefaultJSONNameProvider
|
||||
nameProvider = defaultOptions.provider
|
||||
}
|
||||
|
||||
kind := reflect.Invalid
|
||||
@@ -185,50 +256,130 @@ func (p *Pointer) get(node any, nameProvider *jsonname.NameProvider) (any, refle
|
||||
return node, kind, nil
|
||||
}
|
||||
|
||||
func (p *Pointer) set(node, data any, nameProvider *jsonname.NameProvider) error {
|
||||
func (p *Pointer) set(node, data any, nameProvider NameProvider) (any, error) {
|
||||
knd := reflect.ValueOf(node).Kind()
|
||||
|
||||
if knd != reflect.Pointer && knd != reflect.Struct && knd != reflect.Map && knd != reflect.Slice && knd != reflect.Array {
|
||||
return errors.Join(
|
||||
return node, errors.Join(
|
||||
fmt.Errorf("unexpected type: %T", node), //nolint:err113 // err wrapping is carried out by errors.Join, not fmt.Errorf.
|
||||
ErrUnsupportedValueType,
|
||||
ErrPointer,
|
||||
)
|
||||
}
|
||||
|
||||
l := len(p.referenceTokens)
|
||||
|
||||
// full document when empty
|
||||
if l == 0 {
|
||||
return nil
|
||||
if len(p.referenceTokens) == 0 {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
if nameProvider == nil {
|
||||
nameProvider = jsonname.DefaultJSONNameProvider
|
||||
nameProvider = defaultOptions.provider
|
||||
}
|
||||
|
||||
var decodedToken string
|
||||
lastIndex := l - 1
|
||||
|
||||
if lastIndex > 0 { // skip if we only have one token in pointer
|
||||
for _, token := range p.referenceTokens[:lastIndex] {
|
||||
decodedToken = Unescape(token)
|
||||
next, err := p.resolveNodeForToken(node, decodedToken, nameProvider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
node = next
|
||||
}
|
||||
}
|
||||
|
||||
// last token
|
||||
decodedToken = Unescape(p.referenceTokens[lastIndex])
|
||||
|
||||
return setSingleImpl(node, data, decodedToken, nameProvider)
|
||||
return p.setAt(node, p.referenceTokens, data, nameProvider)
|
||||
}
|
||||
|
||||
func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvider *jsonname.NameProvider) (next any, err error) {
|
||||
// setAt recursively walks the token list, setting the data at the terminal
|
||||
// token and rebinding any 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 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) {
|
||||
decodedToken := Unescape(tokens[0])
|
||||
|
||||
if len(tokens) == 1 {
|
||||
return setSingleImpl(node, data, decodedToken, nameProvider)
|
||||
}
|
||||
|
||||
child, err := p.resolveNodeForToken(node, decodedToken, nameProvider)
|
||||
if err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
newChild, err := p.setAt(child, tokens[1:], data, nameProvider)
|
||||
if err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
return rebindChild(node, decodedToken, newChild, nameProvider)
|
||||
}
|
||||
|
||||
// rebindChild writes newChild back into node at decodedToken.
|
||||
//
|
||||
// For cases where the child was already mutated in place (pointer aliasing,
|
||||
// addressable slice elements) the rebind is a safe no-op. For cases where
|
||||
// the child was returned by value (map entries holding a slice, slices
|
||||
// reached through a non-addressable ancestor), the rebind propagates the
|
||||
// new value into the parent.
|
||||
//
|
||||
// Parents implementing [JSONPointable] are left alone: they took ownership
|
||||
// of the child via JSONLookup and did not opt into a JSONSet-based rebind
|
||||
// on intermediate tokens.
|
||||
func rebindChild(node any, decodedToken string, newChild any, nameProvider NameProvider) (any, error) {
|
||||
if _, ok := node.(JSONPointable); ok {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||
|
||||
switch rValue.Kind() {
|
||||
case reflect.Struct:
|
||||
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||
if !ok {
|
||||
return node, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
|
||||
}
|
||||
fld := rValue.FieldByName(nm)
|
||||
if !fld.CanSet() {
|
||||
return node, nil
|
||||
}
|
||||
assignReflectValue(fld, newChild)
|
||||
return node, nil
|
||||
|
||||
case reflect.Map:
|
||||
rValue.SetMapIndex(reflect.ValueOf(decodedToken), reflect.ValueOf(newChild))
|
||||
return node, nil
|
||||
|
||||
case reflect.Slice:
|
||||
if decodedToken == dashToken {
|
||||
return node, errDashIntermediate()
|
||||
}
|
||||
idx, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return node, errors.Join(err, ErrPointer)
|
||||
}
|
||||
elem := rValue.Index(idx)
|
||||
if !elem.CanSet() {
|
||||
return node, nil
|
||||
}
|
||||
assignReflectValue(elem, newChild)
|
||||
return node, nil
|
||||
|
||||
default:
|
||||
return node, errInvalidReference(decodedToken)
|
||||
}
|
||||
}
|
||||
|
||||
// assignReflectValue assigns src into dst, unwrapping a pointer when dst
|
||||
// expects the pointee type. This tolerates the pointer-wrapping performed
|
||||
// by [typeFromValue] for addressable fields.
|
||||
func assignReflectValue(dst reflect.Value, src any) {
|
||||
nv := reflect.ValueOf(src)
|
||||
if !nv.IsValid() {
|
||||
return
|
||||
}
|
||||
if nv.Type().AssignableTo(dst.Type()) {
|
||||
dst.Set(nv)
|
||||
return
|
||||
}
|
||||
if nv.Kind() == reflect.Pointer && nv.Elem().Type().AssignableTo(dst.Type()) {
|
||||
dst.Set(nv.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvider NameProvider) (next any, err error) {
|
||||
// check for nil during traversal
|
||||
if isNil(node) {
|
||||
return nil, fmt.Errorf("cannot traverse through nil value at %q: %w", decodedToken, ErrPointer)
|
||||
@@ -272,6 +423,9 @@ func (p *Pointer) resolveNodeForToken(node any, decodedToken string, nameProvide
|
||||
return typeFromValue(mv), nil
|
||||
|
||||
case reflect.Slice:
|
||||
if decodedToken == dashToken {
|
||||
return nil, errDashIntermediate()
|
||||
}
|
||||
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return nil, errors.Join(err, ErrPointer)
|
||||
@@ -312,16 +466,23 @@ func typeFromValue(v reflect.Value) any {
|
||||
}
|
||||
|
||||
// GetForToken gets a value for a json pointer token 1 level deep.
|
||||
func GetForToken(document any, decodedToken string) (any, reflect.Kind, error) {
|
||||
return getSingleImpl(document, decodedToken, jsonname.DefaultJSONNameProvider)
|
||||
func GetForToken(document any, decodedToken string, opts ...Option) (any, reflect.Kind, error) {
|
||||
o := optionsWithDefaults(opts)
|
||||
|
||||
return getSingleImpl(document, decodedToken, o.provider)
|
||||
}
|
||||
|
||||
// SetForToken sets a value for a json pointer token 1 level deep.
|
||||
func SetForToken(document any, decodedToken string, value any) (any, error) {
|
||||
return document, setSingleImpl(document, value, decodedToken, jsonname.DefaultJSONNameProvider)
|
||||
//
|
||||
// See [Pointer.Set] for the mutation contract, in particular the handling of
|
||||
// the RFC 6901 "-" token on slices.
|
||||
func SetForToken(document any, decodedToken string, value any, opts ...Option) (any, error) {
|
||||
o := optionsWithDefaults(opts)
|
||||
|
||||
return setSingleImpl(document, value, decodedToken, o.provider)
|
||||
}
|
||||
|
||||
func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NameProvider) (any, reflect.Kind, error) {
|
||||
func getSingleImpl(node any, decodedToken string, nameProvider NameProvider) (any, reflect.Kind, error) {
|
||||
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||
kind := rValue.Kind()
|
||||
if isNil(node) {
|
||||
@@ -361,6 +522,9 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
|
||||
return nil, kind, errNoKey(decodedToken)
|
||||
|
||||
case reflect.Slice:
|
||||
if decodedToken == dashToken {
|
||||
return nil, kind, errDashOnGet()
|
||||
}
|
||||
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return nil, kind, errors.Join(err, ErrPointer)
|
||||
@@ -378,14 +542,14 @@ func getSingleImpl(node any, decodedToken string, nameProvider *jsonname.NamePro
|
||||
}
|
||||
}
|
||||
|
||||
func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.NameProvider) error {
|
||||
func setSingleImpl(node, data any, decodedToken string, nameProvider NameProvider) (any, error) {
|
||||
// check for nil to prevent panic when calling rValue.Type()
|
||||
if isNil(node) {
|
||||
return fmt.Errorf("cannot set field %q on nil value: %w", decodedToken, ErrPointer)
|
||||
return node, fmt.Errorf("cannot set field %q on nil value: %w", decodedToken, ErrPointer)
|
||||
}
|
||||
|
||||
if ns, ok := node.(JSONSetable); ok {
|
||||
return ns.JSONSet(decodedToken, data)
|
||||
return node, ns.JSONSet(decodedToken, data)
|
||||
}
|
||||
|
||||
rValue := reflect.Indirect(reflect.ValueOf(node))
|
||||
@@ -394,12 +558,12 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
|
||||
case reflect.Struct:
|
||||
nm, ok := nameProvider.GetGoNameForType(rValue.Type(), decodedToken)
|
||||
if !ok {
|
||||
return fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
|
||||
return node, fmt.Errorf("object has no field %q: %w", decodedToken, ErrPointer)
|
||||
}
|
||||
|
||||
fld := rValue.FieldByName(nm)
|
||||
if !fld.CanSet() {
|
||||
return fmt.Errorf("can't set struct field %s to %v: %w", nm, data, ErrPointer)
|
||||
return node, fmt.Errorf("can't set struct field %s to %v: %w", nm, data, ErrPointer)
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(data)
|
||||
@@ -407,33 +571,51 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
|
||||
assignedType := fld.Type()
|
||||
|
||||
if !valueType.AssignableTo(assignedType) {
|
||||
return fmt.Errorf("can't set value with type %T to field %s with type %v: %w", data, nm, assignedType, ErrPointer)
|
||||
return node, fmt.Errorf("can't set value with type %T to field %s with type %v: %w", data, nm, assignedType, ErrPointer)
|
||||
}
|
||||
|
||||
fld.Set(value)
|
||||
|
||||
return nil
|
||||
return node, nil
|
||||
|
||||
case reflect.Map:
|
||||
kv := reflect.ValueOf(decodedToken)
|
||||
rValue.SetMapIndex(kv, reflect.ValueOf(data))
|
||||
|
||||
return nil
|
||||
return node, nil
|
||||
|
||||
case reflect.Slice:
|
||||
if decodedToken == dashToken {
|
||||
// RFC 6901 §4 / RFC 6902 append semantics: terminal "-" appends
|
||||
// the value to the slice. We rebind in place when the slice is
|
||||
// reachable via an addressable ancestor; otherwise we return the
|
||||
// new slice header for the parent (or the public Set) to rebind.
|
||||
value := reflect.ValueOf(data)
|
||||
elemType := rValue.Type().Elem()
|
||||
if !value.Type().AssignableTo(elemType) {
|
||||
return node, fmt.Errorf("can't append value of type %T to slice of %v: %w", data, elemType, ErrPointer)
|
||||
}
|
||||
newSlice := reflect.Append(rValue, value)
|
||||
if rValue.CanSet() {
|
||||
rValue.Set(newSlice)
|
||||
return node, nil
|
||||
}
|
||||
return newSlice.Interface(), nil
|
||||
}
|
||||
|
||||
tokenIndex, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return errors.Join(err, ErrPointer)
|
||||
return node, errors.Join(err, ErrPointer)
|
||||
}
|
||||
|
||||
sLength := rValue.Len()
|
||||
if tokenIndex < 0 || tokenIndex >= sLength {
|
||||
return errOutOfBounds(sLength, tokenIndex)
|
||||
return node, errOutOfBounds(sLength, tokenIndex)
|
||||
}
|
||||
|
||||
elem := rValue.Index(tokenIndex)
|
||||
if !elem.CanSet() {
|
||||
return fmt.Errorf("can't set slice index %s to %v: %w", decodedToken, data, ErrPointer)
|
||||
return node, fmt.Errorf("can't set slice index %s to %v: %w", decodedToken, data, ErrPointer)
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(data)
|
||||
@@ -441,15 +623,15 @@ func setSingleImpl(node, data any, decodedToken string, nameProvider *jsonname.N
|
||||
assignedType := elem.Type()
|
||||
|
||||
if !valueType.AssignableTo(assignedType) {
|
||||
return fmt.Errorf("can't set value with type %T to slice element %d with type %v: %w", data, tokenIndex, assignedType, ErrPointer)
|
||||
return node, fmt.Errorf("can't set value with type %T to slice element %d with type %v: %w", data, tokenIndex, assignedType, ErrPointer)
|
||||
}
|
||||
|
||||
elem.Set(value)
|
||||
|
||||
return nil
|
||||
return node, nil
|
||||
|
||||
default:
|
||||
return errInvalidReference(decodedToken)
|
||||
return node, errInvalidReference(decodedToken)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,24 +642,27 @@ func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch tk := tk.(type) {
|
||||
case json.Delim:
|
||||
switch tk {
|
||||
case '{':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case '[':
|
||||
key, ok := tk.(string)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid key token %#v: %w", tk, ErrPointer)
|
||||
}
|
||||
if key == decodedToken {
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// Consume the associated value. Scalars are fully read by a single
|
||||
// Token() call; composite values must be drained.
|
||||
tk, err = dec.Token()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if delim, isDelim := tk.(json.Delim); isDelim {
|
||||
switch delim {
|
||||
case '{', '[':
|
||||
if err = drainSingle(dec); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if tk == decodedToken {
|
||||
return offset, nil
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid token %#v: %w", tk, ErrPointer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +670,9 @@ func offsetSingleObject(dec *json.Decoder, decodedToken string) (int64, error) {
|
||||
}
|
||||
|
||||
func offsetSingleArray(dec *json.Decoder, decodedToken string) (int64, error) {
|
||||
if decodedToken == dashToken {
|
||||
return 0, errDashOnOffset()
|
||||
}
|
||||
idx, err := strconv.Atoi(decodedToken)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("token reference %q is not a number: %w: %w", decodedToken, err, ErrPointer)
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
codecov:
|
||||
notify:
|
||||
after_n_builds: 2
|
||||
|
||||
coverage:
|
||||
status:
|
||||
patch:
|
||||
default:
|
||||
target: 80%
|
||||
+2
@@ -3,3 +3,5 @@
|
||||
.idea
|
||||
.env
|
||||
.mcp.json
|
||||
go.work.sum
|
||||
.worktrees/
|
||||
|
||||
+20
-10
@@ -2,13 +2,9 @@ version: "2"
|
||||
linters:
|
||||
default: all
|
||||
disable:
|
||||
- cyclop
|
||||
- depguard
|
||||
- err113 # disabled temporarily: there are just too many issues to address
|
||||
- errchkjson
|
||||
- errorlint
|
||||
- exhaustruct
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
@@ -16,12 +12,12 @@ linters:
|
||||
- godot
|
||||
- godox
|
||||
- gomoddirectives # moved to mono-repo, multi-modules, so replace directives are needed
|
||||
- gomodguard
|
||||
- gomodguard_v2
|
||||
- gosmopolitan
|
||||
- inamedparam
|
||||
- ireturn
|
||||
- lll
|
||||
- ireturn # this repo adopted a pattern where there are quite many returned interfaces. To be challenged with v2
|
||||
- musttag
|
||||
- nestif
|
||||
- nilerr # nilerr crashes on this repo
|
||||
- nlreturn
|
||||
- noinlineerr
|
||||
@@ -31,7 +27,6 @@ linters:
|
||||
- testpackage
|
||||
- thelper
|
||||
- tparallel
|
||||
- unparam
|
||||
- varnamelen
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
@@ -43,8 +38,17 @@ linters:
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 3
|
||||
cyclop:
|
||||
max-complexity: 25
|
||||
gocyclo:
|
||||
min-complexity: 45
|
||||
min-complexity: 25
|
||||
gocognit:
|
||||
min-complexity: 35
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
default-case-required: true
|
||||
lll:
|
||||
line-length: 180
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
@@ -53,6 +57,7 @@ linters:
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- .worktrees
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
@@ -60,12 +65,17 @@ formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
settings:
|
||||
# local prefixes regroup imports from these packages
|
||||
goimports:
|
||||
local-prefixes:
|
||||
- github.com/go-openapi
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- .worktrees
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
# Maximum issues count per one linter.
|
||||
# Set to 0 to disable.
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# Contributors
|
||||
|
||||
- Repository: ['go-openapi/runtime']
|
||||
|
||||
| Total Contributors | Total Contributions |
|
||||
| --- | --- |
|
||||
| 71 | 557 |
|
||||
|
||||
| Username | All Time Contribution Count | All Commits |
|
||||
| --- | --- | --- |
|
||||
| @casualjim | 268 | <https://github.com/go-openapi/runtime/commits?author=casualjim> |
|
||||
| @fredbi | 132 | <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> |
|
||||
| @elakito | 6 | <https://github.com/go-openapi/runtime/commits?author=elakito> |
|
||||
| @ifraixedes | 5 | <https://github.com/go-openapi/runtime/commits?author=ifraixedes> |
|
||||
| @zeitlinger | 4 | <https://github.com/go-openapi/runtime/commits?author=zeitlinger> |
|
||||
| @Copilot | 3 | <https://github.com/go-openapi/runtime/commits?author=Copilot> |
|
||||
| @jkawamoto | 3 | <https://github.com/go-openapi/runtime/commits?author=jkawamoto> |
|
||||
| @stoyanr | 3 | <https://github.com/go-openapi/runtime/commits?author=stoyanr> |
|
||||
| @keramix | 2 | <https://github.com/go-openapi/runtime/commits?author=keramix> |
|
||||
| @Equanox | 2 | <https://github.com/go-openapi/runtime/commits?author=Equanox> |
|
||||
| @ederavilaprado | 2 | <https://github.com/go-openapi/runtime/commits?author=ederavilaprado> |
|
||||
| @nan0tube | 2 | <https://github.com/go-openapi/runtime/commits?author=nan0tube> |
|
||||
| @thomdixon | 2 | <https://github.com/go-openapi/runtime/commits?author=thomdixon> |
|
||||
| @deborggraever | 2 | <https://github.com/go-openapi/runtime/commits?author=deborggraever> |
|
||||
| @MakarandNsd | 2 | <https://github.com/go-openapi/runtime/commits?author=MakarandNsd> |
|
||||
| @Vadskye | 2 | <https://github.com/go-openapi/runtime/commits?author=Vadskye> |
|
||||
| @jsilland | 2 | <https://github.com/go-openapi/runtime/commits?author=jsilland> |
|
||||
| @Kunde21 | 2 | <https://github.com/go-openapi/runtime/commits?author=Kunde21> |
|
||||
| @bcomnes | 2 | <https://github.com/go-openapi/runtime/commits?author=bcomnes> |
|
||||
| @galaxie | 2 | <https://github.com/go-openapi/runtime/commits?author=galaxie> |
|
||||
| @anfernee | 2 | <https://github.com/go-openapi/runtime/commits?author=anfernee> |
|
||||
| @wahabmk | 1 | <https://github.com/go-openapi/runtime/commits?author=wahabmk> |
|
||||
| @vearutop | 1 | <https://github.com/go-openapi/runtime/commits?author=vearutop> |
|
||||
| @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> |
|
||||
| @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> |
|
||||
| @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> |
|
||||
| @gregmarr | 1 | <https://github.com/go-openapi/runtime/commits?author=gregmarr> |
|
||||
| @jwalter1-quest | 1 | <https://github.com/go-openapi/runtime/commits?author=jwalter1-quest> |
|
||||
| @s4s7 | 1 | <https://github.com/go-openapi/runtime/commits?author=s4s7> |
|
||||
| @stingshen | 1 | <https://github.com/go-openapi/runtime/commits?author=stingshen> |
|
||||
| @tamalsaha | 1 | <https://github.com/go-openapi/runtime/commits?author=tamalsaha> |
|
||||
| @tte | 1 | <https://github.com/go-openapi/runtime/commits?author=tte> |
|
||||
| @martian4202 | 1 | <https://github.com/go-openapi/runtime/commits?author=martian4202> |
|
||||
| @yan-zhuang | 1 | <https://github.com/go-openapi/runtime/commits?author=yan-zhuang> |
|
||||
| @aleksandr-vin | 1 | <https://github.com/go-openapi/runtime/commits?author=aleksandr-vin> |
|
||||
| @azylman | 1 | <https://github.com/go-openapi/runtime/commits?author=azylman> |
|
||||
| @anasmuhmd | 1 | <https://github.com/go-openapi/runtime/commits?author=anasmuhmd> |
|
||||
| @ArFe | 1 | <https://github.com/go-openapi/runtime/commits?author=ArFe> |
|
||||
| @CodeLingoBot | 1 | <https://github.com/go-openapi/runtime/commits?author=CodeLingoBot> |
|
||||
| @dlmiddlecote | 1 | <https://github.com/go-openapi/runtime/commits?author=dlmiddlecote> |
|
||||
| @danny-cheung | 1 | <https://github.com/go-openapi/runtime/commits?author=danny-cheung> |
|
||||
| @calavera | 1 | <https://github.com/go-openapi/runtime/commits?author=calavera> |
|
||||
| @EdwardBetts | 1 | <https://github.com/go-openapi/runtime/commits?author=EdwardBetts> |
|
||||
| @etsangsplk | 1 | <https://github.com/go-openapi/runtime/commits?author=etsangsplk> |
|
||||
| @ericzsplk | 1 | <https://github.com/go-openapi/runtime/commits?author=ericzsplk> |
|
||||
| @faguirre1 | 1 | <https://github.com/go-openapi/runtime/commits?author=faguirre1> |
|
||||
| @florindragos | 1 | <https://github.com/go-openapi/runtime/commits?author=florindragos> |
|
||||
| @gbjk | 1 | <https://github.com/go-openapi/runtime/commits?author=gbjk> |
|
||||
| @taisho6339 | 1 | <https://github.com/go-openapi/runtime/commits?author=taisho6339> |
|
||||
| @jbowes | 1 | <https://github.com/go-openapi/runtime/commits?author=jbowes> |
|
||||
| @JoakimSoderberg | 1 | <https://github.com/go-openapi/runtime/commits?author=JoakimSoderberg> |
|
||||
| @robbert229 | 1 | <https://github.com/go-openapi/runtime/commits?author=robbert229> |
|
||||
| @jonathaningram | 1 | <https://github.com/go-openapi/runtime/commits?author=jonathaningram> |
|
||||
| @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> |
|
||||
|
||||
_this file was generated by the [Contributors GitHub Action](https://github.com/github-community-projects/contributors)_
|
||||
+56
-20
@@ -8,8 +8,7 @@
|
||||
[![Release][release-badge]][release-url] [![Go Report Card][gocard-badge]][gocard-url] [![CodeFactor Grade][codefactor-badge]][codefactor-url] [![License][license-badge]][license-url]
|
||||
<!-- Badges: documentation & support -->
|
||||
<!-- Badges: others & stats -->
|
||||
[![GoDoc][godoc-badge]][godoc-url] [![Discord Channel][discord-badge]][discord-url] [![go version][goversion-badge]][goversion-url] ![Top language][top-badge] ![Commits since latest release][commits-badge]
|
||||
|
||||
[![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]
|
||||
---
|
||||
|
||||
A runtime for go OpenAPI toolkit.
|
||||
@@ -18,13 +17,44 @@ The runtime component for use in code generation or as untyped usage.
|
||||
|
||||
## Announcements
|
||||
|
||||
* **2025-12-19** : new community chat on discord
|
||||
* a new discord community channel is available to be notified of changes and support users
|
||||
* our venerable Slack channel remains open, and will be eventually discontinued on **2026-03-31**
|
||||
[**Complete documentation as github pages**][doc-url]
|
||||
|
||||
You may join the discord community by clicking the invite link on the discord badge (also above). [![Discord Channel][discord-badge]][discord-url]
|
||||
**Changes to the API surface in `v0.30.0`**:
|
||||
|
||||
Or join our Slack channel: [![Slack Channel][slack-logo]![slack-badge]][slack-url]
|
||||
* utility package `header` has now moved to `github.com/go-openapi/runtime/server-middleware/negotiate/header`
|
||||
|
||||
> A shim is provided to support existing programs, with a deprecation notice.
|
||||
|
||||
**Changes in semantics in `v0.30.0`**:
|
||||
|
||||
Function `negotiate.NegotiateContentType` (available as an alias for backward compatibility as `middleware.NegotiateContentType`
|
||||
now performs a full match considering MIME parameters.
|
||||
|
||||
The previous behavior (matching in order of appearance after stripping parameters) may be enabled explicitly with
|
||||
option `negotiate.WithIgnoreParameters`.
|
||||
|
||||
* **2026-05-07** : exposed UI and Spec middleware as a separate, dependency-free module.
|
||||
|
||||
> Newly available package: `github.com/go-openapi/runtime/server-middleware/docui` that now holds our
|
||||
> UI and spec serve middleware.
|
||||
>
|
||||
> A shim is available in `github.com/go-openapi/runtime/middleware` to bridge the older UI options to the new ones,
|
||||
> with a deprecation notice.
|
||||
>
|
||||
> Methods that were unduly exported and purely used to manipulate options (e.g. `SwaggerUIOpts.EnsureDefaults`) have been
|
||||
> removed. New options in `docui` should be used instead.
|
||||
|
||||
> Users may reuse this middleware to serve a Redoc, Rapidoc or SwaggerUI documentation without
|
||||
> importing the complete go-openapi scaffolding.
|
||||
|
||||
* **2026-05-05** : exposed content negotiation methods as a separate, dependency-free module
|
||||
|
||||
> Users may reuse these utilities to support content-negotiation without extra dependencies.
|
||||
>
|
||||
> Newly available module: `github.com/go-openapi/runtime/server-middleware`
|
||||
>
|
||||
> Newly available packages: `github.com/go-openapi/runtime/server-middleware/negotiate` and
|
||||
> `github.com/go-openapi/runtime/server-middleware/mediatype`.
|
||||
|
||||
## Status
|
||||
|
||||
@@ -40,18 +70,21 @@ go get github.com/go-openapi/runtime
|
||||
|
||||
See <https://github.com/go-openapi/runtime/releases>
|
||||
|
||||
For pre-v0.30.0 releases see [release notes](docs/NOTES.md).
|
||||
For v0.29.0 release see [release notes](docs/NOTES.md).
|
||||
From that release onwards, changes are tracked in the github release notes.
|
||||
|
||||
**What coming next?**
|
||||
|
||||
Moving forward, we want to :
|
||||
|
||||
* [ ] continue narrowing down the scope of dependencies:
|
||||
* yaml support in an independent module
|
||||
* [x] fix a few known issues with some file upload requests (e.g. #286)
|
||||
* [] continue narrowing down the scope of dependencies:
|
||||
* [x] split middleware and other useful utilities as a separate dependency-free module
|
||||
* yaml support in an independent module (v2)
|
||||
* introduce more up-to-date support for opentelemetry as a separate module that evolves
|
||||
independently from the main package (to avoid breaking changes, the existing API
|
||||
will remain maintained, but evolve at a slower pace than opentelemetry).
|
||||
* [ ] fix a few known issues with some file upload requests (e.g. #286)
|
||||
will remain maintained, but evolve at a slower pace than opentelemetry). (v2)
|
||||
* [] publish proper documentation and examples
|
||||
|
||||
## Licensing
|
||||
|
||||
@@ -62,11 +95,11 @@ on top of which it has been built.
|
||||
|
||||
## Other documentation
|
||||
|
||||
* [FAQ](docs/FAQ.md)
|
||||
* [FAQ](https://go-openapi.github.io/runtime/tutorials/faq/) · [Media-type selection](https://go-openapi.github.io/runtime/tutorials/media-types/) · [Client keep-alive](https://go-openapi.github.io/runtime/tutorials/keep-alive/)
|
||||
* [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,13 +128,12 @@ Maintainers can cut a new release by either:
|
||||
[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 -->
|
||||
[doc-badge]: https://img.shields.io/badge/doc-site-blue?link=https%3A%2F%2Fgo-openapi.github.io%2Fruntime%2F
|
||||
[doc-url]: https://go-openapi.github.io/runtime
|
||||
[godoc-badge]: https://pkg.go.dev/badge/github.com/go-openapi/runtime
|
||||
[godoc-url]: http://pkg.go.dev/github.com/go-openapi/runtime
|
||||
[slack-logo]: https://a.slack-edge.com/e6a93c1/img/icons/favicon-32.png
|
||||
[slack-badge]: https://img.shields.io/badge/slack-blue?link=https%3A%2F%2Fgoswagger.slack.com%2Farchives%2FC04R30YM
|
||||
[slack-url]: https://goswagger.slack.com/archives/C04R30YMU
|
||||
[discord-badge]: https://img.shields.io/discord/1446918742398341256?logo=discord&label=discord&color=blue
|
||||
[discord-url]: https://discord.gg/twZ9BwT3
|
||||
[discord-url]: https://discord.gg/FfnFYaC3k5
|
||||
|
||||
<!-- Badges: license & compliance -->
|
||||
[license-badge]: http://img.shields.io/badge/license-Apache%20v2-orange.svg
|
||||
@@ -111,3 +143,7 @@ Maintainers can cut a new release by either:
|
||||
[goversion-url]: https://github.com/go-openapi/runtime/blob/master/go.mod
|
||||
[top-badge]: https://img.shields.io/github/languages/top/go-openapi/runtime
|
||||
[commits-badge]: https://img.shields.io/github/commits-since/go-openapi/runtime/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
|
||||
|
||||
+8
-8
@@ -97,7 +97,7 @@ func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
|
||||
}
|
||||
default:
|
||||
// check for the underlying type to be pointer to []byte or string,
|
||||
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Ptr {
|
||||
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Pointer {
|
||||
return errors.New("destination must be a pointer")
|
||||
}
|
||||
|
||||
@@ -126,13 +126,13 @@ func ByteStreamConsumer(opts ...byteStreamOpt) Consumer {
|
||||
//
|
||||
// Supported input underlying types and interfaces, prioritized in this order:
|
||||
//
|
||||
// - [io.WriterTo] (for maximum control)
|
||||
// - [io.Reader] (performs [io.Copy]). A ReadCloser is closed before exiting.
|
||||
// - [encoding.BinaryMarshaler]
|
||||
// - error (writes as a string)
|
||||
// - []byte
|
||||
// - string
|
||||
// - struct, other slices: writes as JSON.
|
||||
// - [io.WriterTo] (for maximum control)
|
||||
// - [io.Reader] (performs [io.Copy]). A ReadCloser is closed before exiting.
|
||||
// - [encoding.BinaryMarshaler]
|
||||
// - error (writes as a string)
|
||||
// - []byte
|
||||
// - string
|
||||
// - struct, other slices: writes as JSON.
|
||||
func ByteStreamProducer(opts ...byteStreamOpt) Producer {
|
||||
var vals byteStreamOpts
|
||||
for _, opt := range opts {
|
||||
|
||||
+21
-3
@@ -19,12 +19,30 @@ type ClientOperation struct {
|
||||
AuthInfo ClientAuthInfoWriter
|
||||
Params ClientRequestWriter
|
||||
Reader ClientResponseReader
|
||||
Context context.Context //nolint:containedctx // we precisely want this type to contain the request context
|
||||
Client *http.Client
|
||||
// Deprecated: prefer [ContextualTransport.SubmitContext] to pass the request context explicitly.
|
||||
Context context.Context //nolint:containedctx // we precisely want this type to contain the request context
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
// A ClientTransport implementor knows how to submit Request objects to some destination.
|
||||
type ClientTransport interface {
|
||||
// Submit(string, RequestWriter, ResponseReader, AuthInfoWriter) (interface{}, error)
|
||||
// Submit the operation and return the deserialized response or an error.
|
||||
Submit(*ClientOperation) (any, error)
|
||||
}
|
||||
|
||||
// ContextualTransport extends [ClientTransport] with an explicit
|
||||
// context-aware submission method.
|
||||
//
|
||||
// Wrappers such as the OpenTelemetry transport type-assert to this
|
||||
// interface so they can forward an explicit context to the underlying
|
||||
// transport without setting the cached [ClientOperation.Context] field.
|
||||
//
|
||||
// In v2, SubmitContext will be folded into [ClientTransport] itself
|
||||
// and the cached [ClientOperation.Context] field removed; this interface
|
||||
// is the v0.x bridge.
|
||||
type ContextualTransport interface {
|
||||
ClientTransport
|
||||
|
||||
// SubmitContext submits the operation using ctx as the request context.
|
||||
SubmitContext(ctx context.Context, operation *ClientOperation) (any, error)
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ func (o *APIError) Error() string {
|
||||
if err, ok := o.Response.(error); ok {
|
||||
resp = []byte("'" + sanitizer.Replace(err.Error()) + "'")
|
||||
} else {
|
||||
resp, _ = json.Marshal(o.Response)
|
||||
resp, _ = json.Marshal(o.Response) //nolint:errchkjson // error swallowed as this is our last best effort attempt
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s (status %d): %s", o.OperationName, o.Code, resp)
|
||||
|
||||
+6
-2
@@ -21,8 +21,12 @@ const (
|
||||
DefaultMime = "application/octet-stream"
|
||||
// JSONMime the json mime type.
|
||||
JSONMime = "application/json"
|
||||
// YAMLMime the [yaml] mime type.
|
||||
YAMLMime = "application/x-yaml"
|
||||
// YAMLMime the [yaml] mime type. Set to the canonical RFC 9512
|
||||
// name (application/yaml). Legacy forms application/x-yaml,
|
||||
// text/yaml, and text/x-yaml — per RFC 9512 §2.1 "Deprecated
|
||||
// alias names for this type" — resolve to the same codec via
|
||||
// the mediatype alias bridge.
|
||||
YAMLMime = "application/yaml"
|
||||
// XMLMime the [xml] mime type.
|
||||
XMLMime = "application/xml"
|
||||
// TextMime the text mime type.
|
||||
|
||||
+9
-9
@@ -100,7 +100,7 @@ func CSVConsumer(opts ...CSVOpt) Consumer {
|
||||
|
||||
default:
|
||||
// support *[][]string, *[]byte, *string
|
||||
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Ptr {
|
||||
if ptr := reflect.TypeOf(data); ptr.Kind() != reflect.Pointer {
|
||||
return errors.New("destination must be a pointer")
|
||||
}
|
||||
|
||||
@@ -159,14 +159,14 @@ func CSVConsumer(opts ...CSVOpt) Consumer {
|
||||
//
|
||||
// Supported input underlying types and interfaces, prioritized in this order:
|
||||
//
|
||||
// - *[csv.Reader]
|
||||
// - [CSVReader] (reader options are ignored)
|
||||
// - [io.Reader]
|
||||
// - [io.WriterTo]
|
||||
// - [encoding.BinaryMarshaler]
|
||||
// - [][]string
|
||||
// - []byte
|
||||
// - string
|
||||
// - *[csv.Reader]
|
||||
// - [CSVReader] (reader options are ignored)
|
||||
// - [io.Reader]
|
||||
// - [io.WriterTo]
|
||||
// - [encoding.BinaryMarshaler]
|
||||
// - [][]string
|
||||
// - []byte
|
||||
// - string
|
||||
//
|
||||
// The producer prioritizes situations where buffering the input is not required.
|
||||
func CSVProducer(opts ...CSVOpt) Producer {
|
||||
|
||||
+6
@@ -5,4 +5,10 @@ package runtime
|
||||
|
||||
import "github.com/go-openapi/swag/fileutils"
|
||||
|
||||
// File represents an uploaded file. Re-exported from
|
||||
// [fileutils.File] for backwards compatibility.
|
||||
//
|
||||
// See [BindForm] (in form.go) for the orchestrator that parses
|
||||
// multipart / urlencoded request bodies and binds declared file
|
||||
// fields onto handler-side targets.
|
||||
type File = fileutils.File
|
||||
|
||||
+355
@@ -0,0 +1,355 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
)
|
||||
|
||||
// DefaultMaxUploadFilenameLength is the default cap applied to
|
||||
// FileHeader.Filename for each declared file when [BindForm] is invoked
|
||||
// without an explicit [BindFormMaxFilenameLen] option.
|
||||
//
|
||||
// Multipart headers are allocated per part; an attacker submitting
|
||||
// multi-MB filenames inflates the parser's memory footprint. 1 KiB
|
||||
// matches the IETF guidance for sane filename length and is enough
|
||||
// for realistic uploads.
|
||||
const DefaultMaxUploadFilenameLength = 1024
|
||||
|
||||
// DefaultMaxUploadBodySize limits the size of the body to upload forms to 32MB.
|
||||
//
|
||||
// Use an explicit [BindFormMaxBody] option to change this limit.
|
||||
const DefaultMaxUploadBodySize = int64(32) << 20
|
||||
|
||||
// filenamePreviewLen caps the byte length of the FileHeader.Filename
|
||||
// preview embedded as the ParseError.Value field when the helper
|
||||
// rejects a too-long filename.
|
||||
const filenamePreviewLen = 32
|
||||
|
||||
// ValidateFilenameLength enforces the FileHeader.Filename length cap
|
||||
// that [BindForm] applies via [BindFormFile] declarations. Untyped
|
||||
// binder paths that fetch the file via [http.Request.FormFile]
|
||||
// directly (rather than declaring the file through [BindFormFile]) call
|
||||
// this to opt into the same protection.
|
||||
//
|
||||
// Returns nil if filename length is within maxLen or maxLen <= 0.
|
||||
// Otherwise returns a [*errors.ParseError] suitable for direct return
|
||||
// from a parameter binder. The error embeds a truncated preview of
|
||||
// the offending filename to keep the error message bounded.
|
||||
func ValidateFilenameLength(paramName, paramIn, filename string, maxLen int) error {
|
||||
if maxLen <= 0 || len(filename) <= maxLen {
|
||||
return nil
|
||||
}
|
||||
preview := filename[:min(len(filename), filenamePreviewLen)]
|
||||
return errors.NewParseError(paramName, paramIn, preview,
|
||||
fmt.Errorf("filename length %d exceeds limit %d", len(filename), maxLen))
|
||||
}
|
||||
|
||||
// FileBinder is the per-file callback invoked by [BindForm] when a
|
||||
// declared file field is present.
|
||||
//
|
||||
// The callback is responsible for BOTH validating the file (size, MIME, etc.) AND assigning the bound
|
||||
// file to its destination — typically using:
|
||||
//
|
||||
// o.FieldName = &runtime.File{Data: file, Header: header}
|
||||
//
|
||||
// Returning a non-nil error surfaces the error in [BindForm]'s per-field
|
||||
// accumulator. Errors from the binder flow through verbatim — the
|
||||
// binder is expected to produce HTTP-aware errors (e.g.
|
||||
// [errors.ExceedsMaximum] from go-openapi/validate).
|
||||
type FileBinder func(file multipart.File, header *multipart.FileHeader) error
|
||||
|
||||
// BindOption configures [BindForm]. The variadic style keeps simple
|
||||
// call sites simple and lets new knobs (security caps, additional
|
||||
// behaviour) be added without breaking the signature.
|
||||
type BindOption func(*bindConfig)
|
||||
|
||||
type bindConfig struct {
|
||||
maxParseMemory int64
|
||||
maxBody int64
|
||||
maxFiles int
|
||||
maxFilenameLen int
|
||||
files []formFileSpec
|
||||
}
|
||||
|
||||
type formFileSpec struct {
|
||||
name string
|
||||
required bool
|
||||
bind FileBinder
|
||||
}
|
||||
|
||||
// BindFormMaxParseMemory caps the in-memory portion of a multipart
|
||||
// body. Bytes beyond this are spilled to temporary files on disk by
|
||||
// the stdlib parser. 0 (the default) defers to the stdlib's 32 MB.
|
||||
//
|
||||
// This option does NOT cap total body bytes — see [BindFormMaxBody]
|
||||
// for that. The default body cap ([DefaultMaxUploadBodySize] = 32 MB)
|
||||
// is applied even when this option is not supplied, so out of the box
|
||||
// [BindForm] is bounded; callers with stricter or looser requirements
|
||||
// adjust via [BindFormMaxBody].
|
||||
func BindFormMaxParseMemory(n int64) BindOption {
|
||||
return func(c *bindConfig) { c.maxParseMemory = n }
|
||||
}
|
||||
|
||||
// BindFormMaxBody caps the size of the body read from a http form before parsing.
|
||||
//
|
||||
// The limit is set to 32MB by default. This default limit is applied for any n=0.
|
||||
//
|
||||
// The limit is disabled for n<0, assuming the caller has already capped the body size upstream.
|
||||
func BindFormMaxBody(n int64) BindOption {
|
||||
return func(c *bindConfig) { c.maxBody = n }
|
||||
}
|
||||
|
||||
// BindFormMaxFiles rejects parses where the total number of file
|
||||
// parts across all field names exceeds n. 0 (the default) means no
|
||||
// cap. Exceeding the cap is a fatal error — [BindForm] returns
|
||||
// fatal=true and no per-file binders run.
|
||||
func BindFormMaxFiles(n int) BindOption {
|
||||
return func(c *bindConfig) { c.maxFiles = n }
|
||||
}
|
||||
|
||||
// BindFormMaxFilenameLen rejects per-file headers whose Filename
|
||||
// length exceeds n. 0 means no cap; the default applied when this
|
||||
// option is not supplied is [DefaultMaxUploadFilenameLength]. The
|
||||
// cap is a per-field bind error (non-fatal); other declared files
|
||||
// still run.
|
||||
func BindFormMaxFilenameLen(n int) BindOption {
|
||||
return func(c *bindConfig) { c.maxFilenameLen = n }
|
||||
}
|
||||
|
||||
// BindFormFile declares a file field to bind under the given form
|
||||
// name. If required is true and the field is absent, [BindForm]
|
||||
// produces the per-field error.
|
||||
//
|
||||
// errors.NewParseError(name, "formData", "", http.ErrMissingFile)
|
||||
//
|
||||
// If required is false, absence is silent (no error, no bind).
|
||||
//
|
||||
// The bind callback runs only when the field is present. It is the
|
||||
// site where both validation and assignment happen — see [FileBinder].
|
||||
//
|
||||
// FileHeader.Filename is attacker-controlled text; the binder MUST
|
||||
// NOT use it directly as a filesystem path. The helper does not
|
||||
// touch the filesystem.
|
||||
func BindFormFile(name string, required bool, bind FileBinder) BindOption {
|
||||
return func(c *bindConfig) {
|
||||
c.files = append(c.files, formFileSpec{
|
||||
name: name,
|
||||
required: required,
|
||||
bind: bind,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BindForm parses r as multipart/form-data, falling back to
|
||||
// application/x-www-form-urlencoded when the request is not
|
||||
// multipart. On success, r.MultipartForm and r.PostForm are populated;
|
||||
// the caller can read non-file form values via [Values](r.Form) after
|
||||
// the call returns.
|
||||
//
|
||||
// All errors produced by BindForm itself (parse failure, missing
|
||||
// required field, cap exceeded) are [*errors.ParseError] values built
|
||||
// via [errors.NewParseError], matching the untyped
|
||||
// middleware/parameter.go path. Errors returned by per-file binders
|
||||
// flow through verbatim — binders own their HTTP-aware error shape.
|
||||
//
|
||||
// Per-file binders declared via [BindFormFile] run in declaration
|
||||
// order after a successful parse. Their errors are accumulated and
|
||||
// returned wrapped in [errors.CompositeValidationError]; the caller
|
||||
// typically appends the returned err to its own []error and continues
|
||||
// with non-file parameter binding.
|
||||
//
|
||||
// Return semantics:
|
||||
//
|
||||
// - fatal=true, err!=nil: parse failure or a hard cap (e.g.
|
||||
// [BindFormMaxFiles]) was exceeded. No per-file binders ran; the
|
||||
// caller MUST return err immediately.
|
||||
// - fatal=false, err!=nil: one or more per-file binders produced
|
||||
// errors. The form parsed successfully; r.Form is populated. The
|
||||
// caller appends err to its accumulator and continues.
|
||||
// - fatal=false, err==nil: full success.
|
||||
//
|
||||
// fatal==true implies err!=nil.
|
||||
//
|
||||
// Defaults applied out of the box:
|
||||
//
|
||||
// - Total body bytes capped at [DefaultMaxUploadBodySize] (32 MB)
|
||||
// via [http.MaxBytesReader]. Adjust with [BindFormMaxBody]
|
||||
// (negative n disables, when the caller has already capped the
|
||||
// body upstream).
|
||||
// - FileHeader.Filename length capped at
|
||||
// [DefaultMaxUploadFilenameLength]. Adjust with
|
||||
// [BindFormMaxFilenameLen].
|
||||
//
|
||||
// Caller responsibilities the helper does NOT cover:
|
||||
//
|
||||
// - Set [http.Server.ReadTimeout] / [http.Server.IdleTimeout] to defend
|
||||
// against slow-read attacks.
|
||||
// - Decompress Content-Encoding: gzip request bodies upstream if
|
||||
// the API accepts them, using a size-limited reader.
|
||||
// - Treat FileHeader.Filename as untrusted user input; never use
|
||||
// it directly as a filesystem path.
|
||||
func BindForm(r *http.Request, opts ...BindOption) (fatal bool, err error) {
|
||||
cfg := bindConfig{
|
||||
maxFilenameLen: DefaultMaxUploadFilenameLength,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&cfg)
|
||||
}
|
||||
|
||||
if perr := parseFormBody(r, cfg.maxParseMemory, cfg.maxBody); perr != nil {
|
||||
// Body-cap hit gets the 413 status; everything else maps to a
|
||||
// 400 ParseError. parseFormBody returns the raw stdlib error
|
||||
// in both cases — the HTTP-aware wrapping happens here.
|
||||
var maxBytesErr *http.MaxBytesError
|
||||
if stderrors.As(perr, &maxBytesErr) {
|
||||
return true, errors.New(http.StatusRequestEntityTooLarge, "formData: %v", perr)
|
||||
}
|
||||
return true, errors.NewParseError("body", "formData", "", perr)
|
||||
}
|
||||
|
||||
if cfg.maxFiles > 0 {
|
||||
if got := countFileParts(r); got > cfg.maxFiles {
|
||||
return true, errors.NewParseError("body", "formData", "",
|
||||
fmt.Errorf("multipart form contains %d file parts, exceeds limit %d", got, cfg.maxFiles))
|
||||
}
|
||||
}
|
||||
|
||||
var bindErrs []error
|
||||
for _, spec := range cfg.files {
|
||||
if e := bindFormFile(r, spec, cfg.maxFilenameLen); e != nil {
|
||||
bindErrs = append(bindErrs, e)
|
||||
}
|
||||
}
|
||||
if len(bindErrs) > 0 {
|
||||
return false, errors.CompositeValidationError(bindErrs...)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// parseFormBody parses the request body. Content-Type drives the
|
||||
// parser: multipart/form-data → r.ParseMultipartForm, everything else
|
||||
// → r.ParseForm (stdlib's parsePostForm only actually reads the body
|
||||
// when Content-Type is application/x-www-form-urlencoded, so calling
|
||||
// ParseForm is safe for unrecognised types).
|
||||
//
|
||||
// Caveat: ParseMultipartForm calls ParseForm internally and discards its error
|
||||
// when the body turns out not to be multipart, returning ErrNotMultipart instead
|
||||
// — the subsequent retry then short-circuits because r.PostForm is already
|
||||
// set. Content-type-based routing avoids the lossy detour.
|
||||
//
|
||||
// Returns the raw stdlib error on failure; the caller (BindForm)
|
||||
// handles HTTP-aware wrapping (413 for MaxBytesError, 400 ParseError
|
||||
// otherwise).
|
||||
//
|
||||
// maxMemory == 0 falls through to the stdlib default (32 MB).
|
||||
// maxBody == 0 defaults to DefaultMaxUploadBodySize; maxBody < 0
|
||||
// disables the body cap (caller has capped upstream).
|
||||
func parseFormBody(r *http.Request, maxMemory, maxBody int64) error {
|
||||
if r.Body != nil && maxBody >= 0 {
|
||||
if maxBody == 0 {
|
||||
maxBody = DefaultMaxUploadBodySize
|
||||
}
|
||||
r.Body = http.MaxBytesReader(nil, r.Body, maxBody)
|
||||
}
|
||||
|
||||
mt, _, _ := ContentType(r.Header)
|
||||
if mt == MultipartFormMime {
|
||||
//nolint:gosec // G120: false positive -- see below
|
||||
// gosec doesn't track the Body.
|
||||
// See https://github.com/securego/gosec/blob/de65614d10a6b84029e3e1215567b8ce7e490f23/testutils/g120_samples.go#L57
|
||||
return r.ParseMultipartForm(maxMemory)
|
||||
}
|
||||
return r.ParseForm()
|
||||
}
|
||||
|
||||
func countFileParts(r *http.Request) int {
|
||||
if r.MultipartForm == nil {
|
||||
return 0
|
||||
}
|
||||
var n int
|
||||
for _, fhs := range r.MultipartForm.File {
|
||||
n += len(fhs)
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// FormFile resolves a file field from a parsed form body, transparently
|
||||
// handling both content types accepted for `type: file` parameters by
|
||||
// the OpenAPI 2.0 spec:
|
||||
//
|
||||
// - multipart/form-data — delegates to [http.Request.FormFile].
|
||||
// - application/x-www-form-urlencoded — looks up the field in
|
||||
// r.PostForm and synthesizes a [multipart.File] backed by the
|
||||
// value bytes plus a [multipart.FileHeader] with Filename equal
|
||||
// to the field name and Size set to the byte length.
|
||||
//
|
||||
// Returns [http.ErrMissingFile] when the field is absent under either
|
||||
// content type. Callers must have parsed the body upstream (e.g. via
|
||||
// [BindForm] or [http.Request.ParseForm]) before reading from the
|
||||
// urlencoded path — [http.Request.FormFile] takes care of parsing on
|
||||
// the multipart path.
|
||||
//
|
||||
// Presence is the only criterion for binding a urlencoded file: an
|
||||
// empty value (e.g. `file=`) is bound as a zero-byte file.
|
||||
func FormFile(r *http.Request, name string) (multipart.File, *multipart.FileHeader, error) {
|
||||
file, header, err := r.FormFile(name)
|
||||
if err == nil {
|
||||
return file, header, nil
|
||||
}
|
||||
if !stderrors.Is(err, http.ErrNotMultipart) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
values, present := r.PostForm[name]
|
||||
if !present {
|
||||
return nil, nil, http.ErrMissingFile
|
||||
}
|
||||
value := values[0]
|
||||
return urlencodedFile{Reader: strings.NewReader(value)},
|
||||
&multipart.FileHeader{Filename: name, Size: int64(len(value))},
|
||||
nil
|
||||
}
|
||||
|
||||
// urlencodedFile adapts a urlencoded form value (already buffered in
|
||||
// memory by [http.Request.ParseForm]) to the [multipart.File]
|
||||
// interface. The embedded [strings.Reader] supplies Read/ReadAt/Seek;
|
||||
// Close is a no-op since there is no resource to release.
|
||||
type urlencodedFile struct {
|
||||
*strings.Reader
|
||||
}
|
||||
|
||||
func (urlencodedFile) Close() error { return nil }
|
||||
|
||||
func bindFormFile(r *http.Request, spec formFileSpec, maxFilenameLen int) error {
|
||||
file, header, err := FormFile(r, spec.name)
|
||||
if err != nil {
|
||||
if stderrors.Is(err, http.ErrMissingFile) {
|
||||
if spec.required {
|
||||
return errors.New(http.StatusBadRequest, "formData: %v", http.ErrMissingFile)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.NewParseError(spec.name, "formData", "", err)
|
||||
}
|
||||
|
||||
if err := ValidateFilenameLength(spec.name, "formData", header.Filename, maxFilenameLen); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if spec.bind == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return spec.bind(file, header)
|
||||
}
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
use (
|
||||
.
|
||||
./client-middleware/opentracing
|
||||
./docs/examples
|
||||
./server-middleware
|
||||
)
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
|
||||
github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU=
|
||||
github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8=
|
||||
github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A=
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8=
|
||||
github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c=
|
||||
github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90=
|
||||
github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q=
|
||||
github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0=
|
||||
github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk=
|
||||
github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc=
|
||||
github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM=
|
||||
github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w=
|
||||
github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI=
|
||||
github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw=
|
||||
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
+22
@@ -99,3 +99,25 @@ type Validatable interface {
|
||||
type ContextValidatable interface {
|
||||
ContextValidate(context.Context, strfmt.Registry) error
|
||||
}
|
||||
|
||||
// ContentTyper is implemented by values that declare their own MIME
|
||||
// content type. The client runtime consults it in two places:
|
||||
//
|
||||
// - on a body payload set via [SetBodyParam]: when the payload is a
|
||||
// stream (io.Reader, io.ReadCloser) and ContentType returns a
|
||||
// non-empty value, that value becomes the wire Content-Type
|
||||
// header instead of the operation's picked consumes entry.
|
||||
//
|
||||
// - on individual file values inside a multipart upload: their per-
|
||||
// part Content-Type header is taken from ContentType() rather
|
||||
// than sniffed via http.DetectContentType.
|
||||
//
|
||||
// An empty string return is treated as "no opinion" and the runtime
|
||||
// falls back to its default selection. Values that have no content
|
||||
// type to declare may simply not implement the interface.
|
||||
//
|
||||
// See docs/MEDIA_TYPES.md for the full client-side selection
|
||||
// algorithm.
|
||||
type ContentTyper interface {
|
||||
ContentType() string
|
||||
}
|
||||
|
||||
+418
-316
@@ -5,10 +5,9 @@ package middleware
|
||||
|
||||
import (
|
||||
stdContext "context"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -17,19 +16,21 @@ import (
|
||||
"github.com/go-openapi/loads"
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag/typeutils"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/logger"
|
||||
"github.com/go-openapi/runtime/middleware/untyped"
|
||||
"github.com/go-openapi/runtime/security"
|
||||
"github.com/go-openapi/runtime/server-middleware/docui"
|
||||
"github.com/go-openapi/runtime/server-middleware/mediatype"
|
||||
"github.com/go-openapi/runtime/server-middleware/negotiate"
|
||||
)
|
||||
|
||||
// Debug when true turns on verbose logging.
|
||||
var Debug = logger.DebugEnabled()
|
||||
|
||||
// Logger is the standard library logger used for printing debug messages.
|
||||
//
|
||||
// (Note: The correct spelling is "library", not "libra". "Libra" is a zodiac sign/constellation and wouldn't make sense in this context.)
|
||||
var Logger logger.Logger = logger.StandardLogger{}
|
||||
|
||||
func debugLogfFunc(lg logger.Logger) func(string, ...any) {
|
||||
@@ -75,121 +76,13 @@ func (fn ResponderFunc) WriteResponse(rw http.ResponseWriter, pr runtime.Produce
|
||||
// used throughout to store request context with the standard context attached
|
||||
// to the [http.Request].
|
||||
type Context struct {
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
router Router
|
||||
debugLogf func(string, ...any) // a logging function to debug context and all components using it
|
||||
}
|
||||
|
||||
type routableUntypedAPI struct {
|
||||
api *untyped.API
|
||||
hlock *sync.Mutex
|
||||
handlers map[string]map[string]http.Handler
|
||||
defaultConsumes string
|
||||
defaultProduces string
|
||||
}
|
||||
|
||||
func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI {
|
||||
var handlers map[string]map[string]http.Handler
|
||||
if spec == nil || api == nil {
|
||||
return nil
|
||||
}
|
||||
analyzer := analysis.New(spec.Spec())
|
||||
for method, hls := range analyzer.Operations() {
|
||||
um := strings.ToUpper(method)
|
||||
for path, op := range hls {
|
||||
schemes := analyzer.SecurityRequirementsFor(op)
|
||||
|
||||
if oh, ok := api.OperationHandlerFor(method, path); ok {
|
||||
if handlers == nil {
|
||||
handlers = make(map[string]map[string]http.Handler)
|
||||
}
|
||||
if b, ok := handlers[um]; !ok || b == nil {
|
||||
handlers[um] = make(map[string]http.Handler)
|
||||
}
|
||||
|
||||
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// lookup route info in the context
|
||||
route, rCtx, _ := context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
r = rCtx
|
||||
}
|
||||
|
||||
// bind and validate the request using reflection
|
||||
var bound any
|
||||
var validation error
|
||||
bound, r, validation = context.BindAndValidate(r, route)
|
||||
if validation != nil {
|
||||
context.Respond(w, r, route.Produces, route, validation)
|
||||
return
|
||||
}
|
||||
|
||||
// actually handle the request
|
||||
result, err := oh.Handle(bound)
|
||||
if err != nil {
|
||||
// respond with failure
|
||||
context.Respond(w, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
|
||||
// respond with success
|
||||
context.Respond(w, r, route.Produces, route, result)
|
||||
})
|
||||
|
||||
if len(schemes) > 0 {
|
||||
handler = newSecureAPI(context, handler)
|
||||
}
|
||||
handlers[um][path] = handler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &routableUntypedAPI{
|
||||
api: api,
|
||||
hlock: new(sync.Mutex),
|
||||
handlers: handlers,
|
||||
defaultProduces: api.DefaultProduces,
|
||||
defaultConsumes: api.DefaultConsumes,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
|
||||
r.hlock.Lock()
|
||||
paths, ok := r.handlers[strings.ToUpper(method)]
|
||||
if !ok {
|
||||
r.hlock.Unlock()
|
||||
return nil, false
|
||||
}
|
||||
handler, ok := paths[path]
|
||||
r.hlock.Unlock()
|
||||
return handler, ok
|
||||
}
|
||||
func (r *routableUntypedAPI) ServeErrorFor(_ string) func(http.ResponseWriter, *http.Request, error) {
|
||||
return r.api.ServeError
|
||||
}
|
||||
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
|
||||
return r.api.ConsumersFor(mediaTypes)
|
||||
}
|
||||
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
|
||||
return r.api.ProducersFor(mediaTypes)
|
||||
}
|
||||
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
|
||||
return r.api.AuthenticatorsFor(schemes)
|
||||
}
|
||||
func (r *routableUntypedAPI) Authorizer() runtime.Authorizer {
|
||||
return r.api.Authorizer()
|
||||
}
|
||||
func (r *routableUntypedAPI) Formats() strfmt.Registry {
|
||||
return r.api.Formats()
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) DefaultProduces() string {
|
||||
return r.defaultProduces
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) DefaultConsumes() string {
|
||||
return r.defaultConsumes
|
||||
spec *loads.Document
|
||||
analyzer *analysis.Spec
|
||||
api RoutableAPI
|
||||
router Router
|
||||
debugLogf func(string, ...any) // a logging function to debug context and all components using it
|
||||
ignoreParameters bool // see SetIgnoreParameters / WithIgnoreParameters
|
||||
matchSuffix bool // see SetMatchSuffix / WithMatchSuffix
|
||||
}
|
||||
|
||||
// NewRoutableContext creates a new context for a routable API.
|
||||
@@ -246,6 +139,156 @@ func Serve(spec *loads.Document, api *untyped.API) http.Handler {
|
||||
return ServeWithBuilder(spec, api, PassthroughBuilder)
|
||||
}
|
||||
|
||||
// SetIgnoreParameters toggles the legacy parameter-stripping behaviour for
|
||||
// Accept negotiation server-wide. When set, every internal call to
|
||||
// [NegotiateContentType] from this Context applies [WithIgnoreParameters].
|
||||
//
|
||||
// Returns the receiver for fluent configuration:
|
||||
//
|
||||
// ctx := middleware.NewContext(spec, api, nil).SetIgnoreParameters(true)
|
||||
//
|
||||
// See [WithIgnoreParameters] for the rationale and an example.
|
||||
func (c *Context) SetIgnoreParameters(ignore bool) *Context {
|
||||
c.ignoreParameters = ignore
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// SetMatchSuffix toggles RFC 6839 structured-syntax suffix tolerance
|
||||
// server-wide. When enabled, both Accept negotiation and codec lookup
|
||||
// fall back through the suffix base for the recognised suffixes
|
||||
// (+json, +xml, +yaml) — so an operation declaring
|
||||
// consumes: [application/json] also accepts request bodies sent with
|
||||
// Content-Type: application/vnd.api+json (or any other +json variant).
|
||||
//
|
||||
// Default: strict (false). Use only when interoperating with clients
|
||||
// that do not strictly abide by the spec.
|
||||
//
|
||||
// Returns the receiver for fluent configuration:
|
||||
//
|
||||
// ctx := middleware.NewContext(spec, api, nil).SetMatchSuffix(true)
|
||||
//
|
||||
// See [negotiate.WithMatchSuffix] for the per-call form and rationale.
|
||||
func (c *Context) SetMatchSuffix(enable bool) *Context {
|
||||
c.matchSuffix = enable
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type routableUntypedAPI struct {
|
||||
api *untyped.API
|
||||
hlock *sync.Mutex
|
||||
handlers map[string]map[string]http.Handler
|
||||
defaultConsumes string
|
||||
defaultProduces string
|
||||
}
|
||||
|
||||
func newRoutableUntypedAPI(spec *loads.Document, api *untyped.API, context *Context) *routableUntypedAPI {
|
||||
var handlers map[string]map[string]http.Handler
|
||||
if spec == nil || api == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
analyzer := analysis.New(spec.Spec())
|
||||
for method, hls := range analyzer.Operations() {
|
||||
um := strings.ToUpper(method)
|
||||
for path, op := range hls {
|
||||
schemes := analyzer.SecurityRequirementsFor(op)
|
||||
|
||||
oh, ok := api.OperationHandlerFor(method, path)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if handlers == nil {
|
||||
handlers = make(map[string]map[string]http.Handler)
|
||||
}
|
||||
if b, ok := handlers[um]; !ok || b == nil {
|
||||
handlers[um] = make(map[string]http.Handler)
|
||||
}
|
||||
|
||||
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// lookup route info in the context
|
||||
route, rCtx, _ := context.RouteInfo(r)
|
||||
if rCtx != nil {
|
||||
r = rCtx
|
||||
}
|
||||
|
||||
// bind and validate the request using reflection
|
||||
var bound any
|
||||
var validation error
|
||||
bound, r, validation = context.BindAndValidate(r, route)
|
||||
if validation != nil {
|
||||
context.Respond(w, r, route.Produces, route, validation)
|
||||
return
|
||||
}
|
||||
|
||||
// actually handle the request
|
||||
result, err := oh.Handle(bound)
|
||||
if err != nil {
|
||||
// respond with failure
|
||||
context.Respond(w, r, route.Produces, route, err)
|
||||
return
|
||||
}
|
||||
|
||||
// respond with success
|
||||
context.Respond(w, r, route.Produces, route, result)
|
||||
})
|
||||
|
||||
if len(schemes) > 0 {
|
||||
handler = newSecureAPI(context, handler)
|
||||
}
|
||||
handlers[um][path] = handler
|
||||
}
|
||||
}
|
||||
|
||||
return &routableUntypedAPI{
|
||||
api: api,
|
||||
hlock: new(sync.Mutex),
|
||||
handlers: handlers,
|
||||
defaultProduces: api.DefaultProduces,
|
||||
defaultConsumes: api.DefaultConsumes,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) HandlerFor(method, path string) (http.Handler, bool) {
|
||||
r.hlock.Lock()
|
||||
paths, ok := r.handlers[strings.ToUpper(method)]
|
||||
if !ok {
|
||||
r.hlock.Unlock()
|
||||
return nil, false
|
||||
}
|
||||
handler, ok := paths[path]
|
||||
r.hlock.Unlock()
|
||||
return handler, ok
|
||||
}
|
||||
func (r *routableUntypedAPI) ServeErrorFor(_ string) func(http.ResponseWriter, *http.Request, error) {
|
||||
return r.api.ServeError
|
||||
}
|
||||
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]runtime.Consumer {
|
||||
return r.api.ConsumersFor(mediaTypes)
|
||||
}
|
||||
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]runtime.Producer {
|
||||
return r.api.ProducersFor(mediaTypes)
|
||||
}
|
||||
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]runtime.Authenticator {
|
||||
return r.api.AuthenticatorsFor(schemes)
|
||||
}
|
||||
func (r *routableUntypedAPI) Authorizer() runtime.Authorizer {
|
||||
return r.api.Authorizer()
|
||||
}
|
||||
func (r *routableUntypedAPI) Formats() strfmt.Registry {
|
||||
return r.api.Formats()
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) DefaultProduces() string {
|
||||
return r.defaultProduces
|
||||
}
|
||||
|
||||
func (r *routableUntypedAPI) DefaultConsumes() string {
|
||||
return r.defaultConsumes
|
||||
}
|
||||
|
||||
// ServeWithBuilder serves the specified spec with the specified api registrations as a [http.Handler] that is decorated
|
||||
// by the Builder.
|
||||
func ServeWithBuilder(spec *loads.Document, api *untyped.API, builder Builder) http.Handler {
|
||||
@@ -319,57 +362,42 @@ func (c *Context) RequiredProduces() []string {
|
||||
// BindValidRequest binds a params object to a request but only when the request is valid
|
||||
// if the request is not valid an error will be returned.
|
||||
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
|
||||
var res []error
|
||||
var requestContentType string
|
||||
|
||||
// check and validate content type, select consumer
|
||||
if runtime.HasBody(request) {
|
||||
ct, _, err := runtime.ContentType(request.Header)
|
||||
ct, cons, err := c.bindRequestBody(request, route)
|
||||
if err != nil {
|
||||
res = append(res, err)
|
||||
} else {
|
||||
c.debugLogf("validating content type for %q against [%s]", ct, strings.Join(route.Consumes, ", "))
|
||||
if err := validateContentType(route.Consumes, ct); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
if len(res) == 0 {
|
||||
cons, ok := route.Consumers[ct]
|
||||
if !ok {
|
||||
res = append(res, errors.New(http.StatusInternalServerError, "no consumer registered for %s", ct))
|
||||
} else {
|
||||
route.Consumer = cons
|
||||
requestContentType = ct
|
||||
}
|
||||
}
|
||||
return errors.CompositeValidationError(err)
|
||||
}
|
||||
|
||||
// happy path
|
||||
requestContentType = ct
|
||||
route.Consumer = cons
|
||||
}
|
||||
|
||||
// check and validate the response format
|
||||
if len(res) == 0 {
|
||||
// if the route does not provide Produces and a default contentType could not be identified
|
||||
// based on a body, typical for GET and DELETE requests, then default contentType to.
|
||||
if len(route.Produces) == 0 && requestContentType == "" {
|
||||
requestContentType = "*/*"
|
||||
}
|
||||
// if the route does not provide Produces and a default contentType could not be identified
|
||||
// based on a body, typical for GET and DELETE requests, then default contentType to.
|
||||
if len(route.Produces) == 0 && requestContentType == "" {
|
||||
requestContentType = "*/*"
|
||||
}
|
||||
|
||||
if str := NegotiateContentType(request, route.Produces, requestContentType); str == "" {
|
||||
res = append(res, errors.InvalidResponseFormat(request.Header.Get(runtime.HeaderAccept), route.Produces))
|
||||
}
|
||||
str := negotiate.ContentType(request, route.Produces, requestContentType, c.negotiateOpts()...)
|
||||
if str == "" {
|
||||
return errors.CompositeValidationError(
|
||||
errors.InvalidResponseFormat(request.Header.Get(runtime.HeaderAccept), route.Produces),
|
||||
)
|
||||
}
|
||||
|
||||
if binder == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// now bind the request with the provided binder
|
||||
// it's assumed the binder will also validate the request and return an error if the
|
||||
// request is invalid
|
||||
if binder != nil && len(res) == 0 {
|
||||
if err := binder.BindRequest(request, route); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
return binder.BindRequest(request, route)
|
||||
}
|
||||
|
||||
// ContentType gets the parsed value of a content type
|
||||
@@ -431,7 +459,7 @@ func (c *Context) ResponseFormat(r *http.Request, offers []string) (string, *htt
|
||||
return v, r
|
||||
}
|
||||
|
||||
format := NegotiateContentType(r, offers, "")
|
||||
format := negotiate.ContentType(r, offers, "", c.negotiateOpts()...)
|
||||
if format != "" {
|
||||
c.debugLogf("[%s %s] set response format %q in context", r.Method, r.URL.Path, format)
|
||||
r = r.WithContext(stdContext.WithValue(rCtx, ctxResponseFormat, format))
|
||||
@@ -453,44 +481,6 @@ func (c *Context) ResetAuth(request *http.Request) *http.Request {
|
||||
return request.WithContext(rctx)
|
||||
}
|
||||
|
||||
// Authorize authorizes the request
|
||||
// Returns the principal object and a shallow copy of the request when its
|
||||
// context doesn't contain the principal, otherwise the same request or an error
|
||||
// (the last) if one of the authenticators returns one or an Unauthenticated error.
|
||||
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (any, *http.Request, error) {
|
||||
if route == nil || !route.HasAuth() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var rCtx = request.Context()
|
||||
if v := rCtx.Value(ctxSecurityPrincipal); v != nil {
|
||||
return v, request, nil
|
||||
}
|
||||
|
||||
applies, usr, err := route.Authenticators.Authenticate(request, route)
|
||||
if !applies || err != nil || !route.Authenticators.AllowsAnonymous() && usr == nil {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, errors.Unauthenticated("invalid credentials")
|
||||
}
|
||||
if route.Authorizer != nil {
|
||||
if err := route.Authorizer.Authorize(request, usr); err != nil {
|
||||
if _, ok := err.(errors.Error); ok {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return nil, nil, errors.New(http.StatusForbidden, "%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
rCtx = request.Context()
|
||||
|
||||
rCtx = stdContext.WithValue(rCtx, ctxSecurityPrincipal, usr)
|
||||
rCtx = stdContext.WithValue(rCtx, ctxSecurityScopes, route.Authenticator.AllScopes())
|
||||
return usr, request.WithContext(rCtx), nil
|
||||
}
|
||||
|
||||
// BindAndValidate binds and validates the request
|
||||
// Returns the validation map and a shallow copy of the request when its context
|
||||
// doesn't contain the validation, otherwise it returns the same request or an
|
||||
@@ -523,91 +513,29 @@ func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
|
||||
// Respond renders the response after doing some content negotiation.
|
||||
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data any) {
|
||||
c.debugLogf("responding to %s %s with produces: %v", r.Method, r.URL.Path, produces)
|
||||
offers := []string{}
|
||||
for _, mt := range produces {
|
||||
if mt != c.api.DefaultProduces() {
|
||||
offers = append(offers, mt)
|
||||
}
|
||||
}
|
||||
// the default producer is last so more specific producers take precedence
|
||||
offers = append(offers, c.api.DefaultProduces())
|
||||
c.debugLogf("offers: %v", offers)
|
||||
offers := c.buildOffers(produces)
|
||||
|
||||
var format string
|
||||
format, r = c.ResponseFormat(r, offers)
|
||||
rw.Header().Set(runtime.HeaderContentType, format)
|
||||
|
||||
if resp, ok := data.(Responder); ok {
|
||||
producers := route.Producers
|
||||
// producers contains keys with normalized format, if a format has MIME type parameter such as `text/plain; charset=utf-8`
|
||||
// then you must provide `text/plain` to get the correct producer. HOWEVER, format here is not normalized.
|
||||
prod, ok := producers[normalizeOffer(format)]
|
||||
if !ok {
|
||||
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
|
||||
pr, ok := prods[c.api.DefaultProduces()]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%d: %s", http.StatusInternalServerError, cantFindProducer(format)))
|
||||
}
|
||||
prod = pr
|
||||
}
|
||||
resp.WriteResponse(rw, prod)
|
||||
c.respondWithResponder(rw, r, route, resp, format)
|
||||
return
|
||||
}
|
||||
|
||||
if err, ok := data.(error); ok {
|
||||
if format == "" {
|
||||
rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)
|
||||
}
|
||||
|
||||
if realm := security.FailedBasicAuth(r); realm != "" {
|
||||
rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
|
||||
}
|
||||
|
||||
if route == nil || route.Operation == nil {
|
||||
c.api.ServeErrorFor("")(rw, r, err)
|
||||
return
|
||||
}
|
||||
c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
|
||||
c.respondWithError(rw, r, produces, route, err, format)
|
||||
return
|
||||
}
|
||||
|
||||
if route == nil || route.Operation == nil {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
producers := c.api.ProducersFor(normalizeOffers(offers))
|
||||
prod, ok := producers[format]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%d: %s", http.StatusInternalServerError, cantFindProducer(format)))
|
||||
}
|
||||
if err := prod.Produce(rw, data); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
c.respondWithoutCode(rw, r, data, format, offers)
|
||||
return
|
||||
}
|
||||
|
||||
if _, code, ok := route.Operation.SuccessResponse(); ok {
|
||||
rw.WriteHeader(code)
|
||||
if code == http.StatusNoContent || r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
|
||||
producers := route.Producers
|
||||
prod, ok := producers[format]
|
||||
if !ok {
|
||||
if !ok {
|
||||
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
|
||||
pr, ok := prods[c.api.DefaultProduces()]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%d: %s", http.StatusInternalServerError, cantFindProducer(format)))
|
||||
}
|
||||
prod = pr
|
||||
}
|
||||
}
|
||||
if err := prod.Produce(rw, data); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
c.respondWithCode(rw, r, route, code, data, format)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -618,57 +546,76 @@ func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []st
|
||||
//
|
||||
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
|
||||
//
|
||||
// A spec UI ([SwaggerUI]) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with uiOptions).
|
||||
// A spec UI ([docui.SwaggerUI]) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with combined [UIOption]).
|
||||
//
|
||||
// Deprecated: use [Context.APIHandlerWithUI] with [docui.SwaggerUI] middleware instead.
|
||||
func (c *Context) APIHandlerSwaggerUI(builder Builder, opts ...UIOption) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
|
||||
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
|
||||
var swaggerUIOpts SwaggerUIOpts
|
||||
fromCommonToAnyOptions(uiOpts, &swaggerUIOpts)
|
||||
|
||||
return Spec(specPath, c.spec.Raw(), SwaggerUI(swaggerUIOpts, c.RoutesHandler(b)), specOpts...)
|
||||
return c.APIHandlerWithUI(builder, docui.UseSwaggerUI, c.uiOptionsForHandler(opts)...)
|
||||
}
|
||||
|
||||
// APIHandlerRapiDoc returns a handler to serve the API.
|
||||
//
|
||||
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
|
||||
//
|
||||
// A spec UI ([RapiDoc]) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with uiOptions).
|
||||
// A spec UI ([docui.RapiDoc]) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with combined [UIOption]).
|
||||
//
|
||||
// Deprecated: use [Context.APIHandlerWithUI] with [docui.UseRapiDoc] middleware instead.
|
||||
func (c *Context) APIHandlerRapiDoc(builder Builder, opts ...UIOption) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
|
||||
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
|
||||
var rapidocUIOpts RapiDocOpts
|
||||
fromCommonToAnyOptions(uiOpts, &rapidocUIOpts)
|
||||
|
||||
return Spec(specPath, c.spec.Raw(), RapiDoc(rapidocUIOpts, c.RoutesHandler(b)), specOpts...)
|
||||
return c.APIHandlerWithUI(builder, docui.UseRapiDoc, c.uiOptionsForHandler(opts)...)
|
||||
}
|
||||
|
||||
// APIHandler returns a handler to serve the API.
|
||||
//
|
||||
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
|
||||
//
|
||||
// A spec UI ([Redoc]) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with uiOptions).
|
||||
// A spec UI ([docui.Redoc]) is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with combined [UIOption]).
|
||||
//
|
||||
// Notice that you may use [Context.APIHandlerWithUI] to use an alternate UI-serving middleware.
|
||||
func (c *Context) APIHandler(builder Builder, opts ...UIOption) http.Handler {
|
||||
return c.APIHandlerWithUI(builder, docui.UseRedoc, c.uiOptionsForHandler(opts)...)
|
||||
}
|
||||
|
||||
// APIHandlerWithUI returns a handler to serve the API with a swagger spec and a UI.
|
||||
//
|
||||
// This handler includes a swagger spec, router and the contract defined in the swagger spec.
|
||||
//
|
||||
// A spec UI is served at {API base path}/docs and the spec document at /swagger.json
|
||||
// (these can be modified with combined [UIOption]).
|
||||
//
|
||||
// Notice that any function that accepts the [docui.Option] set and returns a valid middleware may be injected here.
|
||||
//
|
||||
// [Context.APIHandlerWithUI] extends [Context.APIHandler], and supersedes [Context.APIHandlerRapiDoc] and [Context.APIHandlerSwaggerUI].
|
||||
func (c *Context) APIHandlerWithUI(builder Builder, uiMiddleware docui.UIMiddleware, opts ...docui.Option) http.Handler {
|
||||
b := builder
|
||||
if b == nil {
|
||||
b = PassthroughBuilder
|
||||
}
|
||||
|
||||
specPath, uiOpts, specOpts := c.uiOptionsForHandler(opts)
|
||||
var redocOpts RedocOpts
|
||||
fromCommonToAnyOptions(uiOpts, &redocOpts)
|
||||
// the UI titles defaults to the title in the spec
|
||||
const extraOptions = 2
|
||||
prepend := make([]docui.Option, 0, len(opts)+extraOptions)
|
||||
var title string
|
||||
|
||||
return Spec(specPath, c.spec.Raw(), Redoc(redocOpts, c.RoutesHandler(b)), specOpts...)
|
||||
sp := c.spec.Spec()
|
||||
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
|
||||
title = sp.Info.Title
|
||||
}
|
||||
if title != "" {
|
||||
prepend = append(prepend, docui.WithUITitle(title))
|
||||
}
|
||||
|
||||
prepend = append(prepend, docui.WithUIBasePath(c.BasePath()))
|
||||
prepend = append(prepend, opts...)
|
||||
|
||||
// aligns spec serve path with UI setting to fetch spec document.
|
||||
return docui.UseSpec(c.spec.Raw(), docui.WithSpecPathFromOptions(prepend...))(
|
||||
uiMiddleware(prepend...)(
|
||||
c.RoutesHandler(b),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// RoutesHandler returns a handler to serve the API, just the routes and the contract defined in the swagger spec.
|
||||
@@ -680,37 +627,192 @@ func (c *Context) RoutesHandler(builder Builder) http.Handler {
|
||||
return NewRouter(c, b(NewOperationExecutor(c)))
|
||||
}
|
||||
|
||||
func (c Context) uiOptionsForHandler(opts []UIOption) (string, uiOptions, []SpecOption) {
|
||||
var title string
|
||||
sp := c.spec.Spec()
|
||||
if sp != nil && sp.Info != nil && sp.Info.Title != "" {
|
||||
title = sp.Info.Title
|
||||
// authorizeImpl is the real authentication+authorization body shared
|
||||
// between the production and dev-only variants of [Context.Authorize].
|
||||
// See context_skipauth_disabled.go (default build) and
|
||||
// context_skipauth_enabled.go (the `openapi_unsafe_skipauth` build tag).
|
||||
//
|
||||
// The doc on the exported Authorize describes the user-facing
|
||||
// contract; this function MUST NOT change semantics for the
|
||||
// production path.
|
||||
func (c *Context) authorizeImpl(request *http.Request, route *MatchedRoute) (any, *http.Request, error) {
|
||||
if route == nil || !route.HasAuth() {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// default options (may be overridden)
|
||||
const baseOptions = 2
|
||||
optsForContext := make([]UIOption, 0, len(opts)+baseOptions)
|
||||
optsForContext = append(optsForContext,
|
||||
WithUIBasePath(c.BasePath()),
|
||||
WithUITitle(title),
|
||||
)
|
||||
optsForContext = append(optsForContext, opts...)
|
||||
uiOpts := uiOptionsWithDefaults(optsForContext)
|
||||
|
||||
// If spec URL is provided, there is a non-default path to serve the spec.
|
||||
// This makes sure that the UI middleware is aligned with the Spec middleware.
|
||||
u, _ := url.Parse(uiOpts.SpecURL)
|
||||
var specPath string
|
||||
if u != nil {
|
||||
specPath = u.Path
|
||||
var rCtx = request.Context()
|
||||
if v := rCtx.Value(ctxSecurityPrincipal); v != nil {
|
||||
return v, request, nil
|
||||
}
|
||||
|
||||
pth, doc := path.Split(specPath)
|
||||
if pth == "." {
|
||||
pth = ""
|
||||
applies, usr, err := route.Authenticators.Authenticate(request, route)
|
||||
if !applies || err != nil || !route.Authenticators.AllowsAnonymous() && typeutils.IsZero(usr) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, errors.Unauthenticated("invalid credentials")
|
||||
}
|
||||
if route.Authorizer != nil {
|
||||
if err := route.Authorizer.Authorize(request, usr); err != nil {
|
||||
var apiError errors.Error
|
||||
if stderrors.As(err, &apiError) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return nil, nil, errors.New(http.StatusForbidden, "%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return pth, uiOpts, []SpecOption{WithSpecDocument(doc)}
|
||||
rCtx = request.Context()
|
||||
|
||||
rCtx = stdContext.WithValue(rCtx, ctxSecurityPrincipal, usr)
|
||||
rCtx = stdContext.WithValue(rCtx, ctxSecurityScopes, route.Authenticator.AllScopes())
|
||||
return usr, request.WithContext(rCtx), nil
|
||||
}
|
||||
|
||||
func (c *Context) bindRequestBody(request *http.Request, route *MatchedRoute) (string, runtime.Consumer, error) {
|
||||
ct, _, err := runtime.ContentType(request.Header)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
c.debugLogf("validating content type for %q against [%s]", ct, strings.Join(route.Consumes, ", "))
|
||||
if err := validateContentType(route.Consumes, ct); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
cons, ok := mediatype.Lookup(route.Consumers, ct, c.matchOpts()...)
|
||||
if !ok {
|
||||
return "", nil, errors.New(http.StatusInternalServerError, "no consumer registered for %s", ct)
|
||||
}
|
||||
|
||||
return ct, cons, nil
|
||||
}
|
||||
|
||||
func (c *Context) respondWithResponder(rw http.ResponseWriter, r *http.Request, route *MatchedRoute, resp Responder, format string) {
|
||||
_ = r
|
||||
producers := route.Producers
|
||||
|
||||
// producers contains keys with normalized format, if a format has MIME type parameter such as `text/plain; charset=utf-8`
|
||||
// then you must provide `text/plain` to get the correct producer. HOWEVER, format here is not normalized.
|
||||
prod, ok := producers[normalizeOffer(format)]
|
||||
if !ok {
|
||||
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
|
||||
pr, ok := prods[c.api.DefaultProduces()]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%d: %s", http.StatusInternalServerError, cantFindProducer(format)))
|
||||
}
|
||||
prod = pr
|
||||
}
|
||||
|
||||
resp.WriteResponse(rw, prod)
|
||||
}
|
||||
|
||||
func (c *Context) respondWithError(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, err error, format string) {
|
||||
_ = produces
|
||||
|
||||
if format == "" {
|
||||
rw.Header().Set(runtime.HeaderContentType, runtime.JSONMime)
|
||||
}
|
||||
|
||||
if realm := security.FailedBasicAuth(r); realm != "" {
|
||||
rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm))
|
||||
}
|
||||
|
||||
if route == nil || route.Operation == nil {
|
||||
c.api.ServeErrorFor("")(rw, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
|
||||
}
|
||||
|
||||
func (c *Context) respondWithoutCode(rw http.ResponseWriter, r *http.Request, data any, format string, offers []string) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
|
||||
producers := c.api.ProducersFor(normalizeOffers(offers))
|
||||
prod, ok := producers[format]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%d: %s", http.StatusInternalServerError, cantFindProducer(format)))
|
||||
}
|
||||
|
||||
if err := prod.Produce(rw, data); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) buildOffers(produces []string) []string {
|
||||
offers := make([]string, 0, len(produces)+1)
|
||||
|
||||
for _, mt := range produces {
|
||||
if mt != c.api.DefaultProduces() {
|
||||
offers = append(offers, mt)
|
||||
}
|
||||
}
|
||||
|
||||
// the default producer is last so more specific producers take precedence
|
||||
offers = append(offers, c.api.DefaultProduces())
|
||||
c.debugLogf("offers: %v", offers)
|
||||
|
||||
return offers
|
||||
}
|
||||
|
||||
func (c *Context) respondWithCode(rw http.ResponseWriter, r *http.Request, route *MatchedRoute, code int, data any, format string) {
|
||||
rw.WriteHeader(code)
|
||||
if code == http.StatusNoContent || r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
|
||||
producers := route.Producers
|
||||
prod, ok := producers[format]
|
||||
if !ok {
|
||||
if !ok {
|
||||
prods := c.api.ProducersFor(normalizeOffers([]string{c.api.DefaultProduces()}))
|
||||
pr, ok := prods[c.api.DefaultProduces()]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%d: %s", http.StatusInternalServerError, cantFindProducer(format)))
|
||||
}
|
||||
prod = pr
|
||||
}
|
||||
}
|
||||
|
||||
if err := prod.Produce(rw, data); err != nil {
|
||||
panic(err) // let the recovery middleware deal with this
|
||||
}
|
||||
}
|
||||
|
||||
// uiOptionsForHandler bridges the deprecated [UIOption] set to the new [docui.Option] set.
|
||||
func (c Context) uiOptionsForHandler(opts []UIOption) []docui.Option {
|
||||
uiOpts := uiOptionsWithDefaults(opts)
|
||||
|
||||
return uiOpts.toFuncOptions()
|
||||
}
|
||||
|
||||
func (c *Context) negotiateOpts() []negotiate.Option {
|
||||
var opts []negotiate.Option
|
||||
if c.ignoreParameters {
|
||||
opts = append(opts, negotiate.WithIgnoreParameters(true))
|
||||
}
|
||||
if c.matchSuffix {
|
||||
opts = append(opts, negotiate.WithMatchSuffix(true))
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
// matchOpts builds the mediatype.MatchOption slice that the
|
||||
// codec-lookup and Content-Type validation paths apply server-wide.
|
||||
// Mirrors negotiateOpts but at the mediatype level (without going
|
||||
// through the negotiate.Option wrapper).
|
||||
func (c *Context) matchOpts() []mediatype.MatchOption {
|
||||
if !c.matchSuffix {
|
||||
return nil
|
||||
}
|
||||
|
||||
return []mediatype.MatchOption{mediatype.AllowSuffix()}
|
||||
}
|
||||
|
||||
func cantFindProducer(format string) string {
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !openapi_unsafe_skipauth
|
||||
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Authorize authorizes the request.
|
||||
//
|
||||
// Returns the principal object and a shallow copy of the request when its
|
||||
// context doesn't contain the principal, otherwise the same request or an error
|
||||
// (the last) if one of the authenticators returns one or an Unauthenticated error.
|
||||
//
|
||||
// This is the production variant — compiled when the build tag
|
||||
// `openapi_unsafe_skipauth` is NOT set. There is no skip-auth check
|
||||
// in this codepath; the field, setter, and storage for the bypass
|
||||
// flag are entirely absent from the binary. See the alternate
|
||||
// implementation in context_skipauth_enabled.go for the dev-only
|
||||
// bypass mechanism.
|
||||
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (any, *http.Request, error) {
|
||||
return c.authorizeImpl(request, route)
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build openapi_unsafe_skipauth
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// skipAuthEnabled holds the process-wide skip-auth flag. It only
|
||||
// exists in binaries built with the `openapi_unsafe_skipauth` tag —
|
||||
// production binaries (built without the tag) have no field, no
|
||||
// setter, no storage, and no skip-checking branch in [Context.Authorize].
|
||||
// Reflection, unsafe-pointer arithmetic, or a debugger cannot flip
|
||||
// what is not in the binary.
|
||||
var skipAuthEnabled atomic.Bool
|
||||
|
||||
// SetSkipAuth toggles a PROCESS-WIDE bypass of authentication AND
|
||||
// authorization for every operation served by every Context in the
|
||||
// running program.
|
||||
//
|
||||
// DANGER: this disables ALL authentication and ALL authorization.
|
||||
// Every request to every secured endpoint runs as if it had been
|
||||
// authorized with a nil principal. Use ONLY on developer
|
||||
// workstations during early prototyping (e.g. while
|
||||
// authentication is not yet wired up).
|
||||
//
|
||||
// This function exists only when the build tag
|
||||
// `openapi_unsafe_skipauth` is set:
|
||||
//
|
||||
// go build -tags openapi_unsafe_skipauth ./...
|
||||
//
|
||||
// Production CI MUST NOT pass this tag. Calls compile to a symbol
|
||||
// that does not exist in production binaries.
|
||||
//
|
||||
// Calling with true emits a one-line WARNING via the stdlib `log`
|
||||
// package (stderr by default) so the bypass is visible at startup.
|
||||
// Calling with false silently disables it.
|
||||
func SetSkipAuth(skip bool) {
|
||||
skipAuthEnabled.Store(skip)
|
||||
if skip {
|
||||
log.Println("WARNING: go-openapi/runtime: SetSkipAuth(true) — authentication and authorization are bypassed for ALL operations. This MUST NOT run in production.")
|
||||
}
|
||||
}
|
||||
|
||||
// Authorize is the dev-build variant of the production
|
||||
// [Context.Authorize] (see context_skipauth_disabled.go for the
|
||||
// production path). When [SetSkipAuth] has enabled the bypass, this
|
||||
// returns a nil principal with the original request and no error —
|
||||
// handlers downstream receive a nil-value principal. Otherwise it
|
||||
// delegates to the standard authentication+authorization body.
|
||||
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (any, *http.Request, error) {
|
||||
if skipAuthEnabled.Load() {
|
||||
return nil, request, nil
|
||||
}
|
||||
return c.authorizeImpl(request, route)
|
||||
}
|
||||
+12
-7
@@ -9,6 +9,7 @@ package denco
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
@@ -29,8 +30,8 @@ const (
|
||||
// PathParamCharacter indicates a RESTCONF path param.
|
||||
PathParamCharacter = '='
|
||||
|
||||
// MaxSize is max size of records and internal slice.
|
||||
MaxSize = (1 << 22) - 1 //nolint:mnd
|
||||
// MaxSize is the maximum size of records and internal slice (encoded over 22 bits).
|
||||
MaxSize = (1 << baseBits) - 1
|
||||
)
|
||||
|
||||
// Router represents a URL router.
|
||||
@@ -53,9 +54,12 @@ func New() *Router {
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup returns data and path parameters that associated with path.
|
||||
// Lookup returns data and path parameters which are associated to the path.
|
||||
//
|
||||
// params is a slice of the [Param] that arranged in the order in which parameters appeared.
|
||||
// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice". params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
|
||||
//
|
||||
// e.g. when built routing path is "/path/to/:id/:name" and given path is "/path/to/1/alice",
|
||||
// params order is [{"id": "1"}, {"name": "alice"}], not [{"name": "alice"}, {"id": "1"}].
|
||||
func (rt *Router) Lookup(path string) (data any, params Params, found bool) {
|
||||
if data, found = rt.static[path]; found {
|
||||
return data, nil, true
|
||||
@@ -144,6 +148,7 @@ func newDoubleArray() *doubleArray {
|
||||
type baseCheck uint32
|
||||
|
||||
const (
|
||||
baseBits = 22
|
||||
flagsBits = 10
|
||||
checkBits = 8
|
||||
)
|
||||
@@ -157,7 +162,7 @@ func (bc *baseCheck) SetBase(base int) {
|
||||
}
|
||||
|
||||
func (bc baseCheck) Check() byte {
|
||||
return byte(bc) //nolint:gosec // integer conversion is ok
|
||||
return byte(bc) //nolint:gosec // integer conversion is ok: we pick the last 8 bits
|
||||
}
|
||||
|
||||
func (bc *baseCheck) SetCheck(check byte) {
|
||||
@@ -213,8 +218,8 @@ func (da *doubleArray) lookup(path string, params []Param, idx int) (*node, []Pa
|
||||
}
|
||||
|
||||
BACKTRACKING:
|
||||
for j := len(indices) - 1; j >= 0; j-- {
|
||||
i, idx := int(indices[j]>>indexOffset), int(indices[j]&indexMask)
|
||||
for _, j := range slices.Backward(indices) {
|
||||
i, idx := int(j>>indexOffset), int(j&indexMask)
|
||||
if da.bc[idx].IsSingleParam() {
|
||||
nextIdx := nextIndex(da.bc[idx].Base(), ParamCharacter)
|
||||
if nextIdx >= len(da.bc) {
|
||||
|
||||
+14
-12
@@ -9,7 +9,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Mux represents a multiplexer for HTTP request.
|
||||
// Mux represents a multiplexer for HTTP requests.
|
||||
type Mux struct{}
|
||||
|
||||
// NewMux returns a new [Mux].
|
||||
@@ -17,27 +17,27 @@ func NewMux() *Mux {
|
||||
return &Mux{}
|
||||
}
|
||||
|
||||
// GET is shorthand of [Mux].Handler("GET", path, handler).
|
||||
// GET is shorthand for [Mux.Handler] ("GET", path, handler).
|
||||
func (m *Mux) GET(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("GET", path, handler)
|
||||
}
|
||||
|
||||
// POST is shorthand of [Mux].Handler("POST", path, handler).
|
||||
// POST is shorthand for [Mux.Handler] ("POST", path, handler).
|
||||
func (m *Mux) POST(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("POST", path, handler)
|
||||
}
|
||||
|
||||
// PUT is shorthand of [Mux].Handler("PUT", path, handler).
|
||||
// PUT is shorthand for [Mux.Handler] ("PUT", path, handler).
|
||||
func (m *Mux) PUT(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("PUT", path, handler)
|
||||
}
|
||||
|
||||
// HEAD is shorthand of [Mux].Handler("HEAD", path, handler).
|
||||
// HEAD is shorthand for [Mux.Handler]("HEAD", path, handler).
|
||||
func (m *Mux) HEAD(path string, handler HandlerFunc) Handler {
|
||||
return m.Handler("HEAD", path, handler)
|
||||
}
|
||||
|
||||
// Handler returns a handler for HTTP method.
|
||||
// Handler returns a [Handler] for a HTTP method.
|
||||
func (m *Mux) Handler(method, path string, handler HandlerFunc) Handler {
|
||||
return Handler{
|
||||
Method: method,
|
||||
@@ -63,7 +63,7 @@ func (m *Mux) Build(handlers []Handler) (http.Handler, error) {
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
// Handler represents a handler of HTTP request.
|
||||
// Handler represents a handler of HTTP requests.
|
||||
type Handler struct {
|
||||
// Method is an HTTP method.
|
||||
Method string
|
||||
@@ -75,7 +75,7 @@ type Handler struct {
|
||||
Func HandlerFunc
|
||||
}
|
||||
|
||||
// HandlerFunc is aliased to type of handler function.
|
||||
// HandlerFunc is an alias to the handler function, similar to [http.HandlerFunc].
|
||||
type HandlerFunc func(w http.ResponseWriter, r *http.Request, params Params)
|
||||
|
||||
type serveMux struct {
|
||||
@@ -88,7 +88,7 @@ func newServeMux() *serveMux {
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface.
|
||||
// ServeHTTP implements the [http.Handler] interface.
|
||||
func (mux *serveMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
handler, params := mux.handler(r.Method, r.URL.Path)
|
||||
handler(w, r, params)
|
||||
@@ -97,15 +97,17 @@ func (mux *serveMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (mux *serveMux) handler(method, path string) (HandlerFunc, []Param) {
|
||||
if router, found := mux.routers[method]; found {
|
||||
if handler, params, found := router.Lookup(path); found {
|
||||
return handler.(HandlerFunc), params
|
||||
return handler.(HandlerFunc), params //nolint:forcetypeassert // type is guaranteed when the path is found
|
||||
}
|
||||
}
|
||||
return NotFound, nil
|
||||
}
|
||||
|
||||
// NotFound replies to the request with an HTTP 404 not found error.
|
||||
// NotFound is called when unknown HTTP method or a handler not found.
|
||||
// If you want to use the your own NotFound handler, please overwrite this variable.
|
||||
//
|
||||
// NotFound is called when unknown HTTP methods are being user or a handler not found.
|
||||
//
|
||||
// If you want to use your own NotFound handler, please overwrite this variable.
|
||||
var NotFound = func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// this file was taken from the github.com/golang/gddo repository
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware/header"
|
||||
)
|
||||
|
||||
// NegotiateContentEncoding returns the best offered content encoding for the
|
||||
// request's Accept-Encoding header. If two offers match with equal weight and
|
||||
// then the offer earlier in the list is preferred. If no offers are
|
||||
// acceptable, then "" is returned.
|
||||
func NegotiateContentEncoding(r *http.Request, offers []string) string {
|
||||
bestOffer := "identity"
|
||||
bestQ := -1.0
|
||||
specs := header.ParseAccept(r.Header, "Accept-Encoding")
|
||||
for _, offer := range offers {
|
||||
for _, spec := range specs {
|
||||
if spec.Q > bestQ &&
|
||||
(spec.Value == "*" || spec.Value == offer) {
|
||||
bestQ = spec.Q
|
||||
bestOffer = offer
|
||||
}
|
||||
}
|
||||
}
|
||||
if bestQ == 0 {
|
||||
bestOffer = ""
|
||||
}
|
||||
return bestOffer
|
||||
}
|
||||
|
||||
// NegotiateContentType returns the best offered content type for the request's
|
||||
// Accept header. If two offers match with equal weight, then the more specific
|
||||
// offer is preferred. For example, text/* trumps */*. If two offers match
|
||||
// with equal weight and specificity, then the offer earlier in the list is
|
||||
// preferred. If no offers match, then defaultOffer is returned.
|
||||
func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string {
|
||||
bestOffer := defaultOffer
|
||||
bestQ := -1.0
|
||||
bestWild := 3
|
||||
specs := header.ParseAccept(r.Header, "Accept")
|
||||
for _, rawOffer := range offers {
|
||||
offer := normalizeOffer(rawOffer)
|
||||
// No Accept header: just return the first offer.
|
||||
if len(specs) == 0 {
|
||||
return rawOffer
|
||||
}
|
||||
for _, spec := range specs {
|
||||
switch {
|
||||
case spec.Q == 0.0:
|
||||
// ignore
|
||||
case spec.Q < bestQ:
|
||||
// better match found
|
||||
case spec.Value == "*/*":
|
||||
if spec.Q > bestQ || bestWild > 2 {
|
||||
bestQ = spec.Q
|
||||
bestWild = 2
|
||||
bestOffer = rawOffer
|
||||
}
|
||||
case strings.HasSuffix(spec.Value, "/*"):
|
||||
if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) &&
|
||||
(spec.Q > bestQ || bestWild > 1) {
|
||||
bestQ = spec.Q
|
||||
bestWild = 1
|
||||
bestOffer = rawOffer
|
||||
}
|
||||
default:
|
||||
if spec.Value == offer &&
|
||||
(spec.Q > bestQ || bestWild > 0) {
|
||||
bestQ = spec.Q
|
||||
bestWild = 0
|
||||
bestOffer = rawOffer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestOffer
|
||||
}
|
||||
|
||||
func normalizeOffers(orig []string) (norm []string) {
|
||||
for _, o := range orig {
|
||||
norm = append(norm, normalizeOffer(o))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func normalizeOffer(orig string) string {
|
||||
const maxParts = 2
|
||||
return strings.SplitN(orig, ";", maxParts)[0]
|
||||
}
|
||||
+181
-123
@@ -6,7 +6,7 @@ package middleware
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
stderrors "errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -56,126 +56,153 @@ func (p *untypedParamBinder) Type() reflect.Type {
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) Bind(request *http.Request, routeParams RouteParams, consumer runtime.Consumer, target reflect.Value) error {
|
||||
// fmt.Println("binding", p.name, "as", p.Type())
|
||||
switch p.parameter.In {
|
||||
case "query":
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.URL.Query()), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.bindValue(data, hasKey, target)
|
||||
return p.bindQuery(request, routeParams, consumer, target)
|
||||
|
||||
case "header":
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.Header), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
return p.bindHeader(request, routeParams, consumer, target)
|
||||
|
||||
case "path":
|
||||
data, custom, hasKey, err := p.readValue(routeParams, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
return p.bindPath(request, routeParams, consumer, target)
|
||||
|
||||
case "formData":
|
||||
var err error
|
||||
var mt string
|
||||
return p.bindFormData(request, routeParams, consumer, target)
|
||||
|
||||
mt, _, e := runtime.ContentType(request.Header)
|
||||
if e != nil {
|
||||
// because of the interface conversion go thinks the error is not nil
|
||||
// so we first check for nil and then set the err var if it's not nil
|
||||
err = e
|
||||
}
|
||||
case "body":
|
||||
return p.bindBody(request, routeParams, consumer, target)
|
||||
default:
|
||||
return errors.New(http.StatusInternalServerError, "invalid parameter location: %q", p.parameter.In)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.InvalidContentType("", []string{"multipart/form-data", "application/x-www-form-urlencoded"})
|
||||
}
|
||||
func (p *untypedParamBinder) bindQuery(request *http.Request, _ RouteParams, _ runtime.Consumer, target reflect.Value) error {
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.URL.Query()), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
|
||||
if mt != "multipart/form-data" && mt != "application/x-www-form-urlencoded" {
|
||||
return errors.InvalidContentType(mt, []string{"multipart/form-data", "application/x-www-form-urlencoded"})
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
}
|
||||
|
||||
if mt == "multipart/form-data" {
|
||||
if err = request.ParseMultipartForm(defaultMaxMemory); err != nil {
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", err)
|
||||
}
|
||||
}
|
||||
func (p *untypedParamBinder) bindHeader(request *http.Request, _ RouteParams, _ runtime.Consumer, target reflect.Value) error {
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.Header), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
}
|
||||
|
||||
if err = request.ParseForm(); err != nil {
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", err)
|
||||
}
|
||||
func (p *untypedParamBinder) bindPath(_ *http.Request, routeParams RouteParams, _ runtime.Consumer, target reflect.Value) error {
|
||||
data, custom, hasKey, err := p.readValue(routeParams, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
}
|
||||
|
||||
if p.parameter.Type == "file" {
|
||||
file, header, ffErr := request.FormFile(p.parameter.Name)
|
||||
if ffErr != nil {
|
||||
func (p *untypedParamBinder) bindFormData(request *http.Request, _ RouteParams, _ runtime.Consumer, target reflect.Value) error {
|
||||
mt, _, ctErr := runtime.ContentType(request.Header)
|
||||
if ctErr != nil {
|
||||
return errors.InvalidContentType("", []string{runtime.MultipartFormMime, runtime.URLencodedFormMime})
|
||||
}
|
||||
|
||||
if mt != runtime.MultipartFormMime && mt != runtime.URLencodedFormMime {
|
||||
return errors.InvalidContentType(mt, []string{runtime.MultipartFormMime, runtime.URLencodedFormMime})
|
||||
}
|
||||
|
||||
// Parse via the shared helper. The helper routes on Content-Type
|
||||
// (multipart/form-data → ParseMultipartForm; all non-multipart types,
|
||||
// including application/x-www-form-urlencoded, → ParseForm)
|
||||
// and applies the default 32 MiB body cap via http.MaxBytesReader.
|
||||
// Idempotent across the per-parameter loop: stdlib short-circuits
|
||||
// when r.MultipartForm / r.PostForm are already populated.
|
||||
if _, perr := runtime.BindForm(request, runtime.BindFormMaxParseMemory(defaultMaxMemory)); perr != nil {
|
||||
return perr
|
||||
}
|
||||
|
||||
if p.parameter.Type == "file" {
|
||||
// runtime.FormFile handles both multipart/form-data and
|
||||
// application/x-www-form-urlencoded (OpenAPI 2.0 permits
|
||||
// either consumes for `type: file`), and surfaces a
|
||||
// missing field as http.ErrMissingFile under both.
|
||||
file, header, ffErr := runtime.FormFile(request, p.parameter.Name)
|
||||
if ffErr != nil {
|
||||
if stderrors.Is(ffErr, http.ErrMissingFile) {
|
||||
if p.parameter.Required {
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", ffErr)
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", http.ErrMissingFile)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header}))
|
||||
return nil
|
||||
return errors.NewParseError(p.Name, p.parameter.In, "", ffErr)
|
||||
}
|
||||
|
||||
if request.MultipartForm != nil {
|
||||
data, custom, hasKey, rvErr := p.readValue(runtime.Values(request.MultipartForm.Value), target)
|
||||
if rvErr != nil {
|
||||
return rvErr
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
}
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.PostForm), target)
|
||||
if err != nil {
|
||||
// Mirror the FileHeader.Filename length cap that BindForm
|
||||
// applies to typed (codegen) paths through BindFormFile, so
|
||||
// untyped formData bindings get the same protection.
|
||||
if err := runtime.ValidateFilenameLength(p.Name, p.parameter.In, header.Filename,
|
||||
runtime.DefaultMaxUploadFilenameLength); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target.Set(reflect.ValueOf(runtime.File{Data: file, Header: header}))
|
||||
return nil
|
||||
}
|
||||
|
||||
if request.MultipartForm != nil {
|
||||
data, custom, hasKey, rvErr := p.readValue(runtime.Values(request.MultipartForm.Value), target)
|
||||
if rvErr != nil {
|
||||
return rvErr
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
}
|
||||
data, custom, hasKey, err := p.readValue(runtime.Values(request.PostForm), target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if custom {
|
||||
return nil
|
||||
}
|
||||
return p.bindValue(data, hasKey, target)
|
||||
}
|
||||
|
||||
case "body":
|
||||
newValue := reflect.New(target.Type())
|
||||
if !runtime.HasBody(request) {
|
||||
if p.parameter.Default != nil {
|
||||
target.Set(reflect.ValueOf(p.parameter.Default))
|
||||
}
|
||||
func (p *untypedParamBinder) bindBody(request *http.Request, _ RouteParams, consumer runtime.Consumer, target reflect.Value) error {
|
||||
newValue := reflect.New(target.Type())
|
||||
if !runtime.HasBody(request) {
|
||||
if p.parameter.Default != nil {
|
||||
target.Set(reflect.ValueOf(p.parameter.Default))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := consumer.Consume(request.Body, newValue.Interface()); err != nil {
|
||||
if stderrors.Is(err, io.EOF) && p.parameter.Default != nil {
|
||||
target.Set(reflect.ValueOf(p.parameter.Default))
|
||||
return nil
|
||||
}
|
||||
if err := consumer.Consume(request.Body, newValue.Interface()); err != nil {
|
||||
if err == io.EOF && p.parameter.Default != nil {
|
||||
target.Set(reflect.ValueOf(p.parameter.Default))
|
||||
return nil
|
||||
}
|
||||
tpe := p.parameter.Type
|
||||
if p.parameter.Format != "" {
|
||||
tpe = p.parameter.Format
|
||||
}
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, nil)
|
||||
tpe := p.parameter.Type
|
||||
if p.parameter.Format != "" {
|
||||
tpe = p.parameter.Format
|
||||
}
|
||||
target.Set(reflect.Indirect(newValue))
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%d: invalid parameter location %q", http.StatusInternalServerError, p.parameter.In)
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, nil)
|
||||
}
|
||||
|
||||
target.Set(reflect.Indirect(newValue))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) typeForSchema(tpe, format string, items *spec.Items) reflect.Type {
|
||||
@@ -261,20 +288,51 @@ func (p *untypedParamBinder) bindValue(data []string, hasKey bool, target reflec
|
||||
if p.parameter.Type == typeArray {
|
||||
return p.setSliceFieldValue(target, p.parameter.Default, data, hasKey)
|
||||
}
|
||||
|
||||
var d string
|
||||
if len(data) > 0 {
|
||||
d = data[len(data)-1]
|
||||
}
|
||||
|
||||
return p.setFieldValue(target, p.parameter.Default, d, hasKey)
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue any, data string, hasKey bool) error { //nolint:gocyclo
|
||||
func (p *untypedParamBinder) isMissingAndRequired(hasKey bool, data string) bool {
|
||||
return p.parameter.Required &&
|
||||
p.parameter.Default == nil &&
|
||||
(!hasKey || (!p.parameter.AllowEmptyValue && data == ""))
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) setByte(target, defVal reflect.Value, tpe, data string) error {
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
target.SetBytes(defVal.Bytes())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
b, err = base64.URLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.SetBytes(b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue any, data string, hasKey bool) error {
|
||||
tpe := p.parameter.Type
|
||||
if p.parameter.Format != "" {
|
||||
tpe = p.parameter.Format
|
||||
}
|
||||
|
||||
if (!hasKey || (!p.parameter.AllowEmptyValue && data == "")) && p.parameter.Required && p.parameter.Default == nil {
|
||||
if p.isMissingAndRequired(hasKey, data) {
|
||||
return errors.Required(p.Name, p.parameter.In, data)
|
||||
}
|
||||
|
||||
@@ -292,27 +350,15 @@ func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue an
|
||||
}
|
||||
|
||||
if tpe == "byte" {
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
target.SetBytes(defVal.Bytes())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
b, err = base64.URLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
}
|
||||
if target.CanSet() {
|
||||
target.SetBytes(b)
|
||||
}
|
||||
return nil
|
||||
return p.setByte(target, defVal, tpe, data)
|
||||
}
|
||||
|
||||
switch target.Kind() { //nolint:exhaustive // we want to check only types that map from a swagger parameter
|
||||
return p.setReflectFieldValue(target, defVal, tpe, data, hasKey)
|
||||
}
|
||||
|
||||
//nolint:gocyclo,cyclop // not much we can simplify further significantly: the big case with all types is unavoidable.
|
||||
func (p *untypedParamBinder) setReflectFieldValue(target, defVal reflect.Value, tpe, data string, hasKey bool) error {
|
||||
switch target.Kind() { // we want to check only types that map from a swagger parameter
|
||||
case reflect.Bool:
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
@@ -327,6 +373,7 @@ func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue an
|
||||
if target.CanSet() {
|
||||
target.SetBool(b)
|
||||
}
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if data == "" {
|
||||
if target.CanSet() {
|
||||
@@ -394,8 +441,8 @@ func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue an
|
||||
target.SetString(value)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
if data == "" && defVal.Kind() == reflect.Ptr {
|
||||
case reflect.Pointer:
|
||||
if data == "" && defVal.Kind() == reflect.Pointer {
|
||||
if target.CanSet() {
|
||||
target.Set(defVal)
|
||||
}
|
||||
@@ -412,6 +459,7 @@ func (p *untypedParamBinder) setFieldValue(target reflect.Value, defaultValue an
|
||||
default:
|
||||
return errors.InvalidType(p.Name, p.parameter.In, tpe, data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -419,20 +467,30 @@ func (p *untypedParamBinder) tryUnmarshaler(target reflect.Value, defaultValue a
|
||||
if !target.CanSet() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// When a type implements encoding.TextUnmarshaler we'll use that instead of reflecting some more
|
||||
if reflect.PointerTo(target.Type()).Implements(textUnmarshalType) {
|
||||
if defaultValue != nil && len(data) == 0 {
|
||||
target.Set(reflect.ValueOf(defaultValue))
|
||||
return true, nil
|
||||
}
|
||||
value := reflect.New(target.Type())
|
||||
if err := value.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(data)); err != nil {
|
||||
return true, err
|
||||
}
|
||||
target.Set(reflect.Indirect(value))
|
||||
ttyp := target.Type()
|
||||
if !reflect.PointerTo(ttyp).Implements(textUnmarshalType) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if defaultValue != nil && len(data) == 0 {
|
||||
target.Set(reflect.ValueOf(defaultValue))
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
|
||||
value := reflect.New(ttyp)
|
||||
if !value.CanInterface() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := value.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(data)); err != nil { //nolint:forcetypeassert // this is guaranteed by the reflect check above
|
||||
return true, err
|
||||
}
|
||||
|
||||
target.Set(reflect.Indirect(value))
|
||||