Update dependencies

This commit is contained in:
Ingo Oppermann
2025-01-22 14:08:52 +01:00
parent aadd734c1d
commit bec9ee3bff
458 changed files with 18644 additions and 7880 deletions

73
go.mod
View File

@@ -5,48 +5,48 @@ go 1.22.5
toolchain go1.23.1
require (
github.com/99designs/gqlgen v0.17.55
github.com/Masterminds/semver/v3 v3.3.0
github.com/adhocore/gronx v1.19.3
github.com/99designs/gqlgen v0.17.63
github.com/Masterminds/semver/v3 v3.3.1
github.com/adhocore/gronx v1.19.5
github.com/andybalholm/brotli v1.1.1
github.com/atrox/haikunatorgo/v2 v2.0.1
github.com/caddyserver/certmagic v0.21.4
github.com/datarhei/gosrt v0.7.0
github.com/caddyserver/certmagic v0.21.7
github.com/datarhei/gosrt v0.8.0
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e
github.com/dolthub/swiss v0.2.1
github.com/fujiwara/shapeio v1.0.0
github.com/go-playground/validator/v10 v10.22.1
github.com/go-playground/validator/v10 v10.24.0
github.com/gobwas/glob v0.2.3
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/gops v0.3.28
github.com/google/uuid v1.6.0
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/raft v1.7.1
github.com/hashicorp/raft-boltdb/v2 v2.3.0
github.com/hashicorp/raft v1.7.2
github.com/hashicorp/raft-boltdb/v2 v2.3.1
github.com/invopop/jsonschema v0.4.0
github.com/joho/godotenv v1.5.1
github.com/klauspost/compress v1.17.11
github.com/klauspost/cpuid/v2 v2.2.8
github.com/labstack/echo/v4 v4.12.0
github.com/klauspost/cpuid/v2 v2.2.9
github.com/labstack/echo/v4 v4.13.3
github.com/lestrrat-go/strftime v1.1.0
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/lithammer/shortuuid/v4 v4.2.0
github.com/mattn/go-isatty v0.0.20
github.com/minio/minio-go/v7 v7.0.80
github.com/minio/minio-go/v7 v7.0.84
github.com/prometheus/client_golang v1.20.5
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/puzpuzpuz/xsync/v3 v3.4.1
github.com/shirou/gopsutil/v3 v3.24.5
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
github.com/swaggo/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.4
github.com/tklauser/go-sysconf v0.3.14
github.com/vektah/gqlparser/v2 v2.5.18
github.com/vektah/gqlparser/v2 v2.5.21
github.com/xeipuuv/gojsonschema v1.2.0
go.etcd.io/bbolt v1.3.11
go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.28.0
golang.org/x/mod v0.21.0
golang.org/x/crypto v0.32.0
golang.org/x/mod v0.22.0
)
require (
@@ -58,12 +58,12 @@ require (
github.com/boltdb/bolt v1.3.1 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
@@ -73,10 +73,11 @@ require (
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-msgpack/v2 v2.1.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
@@ -86,25 +87,24 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mholt/acmez/v2 v2.0.3 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mholt/acmez/v3 v3.0.1 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/swaggo/files/v2 v2.0.1 // indirect
github.com/swaggo/files/v2 v2.0.2 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect
github.com/urfave/cli/v2 v2.27.4 // indirect
github.com/urfave/cli/v2 v2.27.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
@@ -113,13 +113,14 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/protobuf v1.36.3 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

203
go.sum
View File

@@ -1,20 +1,22 @@
github.com/99designs/gqlgen v0.17.55 h1:3vzrNWYyzSZjGDFo68e5j9sSauLxfKvLp+6ioRokVtM=
github.com/99designs/gqlgen v0.17.55/go.mod h1:3Bq768f8hgVPGZxL8aY9MaYmbxa6llPM/qu1IGH1EJo=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/99designs/gqlgen v0.17.63 h1:HCdaYDPd9HqUXRchEvmE3EFzELRwLlaJ8DBuyC8Cqto=
github.com/99designs/gqlgen v0.17.63/go.mod h1:sVCM2iwIZisJjTI/DEC3fpH+HFgxY1496ZJ+jbT9IjA=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0=
github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U=
github.com/adhocore/gronx v1.19.3 h1:BNl3pGgiKy12Tt1dNIhzEGL2l+KikeV6NN3A/B2AJLg=
github.com/adhocore/gronx v1.19.3/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
github.com/adhocore/gronx v1.19.5 h1:cwIG4nT1v9DvadxtHBe6MzE+FZ1JDvAUC45U2fl4eSQ=
github.com/adhocore/gronx v1.19.5/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
@@ -35,8 +37,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
github.com/caddyserver/certmagic v0.21.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
github.com/caddyserver/certmagic v0.21.7/go.mod h1:LCPG3WLxcnjVKl/xpjzM0gqh0knrKKKiO5WVttX2eEI=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -44,10 +46,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/datarhei/gosrt v0.7.0 h1:1/IY66HVVgqGA9zkmL5l6jUFuI8t/76WkuamSkJqHqs=
github.com/datarhei/gosrt v0.7.0/go.mod h1:wTDoyog1z4au8Fd/QJBQAndzvccuxjqUL/qMm0EyJxE=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/datarhei/gosrt v0.8.0 h1:fna/FFRbVN7LvwAt2cR6pxwFz7rm979vdRzGfh9zbNM=
github.com/datarhei/gosrt v0.8.0/go.mod h1:ab1q3G0/DxsEU5iH/OCMaqYOWAqUI0SAbJ2sRKeQblA=
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e h1:Qc/0D4xvXrazFkoi/4UGqO15yQ1JN5I8h7RwdzCLgTY=
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -67,16 +69,18 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fujiwara/shapeio v1.0.0 h1:xG5D9oNqCSUUbryZ/jQV3cqe1v2suEjwPIcEg1gKM8M=
github.com/fujiwara/shapeio v1.0.0/go.mod h1:LmEmu6L/8jetyj1oewewFb7bZCNRwE7wLCUNzDLaLVA=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -94,31 +98,40 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark=
github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -129,6 +142,8 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0=
@@ -141,12 +156,12 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/raft v1.7.1 h1:ytxsNx4baHsRZrhUcbt3+79zc4ly8qm7pi0393pSchY=
github.com/hashicorp/raft v1.7.1/go.mod h1:hUeiEwQQR/Nk2iKDD0dkEhklSsu3jcAcqvPzPoZSAEM=
github.com/hashicorp/raft v1.7.2 h1:pyvxhfJ4R8VIAlHKvLoKQWElZspsCVT6YWuxVxsPAgc=
github.com/hashicorp/raft v1.7.2/go.mod h1:DfvCGFxpAUPE0L4Uc8JLlTPtc3GzSbdH0MTJCLgnmJQ=
github.com/hashicorp/raft-boltdb v0.0.0-20230125174641-2a8082862702 h1:RLKEcCuKcZ+qp2VlaaZsYZfLOmIiuJNpEi48Rl8u9cQ=
github.com/hashicorp/raft-boltdb v0.0.0-20230125174641-2a8082862702/go.mod h1:nTakvJ4XYq45UXtn0DbwR4aU9ZdjlnIenpbs6Cd+FM0=
github.com/hashicorp/raft-boltdb/v2 v2.3.0 h1:fPpQR1iGEVYjZ2OELvUHX600VAK5qmdnDEv3eXOwZUA=
github.com/hashicorp/raft-boltdb/v2 v2.3.0/go.mod h1:YHukhB04ChJsLHLJEUD6vjFyLX2L3dsX3wPBZcX4tmc=
github.com/hashicorp/raft-boltdb/v2 v2.3.1 h1:ackhdCNPKblmOhjEU9+4lHSJYFkJd6Jqyvj6eW9pwkc=
github.com/hashicorp/raft-boltdb/v2 v2.3.1/go.mod h1:n4S+g43dXF1tqDT+yzcXHhXM6y7MrlUd3TTwGRcUvQE=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
@@ -156,15 +171,20 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -175,8 +195,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -187,32 +207,29 @@ github.com/lestrrat-go/strftime v1.1.0 h1:gMESpZy44/4pXLO/m+sL0yBd1W6LjgjrrD4a68
github.com/lestrrat-go/strftime v1.1.0/go.mod h1:uzeIB52CeUJenCo1syghlugshMysrqUT51HlxphXVeI=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
github.com/mholt/acmez/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8=
github.com/mholt/acmez/v3 v3.0.1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
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.0.80 h1:2mdUHXEykRdY/BigLt3Iuu1otL0JTogT0Nmltg0wujk=
github.com/minio/minio-go/v7 v7.0.80/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -220,10 +237,12 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -234,6 +253,8 @@ github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -243,15 +264,19 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/puzpuzpuz/xsync/v3 v3.4.1 h1:wWXLKXwzpsduC3kUSahiL45MWxkGb+AQG0dsri4iftA=
github.com/puzpuzpuz/xsync/v3 v3.4.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -268,6 +293,7 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -277,12 +303,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk=
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
@@ -290,14 +316,14 @@ github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYN
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.5.18 h1:zSND3GtutylAQ1JpWnTHcqtaRZjl+y3NROeW8vuNo6Y=
github.com/vektah/gqlparser/v2 v2.5.18/go.mod h1:6HLzf7JKv9Fi3APymudztFQNmLXR5qJeEo6BOFcXVfc=
github.com/vektah/gqlparser/v2 v2.5.21 h1:Zw1rG2dr1pRR4wqwbVq4d6+xk2f4ut/yo+hwr4QjE08=
github.com/vektah/gqlparser/v2 v2.5.21/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -327,50 +353,74 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -380,6 +430,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -12,6 +12,12 @@ linters-settings:
- paramTypeCombine
- preferFprint
- yodaStyleExpr
govet:
enable-all: true
disable:
- fieldalignment
- shadow
- unusedwrite # TODO: fix and enable
errcheck:
exclude-functions:
- (io.Writer).Write
@@ -59,6 +65,7 @@ linters-settings:
- bool-compare
- compares
- empty
- encoded-compare
- error-is-as
- error-nil
- expected-actual
@@ -74,7 +81,9 @@ linters:
disable-all: true
enable:
- bodyclose
- copyloopvar
- dupl
- dupword
- errcheck
- gocritic
- gofmt
@@ -84,6 +93,7 @@ linters:
- ineffassign
- misspell
- nakedret
- nolintlint
- perfsprint
- prealloc
- revive

View File

@@ -1,4 +1,4 @@
Copyright (c) 2020 gqlgen authors
Copyright (c) 2025 gqlgen authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -62,9 +62,9 @@ type User {
}
```
You need to tell gqlgen that it should only fetch friends if the user requested it. There are two ways to do this;
You need to tell gqlgen that it should only fetch friends if the user requested it. There are two ways to do this:
- #### Using Custom Models
### Using Custom Models
Write a custom model that omits the friends field:
@@ -84,7 +84,7 @@ models:
model: github.com/you/pkg/model.User # go import path to the User struct above
```
- #### Using Explicit Resolvers
### Using Explicit Resolvers
If you want to keep using the generated model, mark the field as requiring a resolver explicitly in `gqlgen.yml` like this:

View File

@@ -1,7 +1,7 @@
How to write tests for gqlgen
===
Testing generated code is a little tricky, heres how its currently set up.
Testing generated code is a little tricky, here's how its currently set up.
### Testing responses from a server

View File

@@ -117,16 +117,6 @@ func Generate(cfg *config.Config, option ...Option) error {
return fmt.Errorf("merging type systems failed: %w", err)
}
if err = codegen.GenerateCode(data); err != nil {
return fmt.Errorf("generating core failed: %w", err)
}
if !cfg.SkipModTidy {
if err = cfg.Packages.ModTidy(); err != nil {
return fmt.Errorf("tidy failed: %w", err)
}
}
for _, p := range plugins {
if mut, ok := p.(plugin.CodeGenerator); ok {
err := mut.GenerateCode(data)
@@ -140,6 +130,11 @@ func Generate(cfg *config.Config, option ...Option) error {
return fmt.Errorf("generating core failed: %w", err)
}
if !cfg.SkipModTidy {
if err = cfg.Packages.ModTidy(); err != nil {
return fmt.Errorf("tidy failed: %w", err)
}
}
if !cfg.SkipValidation {
if err := validate(cfg); err != nil {
return fmt.Errorf("validation failed: %w", err)

View File

@@ -1,10 +1,20 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{ range $name, $args := .Args }}
func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
{{ if $useFunctionSyntaxForExecutionContext -}}
func {{ $name }}(ctx context.Context, ec *executionContext, rawArgs map[string]any) (map[string]any, error) {
{{- else -}}
func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
{{- end }}
var err error
args := map[string]interface{}{}
args := map[string]any{}
{{- range $i, $arg := . }}
{{ if $useFunctionSyntaxForExecutionContext -}}
arg{{$i}}, err := {{ $name }}{{$arg.Name | go}}(ctx, ec, rawArgs)
{{- else -}}
arg{{$i}}, err := ec.{{ $name }}{{$arg.Name | go}}(ctx, rawArgs)
{{- end }}
if err != nil {
return nil, err
}
@@ -14,31 +24,44 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]
}
{{- range $i, $arg := . }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func {{ $name }}{{$arg.Name | go}}(
ctx context.Context,
ec *executionContext,
rawArgs map[string]any,
) ({{ $arg.TypeReference.GO | ref}}, error) {
{{- else -}}
func (ec *executionContext) {{ $name }}{{$arg.Name | go}}(
ctx context.Context,
rawArgs map[string]interface{},
rawArgs map[string]any,
) ({{ $arg.TypeReference.GO | ref}}, error) {
{{- end }}
{{- if not .CallArgumentDirectivesWithNull}}
// We won't call the directive if the argument is null.
// Set call_argument_directives_with_null to true to call directives
// even if the argument is null.
_, ok := rawArgs[{{$arg.Name|quote}}]
if !ok {
{{- /*
We won't call the directive if the argument is null.
Set call_argument_directives_with_null to true to call directives
even if the argument is null.
*/ -}}
if _, ok := rawArgs[{{$arg.Name|quote}}]; !ok {
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil
}
{{end}}
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField({{$arg.Name|quote}}))
{{- if $arg.ImplDirectives }}
directive0 := func(ctx context.Context) (interface{}, error) {
directive0 := func(ctx context.Context) (any, error) {
tmp, ok := rawArgs[{{$arg.Name|quote}}]
if !ok {
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil
}
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $arg.TypeReference.UnmarshalFunc }}(ctx, ec, tmp)
{{- else -}}
return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp)
{{- end }}
}
{{ template "implDirectives" $arg }}
{{ template "implDirectives" (dict "Field" $arg "UseFunctionSyntaxForExecutionContext" $useFunctionSyntaxForExecutionContext) }}
tmp, err := directive{{$arg.ImplDirectives|len}}(ctx)
if err != nil {
var zeroVal {{ $arg.TypeReference.GO | ref}}
@@ -57,7 +80,11 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]
}
{{- else }}
if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok {
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $arg.TypeReference.UnmarshalFunc }}(ctx, ec, tmp)
{{- else -}}
return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp)
{{- end }}
}
var zeroVal {{ $arg.TypeReference.GO | ref}}

View File

@@ -63,11 +63,11 @@ func (b *Binder) FindTypeFromName(name string) (types.Type, error) {
func (b *Binder) FindType(pkgName, typeName string) (types.Type, error) {
if pkgName == "" {
if typeName == "map[string]interface{}" {
if typeName == "map[string]any" || typeName == "map[string]interface{}" {
return MapType, nil
}
if typeName == "interface{}" {
if typeName == "any" || typeName == "interface{}" {
return InterfaceType, nil
}
}
@@ -103,11 +103,11 @@ func (b *Binder) DefaultUserObject(name string) (types.Type, error) {
return nil, fmt.Errorf("%s not found in typemap", name)
}
if models[0] == "map[string]interface{}" {
if models[0] == "map[string]any" || models[0] == "map[string]interface{}" {
return MapType, nil
}
if models[0] == "interface{}" {
if models[0] == "any" || models[0] == "interface{}" {
return InterfaceType, nil
}
@@ -126,7 +126,7 @@ func (b *Binder) DefaultUserObject(name string) (types.Type, error) {
func (b *Binder) FindObject(pkgName, typeName string) (types.Object, error) {
if pkgName == "" {
return nil, errors.New("package cannot be nil")
return nil, fmt.Errorf("package cannot be nil in FindObject for type: %s", typeName)
}
pkg := b.pkgs.LoadWithTypes(pkgName)
@@ -258,7 +258,7 @@ func (ref *TypeReference) IsPtrToSlice() bool {
func (ref *TypeReference) IsPtrToIntf() bool {
if ref.IsPtr() {
_, isPointerToInterface := ref.GO.(*types.Pointer).Elem().(*types.Interface)
_, isPointerToInterface := types.Unalias(ref.GO.(*types.Pointer).Elem()).(*types.Interface)
return isPointerToInterface
}
return false
@@ -344,7 +344,7 @@ func isIntf(t types.Type) bool {
if t == nil {
return true
}
_, ok := t.(*types.Interface)
_, ok := types.Unalias(t).(*types.Interface)
return ok
}
@@ -398,7 +398,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
}
for _, model := range b.cfg.Models[schemaType.Name()].Model {
if model == "map[string]interface{}" {
if model == "map[string]any" || model == "map[string]interface{}" {
if !isMap(bindTarget) {
continue
}
@@ -410,7 +410,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
}, nil
}
if model == "interface{}" {
if model == "any" || model == "interface{}" {
if !isIntf(bindTarget) {
continue
}
@@ -499,6 +499,7 @@ func isValid(t types.Type) bool {
}
func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {
base = types.Unalias(base)
if t.Elem != nil {
child := b.CopyModifiersFromAst(t.Elem, base)
if _, isStruct := child.Underlying().(*types.Struct); isStruct && !b.cfg.OmitSliceElementPointers {
@@ -528,11 +529,11 @@ func IsNilable(t types.Type) bool {
return IsNilable(namedType.Underlying())
}
_, isPtr := t.(*types.Pointer)
_, isMap := t.(*types.Map)
_, isNilableMap := t.(*types.Map)
_, isInterface := t.(*types.Interface)
_, isSlice := t.(*types.Slice)
_, isChan := t.(*types.Chan)
return isPtr || isMap || isInterface || isSlice || isChan
return isPtr || isNilableMap || isInterface || isSlice || isChan
}
func hasMethod(it types.Type, name string) bool {
@@ -553,8 +554,9 @@ func hasMethod(it types.Type, name string) bool {
}
func basicUnderlying(it types.Type) *types.Basic {
it = types.Unalias(it)
if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem()
it = types.Unalias(ptr.Elem())
}
namedType, ok := it.(*types.Named)
if !ok {

View File

@@ -22,30 +22,31 @@ import (
)
type Config struct {
SchemaFilename StringList `yaml:"schema,omitempty"`
Exec ExecConfig `yaml:"exec"`
Model PackageConfig `yaml:"model,omitempty"`
Federation PackageConfig `yaml:"federation,omitempty"`
Resolver ResolverConfig `yaml:"resolver,omitempty"`
AutoBind []string `yaml:"autobind"`
Models TypeMap `yaml:"models,omitempty"`
StructTag string `yaml:"struct_tag,omitempty"`
Directives map[string]DirectiveConfig `yaml:"directives,omitempty"`
GoBuildTags StringList `yaml:"go_build_tags,omitempty"`
GoInitialisms GoInitialismsConfig `yaml:"go_initialisms,omitempty"`
OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"`
OmitGetters bool `yaml:"omit_getters,omitempty"`
OmitInterfaceChecks bool `yaml:"omit_interface_checks,omitempty"`
OmitComplexity bool `yaml:"omit_complexity,omitempty"`
OmitGQLGenFileNotice bool `yaml:"omit_gqlgen_file_notice,omitempty"`
OmitGQLGenVersionInFileNotice bool `yaml:"omit_gqlgen_version_in_file_notice,omitempty"`
OmitRootModels bool `yaml:"omit_root_models,omitempty"`
OmitResolverFields bool `yaml:"omit_resolver_fields,omitempty"`
OmitPanicHandler bool `yaml:"omit_panic_handler,omitempty"`
SchemaFilename StringList `yaml:"schema,omitempty"`
Exec ExecConfig `yaml:"exec"`
Model PackageConfig `yaml:"model,omitempty"`
Federation PackageConfig `yaml:"federation,omitempty"`
Resolver ResolverConfig `yaml:"resolver,omitempty"`
AutoBind []string `yaml:"autobind"`
Models TypeMap `yaml:"models,omitempty"`
StructTag string `yaml:"struct_tag,omitempty"`
Directives map[string]DirectiveConfig `yaml:"directives,omitempty"`
GoBuildTags StringList `yaml:"go_build_tags,omitempty"`
GoInitialisms GoInitialismsConfig `yaml:"go_initialisms,omitempty"`
OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"`
OmitGetters bool `yaml:"omit_getters,omitempty"`
OmitInterfaceChecks bool `yaml:"omit_interface_checks,omitempty"`
OmitComplexity bool `yaml:"omit_complexity,omitempty"`
OmitGQLGenFileNotice bool `yaml:"omit_gqlgen_file_notice,omitempty"`
OmitGQLGenVersionInFileNotice bool `yaml:"omit_gqlgen_version_in_file_notice,omitempty"`
OmitRootModels bool `yaml:"omit_root_models,omitempty"`
OmitResolverFields bool `yaml:"omit_resolver_fields,omitempty"`
OmitPanicHandler bool `yaml:"omit_panic_handler,omitempty"`
UseFunctionSyntaxForExecutionContext bool `yaml:"use_function_syntax_for_execution_context,omitempty"`
// If this is set to true, argument directives that
// decorate a field with a null value will still be called.
//
// This enables argumment directives to not just mutate
// This enables argument directives to not just mutate
// argument values but to set them even if they're null.
CallArgumentDirectivesWithNull bool `yaml:"call_argument_directives_with_null,omitempty"`
StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"`
@@ -228,6 +229,7 @@ func (c *Config) Init() error {
if c.Packages == nil {
c.Packages = code.NewPackages(
code.WithBuildTags(c.GoBuildTags...),
code.PackagePrefixToCache("github.com/99designs/gqlgen/graphql"),
)
}
@@ -645,7 +647,10 @@ func (tm TypeMap) ReferencedPackages() []string {
for _, typ := range tm {
for _, model := range typ.Model {
if model == "map[string]interface{}" || model == "interface{}" {
if model == "map[string]any" ||
model == "map[string]interface{}" ||
model == "any" ||
model == "interface{}" {
continue
}
pkg, _ := code.PkgAndType(model)
@@ -807,11 +812,15 @@ func (c *Config) injectBuiltins() {
"Float": {Model: StringList{"github.com/99designs/gqlgen/graphql.FloatContext"}},
"String": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}},
"Boolean": {Model: StringList{"github.com/99designs/gqlgen/graphql.Boolean"}},
"Int": {Model: StringList{
"github.com/99designs/gqlgen/graphql.Int",
"github.com/99designs/gqlgen/graphql.Int32",
"github.com/99designs/gqlgen/graphql.Int64",
}},
"Int": {
// FIXME: using int / int64 for Int is not spec compliant and introduces
// security risks. We should default to int32.
Model: StringList{
"github.com/99designs/gqlgen/graphql.Int",
"github.com/99designs/gqlgen/graphql.Int32",
"github.com/99designs/gqlgen/graphql.Int64",
},
},
"ID": {
Model: StringList{
"github.com/99designs/gqlgen/graphql.ID",
@@ -828,6 +837,12 @@ func (c *Config) injectBuiltins() {
// These are additional types that are injected if defined in the schema as scalars.
extraBuiltins := TypeMap{
"Int64": {
Model: StringList{
"github.com/99designs/gqlgen/graphql.Int",
"github.com/99designs/gqlgen/graphql.Int64",
},
},
"Time": {Model: StringList{"github.com/99designs/gqlgen/graphql.Time"}},
"Map": {Model: StringList{"github.com/99designs/gqlgen/graphql.Map"}},
"Upload": {Model: StringList{"github.com/99designs/gqlgen/graphql.Upload"}},
@@ -845,6 +860,7 @@ func (c *Config) LoadSchema() error {
if c.Packages != nil {
c.Packages = code.NewPackages(
code.WithBuildTags(c.GoBuildTags...),
code.PackagePrefixToCache("github.com/99designs/gqlgen/graphql"),
)
}

View File

@@ -20,6 +20,12 @@ type ExecConfig struct {
// Only for follow-schema layout:
FilenameTemplate string `yaml:"filename_template,omitempty"` // String template with {name} as placeholder for base name.
DirName string `yaml:"dir"`
// Maximum number of goroutines in concurrency to use when running multiple child resolvers
// Suppressing the number of goroutines generated can reduce memory consumption per request,
// but processing time may increase due to the reduced number of concurrences
// Default: 0 (unlimited)
WorkerLimit uint `yaml:"worker_limit"`
}
type ExecLayout string

View File

@@ -19,6 +19,7 @@ type ResolverConfig struct {
DirName string `yaml:"dir"`
OmitTemplateComment bool `yaml:"omit_template_comment,omitempty"`
ResolverTemplate string `yaml:"resolver_template,omitempty"`
PreserveResolver bool `yaml:"preserve_resolver,omitempty"`
}
type ResolverLayout string

View File

@@ -2,7 +2,6 @@ package codegen
import (
"fmt"
"strconv"
"strings"
"github.com/vektah/gqlparser/v2/ast"
@@ -143,7 +142,7 @@ func (d *Directive) CallArgs() string {
args := []string{"ctx", "obj", "n"}
for _, arg := range d.Args {
args = append(args, "args["+strconv.Quote(arg.Name)+"].("+templates.CurrentImports.LookupType(arg.TypeReference.GO)+")")
args = append(args, fmt.Sprintf("args[%q].(%s)", arg.Name, templates.CurrentImports.LookupType(arg.TypeReference.GO)))
}
return strings.Join(args, ", ")
@@ -169,13 +168,13 @@ func (d *Directive) CallName() string {
}
func (d *Directive) Declaration() string {
res := d.CallName() + " func(ctx context.Context, obj interface{}, next graphql.Resolver"
res := d.CallName() + " func(ctx context.Context, obj any, next graphql.Resolver"
for _, arg := range d.Args {
res += fmt.Sprintf(", %s %s", templates.ToGoPrivate(arg.Name), templates.CurrentImports.LookupType(arg.TypeReference.GO))
}
res += ") (res interface{}, err error)"
res += ") (res any, err error)"
return res
}

View File

@@ -1,16 +1,28 @@
{{ define "implDirectives" }}{{ $in := .DirectiveObjName }}
{{ $zeroVal := .TypeReference.GO | ref}}
{{- range $i, $directive := .ImplDirectives -}}
directive{{add $i 1}} := func(ctx context.Context) (interface{}, error) {
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{ define "implDirectives" }}
{{ $in := .Field.DirectiveObjName }}
{{ $useFunctionSyntaxForExecutionContext := .UseFunctionSyntaxForExecutionContext }}
{{ $zeroVal := .Field.TypeReference.GO | ref}}
{{- range $i, $directive := .Field.ImplDirectives -}}
directive{{add $i 1}} := func(ctx context.Context) (any, error) {
{{- range $arg := $directive.Args }}
{{- if notNil "Value" $arg }}
{{ if $useFunctionSyntaxForExecutionContext -}}
{{ $arg.VarName }}, err := {{ $arg.TypeReference.UnmarshalFunc }}(ctx, ec, {{ $arg.Value | dump }})
{{- else -}}
{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Value | dump }})
{{- end }}
if err != nil{
var zeroVal {{$zeroVal}}
return zeroVal, err
}
{{- else if notNil "Default" $arg }}
{{ if $useFunctionSyntaxForExecutionContext -}}
{{ $arg.VarName }}, err := {{ $arg.TypeReference.UnmarshalFunc }}(ctx, ec, {{ $arg.Default | dump }})
{{- else -}}
{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Default | dump }})
{{- end }}
if err != nil{
var zeroVal {{$zeroVal}}
return zeroVal, err
@@ -29,20 +41,25 @@
{{ end }}
{{define "queryDirectives"}}
{{ $useFunctionSyntaxForExecutionContext := .UseFunctionSyntaxForExecutionContext }}
for _, d := range obj.Directives {
switch d.Name {
{{- range $directive := . }}
{{- range $directive := .DirectiveList }}
case "{{$directive.Name}}":
{{- if $directive.Args }}
rawArgs := d.ArgumentMap(ec.Variables)
{{ if $useFunctionSyntaxForExecutionContext -}}
args, err := {{ $directive.ArgsFunc }}(ctx,ec,rawArgs)
{{- else -}}
args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
{{- end }}
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
next = func(ctx context.Context) (any, error) {
{{- template "callDirective" $directive -}}
}
{{- end }}
@@ -70,26 +87,42 @@
{{end}}
{{ if .Directives.LocationDirectives "QUERY" }}
func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
{{ template "queryDirectives" .Directives.LocationDirectives "QUERY" }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func _queryMiddleware(ctx context.Context, ec *executionContext, obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) graphql.Marshaler {
{{- else -}}
func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) graphql.Marshaler {
{{- end }}
{{ template "queryDirectives" (dict "DirectiveList" (.Directives.LocationDirectives "QUERY") "UseFunctionSyntaxForExecutionContext" $useFunctionSyntaxForExecutionContext) }}
}
{{ end }}
{{ if .Directives.LocationDirectives "MUTATION" }}
func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
{{ template "queryDirectives" .Directives.LocationDirectives "MUTATION" }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func _mutationMiddleware(ctx context.Context, ec *executionContext, obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) graphql.Marshaler {
{{- else -}}
func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) graphql.Marshaler {
{{- end }}
{{ template "queryDirectives" (dict "DirectiveList" (.Directives.LocationDirectives "MUTATION") "UseFunctionSyntaxForExecutionContext" $useFunctionSyntaxForExecutionContext) }}
}
{{ end }}
{{ if .Directives.LocationDirectives "SUBSCRIPTION" }}
func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) func(ctx context.Context) graphql.Marshaler {
{{ if $useFunctionSyntaxForExecutionContext -}}
func _subscriptionMiddleware(ctx context.Context, ec *executionContext, obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) func(ctx context.Context) graphql.Marshaler {
{{- else -}}
func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (any, error)) func(ctx context.Context) graphql.Marshaler {
{{- end }}
for _, d := range obj.Directives {
switch d.Name {
{{- range $directive := .Directives.LocationDirectives "SUBSCRIPTION" }}
case "{{$directive.Name}}":
{{- if $directive.Args }}
rawArgs := d.ArgumentMap(ec.Variables)
{{ if $useFunctionSyntaxForExecutionContext -}}
args, err := {{ $directive.ArgsFunc }}(ctx,ec,rawArgs)
{{- else -}}
args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
{{- end }}
if err != nil {
ec.Error(ctx, err)
return func(ctx context.Context) graphql.Marshaler {
@@ -98,7 +131,7 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as
}
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
next = func(ctx context.Context) (any, error) {
{{- template "callDirective" $directive -}}
}
{{- end }}
@@ -122,7 +155,11 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as
{{ end }}
{{ if .Directives.LocationDirectives "FIELD" }}
func (ec *executionContext) _fieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) interface{} {
{{ if $useFunctionSyntaxForExecutionContext -}}
func _fieldMiddleware(ctx context.Context, ec *executionContext, obj any, next graphql.Resolver) any {
{{- else -}}
func (ec *executionContext) _fieldMiddleware(ctx context.Context, obj any, next graphql.Resolver) any {
{{- end }}
{{- if .Directives.LocationDirectives "FIELD" }}
fc := graphql.GetFieldContext(ctx)
for _, d := range fc.Field.Directives {
@@ -131,14 +168,18 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as
case "{{$directive.Name}}":
{{- if $directive.Args }}
rawArgs := d.ArgumentMap(ec.Variables)
{{ if $useFunctionSyntaxForExecutionContext -}}
args, err := {{ $directive.ArgsFunc }}(ctx,ec,rawArgs)
{{- else -}}
args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs)
{{- end }}
if err != nil {
ec.Error(ctx, err)
return nil
}
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
next = func(ctx context.Context) (any, error) {
{{- template "callDirective" $directive -}}
}
{{- end }}

View File

@@ -30,7 +30,7 @@ type Field struct {
Args []*FieldArgument // A list of arguments to be passed to this field
MethodHasContext bool // If this is bound to a go method, does the method also take a context
NoErr bool // If this is bound to a go method, does that method have an error as the second argument
VOkFunc bool // If this is bound to a go method, is it of shape (interface{}, bool)
VOkFunc bool // If this is bound to a go method, is it of shape (any, bool)
Object *Object // A link back to the parent object
Default any // The default value
Stream bool // does this field return a channel?
@@ -602,11 +602,11 @@ func (f *Field) CallArgs() string {
if iface, ok := arg.TypeReference.GO.(*types.Interface); ok && iface.Empty() {
tmp = fmt.Sprintf(`
func () interface{} {
func () any {
if fc.Args["%s"] == nil {
return nil
}
return fc.Args["%s"].(interface{})
return fc.Args["%s"].(any)
}()`, arg.Name, arg.Name,
)
}

View File

@@ -1,11 +1,21 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{- range $object := .Objects }}{{- range $field := $object.Fields }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func _{{$object.Name}}_{{$field.Name}}(ctx context.Context, ec *executionContext, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret {{ if $object.Stream }}func(ctx context.Context){{ end }}graphql.Marshaler) {
{{- else -}}
func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret {{ if $object.Stream }}func(ctx context.Context){{ end }}graphql.Marshaler) {
{{- end }}
{{- $null := "graphql.Null" }}
{{- if $object.Stream }}
{{- $null = "nil" }}
{{- end }}
{{ if $useFunctionSyntaxForExecutionContext -}}
fc, err := {{ $field.FieldContextFunc }}(ctx, ec, field)
{{- else -}}
fc, err := ec.{{ $field.FieldContextFunc }}(ctx, field)
{{- end }}
if err != nil {
return {{ $null }}
}
@@ -25,15 +35,23 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
res := {{ $field.TypeReference.GO | ref }}{}
{{- end }}
fc.Result = res
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $field.TypeReference.MarshalFunc }}(ctx, ec, field.Selections, res)
{{- else -}}
return ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res)
{{- end }}
{{- else}}
{{- if $.AllDirectives.LocationDirectives "FIELD" }}
resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) {
{{ template "field" $field }}
{{ if $useFunctionSyntaxForExecutionContext -}}
resTmp := _fieldMiddleware(ctx, ec, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (any, error) {
{{- else -}}
resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (any, error) {
{{- end }}
{{ template "field" (dict "Field" $field "UseFunctionSyntaxForExecutionContext" $useFunctionSyntaxForExecutionContext) }}
})
{{ else }}
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
{{ template "field" $field }}
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
{{ template "field" (dict "Field" $field "UseFunctionSyntaxForExecutionContext" $useFunctionSyntaxForExecutionContext) }}
})
if err != nil {
ec.Error(ctx, err)
@@ -59,7 +77,11 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
w.Write([]byte{'{'})
graphql.MarshalString(field.Alias).MarshalGQL(w)
w.Write([]byte{':'})
{{ if $useFunctionSyntaxForExecutionContext -}}
{{ $field.TypeReference.MarshalFunc }}(ctx, ec, field.Selections, res).MarshalGQL(w)
{{- else -}}
ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res).MarshalGQL(w)
{{- end }}
w.Write([]byte{'}'})
})
case <-ctx.Done():
@@ -69,12 +91,20 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
{{- else }}
res := resTmp.({{$field.TypeReference.GO | ref}})
fc.Result = res
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $field.TypeReference.MarshalFunc }}(ctx, ec, field.Selections, res)
{{- else -}}
return ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res)
{{- end }}
{{- end }}
{{- end }}
}
{{ if $useFunctionSyntaxForExecutionContext -}}
func {{ $field.FieldContextFunc }}({{ if not $field.Args }}_{{ else }}ctx{{ end }} context.Context, ec *executionContext, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
{{- else -}}
func (ec *executionContext) {{ $field.FieldContextFunc }}({{ if not $field.Args }}_{{ else }}ctx{{ end }} context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
{{- end }}
fc = &graphql.FieldContext{
Object: {{quote $field.Object.Name}},
Field: field,
@@ -89,7 +119,11 @@ func (ec *executionContext) {{ $field.FieldContextFunc }}({{ if not $field.Args
switch field.Name {
{{- range $f := $field.TypeReference.Definition.Fields }}
case "{{ $f.Name }}":
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $field.ChildFieldContextFunc $f.Name }}(ctx, ec, field)
{{- else -}}
return ec.{{ $field.ChildFieldContextFunc $f.Name }}(ctx, field)
{{- end }}
{{- end }}
}
return nil, fmt.Errorf("no field named %q was found under type {{ $field.TypeReference.Definition.Name }}", field.Name)
@@ -106,7 +140,11 @@ func (ec *executionContext) {{ $field.FieldContextFunc }}({{ if not $field.Args
}()
{{- end }}
ctx = graphql.WithFieldContext(ctx, fc)
{{ if $useFunctionSyntaxForExecutionContext -}}
if fc.Args, err = {{ $field.ArgsFunc }}(ctx, ec, field.ArgumentMap(ec.Variables)); err != nil {
{{- else -}}
if fc.Args, err = ec.{{ $field.ArgsFunc }}(ctx, field.ArgumentMap(ec.Variables)); err != nil {
{{- end }}
ec.Error(ctx, err)
return fc, err
}
@@ -117,26 +155,27 @@ func (ec *executionContext) {{ $field.FieldContextFunc }}({{ if not $field.Args
{{- end }}{{- end}}
{{ define "field" }}
{{- if .HasDirectives -}}
directive0 := func(rctx context.Context) (interface{}, error) {
{{- $useFunctionSyntaxForExecutionContext := .UseFunctionSyntaxForExecutionContext -}}
{{- if .Field.HasDirectives -}}
directive0 := func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
{{ template "fieldDefinition" . }}
{{ template "fieldDefinition" .Field }}
}
{{ template "implDirectives" . }}
tmp, err := directive{{.ImplDirectives|len}}(rctx)
{{ template "implDirectives" (dict "Field" .Field "UseFunctionSyntaxForExecutionContext" $useFunctionSyntaxForExecutionContext) }}
tmp, err := directive{{.Field.ImplDirectives|len}}(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.({{if .Stream}}<-chan {{end}}{{ .TypeReference.GO | ref }}) ; ok {
if data, ok := tmp.({{if .Field.Stream}}<-chan {{end}}{{ .Field.TypeReference.GO | ref }}) ; ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be {{if .Stream}}<-chan {{end}}{{ .TypeReference.GO }}`, tmp)
return nil, fmt.Errorf(`unexpected type %T from directive, should be {{if .Field.Stream}}<-chan {{end}}{{ .Field.TypeReference.GO }}`, tmp)
{{- else -}}
ctx = rctx // use context from middleware stack in children
{{ template "fieldDefinition" . }}
{{ template "fieldDefinition" .Field }}
{{- end -}}
{{ end }}

View File

@@ -4,9 +4,7 @@ import (
"embed"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/vektah/gqlparser/v2/ast"
@@ -129,24 +127,18 @@ func addBuild(filename string, p *ast.Position, data *Data, builds *map[string]*
}
}
//go:embed root_.gotpl
var rootTemplate string
// Root file contains top-level definitions that should not be duplicated across the generated
// files for each schema file.
func generateRootFile(data *Data) error {
dir := data.Config.Exec.DirName
path := filepath.Join(dir, "root_.generated.go")
_, thisFile, _, _ := runtime.Caller(0)
rootDir := filepath.Dir(thisFile)
templatePath := filepath.Join(rootDir, "root_.gotpl")
templateBytes, err := os.ReadFile(templatePath)
if err != nil {
return err
}
template := string(templateBytes)
return templates.Render(templates.Options{
PackageName: data.Config.Exec.Package,
Template: template,
Template: rootTemplate,
Filename: path,
Data: data,
RegionTags: false,

View File

@@ -10,11 +10,14 @@
{{ reserveImport "bytes" }}
{{ reserveImport "embed" }}
{{ reserveImport "golang.org/x/sync/semaphore"}}
{{ reserveImport "github.com/vektah/gqlparser/v2" "gqlparser" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }}
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{ if eq .Config.Exec.Layout "single-file" }}
// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface.
func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {
@@ -115,7 +118,7 @@
return parsedSchema
}
func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) {
func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]any) (int, bool) {
ec := executionContext{nil, e, 0, 0, nil}
_ = ec
{{ if not .Config.OmitComplexity -}}
@@ -132,7 +135,11 @@
break
}
{{ if $field.Args }}
{{ if $useFunctionSyntaxForExecutionContext -}}
args, err := {{ $field.ArgsFunc }}(context.TODO(), &ec, rawArgs)
{{- else -}}
args, err := ec.{{ $field.ArgsFunc }}(context.TODO(),rawArgs)
{{- end }}
if err != nil {
return 0, false
}
@@ -150,18 +157,22 @@
}
func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
rc := graphql.GetOperationContext(ctx)
ec := executionContext{rc, e, 0, 0, make(chan graphql.DeferredResult)}
opCtx := graphql.GetOperationContext(ctx)
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
{{- range $input := .Inputs -}}
{{ if not $input.HasUnmarshal }}
{{ if $useFunctionSyntaxForExecutionContext -}}
unmarshalInput{{ $input.Name }},
{{- else -}}
ec.unmarshalInput{{ $input.Name }},
{{- end }}
{{- end }}
{{- end }}
)
first := true
switch rc.Operation.Operation {
switch opCtx.Operation.Operation {
{{- if .QueryRoot }} case ast.Query:
return func(ctx context.Context) *graphql.Response {
var response graphql.Response
@@ -170,11 +181,20 @@
first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "QUERY" -}}
data = ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
{{ if $useFunctionSyntaxForExecutionContext -}}
data = _queryMiddleware(ctx, &ec, opCtx.Operation, func(ctx context.Context) (any, error){
return _{{.QueryRoot.Name}}(ctx, ec, opCtx.Operation.SelectionSet), nil
{{- else -}}
data = ec._queryMiddleware(ctx, opCtx.Operation, func(ctx context.Context) (any, error){
return ec._{{.QueryRoot.Name}}(ctx, opCtx.Operation.SelectionSet), nil
{{- end }}
})
{{- else -}}
data = ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet)
{{ if $useFunctionSyntaxForExecutionContext -}}
data = _{{.QueryRoot.Name}}(ctx, &ec, opCtx.Operation.SelectionSet)
{{- else -}}
data = ec._{{.QueryRoot.Name}}(ctx, opCtx.Operation.SelectionSet)
{{- end }}
{{- end }}
} else {
if atomic.LoadInt32(&ec.pendingDeferred) > 0 {
@@ -206,11 +226,20 @@
first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "MUTATION" -}}
data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
{{ if $useFunctionSyntaxForExecutionContext -}}
data := _mutationMiddleware(ctx, &ec, opCtx.Operation, func(ctx context.Context) (any, error){
return _{{.MutationRoot.Name}}(ctx, ec, opCtx.Operation.SelectionSet), nil
{{- else -}}
data := ec._mutationMiddleware(ctx, opCtx.Operation, func(ctx context.Context) (any, error){
return ec._{{.MutationRoot.Name}}(ctx, opCtx.Operation.SelectionSet), nil
{{- end }}
})
{{- else -}}
data := ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet)
{{ if $useFunctionSyntaxForExecutionContext -}}
data := _{{.MutationRoot.Name}}(ctx, &ec, opCtx.Operation.SelectionSet)
{{- else -}}
data := ec._{{.MutationRoot.Name}}(ctx, opCtx.Operation.SelectionSet)
{{- end }}
{{- end }}
var buf bytes.Buffer
data.MarshalGQL(&buf)
@@ -223,11 +252,20 @@
{{- if .SubscriptionRoot }} case ast.Subscription:
{{ if .Directives.LocationDirectives "SUBSCRIPTION" -}}
next := ec._subscriptionMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.SubscriptionRoot.Name}}(ctx, rc.Operation.SelectionSet),nil
{{ if $useFunctionSyntaxForExecutionContext -}}
next := _subscriptionMiddleware(ctx, &ec, opCtx.Operation, func(ctx context.Context) (any, error){
return _{{.SubscriptionRoot.Name}}(ctx, ec, opCtx.Operation.SelectionSet),nil
{{- else -}}
next := ec._subscriptionMiddleware(ctx, opCtx.Operation, func(ctx context.Context) (any, error){
return ec._{{.SubscriptionRoot.Name}}(ctx, opCtx.Operation.SelectionSet),nil
{{- end }}
})
{{- else -}}
next := ec._{{.SubscriptionRoot.Name}}(ctx, rc.Operation.SelectionSet)
{{ if $useFunctionSyntaxForExecutionContext -}}
next := _{{.SubscriptionRoot.Name}}(ctx, &ec, opCtx.Operation.SelectionSet)
{{- else -}}
next := ec._{{.SubscriptionRoot.Name}}(ctx, opCtx.Operation.SelectionSet)
{{- end }}
{{- end }}
var buf bytes.Buffer

View File

@@ -1,17 +1,23 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{- range $input := .Inputs }}
{{- if not .HasUnmarshal }}
{{- $it := "it" }}
{{- if .PointersInUnmarshalInput }}
{{- $it = "&it" }}
{{- end }}
func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{ if .PointersInUnmarshalInput }}*{{ end }}{{.Type | ref}}, error) {
{{ if $useFunctionSyntaxForExecutionContext -}}
func unmarshalInput{{ .Name }}(ctx context.Context, ec *executionContext, obj any) ({{ if .PointersInUnmarshalInput }}*{{ end }}{{.Type | ref}}, error) {
{{- else -}}
func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj any) ({{ if .PointersInUnmarshalInput }}*{{ end }}{{.Type | ref}}, error) {
{{- end }}
{{- if $input.IsMap }}
it := make(map[string]interface{}, len(obj.(map[string]interface{})))
it := make(map[string]any, len(obj.(map[string]any)))
{{- else }}
var it {{.Type | ref}}
{{- end }}
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
{{ range $field := .Fields}}
@@ -37,8 +43,12 @@
{{- end }}
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField({{$field.Name|quote}}))
{{- if $field.ImplDirectives }}
directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) }
{{ template "implDirectives" $field }}
{{ if $useFunctionSyntaxForExecutionContext -}}
directive0 := func(ctx context.Context) (any, error) { return {{ $field.TypeReference.UnmarshalFunc }}(ctx, ec, v) }
{{- else -}}
directive0 := func(ctx context.Context) (any, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) }
{{- end }}
{{ template "implDirectives" (dict "Field" $field "UseFunctionSyntaxForExecutionContext" $useFunctionSyntaxForExecutionContext) }}
tmp, err := directive{{$field.ImplDirectives|len}}(ctx)
if err != nil {
return {{$it}}, graphql.ErrorOnPath(ctx, err)
@@ -71,7 +81,11 @@
}
{{- else }}
{{- if $field.IsResolver }}
{{ if $useFunctionSyntaxForExecutionContext -}}
data, err := {{ $field.TypeReference.UnmarshalFunc }}(ctx, ec, v)
{{- else -}}
data, err := ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v)
{{- end }}
if err != nil {
return {{$it}}, err
}
@@ -79,7 +93,11 @@
return {{$it}}, err
}
{{- else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
data, err := {{ $field.TypeReference.UnmarshalFunc }}(ctx, ec, v)
{{- else -}}
data, err := ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v)
{{- end }}
if err != nil {
return {{$it}}, err
}

View File

@@ -1,6 +1,12 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{- range $interface := .Interfaces }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func _{{$interface.Name}}(ctx context.Context, ec *executionContext, sel ast.SelectionSet, obj {{$interface.Type | ref}}) graphql.Marshaler {
{{- else -}}
func (ec *executionContext) _{{$interface.Name}}(ctx context.Context, sel ast.SelectionSet, obj {{$interface.Type | ref}}) graphql.Marshaler {
{{- end }}
switch obj := (obj).(type) {
case nil:
return graphql.Null
@@ -11,7 +17,11 @@ func (ec *executionContext) _{{$interface.Name}}(ctx context.Context, sel ast.Se
return graphql.Null
}
{{- end }}
{{ if $useFunctionSyntaxForExecutionContext -}}
return _{{$implementor.Name}}(ctx, ec, sel, {{ if $implementor.TakeRef }}&{{ end }}obj)
{{- else -}}
return ec._{{$implementor.Name}}(ctx, sel, {{ if $implementor.TakeRef }}&{{ end }}obj)
{{- end }}
{{- end }}
default:
panic(fmt.Errorf("unexpected type %T", obj))

View File

@@ -1,9 +1,15 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{- range $object := .Objects }}
var {{ $object.Name|lcFirst}}Implementors = {{$object.Implementors}}
{{- if .Stream }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func _{{$object.Name}}(ctx context.Context, ec *executionContext, sel ast.SelectionSet) func(ctx context.Context) graphql.Marshaler {
{{- else -}}
func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet) func(ctx context.Context) graphql.Marshaler {
{{- end }}
fields := graphql.CollectFields(ec.OperationContext, sel, {{$object.Name|lcFirst}}Implementors)
ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{
Object: {{$object.Name|quote}},
@@ -16,14 +22,23 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec
switch fields[0].Name {
{{- range $field := $object.Fields }}
case "{{$field.Name}}":
{{ if $useFunctionSyntaxForExecutionContext -}}
return _{{$object.Name}}_{{$field.Name}}(ctx, ec, fields[0])
{{- else -}}
return ec._{{$object.Name}}_{{$field.Name}}(ctx, fields[0])
{{- end }}
{{- end }}
default:
panic("unknown field " + strconv.Quote(fields[0].Name))
}
}
{{- else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func _{{$object.Name}}(ctx context.Context, ec *executionContext, sel ast.SelectionSet{{ if not $object.Root }},obj {{$object.Reference | ref }}{{ end }}) graphql.Marshaler {
{{- else -}}
func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet{{ if not $object.Root }},obj {{$object.Reference | ref }}{{ end }}) graphql.Marshaler {
{{- end }}
fields := graphql.CollectFields(ec.OperationContext, sel, {{$object.Name|lcFirst}}Implementors)
{{- if $object.Root }}
ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{
@@ -56,7 +71,11 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec
}
}()
{{- end }}
{{ if $useFunctionSyntaxForExecutionContext -}}
res = _{{$object.Name}}_{{$field.Name}}(ctx, ec, field{{if not $object.Root}}, obj{{end}})
{{- else -}}
res = ec._{{$object.Name}}_{{$field.Name}}(ctx, field{{if not $object.Root}}, obj{{end}})
{{- end }}
{{- if $field.TypeReference.GQL.NonNull }}
if res == graphql.Null {
{{- if $object.IsConcurrent }}
@@ -107,10 +126,18 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec
{{- else }}
{{- if $object.Root -}}
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
{{ if $useFunctionSyntaxForExecutionContext -}}
return _{{$object.Name}}_{{$field.Name}}(ctx, ec, field)
{{- else -}}
return ec._{{$object.Name}}_{{$field.Name}}(ctx, field)
{{- end }}
})
{{- else -}}
{{ if $useFunctionSyntaxForExecutionContext -}}
out.Values[i] = _{{$object.Name}}_{{$field.Name}}(ctx, ec, field, obj)
{{- else -}}
out.Values[i] = ec._{{$object.Name}}_{{$field.Name}}(ctx, field, obj)
{{- end }}
{{- end -}}
{{- if $field.TypeReference.GQL.NonNull }}

View File

@@ -15,6 +15,8 @@
{{ reserveImport "github.com/99designs/gqlgen/graphql" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }}
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
// NewExecutableSchema creates an ExecutableSchema from the ResolverRoot interface.
func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {
return &executableSchema{
@@ -88,7 +90,7 @@ func (e *executableSchema) Schema() *ast.Schema {
return parsedSchema
}
func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) {
func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]any) (int, bool) {
ec := executionContext{nil, e, 0, 0, nil}
_ = ec
{{- if not .Config.OmitComplexity }}
@@ -105,7 +107,11 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
break
}
{{ if $field.Args }}
{{ if $useFunctionSyntaxForExecutionContext -}}
args, err := {{ $field.ArgsFunc }}(context.TODO(), &ec, rawArgs)
{{- else -}}
args, err := ec.{{ $field.ArgsFunc }}(context.TODO(),rawArgs)
{{- end }}
if err != nil {
return 0, false
}
@@ -123,18 +129,22 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
}
func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
rc := graphql.GetOperationContext(ctx)
ec := executionContext{rc, e, 0, 0, make(chan graphql.DeferredResult)}
opCtx := graphql.GetOperationContext(ctx)
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
{{- range $input := .Inputs -}}
{{ if not $input.HasUnmarshal }}
{{ if $useFunctionSyntaxForExecutionContext -}}
unmarshalInput{{ $input.Name }},
{{- else -}}
ec.unmarshalInput{{ $input.Name }},
{{- end }}
{{- end }}
{{- end }}
)
first := true
switch rc.Operation.Operation {
switch opCtx.Operation.Operation {
{{- if .QueryRoot }} case ast.Query:
return func(ctx context.Context) *graphql.Response {
var response graphql.Response
@@ -143,11 +153,20 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "QUERY" -}}
data = ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
{{ if $useFunctionSyntaxForExecutionContext -}}
data = _queryMiddleware(ctx, ec, opCtx.Operation, func(ctx context.Context) (any, error){
return _{{.QueryRoot.Name}}(ctx, &ec, opCtx.Operation.SelectionSet), nil
{{- else -}}
data = ec._queryMiddleware(ctx, opCtx.Operation, func(ctx context.Context) (any, error){
return ec._{{.QueryRoot.Name}}(ctx, opCtx.Operation.SelectionSet), nil
{{- end }}
})
{{- else -}}
data = ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet)
{{ if $useFunctionSyntaxForExecutionContext -}}
data = _{{.QueryRoot.Name}}(ctx, &ec, opCtx.Operation.SelectionSet)
{{- else -}}
data = ec._{{.QueryRoot.Name}}(ctx, opCtx.Operation.SelectionSet)
{{- end }}
{{- end }}
} else {
if atomic.LoadInt32(&ec.pendingDeferred) > 0 {
@@ -179,11 +198,20 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
first = false
ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
{{ if .Directives.LocationDirectives "MUTATION" -}}
data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet), nil
{{ if $useFunctionSyntaxForExecutionContext -}}
data := _mutationMiddleware(ctx, &ec, opCtx.Operation, func(ctx context.Context) (any, error){
return _{{.MutationRoot.Name}}(ctx, ec, opCtx.Operation.SelectionSet), nil
{{- else -}}
data := ec._mutationMiddleware(ctx, opCtx.Operation, func(ctx context.Context) (any, error){
return ec._{{.MutationRoot.Name}}(ctx, opCtx.Operation.SelectionSet), nil
{{- end }}
})
{{- else -}}
data := ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet)
{{ if $useFunctionSyntaxForExecutionContext -}}
data := _{{.MutationRoot.Name}}(ctx, &ec, opCtx.Operation.SelectionSet)
{{- else -}}
data := ec._{{.MutationRoot.Name}}(ctx, opCtx.Operation.SelectionSet)
{{- end }}
{{- end }}
var buf bytes.Buffer
data.MarshalGQL(&buf)
@@ -196,11 +224,20 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
{{- if .SubscriptionRoot }} case ast.Subscription:
{{ if .Directives.LocationDirectives "SUBSCRIPTION" -}}
next := ec._subscriptionMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){
return ec._{{.SubscriptionRoot.Name}}(ctx, rc.Operation.SelectionSet),nil
{{ if $useFunctionSyntaxForExecutionContext -}}
next := _subscriptionMiddleware(ctx, &ec, opCtx.Operation, func(ctx context.Context) (any, error){
return _{{.SubscriptionRoot.Name}}(ctx, ec, opCtx.Operation.SelectionSet),nil
{{- else -}}
next := ec._subscriptionMiddleware(ctx, opCtx.Operation, func(ctx context.Context) (any, error){
return ec._{{.SubscriptionRoot.Name}}(ctx, opCtx.Operation.SelectionSet),nil
{{- end }}
})
{{- else -}}
next := ec._{{.SubscriptionRoot.Name}}(ctx, rc.Operation.SelectionSet)
{{ if $useFunctionSyntaxForExecutionContext -}}
next := _{{.SubscriptionRoot.Name}}(ctx, &ec, opCtx.Operation.SelectionSet)
{{- else -}}
next := ec._{{.SubscriptionRoot.Name}}(ctx, opCtx.Operation.SelectionSet)
{{- end }}
{{- end }}
var buf bytes.Buffer

View File

@@ -205,6 +205,7 @@ func Funcs() template.FuncMap {
"obj": obj,
"ts": TypeIdentifier,
"call": Call,
"dict": dict,
"prefixLines": prefixLines,
"notNil": notNil,
"strSplit": StrSplit,
@@ -247,7 +248,13 @@ func isDelimiter(c rune) bool {
}
func ref(p types.Type) string {
return CurrentImports.LookupType(p)
typeString := CurrentImports.LookupType(p)
// TODO(steve): figure out why this is needed
// otherwise inconsistent sometimes
if typeString == "interface{}" {
return "any"
}
return typeString
}
func obj(obj types.Object) string {
@@ -274,6 +281,21 @@ func Call(p *types.Func) string {
return pkg + p.Name()
}
func dict(values ...any) (map[string]any, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call: arguments must be key-value pairs")
}
m := make(map[string]any, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
m[key] = values[i+1]
}
return m, nil
}
func resetModelNames() {
modelNamesMu.Lock()
defer modelNamesMu.Unlock()
@@ -606,10 +628,10 @@ func Dump(val any) string {
for _, part := range val {
parts = append(parts, Dump(part))
}
return "[]interface{}{" + strings.Join(parts, ",") + "}"
return "[]any{" + strings.Join(parts, ",") + "}"
case map[string]any:
buf := bytes.Buffer{}
buf.WriteString("map[string]interface{}{")
buf.WriteString("map[string]any{")
var keys []string
for key := range val {
keys = append(keys, key)

View File

@@ -1,14 +1,23 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{- range $type := .ReferencedTypes }}
{{ with $type.UnmarshalFunc }}
func (ec *executionContext) {{ . }}(ctx context.Context, v interface{}) ({{ $type.GO | ref }}, error) {
{{ if $useFunctionSyntaxForExecutionContext -}}
func {{ . }}(ctx context.Context, ec *executionContext, v any) ({{ $type.GO | ref }}, error) {
{{- else -}}
func (ec *executionContext) {{ . }}(ctx context.Context, v any) ({{ $type.GO | ref }}, error) {
{{- end -}}
{{- if and $type.IsNilable (not $type.GQL.NonNull) (not $type.IsPtrToPtr) }}
if v == nil { return nil, nil }
{{- end }}
{{- if or $type.IsPtrToSlice $type.IsPtrToIntf }}
{{ if $useFunctionSyntaxForExecutionContext -}}
res, err := {{ $type.Elem.UnmarshalFunc }}(ctx, ec, v)
{{- else -}}
res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v)
{{- end }}
return &res, graphql.ErrorOnPath(ctx, err)
{{- else if $type.IsSlice }}
var vSlice []interface{}
var vSlice []any
if v != nil {
vSlice = graphql.CoerceList(v)
}
@@ -16,7 +25,11 @@
res := make([]{{$type.GO.Elem | ref}}, len(vSlice))
for i := range vSlice {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
{{ if $useFunctionSyntaxForExecutionContext -}}
res[i], err = {{ $type.Elem.UnmarshalFunc }}(ctx, ec, vSlice[i])
{{- else -}}
res[i], err = ec.{{ $type.Elem.UnmarshalFunc }}(ctx, vSlice[i])
{{- end }}
if err != nil {
return nil, err
}
@@ -25,7 +38,11 @@
{{- else if and $type.IsPtrToPtr (not $type.Unmarshaler) (not $type.IsMarshaler) }}
var pres {{ $type.Elem.GO | ref }}
if v != nil {
{{ if $useFunctionSyntaxForExecutionContext -}}
res, err := {{ $type.Elem.UnmarshalFunc }}(ctx, ec, v)
{{- else -}}
res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v)
{{- end }}
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
@@ -75,7 +92,11 @@
{{- end }}
return res, graphql.ErrorOnPath(ctx, err)
{{- else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
res, err := unmarshalInput{{ $type.GQL.Name }}(ctx, ec, v)
{{- else -}}
res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v)
{{- end }}
{{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUnmarshalInput) }}
return &res, graphql.ErrorOnPath(ctx, err)
{{- else if and (not $type.IsNilable) $type.PointersInUnmarshalInput }}
@@ -89,9 +110,17 @@
{{- end }}
{{ with $type.MarshalFunc }}
{{ if $useFunctionSyntaxForExecutionContext -}}
func {{ . }}(ctx context.Context, ec *executionContext, sel ast.SelectionSet, v {{ $type.GO | ref }}) graphql.Marshaler {
{{- else -}}
func (ec *executionContext) {{ . }}(ctx context.Context, sel ast.SelectionSet, v {{ $type.GO | ref }}) graphql.Marshaler {
{{- end -}}
{{- if or $type.IsPtrToSlice $type.IsPtrToIntf }}
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $type.Elem.MarshalFunc }}(ctx, ec, sel, *v)
{{- else -}}
return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v)
{{- end }}
{{- else if $type.IsSlice }}
{{- if not $type.GQL.NonNull }}
if v == nil {
@@ -101,6 +130,9 @@
ret := make(graphql.Array, len(v))
{{- if not $type.IsScalar }}
var wg sync.WaitGroup
{{- if gt $.Config.Exec.WorkerLimit 0 }}
sm := semaphore.NewWeighted({{ $.Config.Exec.WorkerLimit }})
{{- end }}
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
@@ -124,17 +156,40 @@
}()
{{- end }}
if !isLen1 {
defer wg.Done()
{{- if gt $.Config.Exec.WorkerLimit 0 }}
defer func(){
sm.Release(1)
wg.Done()
}()
{{- else }}
defer wg.Done()
{{- end }}
}
{{ if $useFunctionSyntaxForExecutionContext -}}
ret[i] = {{ $type.Elem.MarshalFunc }}(ctx, ec, sel, v[i])
{{- else -}}
ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
{{- end }}
}
if isLen1 {
f(i)
} else {
go f(i)
{{- if gt $.Config.Exec.WorkerLimit 0 }}
if err := sm.Acquire(ctx, 1); err != nil {
ec.Error(ctx, ctx.Err())
} else {
go f(i)
}
{{- else }}
go f(i)
{{- end }}
}
{{ else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
ret[i] = {{ $type.Elem.MarshalFunc }}(ctx, ec, sel, v[i])
{{- else -}}
ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
{{- end }}
{{- end }}
}
{{ if not $type.IsScalar }} wg.Wait() {{ end }}
@@ -150,7 +205,11 @@
if v == nil {
return graphql.Null
}
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $type.Elem.MarshalFunc }}(ctx, ec, sel, *v)
{{- else -}}
return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v)
{{- end }}
{{- else }}
{{- if $type.IsNilable }}
if v == nil {
@@ -195,13 +254,25 @@
{{- end }}
{{- else if $type.IsRoot }}
{{- if eq $type.Definition.Name "Subscription" }}
{{ if $useFunctionSyntaxForExecutionContext -}}
res := _{{$type.Definition.Name}}(ctx, ec, sel)
{{- else -}}
res := ec._{{$type.Definition.Name}}(ctx, sel)
{{- end }}
return res(ctx)
{{- else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
return _{{$type.Definition.Name}}(ctx, ec, sel)
{{- else -}}
return ec._{{$type.Definition.Name}}(ctx, sel)
{{- end }}
{{- end }}
{{- else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
return _{{$type.Definition.Name}}(ctx, ec, sel, {{ if not $type.IsNilable}}&{{end}} v)
{{- else -}}
return ec._{{$type.Definition.Name}}(ctx, sel, {{ if not $type.IsNilable}}&{{end}} v)
{{- end }}
{{- end }}
{{- end }}
}

View File

@@ -23,9 +23,13 @@ func (m MapCache[T]) Get(_ context.Context, key string) (value T, ok bool) {
// Add adds a value to the cache.
func (m MapCache[T]) Add(_ context.Context, key string, value T) { m[key] = value }
type NoCache[T any, T2 *T] struct{}
type NoCache[T any] struct{}
var _ Cache[*string] = (*NoCache[string, *string])(nil)
var _ Cache[string] = (*NoCache[string])(nil)
func (n NoCache[T, T2]) Get(_ context.Context, _ string) (value T2, ok bool) { return nil, false }
func (n NoCache[T, T2]) Add(_ context.Context, _ string, _ T2) {}
func (n NoCache[T]) Get(_ context.Context, _ string) (value T, ok bool) {
var val T
return val, false
}
func (n NoCache[T]) Add(_ context.Context, _ string, _ T) {}

View File

@@ -34,16 +34,16 @@ type FieldContext struct {
// Note that, the returned child FieldContext represents the context as it was
// before the execution of the field resolver. For example:
//
// srv.AroundFields(func(ctx context.Context, next graphql.Resolver) (interface{}, error) {
// srv.AroundFields(func(ctx context.Context, next graphql.Resolver) (any, error) {
// fc := graphql.GetFieldContext(ctx)
// op := graphql.GetOperationContext(ctx)
// opCtx := graphql.GetOperationContext(ctx)
// collected := graphql.CollectFields(opCtx, fc.Field.Selections, []string{"User"})
//
// child, err := fc.Child(ctx, collected[0])
// if err != nil {
// return nil, err
// }
// fmt.Println("child context %q with args: %v", child.Field.Name, child.Args)
// fmt.Printf("child context %q with args: %v\n", child.Field.Name, child.Args)
//
// return next(ctx)
// })

View File

@@ -65,8 +65,8 @@ func GetOperationContext(ctx context.Context) *OperationContext {
panic("missing operation context")
}
func WithOperationContext(ctx context.Context, rc *OperationContext) context.Context {
return context.WithValue(ctx, operationCtx, rc)
func WithOperationContext(ctx context.Context, opCtx *OperationContext) context.Context {
return context.WithValue(ctx, operationCtx, opCtx)
}
// HasOperationContext checks if the given context is part of an ongoing operation
@@ -77,7 +77,7 @@ func HasOperationContext(ctx context.Context) bool {
return ok && val != nil
}
// This is just a convenient wrapper method for CollectFields
// CollectFieldsCtx is just a convenient wrapper method for CollectFields.
func CollectFieldsCtx(ctx context.Context, satisfies []string) []CollectedField {
resctx := GetFieldContext(ctx)
return CollectFields(GetOperationContext(ctx), resctx.Field.Selections, satisfies)

View File

@@ -51,6 +51,9 @@ func AddErrorf(ctx context.Context, format string, args ...any) {
// AddError sends an error to the client, first passing it through the error presenter.
func AddError(ctx context.Context, err error) {
if err == nil {
return
}
c := getResponseContext(ctx)
presentedError := c.errorPresenter(ctx, ErrorOnPath(ctx, err))

View File

@@ -10,6 +10,9 @@ import (
type ErrorPresenterFunc func(ctx context.Context, err error) *gqlerror.Error
func DefaultErrorPresenter(ctx context.Context, err error) *gqlerror.Error {
if err == nil {
return nil
}
var gqlErr *gqlerror.Error
if errors.As(err, &gqlErr) {
return gqlErr

View File

@@ -17,8 +17,7 @@ type ExecutableSchema interface {
}
// CollectFields returns the set of fields from an ast.SelectionSet where all collected fields satisfy at least one of the GraphQL types
// passed through satisfies. Providing an empty or nil slice for satisfies will return collect all fields regardless of fragment
// type conditions.
// passed through satisfies. Providing an empty slice for satisfies will collect all fields regardless of fragment type conditions.
func CollectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string) []CollectedField {
return collectFields(reqCtx, selSet, satisfies, map[string]bool{})
}
@@ -39,13 +38,22 @@ func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies
f.Selections = append(f.Selections, sel.SelectionSet...)
case *ast.InlineFragment:
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
if len(satisfies) > 0 && !instanceOf(sel.TypeCondition, satisfies) {
// To allow simplified "collect all" types behavior, pass an empty list
// of types that the type condition must satisfy: we will apply the
// fragment regardless of type condition.
//
// When the type condition is not set (... { field }) we will apply the
// fragment to any satisfying types.
//
// We will only NOT apply the fragment when we have at least one type in
// the list we must satisfy and a type condition to compare them to.
if len(satisfies) > 0 && sel.TypeCondition != "" && !instanceOf(sel.TypeCondition, satisfies) {
continue
}
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
shouldDefer, label := deferrable(sel.Directives, reqCtx.Variables)
for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) {
@@ -61,9 +69,6 @@ func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies
}
case *ast.FragmentSpread:
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
fragmentName := sel.Name
if _, seen := visited[fragmentName]; seen {
continue
@@ -80,6 +85,9 @@ func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies
continue
}
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
shouldDefer, label := deferrable(sel.Directives, reqCtx.Variables)
for _, childField := range collectFields(reqCtx, fragment.SelectionSet, satisfies, visited) {

View File

@@ -7,6 +7,7 @@ import (
"github.com/vektah/gqlparser/v2/gqlerror"
"github.com/vektah/gqlparser/v2/parser"
"github.com/vektah/gqlparser/v2/validator"
"github.com/vektah/gqlparser/v2/validator/rules"
"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/errcode"
@@ -24,7 +25,8 @@ type Executor struct {
recoverFunc graphql.RecoverFunc
queryCache graphql.Cache[*ast.QueryDocument]
parserTokenLimit int
parserTokenLimit int
disableSuggestion bool
}
var _ graphql.GraphExecutor = &Executor{}
@@ -36,7 +38,7 @@ func New(es graphql.ExecutableSchema) *Executor {
es: es,
errorPresenter: graphql.DefaultErrorPresenter,
recoverFunc: graphql.DefaultRecover,
queryCache: graphql.NoCache[ast.QueryDocument, *ast.QueryDocument]{},
queryCache: graphql.NoCache[*ast.QueryDocument]{},
ext: processExtensions(nil),
parserTokenLimit: parserTokenNoLimit,
}
@@ -47,7 +49,7 @@ func (e *Executor) CreateOperationContext(
ctx context.Context,
params *graphql.RawParams,
) (*graphql.OperationContext, gqlerror.List) {
rc := &graphql.OperationContext{
opCtx := &graphql.OperationContext{
DisableIntrospection: true,
RecoverFunc: e.recoverFunc,
ResolverMiddleware: e.ext.fieldMiddleware,
@@ -57,56 +59,56 @@ func (e *Executor) CreateOperationContext(
OperationStart: graphql.GetStartTime(ctx),
},
}
ctx = graphql.WithOperationContext(ctx, rc)
ctx = graphql.WithOperationContext(ctx, opCtx)
for _, p := range e.ext.operationParameterMutators {
if err := p.MutateOperationParameters(ctx, params); err != nil {
return rc, gqlerror.List{err}
return opCtx, gqlerror.List{err}
}
}
rc.RawQuery = params.Query
rc.OperationName = params.OperationName
rc.Headers = params.Headers
opCtx.RawQuery = params.Query
opCtx.OperationName = params.OperationName
opCtx.Headers = params.Headers
var listErr gqlerror.List
rc.Doc, listErr = e.parseQuery(ctx, &rc.Stats, params.Query)
opCtx.Doc, listErr = e.parseQuery(ctx, &opCtx.Stats, params.Query)
if len(listErr) != 0 {
return rc, listErr
return opCtx, listErr
}
rc.Operation = rc.Doc.Operations.ForName(params.OperationName)
if rc.Operation == nil {
opCtx.Operation = opCtx.Doc.Operations.ForName(params.OperationName)
if opCtx.Operation == nil {
err := gqlerror.Errorf("operation %s not found", params.OperationName)
errcode.Set(err, errcode.ValidationFailed)
return rc, gqlerror.List{err}
return opCtx, gqlerror.List{err}
}
var err error
rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables)
opCtx.Variables, err = validator.VariableValues(e.es.Schema(), opCtx.Operation, params.Variables)
if err != nil {
gqlErr, ok := err.(*gqlerror.Error)
if ok {
errcode.Set(gqlErr, errcode.ValidationFailed)
return rc, gqlerror.List{gqlErr}
return opCtx, gqlerror.List{gqlErr}
}
}
rc.Stats.Validation.End = graphql.Now()
opCtx.Stats.Validation.End = graphql.Now()
for _, p := range e.ext.operationContextMutators {
if err := p.MutateOperationContext(ctx, rc); err != nil {
return rc, gqlerror.List{err}
if err := p.MutateOperationContext(ctx, opCtx); err != nil {
return opCtx, gqlerror.List{err}
}
}
return rc, nil
return opCtx, nil
}
func (e *Executor) DispatchOperation(
ctx context.Context,
rc *graphql.OperationContext,
opCtx *graphql.OperationContext,
) (graphql.ResponseHandler, context.Context) {
ctx = graphql.WithOperationContext(ctx, rc)
ctx = graphql.WithOperationContext(ctx, opCtx)
var innerCtx context.Context
res := e.ext.operationMiddleware(ctx, func(ctx context.Context) graphql.ResponseHandler {
@@ -177,6 +179,10 @@ func (e *Executor) SetParserTokenLimit(limit int) {
e.parserTokenLimit = limit
}
func (e *Executor) SetDisableSuggestion(value bool) {
e.disableSuggestion = value
}
// parseQuery decodes the incoming query and validates it, pulling from cache if present.
//
// NOTE: This should NOT look at variables, they will change per request. It should only parse and
@@ -216,6 +222,15 @@ func (e *Executor) parseQuery(
return nil, gqlerror.List{gqlErr}
}
// swap out the FieldsOnCorrectType rule with one that doesn't provide suggestions
if e.disableSuggestion {
validator.RemoveRule("FieldsOnCorrectType")
rule := rules.FieldsOnCorrectTypeRuleWithoutSuggestions
// rule may already have been added
validator.ReplaceRule(rule.Name, rule.RuleFunc)
}
listErr := validator.Validate(e.es.Schema(), doc)
if len(listErr) != 0 {
for _, e := range listErr {

View File

@@ -34,7 +34,7 @@ type (
GraphExecutor interface {
CreateOperationContext(ctx context.Context, params *RawParams) (*OperationContext, gqlerror.List)
DispatchOperation(ctx context.Context, rc *OperationContext) (ResponseHandler, context.Context)
DispatchOperation(ctx context.Context, opCtx *OperationContext) (ResponseHandler, context.Context)
DispatchError(ctx context.Context, list gqlerror.List) *Response
}
@@ -65,7 +65,7 @@ type (
// OperationContextMutator is called after creating the request context, but before executing the root resolver.
OperationContextMutator interface {
MutateOperationContext(ctx context.Context, rc *OperationContext) *gqlerror.Error
MutateOperationContext(ctx context.Context, opCtx *OperationContext) *gqlerror.Error
}
// OperationInterceptor is called for each incoming query, for basic requests the writer will be invoked once,

View File

@@ -6,7 +6,7 @@ import (
"encoding/hex"
"errors"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
"github.com/vektah/gqlparser/v2/gqlerror"
"github.com/99designs/gqlgen/graphql"
@@ -98,12 +98,12 @@ func (a AutomaticPersistedQuery) MutateOperationParameters(ctx context.Context,
}
func GetApqStats(ctx context.Context) *ApqStats {
rc := graphql.GetOperationContext(ctx)
if rc == nil {
opCtx := graphql.GetOperationContext(ctx)
if opCtx == nil {
return nil
}
s, _ := rc.Stats.GetExtension(apqExtension).(*ApqStats)
s, _ := opCtx.Stats.GetExtension(apqExtension).(*ApqStats)
return s
}

View File

@@ -17,7 +17,7 @@ const errComplexityLimit = "COMPLEXITY_LIMIT_EXCEEDED"
//
// If a query is submitted that exceeds the limit, a 422 status code will be returned.
type ComplexityLimit struct {
Func func(ctx context.Context, rc *graphql.OperationContext) int
Func func(ctx context.Context, opCtx *graphql.OperationContext) int
es graphql.ExecutableSchema
}
@@ -40,7 +40,7 @@ type ComplexityStats struct {
// FixedComplexityLimit sets a complexity limit that does not change
func FixedComplexityLimit(limit int) *ComplexityLimit {
return &ComplexityLimit{
Func: func(ctx context.Context, rc *graphql.OperationContext) int {
Func: func(ctx context.Context, opCtx *graphql.OperationContext) int {
return limit
},
}
@@ -58,13 +58,13 @@ func (c *ComplexityLimit) Validate(schema graphql.ExecutableSchema) error {
return nil
}
func (c ComplexityLimit) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error {
op := rc.Doc.Operations.ForName(rc.OperationName)
complexityCalcs := complexity.Calculate(c.es, op, rc.Variables)
func (c ComplexityLimit) MutateOperationContext(ctx context.Context, opCtx *graphql.OperationContext) *gqlerror.Error {
op := opCtx.Doc.Operations.ForName(opCtx.OperationName)
complexityCalcs := complexity.Calculate(c.es, op, opCtx.Variables)
limit := c.Func(ctx, rc)
limit := c.Func(ctx, opCtx)
rc.Stats.SetExtension(complexityExtension, &ComplexityStats{
opCtx.Stats.SetExtension(complexityExtension, &ComplexityStats{
Complexity: complexityCalcs,
ComplexityLimit: limit,
})
@@ -79,11 +79,11 @@ func (c ComplexityLimit) MutateOperationContext(ctx context.Context, rc *graphql
}
func GetComplexityStats(ctx context.Context) *ComplexityStats {
rc := graphql.GetOperationContext(ctx)
if rc == nil {
opCtx := graphql.GetOperationContext(ctx)
if opCtx == nil {
return nil
}
s, _ := rc.Stats.GetExtension(complexityExtension).(*ComplexityStats)
s, _ := opCtx.Stats.GetExtension(complexityExtension).(*ComplexityStats)
return s
}

View File

@@ -24,7 +24,7 @@ func (c Introspection) Validate(schema graphql.ExecutableSchema) error {
return nil
}
func (c Introspection) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error {
rc.DisableIntrospection = false
func (c Introspection) MutateOperationContext(ctx context.Context, opCtx *graphql.OperationContext) *gqlerror.Error {
opCtx.DisableIntrospection = false
return nil
}

View File

@@ -31,6 +31,21 @@ func New(es graphql.ExecutableSchema) *Server {
}
}
// NewDefaultServer is a demonstration only. Not for prod.
//
// Currently, the server just picks the first available transport,
// so this example NewDefaultServer orders them, but it is just
// for demonstration purposes.
// You will likely want to tune and better configure Websocket transport
// since adding a new one (To configure it) doesn't have effect.
//
// Also SSE support is not in here at all!
// SSE when used over HTTP/1.1 (but not HTTP/2 or HTTP/3),
// SSE suffers from a severe limitation to the maximum number
// of open connections of 6 per browser. See:
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#sect1
//
// Deprecated: This was and is just an example.
func NewDefaultServer(es graphql.ExecutableSchema) *Server {
srv := New(es)
@@ -72,6 +87,10 @@ func (s *Server) SetParserTokenLimit(limit int) {
s.exec.SetParserTokenLimit(limit)
}
func (s *Server) SetDisableSuggestion(value bool) {
s.exec.SetDisableSuggestion(value)
}
func (s *Server) Use(extension graphql.HandlerExtension) {
s.exec.Use(extension)
}

View File

@@ -66,21 +66,21 @@ func (h GET) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecut
raw.ReadTime.End = graphql.Now()
rc, gqlError := exec.CreateOperationContext(r.Context(), raw)
opCtx, gqlError := exec.CreateOperationContext(r.Context(), raw)
if gqlError != nil {
w.WriteHeader(statusFor(gqlError))
resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), rc), gqlError)
resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), opCtx), gqlError)
writeJson(w, resp)
return
}
op := rc.Doc.Operations.ForName(rc.OperationName)
op := opCtx.Doc.Operations.ForName(opCtx.OperationName)
if op.Operation != ast.Query {
w.WriteHeader(http.StatusNotAcceptable)
writeJsonError(w, "GET requests only allow query operations")
return
}
responses, ctx := exec.DispatchOperation(r.Context(), rc)
responses, ctx := exec.DispatchOperation(r.Context(), opCtx)
writeJson(w, responses(ctx))
}

View File

@@ -0,0 +1,298 @@
package transport
import (
"encoding/json"
"fmt"
"io"
"log"
"mime"
"net/http"
"strings"
"sync"
"time"
"github.com/vektah/gqlparser/v2/gqlerror"
"github.com/99designs/gqlgen/graphql"
)
// MultipartMixed is a transport that supports the multipart/mixed spec
type MultipartMixed struct {
Boundary string
DeliveryTimeout time.Duration
}
var _ graphql.Transport = MultipartMixed{}
// Supports checks if the request supports the multipart/mixed spec
// Might be worth check the spec required, but Apollo Client mislabel the spec in the headers.
func (t MultipartMixed) Supports(r *http.Request) bool {
if !strings.Contains(r.Header.Get("Accept"), "multipart/mixed") {
return false
}
mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
return false
}
return r.Method == http.MethodPost && mediaType == "application/json"
}
// Do implements the multipart/mixed spec as a multipart/mixed response
func (t MultipartMixed) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) {
// Implements the multipart/mixed spec as a multipart/mixed response:
// * https://github.com/graphql/graphql-wg/blob/e4ef5f9d5997815d9de6681655c152b6b7838b4c/rfcs/DeferStream.md
// 2022/08/23 as implemented by gqlgen.
// * https://github.com/graphql/graphql-wg/blob/f22ea7748c6ebdf88fdbf770a8d9e41984ebd429/rfcs/DeferStream.md June 2023 Spec for the
// `incremental` field
// * https://github.com/graphql/graphql-over-http/blob/main/rfcs/IncrementalDelivery.md
// multipart specification
// Follows the format that is used in the Apollo Client tests:
// https://github.com/apollographql/apollo-client/blob/v3.11.8/src/link/http/__tests__/responseIterator.ts#L68
// Apollo Client, despite mentioning in its requests that they require the 2022 spec, it wants the
// `incremental` field to be an array of responses, not a single response. Theoretically we could
// batch responses in the `incremental` field, if we wanted to optimize this code.
ctx := r.Context()
flusher, ok := w.(http.Flusher)
if !ok {
SendErrorf(w, http.StatusInternalServerError, "streaming unsupported")
return
}
defer flusher.Flush()
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
// This header will be replaced below, but it's required in case we return errors.
w.Header().Set("Content-Type", "application/json")
boundary := t.Boundary
if boundary == "" {
boundary = "-"
}
timeout := t.DeliveryTimeout
if timeout.Milliseconds() < 1 {
// If the timeout is less than 1ms, we'll set it to 1ms to avoid a busy loop
timeout = 1 * time.Millisecond
}
params := &graphql.RawParams{}
start := graphql.Now()
params.Headers = r.Header
params.ReadTime = graphql.TraceTiming{
Start: start,
End: graphql.Now(),
}
bodyString, err := getRequestBody(r)
if err != nil {
gqlErr := gqlerror.Errorf("could not get json request body: %+v", err)
resp := exec.DispatchError(ctx, gqlerror.List{gqlErr})
log.Printf("could not get json request body: %+v", err.Error())
writeJson(w, resp)
return
}
bodyReader := io.NopCloser(strings.NewReader(bodyString))
if err = jsonDecode(bodyReader, &params); err != nil {
w.WriteHeader(http.StatusBadRequest)
gqlErr := gqlerror.Errorf(
"json request body could not be decoded: %+v body:%s",
err,
bodyString,
)
resp := exec.DispatchError(ctx, gqlerror.List{gqlErr})
log.Printf("decoding error: %+v body:%s", err.Error(), bodyString)
writeJson(w, resp)
return
}
rc, opErr := exec.CreateOperationContext(ctx, params)
ctx = graphql.WithOperationContext(ctx, rc)
if opErr != nil {
w.WriteHeader(statusFor(opErr))
resp := exec.DispatchError(ctx, opErr)
writeJson(w, resp)
return
}
// Example of the response format (note the new lines and boundaries are important!):
// https://github.com/graphql/graphql-over-http/blob/main/rfcs/IncrementalDelivery.md
// --graphql
// Content-Type: application/json
//
// {"data":{"apps":{"apps":[ .. ],"totalNumApps":161,"__typename":"AppsOutput"}},"hasNext":true}
// --graphql
// Content-Type: application/json
//
// {"incremental":[{"data":{"groupAccessCount":0},"label":"test","path":["apps","apps",7],"hasNext":true}],"hasNext":true}
// --graphql
// ...
// --graphql--
// Last boundary is a closing boundary with two dashes at the end.
w.Header().Set(
"Content-Type",
fmt.Sprintf(`multipart/mixed;boundary="%s";deferSpec=20220824`, boundary),
)
a := newMultipartResponseAggregator(w, boundary, timeout)
defer a.Done(w)
responses, ctx := exec.DispatchOperation(ctx, rc)
initialResponse := true
for {
response := responses(ctx)
if response == nil {
break
}
a.Add(response, initialResponse)
initialResponse = false
}
}
func writeIncrementalJson(w io.Writer, responses []*graphql.Response, hasNext bool) {
// TODO: Remove this wrapper on response once gqlgen supports the 2023 spec
b, err := json.Marshal(struct {
Incremental []*graphql.Response `json:"incremental"`
HasNext bool `json:"hasNext"`
}{
Incremental: responses,
HasNext: hasNext,
})
if err != nil {
panic(err)
}
w.Write(b)
}
func writeBoundary(w io.Writer, boundary string, finalResponse bool) {
if finalResponse {
fmt.Fprintf(w, "--%s--\r\n", boundary)
return
}
fmt.Fprintf(w, "--%s\r\n", boundary)
}
func writeContentTypeHeader(w io.Writer) {
fmt.Fprintf(w, "Content-Type: application/json\r\n\r\n")
}
// multipartResponseAggregator helps us reduce the number of responses sent to the frontend by batching all the
// incremental responses together.
type multipartResponseAggregator struct {
mu sync.Mutex
boundary string
initialResponse *graphql.Response
deferResponses []*graphql.Response
done chan bool
}
// newMultipartResponseAggregator creates a new multipartResponseAggregator
// The aggregator will flush responses to the client every `tickerDuration` (default 1ms) so that
// multiple incremental responses are batched together.
func newMultipartResponseAggregator(
w http.ResponseWriter,
boundary string,
tickerDuration time.Duration,
) *multipartResponseAggregator {
a := &multipartResponseAggregator{
boundary: boundary,
done: make(chan bool, 1),
}
go func() {
ticker := time.NewTicker(tickerDuration)
defer ticker.Stop()
for {
select {
case <-a.done:
return
case <-ticker.C:
a.flush(w)
}
}
}()
return a
}
// Done flushes the remaining responses
func (a *multipartResponseAggregator) Done(w http.ResponseWriter) {
a.done <- true
a.flush(w)
}
// Add accumulates the responses
func (a *multipartResponseAggregator) Add(resp *graphql.Response, initialResponse bool) {
a.mu.Lock()
defer a.mu.Unlock()
if initialResponse {
a.initialResponse = resp
return
}
a.deferResponses = append(a.deferResponses, resp)
}
// flush sends the accumulated responses to the client
func (a *multipartResponseAggregator) flush(w http.ResponseWriter) {
a.mu.Lock()
defer a.mu.Unlock()
// If we don't have any responses, we can return early
if a.initialResponse == nil && len(a.deferResponses) == 0 {
return
}
flusher, ok := w.(http.Flusher)
if !ok {
// This should never happen, as we check for this much earlier on
panic("response writer does not support flushing")
}
hasNext := false
if a.initialResponse != nil {
// Initial response will need to begin with the boundary
writeBoundary(w, a.boundary, false)
writeContentTypeHeader(w)
writeJson(w, a.initialResponse)
hasNext = a.initialResponse.HasNext != nil && *a.initialResponse.HasNext
// Handle when initial is aggregated with deferred responses.
if len(a.deferResponses) > 0 {
fmt.Fprintf(w, "\r\n")
writeBoundary(w, a.boundary, false)
}
// Reset the initial response so we don't send it again
a.initialResponse = nil
}
if len(a.deferResponses) > 0 {
writeContentTypeHeader(w)
// Note: while the 2023 spec that includes "incremental" does not
// explicitly list the fields that should be included as part of the
// incremental object, it shows hasNext only on the response payload
// (marking the status of the operation as a whole), and instead the
// response payload implements pending and complete fields to mark the
// status of the incrementally delivered data.
//
// TODO: use the "HasNext" status of deferResponses items to determine
// the operation status and pending / complete fields, but remove from
// the incremental (deferResponses) object.
hasNext = a.deferResponses[len(a.deferResponses)-1].HasNext != nil &&
*a.deferResponses[len(a.deferResponses)-1].HasNext
writeIncrementalJson(w, a.deferResponses, hasNext)
// Reset the deferResponses so we don't send them again
a.deferResponses = nil
}
// Make sure to put the delimiter after every request, so that Apollo Client knows that the
// current payload has been sent, and updates the UI. This is particular important for the first
// response and the last response, which may either hang or never get handled.
// Final response will have a closing boundary with two dashes at the end.
fmt.Fprintf(w, "\r\n")
writeBoundary(w, a.boundary, !hasNext)
flusher.Flush()
}

View File

@@ -1,11 +1,12 @@
package transport
import (
"bytes"
"fmt"
"io"
"mime"
"net/http"
"strings"
"sync"
"github.com/vektah/gqlparser/v2/gqlerror"
@@ -46,32 +47,49 @@ func getRequestBody(r *http.Request) (string, error) {
return string(body), nil
}
var pool = sync.Pool{
New: func() any {
return &graphql.RawParams{}
},
}
func (h POST) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) {
ctx := r.Context()
writeHeaders(w, h.ResponseHeaders)
params := &graphql.RawParams{}
start := graphql.Now()
params := pool.Get().(*graphql.RawParams)
defer func() {
params.Headers = nil
params.ReadTime = graphql.TraceTiming{}
params.Extensions = nil
params.OperationName = ""
params.Query = ""
params.Variables = nil
pool.Put(params)
}()
params.Headers = r.Header
start := graphql.Now()
params.ReadTime = graphql.TraceTiming{
Start: start,
End: graphql.Now(),
}
bodyString, err := getRequestBody(r)
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
gqlErr := gqlerror.Errorf("could not get json request body: %+v", err)
gqlErr := gqlerror.Errorf("could not read request body: %+v", err)
resp := exec.DispatchError(ctx, gqlerror.List{gqlErr})
writeJson(w, resp)
return
}
bodyReader := io.NopCloser(strings.NewReader(bodyString))
if err = jsonDecode(bodyReader, &params); err != nil {
bodyReader := bytes.NewReader(bodyBytes)
if err := jsonDecode(bodyReader, &params); err != nil {
w.WriteHeader(http.StatusBadRequest)
gqlErr := gqlerror.Errorf(
"json request body could not be decoded: %+v body:%s",
err,
bodyString,
string(bodyBytes),
)
resp := exec.DispatchError(ctx, gqlerror.List{gqlErr})
writeJson(w, resp)

View File

@@ -13,7 +13,7 @@ import (
func writeJson(w io.Writer, response *graphql.Response) {
b, err := json.Marshal(response)
if err != nil {
panic(err)
panic(fmt.Errorf("unable to marshal %s: %w", string(response.Data), err))
}
w.Write(b)
}

View File

@@ -54,6 +54,7 @@ type (
receivedPong bool
exec graphql.GraphExecutor
closed bool
headers http.Header
initPayload InitPayload
}
@@ -119,6 +120,7 @@ func (t Websocket) Do(w http.ResponseWriter, r *http.Request, exec graphql.Graph
ctx: r.Context(),
exec: exec,
me: me,
headers: r.Header,
Websocket: t,
}
@@ -387,6 +389,8 @@ func (c *wsConnection) subscribe(start time.Time, msg *message) {
End: graphql.Now(),
}
params.Headers = c.headers
rc, err := c.exec.CreateOperationContext(ctx, params)
if err != nil {
resp := c.exec.DispatchError(graphql.WithOperationContext(ctx, rc), err)

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
"math"
"strconv"
)
@@ -62,24 +63,62 @@ func MarshalInt32(i int32) Marshaler {
func UnmarshalInt32(v any) (int32, error) {
switch v := v.(type) {
case string:
iv, err := strconv.ParseInt(v, 10, 32)
iv, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, err
}
return int32(iv), nil
return safeCastInt32(iv)
case int:
return int32(v), nil
return safeCastInt32(int64(v))
case int64:
return int32(v), nil
return safeCastInt32(v)
case json.Number:
iv, err := strconv.ParseInt(string(v), 10, 32)
iv, err := strconv.ParseInt(string(v), 10, 64)
if err != nil {
return 0, err
}
return int32(iv), nil
return safeCastInt32(iv)
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an int", v)
}
}
// IntegerError is an error type that allows users to identify errors associated
// with receiving an integer value that is not valid for the specific integer
// type designated by the API. IntegerErrors designate otherwise valid unsigned
// or signed 64-bit integers that are invalid in a specific context: they do not
// designate integers that overflow 64-bit versions of the current type.
type IntegerError struct {
Message string
}
func (e IntegerError) Error() string {
return e.Message
}
type Int32OverflowError struct {
Value int64
*IntegerError
}
func newInt32OverflowError(i int64) *Int32OverflowError {
return &Int32OverflowError{
Value: i,
IntegerError: &IntegerError{
Message: fmt.Sprintf("%d overflows signed 32-bit integer", i),
},
}
}
func (e *Int32OverflowError) Unwrap() error {
return e.IntegerError
}
func safeCastInt32(i int64) (int32, error) {
if i > math.MaxInt32 || i < math.MinInt32 {
return 0, newInt32OverflowError(i)
}
return int32(i), nil
}

View File

@@ -6,7 +6,7 @@ import (
"io"
)
func MarshalMap(val map[string]interface{}) Marshaler {
func MarshalMap(val map[string]any) Marshaler {
return WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(val)
if err != nil {
@@ -15,8 +15,8 @@ func MarshalMap(val map[string]interface{}) Marshaler {
})
}
func UnmarshalMap(v interface{}) (map[string]interface{}, error) {
if m, ok := v.(map[string]interface{}); ok {
func UnmarshalMap(v any) (map[string]any, error) {
if m, ok := v.(map[string]any); ok {
return m, nil
}

View File

@@ -92,7 +92,10 @@ func Handler(title, endpoint string) http.HandlerFunc {
// HandlerWithHeaders sets up the playground.
// fetcherHeaders are used by the playground's fetcher instance and will not be visible in the UI.
// uiHeaders are default headers that will show up in the UI headers editor.
func HandlerWithHeaders(title, endpoint string, fetcherHeaders, uiHeaders map[string]string) http.HandlerFunc {
func HandlerWithHeaders(
title, endpoint string,
fetcherHeaders, uiHeaders map[string]string,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html; charset=UTF-8")
err := page.Execute(w, map[string]any{
@@ -102,9 +105,9 @@ func HandlerWithHeaders(title, endpoint string, fetcherHeaders, uiHeaders map[st
"uiHeaders": uiHeaders,
"endpointIsAbsolute": endpointHasScheme(endpoint),
"subscriptionEndpoint": getSubscriptionEndpoint(endpoint),
"version": "3.0.6",
"cssSRI": "sha256-wTzfn13a+pLMB5rMeysPPR1hO7x0SwSeQI+cnw7VdbE=",
"jsSRI": "sha256-eNxH+Ah7Z9up9aJYTQycgyNuy953zYZwE9Rqf5rH+r4=",
"version": "3.7.0",
"cssSRI": "sha256-Dbkv2LUWis+0H4Z+IzxLBxM2ka1J133lSjqqtSu49o8=",
"jsSRI": "sha256-qsScAZytFdTAEOM8REpljROHu8DvdvxXBK7xhoq5XD0=",
"reactSRI": "sha256-S0lp+k7zWUMk2ixteM6HZvu8L9Eh//OVrt+ZfbCpmgY=",
"reactDOMSRI": "sha256-IXWO0ITNDjfnNXIu5POVfqlgYoop36bDzhodR6LW5Pc=",
})

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"math"
"strconv"
)
@@ -18,21 +19,33 @@ func UnmarshalUint(v any) (uint, error) {
switch v := v.(type) {
case string:
u64, err := strconv.ParseUint(v, 10, 64)
if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(v) {
return 0, newUintSignError(v)
}
return 0, err
}
return uint(u64), err
case int:
if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint")
return 0, newUintSignError(strconv.FormatInt(int64(v), 10))
}
return uint(v), nil
case int64:
if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint")
return 0, newUintSignError(strconv.FormatInt(v, 10))
}
return uint(v), nil
case json.Number:
u64, err := strconv.ParseUint(string(v), 10, 64)
if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(string(v)) {
return 0, newUintSignError(string(v))
}
return 0, err
}
return uint(u64), err
case nil:
return 0, nil
@@ -50,21 +63,35 @@ func MarshalUint64(i uint64) Marshaler {
func UnmarshalUint64(v any) (uint64, error) {
switch v := v.(type) {
case string:
return strconv.ParseUint(v, 10, 64)
i, err := strconv.ParseUint(v, 10, 64)
if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(v) {
return 0, newUintSignError(v)
}
return 0, err
}
return i, nil
case int:
if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint64")
return 0, newUintSignError(strconv.FormatInt(int64(v), 10))
}
return uint64(v), nil
case int64:
if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint64")
return 0, newUintSignError(strconv.FormatInt(v, 10))
}
return uint64(v), nil
case json.Number:
return strconv.ParseUint(string(v), 10, 64)
i, err := strconv.ParseUint(string(v), 10, 64)
if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(string(v)) {
return 0, newUintSignError(string(v))
}
return 0, err
}
return i, nil
case nil:
return 0, nil
default:
@@ -81,32 +108,92 @@ func MarshalUint32(i uint32) Marshaler {
func UnmarshalUint32(v any) (uint32, error) {
switch v := v.(type) {
case string:
iv, err := strconv.ParseUint(v, 10, 32)
iv, err := strconv.ParseUint(v, 10, 64)
if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(v) {
return 0, newUintSignError(v)
}
return 0, err
}
return uint32(iv), nil
return safeCastUint32(iv)
case int:
if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint32")
return 0, newUintSignError(strconv.FormatInt(int64(v), 10))
}
return uint32(v), nil
return safeCastUint32(uint64(v))
case int64:
if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint32")
return 0, newUintSignError(strconv.FormatInt(v, 10))
}
return uint32(v), nil
return safeCastUint32(uint64(v))
case json.Number:
iv, err := strconv.ParseUint(string(v), 10, 32)
iv, err := strconv.ParseUint(string(v), 10, 64)
if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(string(v)) {
return 0, newUintSignError(string(v))
}
return 0, err
}
return uint32(iv), nil
return safeCastUint32(iv)
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an uint", v)
}
}
type UintSignError struct {
*IntegerError
}
func newUintSignError(v string) *UintSignError {
return &UintSignError{
IntegerError: &IntegerError{
Message: fmt.Sprintf("%v is an invalid unsigned integer: includes sign", v),
},
}
}
func (e *UintSignError) Unwrap() error {
return e.IntegerError
}
func isSignedInteger(v string) bool {
if v == "" {
return false
}
if v[0] != '-' && v[0] != '+' {
return false
}
if _, err := strconv.ParseUint(v[1:], 10, 64); err == nil {
return true
}
return false
}
type Uint32OverflowError struct {
Value uint64
*IntegerError
}
func newUint32OverflowError(i uint64) *Uint32OverflowError {
return &Uint32OverflowError{
Value: i,
IntegerError: &IntegerError{
Message: fmt.Sprintf("%d overflows unsigned 32-bit integer", i),
},
}
}
func (e *Uint32OverflowError) Unwrap() error {
return e.IntegerError
}
func safeCastUint32(i uint64) (uint32, error) {
if i > math.MaxUint32 {
return 0, newUint32OverflowError(i)
}
return uint32(i), nil
}

View File

@@ -1,3 +1,3 @@
package graphql
const Version = "v0.17.55"
const Version = "v0.17.63"

View File

@@ -4,15 +4,25 @@ schema:
# Where should the generated server code go?
exec:
filename: graph/generated.go
package: graph
layout: single-file # Only other option is "follow-schema," ie multi-file.
# Only for single-file layout:
filename: graph/generated.go
# Only for follow-schema layout:
# dir: graph
# filename_template: "{name}.generated.go"
# Optional: Maximum number of goroutines in concurrency to use per child resolvers(default: unlimited)
# worker_limit: 1000
# Uncomment to enable federation
# federation:
# filename: graph/federation.go
# package: graph
# version: 2
# options
# options:
# computed_requires: true
# Where should any generated models go?
@@ -20,14 +30,27 @@ model:
filename: graph/model/models_gen.go
package: model
# Optional: Pass in a path to a new gotpl template to use for generating the models
# model_template: [your/path/model.gotpl]
# Where should the resolver implementations go?
resolver:
layout: follow-schema
dir: graph
package: graph
layout: follow-schema # Only other option is "single-file."
# Only for single-file layout:
# filename: graph/resolver.go
# Only for follow-schema layout:
dir: graph
filename_template: "{name}.resolvers.go"
# Optional: turn on to not generate template comments above resolvers
# omit_template_comment: false
# Optional: Pass in a path to a new gotpl template to use for generating resolvers
# resolver_template: [your/path/resolver.gotpl]
# Optional: turn on to avoid rewriting existing resolver(s) when generating
# preserve_resolver: false
# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
# struct_tag: json
@@ -36,7 +59,7 @@ resolver:
# omit_slice_element_pointers: false
# Optional: turn on to omit Is<Name>() methods to interface and unions
# omit_interface_checks : true
# omit_interface_checks: true
# Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function
# omit_complexity: false
@@ -47,6 +70,12 @@ resolver:
# Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true.
# omit_gqlgen_version_in_file_notice: false
# Optional: turn on to exclude root models such as Query and Mutation from the generated models file.
# omit_root_models: false
# Optional: turn on to exclude resolver fields from the generated models file.
# omit_resolver_fields: false
# Optional: turn off to make struct-type struct fields not use pointers
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
# struct_fields_always_pointers: true
@@ -73,6 +102,18 @@ resolver:
# argument values but to set them even if they're null.
call_argument_directives_with_null: true
# Optional: set build tags that will be used to load packages
# go_build_tags:
# - private
# - enterprise
# Optional: set to modify the initialisms regarded for Go names
# go_initialisms:
# replace_defaults: false # if true, the default initialisms will get dropped in favor of the new ones instead of being added
# initialisms: # List of initialisms to for Go names
# - 'CC'
# - 'BCC'
# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
@@ -90,8 +131,25 @@ models:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
# gqlgen provides a default GraphQL UUID convenience wrapper for github.com/google/uuid
# but you can override this to provide your own GraphQL UUID implementation
UUID:
model:
- github.com/99designs/gqlgen/graphql.UUID
# The GraphQL spec explicitly states that the Int type is a signed 32-bit
# integer. Using Go int or int64 to represent it can lead to unexpected
# behavior, and some GraphQL tools like Apollo Router will fail when
# communicating numbers that overflow 32-bits.
#
# You may choose to use the custom, built-in Int64 scalar to represent 64-bit
# integers, or ignore the spec and bind Int to graphql.Int / graphql.Int64
# (the default behavior of gqlgen). This is fine in simple use cases when you
# do not need to worry about interoperability and only expect small numbers.
Int:
model:
- github.com/99designs/gqlgen/graphql.Int32
Int64:
model:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32

View File

@@ -1,5 +1,3 @@
//go:build !go1.23
package code
import (
@@ -7,7 +5,13 @@ import (
)
// Unalias unwraps an alias type
// TODO: Drop this function when we drop support for go1.22
func Unalias(t types.Type) types.Type {
return t // No-op
if p, ok := t.(*types.Pointer); ok {
// If the type come from auto-binding,
// it will be a pointer to an alias type.
// (e.g: `type Cursor = entgql.Cursor[int]`)
// *ent.Cursor is the type we got from auto-binding.
return types.NewPointer(Unalias(p.Elem()))
}
return types.Unalias(t)
}

View File

@@ -1,19 +0,0 @@
//go:build go1.23
package code
import (
"go/types"
)
// Unalias unwraps an alias type
func Unalias(t types.Type) types.Type {
if p, ok := t.(*types.Pointer); ok {
// If the type come from auto-binding,
// it will be a pointer to an alias type.
// (e.g: `type Cursor = entgql.Cursor[int]`)
// *ent.Cursor is the type we got from auto-binding.
return types.NewPointer(Unalias(p.Elem()))
}
return types.Unalias(t)
}

View File

@@ -7,18 +7,11 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime/debug"
"strings"
"sync"
"golang.org/x/tools/go/packages"
)
var (
once = sync.Once{}
modInfo *debug.BuildInfo
)
var mode = packages.NeedName |
packages.NeedFiles |
packages.NeedTypes |
@@ -30,10 +23,11 @@ type (
// Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages
// that can be invalidated as writes are made and packages are known to change.
Packages struct {
packages map[string]*packages.Package
importToName map[string]string
loadErrors []error
buildFlags []string
packages map[string]*packages.Package
importToName map[string]string
loadErrors []error
buildFlags []string
packagesToCachePrefix string
numLoadCalls int // stupid test steam. ignore.
numNameCalls int // stupid test steam. ignore.
@@ -42,13 +36,21 @@ type (
Option func(p *Packages)
)
// WithBuildTags adds build tags to the packages.Load call
// WithBuildTags option for NewPackages adds build tags to the packages.Load call
func WithBuildTags(tags ...string) func(p *Packages) {
return func(p *Packages) {
p.buildFlags = append(p.buildFlags, "-tags", strings.Join(tags, ","))
}
}
// PackagePrefixToCache option for NewPackages
// will not reset gqlgen packages in packages.Load call
func PackagePrefixToCache(prefixPath string) func(p *Packages) {
return func(p *Packages) {
p.packagesToCachePrefix = prefixPath
}
}
// NewPackages creates a new packages cache
// It will load all packages in the current module, and any packages that are passed to Load or LoadAll
func NewPackages(opts ...Option) *Packages {
@@ -60,27 +62,23 @@ func NewPackages(opts ...Option) *Packages {
}
func (p *Packages) CleanupUserPackages() {
once.Do(func() {
var ok bool
modInfo, ok = debug.ReadBuildInfo()
if !ok {
modInfo = nil
}
})
// Don't cleanup github.com/99designs/gqlgen prefixed packages,
// they haven't changed and do not need to be reloaded
if modInfo != nil {
if p.packagesToCachePrefix == "" {
// Cleanup all packages if we don't know which ones to keep
p.packages = nil
} else {
// Don't clean up github.com/99designs/gqlgen prefixed packages,
// they haven't changed and do not need to be reloaded
// if you are using a fork, then you need to have customized
// the prefix using PackagePrefixToCache
var toRemove []string
for k := range p.packages {
if !strings.HasPrefix(k, modInfo.Main.Path) {
if !strings.HasPrefix(k, p.packagesToCachePrefix) {
toRemove = append(toRemove, k)
}
}
for _, k := range toRemove {
delete(p.packages, k)
}
} else {
p.packages = nil // Cleanup all packages if we don't know for some reason which ones to keep
}
}
@@ -231,8 +229,7 @@ func (p *Packages) ModTidy() error {
// Errors returns any errors that were returned by Load, either from the call itself or any of the loaded packages.
func (p *Packages) Errors() PkgErrors {
var res []error //nolint:prealloc
res = append(res, p.loadErrors...)
res := append([]error{}, p.loadErrors...)
for _, pkg := range p.packages {
for _, err := range pkg.Errors {
res = append(res, err)

View File

@@ -1,8 +1,9 @@
package federation
import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/vektah/gqlparser/v2/ast"
"github.com/99designs/gqlgen/codegen/config"
)
// The name of the field argument that is injected into the resolver to support @requires.

View File

@@ -279,10 +279,13 @@ func (f *Federation) GenerateCode(data *codegen.Data) error {
typeString := strings.Split(obj.Type.String(), ".")
requiresImports[strings.Join(typeString[:len(typeString)-1], ".")] = true
if containsUnionField(reqField) {
continue
}
cgField := reqField.Field.TypeReference(obj, data.Objects)
reqField.Type = cgField.TypeReference
}
// add type info to entity
e.Type = obj.Type
}
@@ -321,14 +324,24 @@ func (f *Federation) GenerateCode(data *codegen.Data) error {
Filename: data.Config.Federation.Filename,
Data: struct {
Federation
UsePointers bool
}{*f, data.Config.ResolversAlwaysReturnPointers},
UsePointers bool
UseFunctionSyntaxForExecutionContext bool
}{*f, data.Config.ResolversAlwaysReturnPointers, data.Config.UseFunctionSyntaxForExecutionContext},
GeneratedHeader: true,
Packages: data.Config.Packages,
Template: federationTemplate,
})
}
func containsUnionField(reqField *Requires) bool {
for _, requireFields := range reqField.Field {
if strings.HasPrefix(requireFields, "... on") {
return true
}
}
return false
}
// Fill in types for key fields
func populateKeyFieldTypes(
resolver *EntityResolver,

View File

@@ -8,6 +8,8 @@
{{ $options := .PackageOptions }}
{{ $usePointers := .UsePointers }}
{{ $useFunctionSyntaxForExecutionContext := .UseFunctionSyntaxForExecutionContext }}
var (
ErrUnknownType = errors.New("unknown type")
ErrTypeNotFound = errors.New("type not found")
@@ -33,7 +35,7 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.
}
{{if .Entities}}
func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity {
func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]any) []fedruntime.Entity {
list := make([]fedruntime.Entity, len(representations))
repsMap := ec.buildRepresentationGroups(ctx, representations)
@@ -169,7 +171,11 @@ func (ec *executionContext) resolveEntity(
{{ range $i, $resolver := .Resolvers }}
case "{{.ResolverName}}":
{{- range $j, $keyField := .KeyFields }}
id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
{{ if $useFunctionSyntaxForExecutionContext -}}
id{{$j}}, err := {{.Type.UnmarshalFunc}}(ctx, ec, rep["{{.Field.Join `"].(map[string]any)["`}}"])
{{- else -}}
id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]any)["`}}"])
{{- end }}
if err != nil {
return nil, fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err)
}
@@ -187,7 +193,11 @@ func (ec *executionContext) resolveEntity(
}
{{- else }}
{{ range $entity.Requires }}
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
{{ if $useFunctionSyntaxForExecutionContext -}}
entity.{{.Field.JoinGo `.`}}, err = {{.Type.UnmarshalFunc}}(ctx, ec, rep["{{.Field.Join `"].(map[string]any)["`}}"])
{{- else -}}
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]any)["`}}"])
{{- end }}
if err != nil {
return nil, err
}
@@ -231,7 +241,11 @@ func (ec *executionContext) resolveManyEntities(
for i, rep := range reps {
{{ range $i, $keyField := .KeyFields -}}
id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep.entity["{{.Field.Join `"].(map[string]interface{})["`}}"])
{{ if $useFunctionSyntaxForExecutionContext -}}
id{{$i}}, err := {{.Type.UnmarshalFunc}}(ctx, ec, rep.entity["{{.Field.Join `"].(map[string]any)["`}}"])
{{- else -}}
id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep.entity["{{.Field.Join `"].(map[string]any)["`}}"])
{{- end }}
if err != nil {
return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}"))
}
@@ -251,7 +265,11 @@ func (ec *executionContext) resolveManyEntities(
for i, entity := range entities {
{{- range $entity.Requires }}
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i].entity["{{.Field.Join `"].(map[string]interface{})["`}}"])
{{ if $useFunctionSyntaxForExecutionContext -}}
entity.{{.Field.JoinGo `.`}}, err = {{.Type.UnmarshalFunc}}(ctx, ec, reps[i].entity["{{.Field.Join `"].(map[string]any)["`}}"])
{{- else -}}
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i].entity["{{.Field.Join `"].(map[string]any)["`}}"])
{{- end }}
if err != nil {
return err
}
@@ -276,11 +294,14 @@ func (ec *executionContext) resolveManyEntities(
{{- if .Resolvers }}
func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep EntityRepresentation) (string, error) {
// we collect errors because a later entity resolver may work fine
// when an entity has multiple keys
entityResolverErrs := []error{}
{{- range .Resolvers }}
for {
var (
m EntityRepresentation
val interface{}
val any
ok bool
)
_ = val
@@ -292,10 +313,15 @@ func (ec *executionContext) resolveManyEntities(
{{- range $i, $field := .Field }}
val, ok = m["{{.}}"]
if !ok {
entityResolverErrs = append(entityResolverErrs,
fmt.Errorf("%w due to missing Key Field \"{{.}}\" for {{$entity.Name}}", ErrTypeNotFound))
break
}
{{- if (ne $i $keyField.Field.LastIndex ) }}
if m, ok = val.(map[string]interface{}); !ok {
if m, ok = val.(map[string]any); !ok {
// nested field value is not a map[string]interface so don't use it
entityResolverErrs = append(entityResolverErrs,
fmt.Errorf("%w due to nested Key Field \"{{.}}\" value not matching map[string]any for {{$entity.Name}}", ErrTypeNotFound))
break
}
{{- else}}
@@ -306,12 +332,15 @@ func (ec *executionContext) resolveManyEntities(
{{- end}}
{{- end }}
if allNull {
entityResolverErrs = append(entityResolverErrs,
fmt.Errorf("%w due to all null value KeyFields for {{$entity.Name}}", ErrTypeNotFound))
break
}
return "{{.ResolverName}}", nil
}
{{- end }}
return "", fmt.Errorf("%w for {{$entity.Name}}", ErrTypeNotFound)
return "", fmt.Errorf("%w for {{$entity.Name}} due to %v", ErrTypeNotFound,
errors.Join(entityResolverErrs...).Error())
}
{{- end }}
{{- end }}

View File

@@ -133,8 +133,22 @@ func (f Field) LastIndex() int {
// parseUnnestedKeyFieldSet // handles simple case where none of the fields are nested.
func parseUnnestedKeyFieldSet(raw string, prefix []string) Set {
ret := Set{}
unionField := false
for _, s := range strings.Fields(raw) {
if s == "..." {
continue
}
if s == "on" {
unionField = true
continue
}
if unionField {
s = "... on " + s
unionField = false
}
next := append(prefix[0:len(prefix):len(prefix)], s) //nolint:gocritic // set cap=len in order to force slice reallocation
ret = append(ret, next)
}

View File

@@ -12,7 +12,7 @@
{{- else -}}
// {{.FuncName}} is the requires populator for the {{.Entity.Def.Name}} entity.
{{- end }}
func (ec *executionContext) {{.FuncName}}(ctx context.Context, entity *{{.Entity.GetTypeInfo}}, reps map[string]interface{}) error {
func (ec *executionContext) {{.FuncName}}(ctx context.Context, entity *{{.Entity.GetTypeInfo}}, reps map[string]any) error {
{{.Implementation}}
}
{{ end }}

View File

@@ -356,7 +356,6 @@ func (m *Plugin) generateField(
schemaType *ast.Definition,
field *ast.FieldDefinition,
) (*Field, error) {
var omittableType types.Type
var typ types.Type
fieldDef := cfg.Schema.Types[field.Type.Name()]
@@ -449,13 +448,9 @@ func (m *Plugin) generateField(
return nil, fmt.Errorf("generror: field %v.%v: omittable is only applicable to nullable input fields", schemaType.Name, field.Name)
}
var err error
if omittableType == nil {
omittableType, err = binder.FindTypeFromName("github.com/99designs/gqlgen/graphql.Omittable")
if err != nil {
return nil, err
}
omittableType, err := binder.FindTypeFromName("github.com/99designs/gqlgen/graphql.Omittable")
if err != nil {
return nil, err
}
f.Type, err = binder.InstantiateType(omittableType, []types.Type{f.Type})

View File

@@ -84,7 +84,7 @@
return string(e)
}
func (e *{{ goModelName .Name }}) UnmarshalGQL(v interface{}) error {
func (e *{{ goModelName .Name }}) UnmarshalGQL(v any) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")

View File

@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"go/ast"
"io/fs"
"os"
"path/filepath"
"strings"
@@ -45,6 +44,7 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error {
case config.LayoutSingleFile:
return m.generateSingleFile(data)
case config.LayoutFollowSchema:
return m.generatePerSchema(data)
}
@@ -53,6 +53,14 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error {
func (m *Plugin) generateSingleFile(data *codegen.Data) error {
file := File{}
if fileExists(data.Config.Resolver.Filename) &&
data.Config.Resolver.PreserveResolver {
// file already exists and config says not to update resolver
// so just return
return nil
}
rewriter, err := rewrite.New(data.Config.Resolver.Dir())
if err != nil {
return err
@@ -85,7 +93,7 @@ func (m *Plugin) generateSingleFile(data *codegen.Data) error {
}
}
if _, err := os.Stat(data.Config.Resolver.Filename); err == nil {
if fileExists(data.Config.Resolver.Filename) {
file.name = data.Config.Resolver.Filename
file.imports = rewriter.ExistingImports(file.name)
file.RemainingSource = rewriter.RemainingSource(file.name)
@@ -104,9 +112,14 @@ func (m *Plugin) generateSingleFile(data *codegen.Data) error {
newResolverTemplate = readResolverTemplate(data.Config.Resolver.ResolverTemplate)
}
fileNotice := `// THIS CODE WILL BE UPDATED WITH SCHEMA CHANGES. PREVIOUS IMPLEMENTATION FOR SCHEMA CHANGES WILL BE KEPT IN THE COMMENT SECTION. IMPLEMENTATION FOR UNCHANGED SCHEMA WILL BE KEPT.`
if data.Config.Resolver.PreserveResolver {
fileNotice = `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`
}
return templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package,
FileNotice: `// THIS CODE WILL BE UPDATED WITH SCHEMA CHANGES. PREVIOUS IMPLEMENTATION FOR SCHEMA CHANGES WILL BE KEPT IN THE COMMENT SECTION. IMPLEMENTATION FOR UNCHANGED SCHEMA WILL BE KEPT.`,
FileNotice: fileNotice,
Filename: data.Config.Resolver.Filename,
Data: resolverBuild,
Packages: data.Config.Packages,
@@ -146,6 +159,7 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error {
continue
}
structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)
// TODO(steve): Why do we need to trimLeft "\" here? Some bazel thing?
comment := strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment(structName, f.GoFieldName), `\`))
implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName))
resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation, nil}
@@ -183,6 +197,11 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error {
}
for _, file := range files {
if fileExists(file.name) &&
data.Config.Resolver.PreserveResolver {
// file already exists and config says not to update resolver
continue
}
resolverBuild := &ResolverBuild{
File: file,
PackageName: data.Config.Resolver.Package,
@@ -216,7 +235,7 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error {
}
}
if _, err := os.Stat(data.Config.Resolver.Filename); errors.Is(err, fs.ErrNotExist) {
if !fileExists(data.Config.Resolver.Filename) {
err := templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package,
FileNotice: `
@@ -304,3 +323,10 @@ func readResolverTemplate(customResolverTemplate string) string {
}
return string(contentBytes)
}
func fileExists(fileName string) bool {
if _, err := os.Stat(fileName); err == nil {
return true
}
return false
}

View File

@@ -20,7 +20,7 @@
{{ range $resolver := .Resolvers -}}
{{ if $resolver.Comment -}}
// {{ $resolver.Comment }}
{{with $resolver.Comment}}{{.|prefixLines "// "}}{{end}}
{{- else if not $.OmitTemplateComment -}}
// {{ $resolver.Field.GoFieldName }} is the resolver for the {{ $resolver.Field.Name }} field.
{{- end }}

View File

@@ -2,8 +2,12 @@
{{ reserveImport "log" }}
{{ reserveImport "net/http" }}
{{ reserveImport "os" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/playground" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/handler" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/handler/extension" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/handler/lru" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/handler/transport" }}
const defaultPort = "8080"
@@ -13,7 +17,18 @@ func main() {
port = defaultPort
}
srv := handler.NewDefaultServer({{ lookupImport .ExecPackageName }}.NewExecutableSchema({{ lookupImport .ExecPackageName}}.Config{Resolvers: &{{ lookupImport .ResolverPackageName}}.Resolver{}}))
srv := handler.New({{ lookupImport .ExecPackageName }}.NewExecutableSchema({{ lookupImport .ExecPackageName}}.Config{Resolvers: &{{ lookupImport .ResolverPackageName}}.Resolver{}}))
srv.AddTransport(transport.Options{})
srv.AddTransport(transport.GET{})
srv.AddTransport(transport.POST{})
srv.SetQueryCache(lru.New[*ast.QueryDocument](1000))
srv.Use(extension.Introspection{})
srv.Use(extension.AutomaticPersistedQuery{
Cache: lru.New[string](100),
})
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
http.Handle("/query", srv)

View File

@@ -39,9 +39,11 @@ var (
)
// semVerRegex is the regular expression used to parse a semantic version.
const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
// 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.
const semVerRegex string = `v?(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:\.(0|[1-9]\d*))?` +
`(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?` +
`(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?`
// Version represents a single semantic version.
type Version struct {
@@ -146,8 +148,8 @@ func NewVersion(v string) (*Version, error) {
}
sv := &Version{
metadata: m[8],
pre: m[5],
metadata: m[5],
pre: m[4],
original: v,
}
@@ -158,7 +160,7 @@ func NewVersion(v string) (*Version, error) {
}
if m[2] != "" {
sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64)
sv.minor, err = strconv.ParseUint(m[2], 10, 64)
if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err)
}
@@ -167,7 +169,7 @@ func NewVersion(v string) (*Version, error) {
}
if m[3] != "" {
sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64)
sv.patch, err = strconv.ParseUint(m[3], 10, 64)
if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err)
}
@@ -612,7 +614,9 @@ func containsOnly(s string, comp string) bool {
func validatePrerelease(p string) error {
eparts := strings.Split(p, ".")
for _, p := range eparts {
if containsOnly(p, num) {
if p == "" {
return ErrInvalidMetadata
} else if containsOnly(p, num) {
if len(p) > 1 && p[0] == '0' {
return ErrSegmentStartsZero
}
@@ -631,7 +635,9 @@ func validatePrerelease(p string) error {
func validateMetadata(m string) error {
eparts := strings.Split(m, ".")
for _, p := range eparts {
if !containsOnly(p, allowed) {
if p == "" {
return ErrInvalidMetadata
} else if !containsOnly(p, allowed) {
return ErrInvalidMetadata
}
}

View File

@@ -29,7 +29,7 @@ func NextTickAfter(expr string, start time.Time, inclRefTime bool) (time.Time, e
}
segments, _ := Segments(expr)
if len(segments) > 6 && isUnreachableYear(segments[6], next, inclRefTime, false) {
if len(segments) > 6 && isUnreachableYear(segments[6], next, false) {
return next, fmt.Errorf("unreachable year segment: %s", segments[6])
}
@@ -123,25 +123,19 @@ over:
var dashRe = regexp.MustCompile(`/.*$`)
func isUnreachableYear(year string, ref time.Time, incl bool, reverse bool) bool {
func isUnreachableYear(year string, ref time.Time, reverse bool) bool {
if year == "*" || year == "?" {
return false
}
edge, inc := ref.Year(), 1
if !incl {
if reverse {
inc = -1
}
edge += inc
}
edge := ref.Year()
for _, offset := range strings.Split(year, ",") {
if strings.Index(offset, "*/") == 0 || strings.Index(offset, "0/") == 0 {
return false
}
for _, part := range strings.Split(dashRe.ReplaceAllString(offset, ""), "-") {
val, err := strconv.Atoi(part)
if err != nil || (!reverse && val >= edge) || (reverse && val < edge) {
if err != nil || (!reverse && val >= edge) || (reverse && val <= edge) {
return false
}
}

View File

@@ -19,7 +19,7 @@ func PrevTickBefore(expr string, start time.Time, inclRefTime bool) (time.Time,
}
segments, _ := Segments(expr)
if len(segments) > 6 && isUnreachableYear(segments[6], prev, inclRefTime, true) {
if len(segments) > 6 && isUnreachableYear(segments[6], prev, true) {
return prev, fmt.Errorf("unreachable year segment: %s", segments[6])
}

View File

@@ -90,7 +90,7 @@ CertMagic - Automatic HTTPS using Let's Encrypt
- Exponential backoff with carefully-tuned intervals
- Retries with optional test/staging CA endpoint instead of production, to avoid rate limits
- Written in Go, a language with memory-safety guarantees
- Powered by [ACMEz](https://github.com/mholt/acmez/v2), _the_ premier ACME client library for Go
- Powered by [ACMEz](https://github.com/mholt/acmez/v3), _the_ premier ACME client library for Go
- All [libdns](https://github.com/libdns) DNS providers work out-of-the-box
- Pluggable storage backends (default: file system)
- Pluggable key sources
@@ -567,7 +567,7 @@ We welcome your contributions! Please see our **[contributing guidelines](https:
## Project History
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [ACMEz](https://github.com/mholt/acmez/v2). CertMagic's code was originally a central part of Caddy even before Let's Encrypt entered public beta in 2015.
CertMagic is the core of Caddy's advanced TLS automation code, extracted into a library. The underlying ACME client implementation is [ACMEz](https://github.com/mholt/acmez/v3). CertMagic's code was originally a central part of Caddy even before Let's Encrypt entered public beta in 2015.
In the years since then, Caddy's TLS automation techniques have been widely adopted, tried and tested in production, and served millions of sites and secured trillions of connections.
@@ -579,9 +579,9 @@ Caddy was also the first to sport "on-demand" issuance technology, which obtains
Consequently, CertMagic brings all these (and more) features and capabilities right into your own Go programs.
You can [watch a 2016 dotGo talk](https://www.dotconferences.com/2016/10/matthew-holt-go-with-acme) by the author of this library about using ACME to automate certificate management in Go programs:
You can [watch a 2016 dotGo talk](https://youtu.be/KdX51QJWQTA) by the author of this library about using ACME to automate certificate management in Go programs:
[![Matthew Holt speaking at dotGo 2016 about ACME in Go](https://user-images.githubusercontent.com/1128849/49921557-2d506780-fe6b-11e8-97bf-6053b6b4eb48.png)](https://www.dotconferences.com/2016/10/matthew-holt-go-with-acme)
[![Matthew Holt speaking at dotGo 2016 about ACME in Go](https://user-images.githubusercontent.com/1128849/49921557-2d506780-fe6b-11e8-97bf-6053b6b4eb48.png)](https://youtu.be/KdX51QJWQTA)

View File

@@ -32,7 +32,7 @@ import (
"strings"
"sync"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
)

View File

@@ -18,6 +18,7 @@ import (
"context"
"crypto/x509"
"fmt"
"log/slog"
"net"
"net/http"
"net/url"
@@ -26,9 +27,10 @@ import (
"sync"
"time"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
)
// acmeClient holds state necessary to perform ACME operations
@@ -276,7 +278,7 @@ func (iss *ACMEIssuer) newBasicACMEClient() (*acmez.Client, error) {
Directory: caURL,
UserAgent: buildUAString(),
HTTPClient: iss.httpClient,
Logger: iss.Logger.Named("acme_client"),
Logger: slog.New(zapslog.NewHandler(iss.Logger.Named("acme_client").Core())),
},
}, nil
}

View File

@@ -28,8 +28,8 @@ import (
"sync"
"time"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
)
@@ -69,6 +69,15 @@ type ACMEIssuer struct {
// with this ACME account
ExternalAccount *acme.EAB
// Optionally select an ACME profile offered
// by the ACME server. The list of supported
// profile names can be obtained from the ACME
// server's directory endpoint. For details:
// https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/
//
// (EXPERIMENTAL: Subject to change.)
Profile string
// Optionally specify the validity period of
// the certificate(s) here as offsets from the
// approximate time of certificate issuance,
@@ -282,7 +291,7 @@ func NewACMEIssuer(cfg *Config, template ACMEIssuer) *ACMEIssuer {
}
// IssuerKey returns the unique issuer key for the
// confgured CA endpoint.
// configured CA endpoint.
func (am *ACMEIssuer) IssuerKey() string {
return am.issuerKey(am.CA)
}
@@ -450,6 +459,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
if am.NotAfter != 0 {
params.NotAfter = time.Now().Add(am.NotAfter)
}
params.Profile = am.Profile
// Notify the ACME server we are replacing a certificate (if the caller says we are),
// only if the following conditions are met:
@@ -482,7 +492,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
zap.String("account_id", client.account.Location),
zap.Strings("account_contact", client.account.Contact),
zap.String("key_location", am.storageKeyUserPrivateKey(client.acmeClient.Directory, am.getEmail())),
zap.Object("problem", prob))
zap.Any("problem", prob))
// the account we have no longer exists on the CA, so we need to create a new one;
// we could use the same key pair, but this is a good opportunity to rotate keys

View File

@@ -26,7 +26,7 @@ import (
"strings"
"time"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
"golang.org/x/crypto/ocsp"
)
@@ -87,6 +87,14 @@ func (cert Certificate) NeedsRenewal(cfg *Config) bool {
// call it again to see if the cert in storage still needs renewal -- you probably don't want
// to log the second time for checking the cert in storage which is mainly for synchronization.
func (cfg *Config) certNeedsRenewal(leaf *x509.Certificate, ari acme.RenewalInfo, emitLogs bool) bool {
// though this should never happen, safeguard to avoid panics which happened before (since patched; but just in case)
if leaf == nil {
if emitLogs {
cfg.Logger.Error("cannot check if nil leaf cert needs renewal")
}
return false
}
expiration := expiresAt(leaf)
var logger *zap.Logger
@@ -108,7 +116,7 @@ func (cfg *Config) certNeedsRenewal(leaf *x509.Certificate, ari acme.RenewalInfo
// (notice that we don't strictly require an ARI window to also exist; we presume
// that if a time has been selected, a window does or did exist, even if it didn't
// get stored/encoded for some reason - but also: this allows administrators to
// manually or explicitly schedule a renewal time indepedently of ARI which could
// manually or explicitly schedule a renewal time independently of ARI which could
// be useful)
selectedTime := ari.SelectedTime
@@ -145,7 +153,7 @@ func (cfg *Config) certNeedsRenewal(leaf *x509.Certificate, ari acme.RenewalInfo
// possibility of a bug in ARI compromising a site's uptime: we should always always
// always give heed to actual validity period
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) {
logger.Warn("certificate is in emergency renewal window; superceding ARI",
logger.Warn("certificate is in emergency renewal window; superseding ARI",
zap.Duration("remaining", time.Until(expiration)),
zap.Time("renewal_cutoff", cutoff))
return true
@@ -188,6 +196,14 @@ func (cert Certificate) Expired() bool {
return time.Now().After(expiresAt(cert.Leaf))
}
// Lifetime returns the duration of the certificate's validity.
func (cert Certificate) Lifetime() time.Duration {
if cert.Leaf == nil || cert.Leaf.NotAfter.IsZero() {
return 0
}
return expiresAt(cert.Leaf).Sub(cert.Leaf.NotBefore)
}
// currentlyInRenewalWindow returns true if the current time is within
// (or after) the renewal window, according to the given start/end
// dates and the ratio of the renewal window. If true is returned,

View File

@@ -28,7 +28,7 @@
// you use this lower-level API, you'll have to be sure to solve the HTTP
// and TLS-ALPN challenges yourself (unless you disabled them or use the
// DNS challenge) by using the provided Config.GetCertificate function
// in your tls.Config and/or Config.HTTPChallangeHandler in your HTTP
// in your tls.Config and/or Config.HTTPChallengeHandler in your HTTP
// handler.
//
// See the package's README for more instruction.
@@ -261,7 +261,7 @@ func ManageAsync(ctx context.Context, domainNames []string) error {
// containing the names passed into those functions if
// no DecisionFunc is set. This ensures some degree of
// control by default to avoid certificate operations for
// aribtrary domain names. To override this whitelist,
// arbitrary domain names. To override this whitelist,
// manually specify a DecisionFunc. To impose rate limits,
// specify your own DecisionFunc.
type OnDemandConfig struct {

View File

@@ -35,8 +35,8 @@ import (
"strings"
"time"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
"golang.org/x/crypto/ocsp"
"golang.org/x/net/idna"
@@ -874,7 +874,7 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
// are compliant, so their CSR requirements just needlessly add friction, complexity,
// and inefficiency for clients. CommonName has been deprecated for 25+ years.
useCSR := csr
if _, ok := issuer.(*ZeroSSLIssuer); ok {
if issuer.IssuerKey() == "zerossl" {
useCSR, err = cfg.generateCSR(privateKey, []string{name}, true)
if err != nil {
return err

View File

@@ -25,7 +25,7 @@ import (
"sync"
"time"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v3"
"go.uber.org/zap"
"golang.org/x/crypto/ocsp"
)
@@ -231,9 +231,16 @@ func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certific
// otherwise it returns an expired certificate that the client supports,
// otherwise it just returns the first certificate in the list of choices.
func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificate) (Certificate, error) {
if len(choices) == 1 {
// Fast path: There's only one choice, so we would always return that one
// regardless of whether it is expired or not compatible.
return choices[0], nil
}
if len(choices) == 0 {
return Certificate{}, fmt.Errorf("no certificates available")
}
// Slow path: There are choices, so we need to check each of them.
now := time.Now()
best := choices[0]
for _, choice := range choices {
@@ -549,12 +556,31 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
//
// This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) {
logger := cfg.Logger.Named("on_demand")
logger := cfg.Logger.Named("on_demand").With(
zap.Strings("identifiers", cert.Names),
zap.String("server_name", hello.ServerName))
renewIfNecessary := func(ctx context.Context, hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) {
if cert.Leaf == nil {
return cert, fmt.Errorf("leaf certificate is unexpectedly nil: either the Certificate got replaced by an empty value, or it was not properly initialized")
}
if cfg.certNeedsRenewal(cert.Leaf, cert.ari, true) {
// Check if the certificate still exists on disk. If not, we need to obtain a new one.
// This can happen if the certificate was cleaned up by the storage cleaner, but still
// remains in the in-memory cache.
if !cfg.storageHasCertResourcesAnyIssuer(ctx, cert.Names[0]) {
logger.Debug("certificate not found on disk; obtaining new certificate")
return cfg.obtainOnDemandCertificate(ctx, hello)
}
// Otherwise, renew the certificate.
return cfg.renewDynamicCertificate(ctx, hello, cert)
}
return cert, nil
}
// Check OCSP staple validity
if cert.ocsp != nil && !freshOCSP(cert.ocsp) {
logger.Debug("OCSP response needs refreshing",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
@@ -563,13 +589,9 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
if err != nil {
// An error with OCSP stapling is not the end of the world, and in fact, is
// quite common considering not all certs have issuer URLs that support it.
logger.Warn("stapling OCSP",
zap.String("server_name", hello.ServerName),
zap.Strings("sans", cert.Names),
zap.Error(err))
logger.Warn("stapling OCSP", zap.Error(err))
} else {
logger.Debug("successfully stapled new OCSP response",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
@@ -581,21 +603,39 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
cfg.certCache.mu.Unlock()
}
// Check ARI status
if !cfg.DisableARI && cert.ari.NeedsRefresh() {
// we ignore the second return value here because we go on to check renewal status below regardless
var err error
cert, _, err = cfg.updateARI(ctx, cert, logger)
if err != nil {
logger.Error("updated ARI", zap.Error(err))
}
// Check ARI status, but it's only relevant if the certificate is not expired (otherwise, we already know it needs renewal!)
if !cfg.DisableARI && cert.ari.NeedsRefresh() && time.Now().Before(cert.Leaf.NotAfter) {
// update ARI in a goroutine to avoid blocking an active handshake, since the results of
// this do not strictly affect the handshake; even though the cert may be updated with
// the new ARI, it is also updated in the cache and in storage, so future handshakes
// will utilize it
go func(hello *tls.ClientHelloInfo, cert Certificate, logger *zap.Logger) {
// TODO: a different context that isn't tied to the handshake is probably better
// than a generic background context; maybe a longer-lived server config context,
// or something that the importing package sets on the Config struct; for example,
// a Caddy config context could be good, so that ARI updates will continue after
// the handshake goes away, but will be stopped if the underlying server is stopped
// (for now, use an unusual timeout to help recognize it in log patterns, if needed)
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Minute)
defer cancel()
var err error
// we ignore the second return value here because we check renewal status below regardless
cert, _, err = cfg.updateARI(ctx, cert, logger)
if err != nil {
logger.Error("updating ARI", zap.Error(err))
}
_, err = renewIfNecessary(ctx, hello, cert)
if err != nil {
logger.Error("renewing certificate based on updated ARI", zap.Error(err))
}
}(hello, cert, logger)
}
// We attempt to replace any certificates that were revoked.
// Crucially, this happens OUTSIDE a lock on the certCache.
if certShouldBeForceRenewed(cert) {
logger.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("revoked_at", cert.ocsp.RevokedAt),
zap.Time("this_update", cert.ocsp.ThisUpdate),
@@ -603,21 +643,8 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
return cfg.renewDynamicCertificate(ctx, hello, cert)
}
// Check cert expiration
if cfg.certNeedsRenewal(cert.Leaf, cert.ari, true) {
// Check if the certificate still exists on disk. If not, we need to obtain a new one.
// This can happen if the certificate was cleaned up by the storage cleaner, but still
// remains in the in-memory cache.
if !cfg.storageHasCertResourcesAnyIssuer(ctx, cert.Names[0]) {
logger.Debug("certificate not found on disk; obtaining new certificate",
zap.Strings("identifiers", cert.Names))
return cfg.obtainOnDemandCertificate(ctx, hello)
}
// Otherwise, renew the certificate.
return cfg.renewDynamicCertificate(ctx, hello, cert)
}
return cert, nil
// Since renewal conditions may have changed, do a renewal if necessary
return renewIfNecessary(ctx, hello, cert)
}
// renewDynamicCertificate renews the certificate for name using cfg. It returns the
@@ -943,6 +970,6 @@ type helloInfoCtxKey string
// a context.Context within a DecisionFunc. However, be advised that it is best practice
// that the decision whether to obtain a certificate is be based solely on the name,
// not other properties of the specific connection/client requesting the connection.
// For example, it is not adviseable to use a client's IP address to decide whether to
// For example, it is not advisable to use a client's IP address to decide whether to
// allow a certificate. Instead, the ClientHello can be useful for logging, etc.
const ClientHelloInfoCtxKey helloInfoCtxKey = "certmagic:ClientHelloInfo"

View File

@@ -19,7 +19,7 @@ import (
"net/url"
"strings"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
)

View File

@@ -27,7 +27,7 @@ import (
"strings"
"time"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
"golang.org/x/crypto/ocsp"
)
@@ -507,7 +507,19 @@ func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.
if err == nil && gotNewARI {
// great, storage has a newer one we can use
cfg.certCache.mu.Lock()
updatedCert = cfg.certCache.cache[cert.hash]
var ok bool
updatedCert, ok = cfg.certCache.cache[cert.hash]
if !ok {
// cert is no longer in the cache... why? what's the right thing to do here?
cfg.certCache.mu.Unlock()
updatedCert = cert // return input cert, not an empty one
updatedCert.ari = newARI // might as well give it the new ARI for the benefit of our caller, but it won't be updated in the cache or in storage
logger.Warn("loaded newer ARI from storage, but certificate is no longer in cache; newer ARI will be returned to caller, but not persisted in the cache",
zap.Time("selected_time", newARI.SelectedTime),
zap.Timep("next_update", newARI.RetryAfter),
zap.String("explanation_url", newARI.ExplanationURL))
return
}
updatedCert.ari = newARI
cfg.certCache.cache[cert.hash] = updatedCert
cfg.certCache.mu.Unlock()
@@ -523,7 +535,7 @@ func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.
// of the issuers configured, hopefully one of them is the ACME CA we got the cert from
for _, iss := range cfg.Issuers {
if ariGetter, ok := iss.(RenewalInfoGetter); ok {
if ariGetter, ok := iss.(RenewalInfoGetter); ok && iss.IssuerKey() == cert.issuerKey {
newARI, err = ariGetter.GetRenewalInfo(ctx, cert) // be sure to use existing newARI variable so we can compare against old value in the defer
if err != nil {
// could be anything, but a common error might simply be the "wrong" ACME CA
@@ -549,7 +561,21 @@ func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.
// be sure we get the cert from the cache while inside a lock to avoid logical races
cfg.certCache.mu.Lock()
updatedCert = cfg.certCache.cache[cert.hash]
updatedCert, ok = cfg.certCache.cache[cert.hash]
if !ok {
// cert is no longer in the cache; this can happen for several reasons (past expiration,
// rejected by on-demand permission module, random eviction due to full cache, etc), but
// it probably means we don't have use of this ARI update now, so while we can return it
// to the caller, we don't persist it anywhere beyond that...
cfg.certCache.mu.Unlock()
updatedCert = cert // return input cert, not an empty one
updatedCert.ari = newARI // might as well give it the new ARI for the benefit of our caller, but it won't be updated in the cache or in storage
logger.Warn("obtained ARI update, but certificate no longer in cache; ARI update will be returned to caller, but not stored",
zap.Time("selected_time", newARI.SelectedTime),
zap.Timep("next_update", newARI.RetryAfter),
zap.String("explanation_url", newARI.ExplanationURL))
return
}
updatedCert.ari = newARI
cfg.certCache.cache[cert.hash] = updatedCert
cfg.certCache.mu.Unlock()
@@ -581,7 +607,7 @@ func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.
return
}
logger.Info("updated ACME renewal information",
logger.Info("updated and stored ACME renewal information",
zap.Time("selected_time", newARI.SelectedTime),
zap.Timep("next_update", newARI.RetryAfter),
zap.String("explanation_url", newARI.ExplanationURL))

View File

@@ -93,11 +93,16 @@ func stapleOCSP(ctx context.Context, ocspConfig OCSPConfig, storage Storage, cer
// then we need to request it from the OCSP responder
if ocspResp == nil || len(ocspBytes) == 0 {
ocspBytes, ocspResp, ocspErr = getOCSPForCert(ocspConfig, pemBundle)
// An error here is not a problem because a certificate
// may simply not contain a link to an OCSP server.
if ocspErr != nil {
// An error here is not a problem because a certificate may simply
// not contain a link to an OCSP server. But we should log it anyway.
// For short-lived certificates, this is fine and we can ignore
// logging because OCSP doesn't make much sense for them anyway.
if cert.Lifetime() < 7*24*time.Hour {
return nil
}
// There's nothing else we can do to get OCSP for this certificate,
// so we can return here with the error.
// so we can return here with the error to warn about it.
return fmt.Errorf("no OCSP stapling for %v: %w", cert.Names, ocspErr)
}
gotNewOCSP = true

View File

@@ -30,8 +30,8 @@ import (
"time"
"github.com/libdns/libdns"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"github.com/miekg/dns"
"go.uber.org/zap"
)
@@ -547,14 +547,15 @@ func (s *DNSManager) getDNSPresentMemory(dnsName, recType, value string) (dnsPre
defer s.recordsMu.Unlock()
var memory dnsPresentMemory
var found bool
for _, mem := range s.records[dnsName] {
if mem.zoneRec.record.Type == recType && mem.zoneRec.record.Value == value {
memory = mem
found = true
break
}
}
if memory.zoneRec.record.Name == "" {
if !found {
return dnsPresentMemory{}, fmt.Errorf("no memory of presenting a DNS record for %q (usually OK if presenting also failed)", dnsName)
}

View File

@@ -26,8 +26,8 @@ import (
"time"
"github.com/caddyserver/zerossl"
"github.com/mholt/acmez/v2"
"github.com/mholt/acmez/v2/acme"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
)
@@ -62,6 +62,9 @@ type ZeroSSLIssuer struct {
// validation, set this field.
CNAMEValidation *DNSManager
// Delay between poll attempts.
PollInterval time.Duration
// An optional (but highly recommended) logger.
Logger *zap.Logger
}
@@ -203,8 +206,8 @@ func (iss *ZeroSSLIssuer) Issue(ctx context.Context, csr *x509.CertificateReques
}, nil
}
func (*ZeroSSLIssuer) waitForCertToBeIssued(ctx context.Context, client zerossl.Client, cert zerossl.CertificateObject) (zerossl.CertificateObject, error) {
ticker := time.NewTicker(5 * time.Second)
func (iss *ZeroSSLIssuer) waitForCertToBeIssued(ctx context.Context, client zerossl.Client, cert zerossl.CertificateObject) (zerossl.CertificateObject, error) {
ticker := time.NewTicker(iss.pollInterval())
defer ticker.Stop()
for {
@@ -227,6 +230,13 @@ func (*ZeroSSLIssuer) waitForCertToBeIssued(ctx context.Context, client zerossl.
}
}
func (iss *ZeroSSLIssuer) pollInterval() time.Duration {
if iss.PollInterval == 0 {
return defaultPollInterval
}
return iss.PollInterval
}
func (iss *ZeroSSLIssuer) getClient() zerossl.Client {
return zerossl.Client{AccessKey: iss.APIKey}
}
@@ -299,9 +309,8 @@ func (iss *ZeroSSLIssuer) getDistributedValidationInfo(ctx context.Context, iden
}
const (
zerosslAPIBase = "https://" + zerossl.BaseURL + "/acme"
zerosslValidationPathPrefix = "/.well-known/pki-validation/"
zerosslIssuerKey = "zerossl"
zerosslIssuerKey = "zerossl"
defaultPollInterval = 5 * time.Second
)
// Interface guards

View File

@@ -0,0 +1,62 @@
package md2man
import (
"fmt"
"io"
"os"
"strings"
"github.com/russross/blackfriday/v2"
)
func fmtListFlags(flags blackfriday.ListType) string {
knownFlags := []struct {
name string
flag blackfriday.ListType
}{
{"ListTypeOrdered", blackfriday.ListTypeOrdered},
{"ListTypeDefinition", blackfriday.ListTypeDefinition},
{"ListTypeTerm", blackfriday.ListTypeTerm},
{"ListItemContainsBlock", blackfriday.ListItemContainsBlock},
{"ListItemBeginningOfList", blackfriday.ListItemBeginningOfList},
{"ListItemEndOfList", blackfriday.ListItemEndOfList},
}
var f []string
for _, kf := range knownFlags {
if flags&kf.flag != 0 {
f = append(f, kf.name)
flags &^= kf.flag
}
}
if flags != 0 {
f = append(f, fmt.Sprintf("Unknown(%#x)", flags))
}
return strings.Join(f, "|")
}
type debugDecorator struct {
blackfriday.Renderer
}
func depth(node *blackfriday.Node) int {
d := 0
for n := node.Parent; n != nil; n = n.Parent {
d++
}
return d
}
func (d *debugDecorator) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
fmt.Fprintf(os.Stderr, "%s%s %v %v\n",
strings.Repeat(" ", depth(node)),
map[bool]string{true: "+", false: "-"}[entering],
node,
fmtListFlags(node.ListFlags))
var b strings.Builder
status := d.Renderer.RenderNode(io.MultiWriter(&b, w), node, entering)
if b.Len() > 0 {
fmt.Fprintf(os.Stderr, ">> %q\n", b.String())
}
return status
}

View File

@@ -1,16 +1,23 @@
package md2man
import (
"os"
"strconv"
"github.com/russross/blackfriday/v2"
)
// Render converts a markdown document into a roff formatted document.
func Render(doc []byte) []byte {
renderer := NewRoffRenderer()
var r blackfriday.Renderer = renderer
if v, _ := strconv.ParseBool(os.Getenv("MD2MAN_DEBUG")); v {
r = &debugDecorator{Renderer: r}
}
return blackfriday.Run(doc,
[]blackfriday.Option{
blackfriday.WithRenderer(renderer),
blackfriday.WithRenderer(r),
blackfriday.WithExtensions(renderer.GetExtensions()),
}...)
}

View File

@@ -14,10 +14,8 @@ import (
// roffRenderer implements the blackfriday.Renderer interface for creating
// roff format (manpages) from markdown text
type roffRenderer struct {
extensions blackfriday.Extensions
listCounters []int
firstHeader bool
firstDD bool
listDepth int
}
@@ -43,7 +41,7 @@ const (
quoteTag = "\n.PP\n.RS\n"
quoteCloseTag = "\n.RE\n"
listTag = "\n.RS\n"
listCloseTag = "\n.RE\n"
listCloseTag = ".RE\n"
dtTag = "\n.TP\n"
dd2Tag = "\n"
tableStart = "\n.TS\nallbox;\n"
@@ -56,23 +54,18 @@ const (
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown
func NewRoffRenderer() *roffRenderer { // nolint: golint
var extensions blackfriday.Extensions
extensions |= blackfriday.NoIntraEmphasis
extensions |= blackfriday.Tables
extensions |= blackfriday.FencedCode
extensions |= blackfriday.SpaceHeadings
extensions |= blackfriday.Footnotes
extensions |= blackfriday.Titleblock
extensions |= blackfriday.DefinitionLists
return &roffRenderer{
extensions: extensions,
}
return &roffRenderer{}
}
// GetExtensions returns the list of extensions used by this renderer implementation
func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
return r.extensions
func (*roffRenderer) GetExtensions() blackfriday.Extensions {
return blackfriday.NoIntraEmphasis |
blackfriday.Tables |
blackfriday.FencedCode |
blackfriday.SpaceHeadings |
blackfriday.Footnotes |
blackfriday.Titleblock |
blackfriday.DefinitionLists
}
// RenderHeader handles outputting the header at document start
@@ -103,7 +96,23 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
switch node.Type {
case blackfriday.Text:
escapeSpecialChars(w, node.Literal)
// Special case: format the NAME section as required for proper whatis parsing.
// Refer to the lexgrog(1) and groff_man(7) manual pages for details.
if node.Parent != nil &&
node.Parent.Type == blackfriday.Paragraph &&
node.Parent.Prev != nil &&
node.Parent.Prev.Type == blackfriday.Heading &&
node.Parent.Prev.FirstChild != nil &&
bytes.EqualFold(node.Parent.Prev.FirstChild.Literal, []byte("NAME")) {
before, after, found := bytes.Cut(node.Literal, []byte(" - "))
escapeSpecialChars(w, before)
if found {
out(w, ` \- `)
escapeSpecialChars(w, after)
}
} else {
escapeSpecialChars(w, node.Literal)
}
case blackfriday.Softbreak:
out(w, crTag)
case blackfriday.Hardbreak:
@@ -141,14 +150,25 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
case blackfriday.Document:
break
case blackfriday.Paragraph:
// roff .PP markers break lists
if r.listDepth > 0 {
return blackfriday.GoToNext
}
if entering {
out(w, paraTag)
if r.listDepth > 0 {
// roff .PP markers break lists
if node.Prev != nil { // continued paragraph
if node.Prev.Type == blackfriday.List && node.Prev.ListFlags&blackfriday.ListTypeDefinition == 0 {
out(w, ".IP\n")
} else {
out(w, crTag)
}
}
} else if node.Prev != nil && node.Prev.Type == blackfriday.Heading {
out(w, crTag)
} else {
out(w, paraTag)
}
} else {
out(w, crTag)
if node.Next == nil || node.Next.Type != blackfriday.List {
out(w, crTag)
}
}
case blackfriday.BlockQuote:
if entering {
@@ -211,6 +231,10 @@ func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, enteri
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
openTag := listTag
closeTag := listCloseTag
if (entering && r.listDepth == 0) || (!entering && r.listDepth == 1) {
openTag = crTag
closeTag = ""
}
if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// tags for definition lists handled within Item node
openTag = ""
@@ -239,23 +263,25 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
// DT (definition term): line just before DD (see below).
out(w, dtTag)
r.firstDD = true
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// DD (definition description): line that starts with ": ".
//
// We have to distinguish between the first DD and the
// subsequent ones, as there should be no vertical
// whitespace between the DT and the first DD.
if r.firstDD {
r.firstDD = false
} else {
out(w, dd2Tag)
if node.Prev != nil && node.Prev.ListFlags&(blackfriday.ListTypeTerm|blackfriday.ListTypeDefinition) == blackfriday.ListTypeDefinition {
if node.Prev.Type == blackfriday.Item &&
node.Prev.LastChild != nil &&
node.Prev.LastChild.Type == blackfriday.List &&
node.Prev.LastChild.ListFlags&blackfriday.ListTypeDefinition == 0 {
out(w, ".IP\n")
} else {
out(w, dd2Tag)
}
}
} else {
out(w, ".IP \\(bu 2\n")
}
} else {
out(w, "\n")
}
}

View File

@@ -15,7 +15,7 @@ const (
MIN_PAYLOAD_SIZE = MIN_MSS_SIZE - UDP_HEADER_SIZE - SRT_HEADER_SIZE
MAX_PAYLOAD_SIZE = MAX_MSS_SIZE - UDP_HEADER_SIZE - SRT_HEADER_SIZE
MIN_PASSPHRASE_SIZE = 10
MAX_PASSPHRASE_SIZE = 79
MAX_PASSPHRASE_SIZE = 80
MAX_STREAMID_SIZE = 512
SRT_VERSION = 0x010401
)
@@ -169,6 +169,9 @@ type Config struct {
// An implementation of the Logger interface
Logger Logger
// if a new IP starts sending data on an existing socket id, allow it
AllowPeerIpChange bool
}
// DefaultConfig is the default configuration for a SRT connection
@@ -209,6 +212,7 @@ var defaultConfig Config = Config{
TooLatePacketDrop: true,
TransmissionType: "live",
TSBPDMode: true,
AllowPeerIpChange: false,
}
// DefaultConfig returns the default configuration for Dial and Listen.

View File

@@ -265,11 +265,7 @@ func (r *receiver) periodicACK(now uint64) (ok bool, sequenceNumber circular.Num
}
minPktTsbpdTime, maxPktTsbpdTime := uint64(0), uint64(0)
ackSequenceNumber := r.lastDeliveredSequenceNumber
// Find the sequence number up until we have all in a row.
// Where the first gap is (or at the end of the list) is where we can ACK to.
ackSequenceNumber := r.lastACKSequenceNumber
e := r.packetList.Front()
if e != nil {
@@ -277,49 +273,42 @@ func (r *receiver) periodicACK(now uint64) (ok bool, sequenceNumber circular.Num
minPktTsbpdTime = p.Header().PktTsbpdTime
maxPktTsbpdTime = p.Header().PktTsbpdTime
}
// If there are packets that should be delivered by now, move foward.
if p.Header().PktTsbpdTime <= now {
for e = e.Next(); e != nil; e = e.Next() {
p = e.Value.(packet.Packet)
// Find the sequence number up until we have all in a row.
// Where the first gap is (or at the end of the list) is where we can ACK to.
if p.Header().PktTsbpdTime > now {
break
}
}
for e := r.packetList.Front(); e != nil; e = e.Next() {
p := e.Value.(packet.Packet)
ackSequenceNumber = p.Header().PacketSequenceNumber
maxPktTsbpdTime = p.Header().PktTsbpdTime
if e != nil {
if e = e.Next(); e != nil {
p = e.Value.(packet.Packet)
}
}
// Skip packets that we already ACK'd.
if p.Header().PacketSequenceNumber.Lte(ackSequenceNumber) {
continue
}
// If there are packets that should have been delivered by now, move forward.
if p.Header().PktTsbpdTime <= now {
ackSequenceNumber = p.Header().PacketSequenceNumber
continue
}
// Check if the packet is the next in the row.
if p.Header().PacketSequenceNumber.Equals(ackSequenceNumber.Inc()) {
ackSequenceNumber = p.Header().PacketSequenceNumber
for e = e.Next(); e != nil; e = e.Next() {
p = e.Value.(packet.Packet)
if !p.Header().PacketSequenceNumber.Equals(ackSequenceNumber.Inc()) {
break
}
ackSequenceNumber = p.Header().PacketSequenceNumber
maxPktTsbpdTime = p.Header().PktTsbpdTime
}
maxPktTsbpdTime = p.Header().PktTsbpdTime
continue
}
ok = true
sequenceNumber = ackSequenceNumber.Inc()
// Keep track of the last ACK's sequence. with this we can faster ignore
// packets that come in that have a lower sequence number.
r.lastACKSequenceNumber = ackSequenceNumber
break
}
ok = true
sequenceNumber = ackSequenceNumber.Inc()
// Keep track of the last ACK's sequence number. With this we can faster ignore
// packets that come in late that have a lower sequence number.
r.lastACKSequenceNumber = ackSequenceNumber
r.lastPeriodicACK = now
r.nPackets = 0
@@ -338,13 +327,19 @@ func (r *receiver) periodicNAK(now uint64) (ok bool, from, to circular.Number) {
// Send a periodic NAK
ackSequenceNumber := r.lastDeliveredSequenceNumber
ackSequenceNumber := r.lastACKSequenceNumber
// Send a NAK only for the first gap.
// Alternatively send a NAK for max. X gaps because the size of the NAK packet is limited.
for e := r.packetList.Front(); e != nil; e = e.Next() {
p := e.Value.(packet.Packet)
// Skip packets that we already ACK'd.
if p.Header().PacketSequenceNumber.Lte(ackSequenceNumber) {
continue
}
// If this packet is not in sequence, we stop here and report that gap.
if !p.Header().PacketSequenceNumber.Equals(ackSequenceNumber.Inc()) {
nackSequenceNumber := ackSequenceNumber.Inc()

View File

@@ -7,6 +7,7 @@ import (
"github.com/datarhei/gosrt/crypto"
"github.com/datarhei/gosrt/packet"
"github.com/datarhei/gosrt/rand"
)
// ConnRequest is an incoming connection request
@@ -206,6 +207,18 @@ func newConnRequest(ln *listener, p packet.Packet) *connRequest {
return nil
}
// We only support live congestion control
if cif.HasCongestionCtl && cif.CongestionCtl != "live" {
cif.HandshakeType = packet.HandshakeType(REJ_CONGESTION)
ln.log("handshake:recv:error", func() string { return "only live congestion control is supported" })
p.MarshalCIF(cif)
ln.log("handshake:send:dump", func() string { return p.Dump() })
ln.log("handshake:send:cif", func() string { return cif.String() })
ln.send(p)
return nil
}
} else {
cif.HandshakeType = packet.HandshakeType(REJ_ROGUE)
ln.log("handshake:recv:error", func() string { return fmt.Sprintf("only HSv4 and HSv5 are supported (got HSv%d)", cif.Version) })
@@ -328,6 +341,23 @@ func (req *connRequest) Reject(reason RejectionReason) {
delete(req.ln.connReqs, req.socketId)
}
// generateSocketId generates an SRT SocketID that can be used for this connection
func (req *connRequest) generateSocketId() (uint32, error) {
for i := 0; i < 10; i++ {
socketId, err := rand.Uint32()
if err != nil {
return 0, fmt.Errorf("could not generate random socket id")
}
// check that the socket id is not already in use
if _, found := req.ln.conns[socketId]; !found {
return socketId, nil
}
}
return 0, fmt.Errorf("could not generate unused socketid")
}
func (req *connRequest) Accept() (Conn, error) {
if req.crypto != nil && len(req.passphrase) == 0 {
req.Reject(REJ_BADSECRET)
@@ -342,7 +372,10 @@ func (req *connRequest) Accept() (Conn, error) {
}
// Create a new socket ID
socketId := uint32(time.Since(req.ln.start).Microseconds())
socketId, err := req.generateSocketId()
if err != nil {
return nil, fmt.Errorf("could not generate socket id: %w", err)
}
// Select the largest TSBPD delay advertised by the caller, but at least 120ms
recvTsbpdDelay := uint16(req.config.ReceiverLatency.Milliseconds())

View File

@@ -69,6 +69,51 @@ type Conn interface {
Version() uint32
}
type rtt struct {
rtt float64 // microseconds
rttVar float64 // microseconds
lock sync.RWMutex
}
func (r *rtt) Recalculate(rtt time.Duration) {
// 4.10. Round-Trip Time Estimation
lastRTT := float64(rtt.Microseconds())
r.lock.Lock()
defer r.lock.Unlock()
r.rtt = r.rtt*0.875 + lastRTT*0.125
r.rttVar = r.rttVar*0.75 + math.Abs(r.rtt-lastRTT)*0.25
}
func (r *rtt) RTT() float64 {
r.lock.RLock()
defer r.lock.RUnlock()
return r.rtt
}
func (r *rtt) RTTVar() float64 {
r.lock.RLock()
defer r.lock.RUnlock()
return r.rttVar
}
func (r *rtt) NAKInterval() float64 {
r.lock.RLock()
defer r.lock.RUnlock()
// 4.8.2. Packet Retransmission (NAKs)
nakInterval := (r.rtt + 4*r.rttVar) / 2
if nakInterval < 20000 {
nakInterval = 20000 // 20ms
}
return nakInterval
}
type connStats struct {
headerSize uint64
pktSentACK uint64
@@ -108,19 +153,16 @@ type srtConn struct {
config Config
cryptoLock sync.Mutex
crypto crypto.Crypto
keyBaseEncryption packet.PacketEncryption
kmPreAnnounceCountdown uint64
kmRefreshCountdown uint64
kmConfirmed bool
cryptoLock sync.Mutex
peerIdleTimeout *time.Timer
rtt float64 // microseconds
rttVar float64 // microseconds
nakInterval float64
rtt rtt // microseconds
ackLock sync.RWMutex
ackNumbers map[uint32]time.Time
@@ -232,10 +274,10 @@ func newSRTConn(config srtConnConfig) *srtConn {
c.kmRefreshCountdown = c.config.KMRefreshRate
// 4.10. Round-Trip Time Estimation
c.rtt = float64((100 * time.Millisecond).Microseconds())
c.rttVar = float64((50 * time.Millisecond).Microseconds())
c.nakInterval = float64((20 * time.Millisecond).Microseconds())
c.rtt = rtt{
rtt: float64((100 * time.Millisecond).Microseconds()),
rttVar: float64((50 * time.Millisecond).Microseconds()),
}
c.networkQueue = make(chan packet.Packet, 1024)
@@ -788,7 +830,7 @@ func (c *srtConn) handleNAK(p packet.Packet) {
// handleACKACK updates the RTT and NAK interval for the congestion control.
func (c *srtConn) handleACKACK(p packet.Packet) {
c.ackLock.RLock()
c.ackLock.Lock()
c.statisticsLock.Lock()
c.statistics.pktRecvACKACK++
@@ -814,31 +856,17 @@ func (c *srtConn) handleACKACK(p packet.Packet) {
}
}
nakInterval := uint64(c.nakInterval)
c.ackLock.Unlock()
c.ackLock.RUnlock()
c.recv.SetNAKInterval(nakInterval)
c.recv.SetNAKInterval(uint64(c.rtt.NAKInterval()))
}
// recalculateRTT recalculates the RTT based on a full ACK exchange
func (c *srtConn) recalculateRTT(rtt time.Duration) {
// 4.10. Round-Trip Time Estimation
lastRTT := float64(rtt.Microseconds())
c.rtt = c.rtt*0.875 + lastRTT*0.125
c.rttVar = c.rttVar*0.75 + math.Abs(c.rtt-lastRTT)*0.25
// 4.8.2. Packet Retransmission (NAKs)
nakInterval := (c.rtt + 4*c.rttVar) / 2
if nakInterval < 20000 {
c.nakInterval = 20000 // 20ms
} else {
c.nakInterval = nakInterval
}
c.rtt.Recalculate(rtt)
c.log("connection:rtt", func() string {
return fmt.Sprintf("RTT=%.0fus RTTVar=%.0fus NAKInterval=%.0fms", c.rtt, c.rttVar, c.nakInterval/1000)
return fmt.Sprintf("RTT=%.0fus RTTVar=%.0fus NAKInterval=%.0fms", c.rtt.RTT(), c.rtt.RTTVar(), c.rtt.NAKInterval()/1000)
})
}
@@ -1219,8 +1247,8 @@ func (c *srtConn) sendACK(seq circular.Number, lite bool) {
} else {
pps, bps, capacity := c.recv.PacketRate()
cif.RTT = uint32(c.rtt)
cif.RTTVar = uint32(c.rttVar)
cif.RTT = uint32(c.rtt.RTT())
cif.RTTVar = uint32(c.rtt.RTTVar())
cif.AvailableBufferSize = c.config.FC // TODO: available buffer size (packets)
cif.PacketsReceivingRate = uint32(pps) // packets receiving rate (packets/s)
cif.EstimatedLinkCapacity = uint32(capacity) // estimated link capacity (packets/s), not relevant for live mode
@@ -1488,7 +1516,7 @@ func (c *srtConn) Stats(s *Statistics) {
UsPktSendPeriod: send.UsPktSndPeriod,
PktFlowWindow: uint64(c.config.FC),
PktFlightSize: send.PktFlightSize,
MsRTT: c.rtt / 1000,
MsRTT: c.rtt.RTT() / 1000,
MbpsSentRate: send.MbpsEstimatedSentBandwidth,
MbpsRecvRate: recv.MbpsEstimatedRecvBandwidth,
MbpsLinkCapacity: recv.MbpsEstimatedLinkCapacity,

View File

@@ -407,6 +407,14 @@ func (ln *listener) reader(ctx context.Context) {
break
}
if !ln.config.AllowPeerIpChange {
if p.Header().Addr.String() != conn.RemoteAddr().String() {
// ignore the packet, it's not from the expected peer
// https://haivision.github.io/srt-rfc/draft-sharabayko-srt.html#name-security-considerations
break
}
}
conn.push(p)
}
}

View File

@@ -33,7 +33,7 @@ const (
CTRLTYPE_SHUTDOWN CtrlType = 0x0005
CTRLTYPE_ACKACK CtrlType = 0x0006
CRTLTYPE_DROPREQ CtrlType = 0x0007 // unimplemented, sender->receiver
CRTLTYPE_PEERERROR CtrlType = 0x0008 // unimplemented, receiver->sender
CRTLTYPE_PEERERROR CtrlType = 0x0008 // unimplemented, receiver->sender (only for file transfers)
CTRLTYPE_USER CtrlType = 0x7FFF
)
@@ -141,8 +141,8 @@ const (
EXTTYPE_KMRSP CtrlSubType = 4
EXTTYPE_SID CtrlSubType = 5
EXTTYPE_CONGESTION CtrlSubType = 6
EXTTYPE_FILTER CtrlSubType = 7
EXTTYPE_GROUP CtrlSubType = 8
EXTTYPE_FILTER CtrlSubType = 7 // unimplemented
EXTTYPE_GROUP CtrlSubType = 8 // unimplemented
)
func (h CtrlSubType) String() string {
@@ -484,7 +484,7 @@ type CIFHandshake struct {
Version uint32 // A base protocol version number. Currently used values are 4 and 5. Values greater than 5 are reserved for future use.
EncryptionField uint16 // Block cipher family and key size. The values of this field are described in Table 2. The default value is AES-128.
ExtensionField uint16 // This field is message specific extension related to Handshake Type field. The value MUST be set to 0 except for the following cases. (1) If the handshake control packet is the INDUCTION message, this field is sent back by the Listener. (2) In the case of a CONCLUSION message, this field value should contain a combination of Extension Type values. For more details, see Section 4.3.1.
ExtensionField uint16 // This field is a message specific extension related to Handshake Type field. The value MUST be set to 0 except for the following cases. (1) If the handshake control packet is the INDUCTION message, this field is sent back by the Listener. (2) In the case of a CONCLUSION message, this field value should contain a combination of Extension Type values. For more details, see Section 4.3.1.
InitialPacketSequenceNumber circular.Number // The sequence number of the very first data packet to be sent.
MaxTransmissionUnitSize uint32 // This value is typically set to 1500, which is the default Maximum Transmission Unit (MTU) size for Ethernet, but can be less.
MaxFlowWindowSize uint32 // The value of this field is the maximum number of data packets allowed to be "in flight" (i.e. the number of sent packets for which an ACK control packet has not yet been received).
@@ -493,9 +493,10 @@ type CIFHandshake struct {
SynCookie uint32 // Randomized value for processing a handshake. The value of this field is specified by the handshake message type. See Section 4.3.
PeerIP srtnet.IP // IPv4 or IPv6 address of the packet's sender. The value consists of four 32-bit fields. In the case of IPv4 addresses, fields 2, 3 and 4 are filled with zeroes.
HasHS bool
HasKM bool
HasSID bool
HasHS bool
HasKM bool
HasSID bool
HasCongestionCtl bool
// 3.2.1.1. Handshake Extension Message
SRTHS *CIFHandshakeExtension
@@ -505,6 +506,9 @@ type CIFHandshake struct {
// 3.2.1.3. Stream ID Extension Message
StreamId string
// ??? Congestion Control Extension message (handshake.md #### Congestion controller)
CongestionCtl string
}
func (c CIFHandshake) String() string {
@@ -537,6 +541,12 @@ func (c CIFHandshake) String() string {
fmt.Fprintf(&b, " streamId : %s\n", c.StreamId)
fmt.Fprintf(&b, "--- /SIDExt ---\n")
}
if c.HasCongestionCtl {
fmt.Fprintf(&b, "--- CongestionExt ---\n")
fmt.Fprintf(&b, " congestion : %s\n", c.CongestionCtl)
fmt.Fprintf(&b, "--- /CongestionExt ---\n")
}
}
fmt.Fprintf(&b, "--- /handshake ---")
@@ -599,7 +609,7 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
if extensionType == EXTTYPE_HSREQ || extensionType == EXTTYPE_HSRSP {
// 3.2.1.1. Handshake Extension Message
if extensionLength != 12 || len(pivot) < extensionLength {
return fmt.Errorf("invalid extension length")
return fmt.Errorf("invalid extension length of %d bytes (%s)", extensionLength, extensionType.String())
}
c.HasHS = true
@@ -612,7 +622,7 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
} else if extensionType == EXTTYPE_KMREQ || extensionType == EXTTYPE_KMRSP {
// 3.2.1.2. Key Material Extension Message
if len(pivot) < extensionLength {
return fmt.Errorf("invalid extension length")
return fmt.Errorf("invalid extension length of %d bytes (%s)", extensionLength, extensionType.String())
}
c.HasKM = true
@@ -638,7 +648,7 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
} else if extensionType == EXTTYPE_SID {
// 3.2.1.3. Stream ID Extension Message
if extensionLength > 512 || len(pivot) < extensionLength {
return fmt.Errorf("invalid extension length")
return fmt.Errorf("invalid extension length of %d bytes (%s)", extensionLength, extensionType.String())
}
c.HasSID = true
@@ -653,8 +663,30 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
}
c.StreamId = strings.TrimRight(b.String(), "\x00")
} else if extensionType == EXTTYPE_CONGESTION {
// ??? Congestion Control Extension message (handshake.md #### Congestion controller)
if extensionLength > 4 || len(pivot) < extensionLength {
return fmt.Errorf("invalid extension length of %d bytes (%s)", extensionLength, extensionType.String())
}
c.HasCongestionCtl = true
var b strings.Builder
for i := 0; i < extensionLength; i += 4 {
b.WriteByte(pivot[i+3])
b.WriteByte(pivot[i+2])
b.WriteByte(pivot[i+1])
b.WriteByte(pivot[i+0])
}
c.CongestionCtl = strings.TrimRight(b.String(), "\x00")
} else if extensionType == EXTTYPE_FILTER || extensionType == EXTTYPE_GROUP {
if len(pivot) < extensionLength {
return fmt.Errorf("invalid extension length of %d bytes (%s)", extensionLength, extensionType.String())
}
} else {
return fmt.Errorf("unimplemented extension (%d)", extensionType)
return fmt.Errorf("unknown extension (%d)", extensionType)
}
if len(pivot) > extensionLength {
@@ -695,6 +727,10 @@ func (c *CIFHandshake) Marshal(w io.Writer) {
if c.HasSID {
c.ExtensionField = c.ExtensionField | 4
}
if c.HasCongestionCtl {
c.ExtensionField = c.ExtensionField | 4
}
} else {
c.EncryptionField = 0
c.ExtensionField = 2
@@ -773,6 +809,33 @@ func (c *CIFHandshake) Marshal(w io.Writer) {
w.Write(buffer[:4])
}
}
if c.HasCongestionCtl && c.CongestionCtl != "live" {
congestion := bytes.NewBufferString(c.CongestionCtl)
missing := (4 - congestion.Len()%4)
if missing < 4 {
for i := 0; i < missing; i++ {
congestion.WriteByte(0)
}
}
binary.BigEndian.PutUint16(buffer[0:], EXTTYPE_CONGESTION.Value())
binary.BigEndian.PutUint16(buffer[2:], uint16(congestion.Len()/4))
w.Write(buffer[:4])
b := congestion.Bytes()
for i := 0; i < len(b); i += 4 {
buffer[0] = b[i+3]
buffer[1] = b[i+2]
buffer[2] = b[i+1]
buffer[3] = b[i+0]
w.Write(buffer[:4])
}
}
}
// 3.2.1.1.1. Handshake Extension Message Flags

View File

@@ -12,7 +12,7 @@ func RandomString(length int, charset string) (string, error) {
if err != nil {
return "", err
}
b[i] = charset[int(j)]
b[i] = charset[j]
}
return string(b), nil

View File

@@ -52,10 +52,15 @@ func InstallShieldCab(raw []byte, _ uint32) bool {
}
// Zstd matches a Zstandard archive file.
// https://github.com/facebook/zstd/blob/dev/doc/zstd_compression_format.md
func Zstd(raw []byte, limit uint32) bool {
return len(raw) >= 4 &&
(0x22 <= raw[0] && raw[0] <= 0x28 || raw[0] == 0x1E) && // Different Zstandard versions.
bytes.HasPrefix(raw[1:], []byte{0xB5, 0x2F, 0xFD})
if len(raw) < 4 {
return false
}
sig := binary.LittleEndian.Uint32(raw)
// Check for Zstandard frames and skippable frames.
return (sig >= 0xFD2FB522 && sig <= 0xFD2FB528) ||
(sig >= 0x184D2A50 && sig <= 0x184D2A5F)
}
// CRX matches a Chrome extension file: a zip archive prepended by a package header.

View File

@@ -23,6 +23,8 @@ var (
Torrent = prefix([]byte("d8:announce"))
// PAR1 matches a parquet file.
Par1 = prefix([]byte{0x50, 0x41, 0x52, 0x31})
// CBOR matches a Concise Binary Object Representation https://cbor.io/
CBOR = prefix([]byte{0xD9, 0xD9, 0xF7})
)
// Java bytecode and Mach-O binaries share the same magic number.
@@ -34,7 +36,7 @@ func classOrMachOFat(in []byte) bool {
return false
}
return bytes.HasPrefix(in, []byte{0xCA, 0xFE, 0xBA, 0xBE})
return binary.BigEndian.Uint32(in) == macho.MagicFat
}
// Class matches a java class file.
@@ -44,7 +46,7 @@ func Class(raw []byte, limit uint32) bool {
// MachO matches Mach-O binaries format.
func MachO(raw []byte, limit uint32) bool {
if classOrMachOFat(raw) && raw[7] < 20 {
if classOrMachOFat(raw) && raw[7] < 0x14 {
return true
}
@@ -156,11 +158,11 @@ func Marc(raw []byte, limit uint32) bool {
// the GL transmission Format (glTF).
// GLB uses little endian and its header structure is as follows:
//
// <-- 12-byte header -->
// | magic | version | length |
// | (uint32) | (uint32) | (uint32) |
// | \x67\x6C\x54\x46 | \x01\x00\x00\x00 | ... |
// | g l T F | 1 | ... |
// <-- 12-byte header -->
// | magic | version | length |
// | (uint32) | (uint32) | (uint32) |
// | \x67\x6C\x54\x46 | \x01\x00\x00\x00 | ... |
// | g l T F | 1 | ... |
//
// Visit [glTF specification] and [IANA glTF entry] for more details.
//
@@ -172,14 +174,15 @@ var Glb = prefix([]byte("\x67\x6C\x54\x46\x02\x00\x00\x00"),
// TzIf matches a Time Zone Information Format (TZif) file.
// See more: https://tools.ietf.org/id/draft-murchison-tzdist-tzif-00.html#rfc.section.3
// Its header structure is shown below:
// +---------------+---+
// | magic (4) | <-+-- version (1)
// +---------------+---+---------------------------------------+
// | [unused - reserved for future use] (15) |
// +---------------+---------------+---------------+-----------+
// | isutccnt (4) | isstdcnt (4) | leapcnt (4) |
// +---------------+---------------+---------------+
// | timecnt (4) | typecnt (4) | charcnt (4) |
//
// +---------------+---+
// | magic (4) | <-+-- version (1)
// +---------------+---+---------------------------------------+
// | [unused - reserved for future use] (15) |
// +---------------+---------------+---------------+-----------+
// | isutccnt (4) | isstdcnt (4) | leapcnt (4) |
// +---------------+---------------+---------------+
// | timecnt (4) | typecnt (4) | charcnt (4) |
func TzIf(raw []byte, limit uint32) bool {
// File is at least 44 bytes (header size).
if len(raw) < 44 {

View File

@@ -110,3 +110,22 @@ func zipContains(raw, sig []byte, msoCheck bool) bool {
}
return false
}
// APK matches an Android Package Archive.
// The source of signatures is https://github.com/file/file/blob/1778642b8ba3d947a779a36fcd81f8e807220a19/magic/Magdir/archive#L1820-L1887
func APK(raw []byte, _ uint32) bool {
apkSignatures := [][]byte{
[]byte("AndroidManifest.xml"),
[]byte("META-INF/com/android/build/gradle/app-metadata.properties"),
[]byte("classes.dex"),
[]byte("resources.arsc"),
[]byte("res/drawable"),
}
for _, sig := range apkSignatures {
if zipContains(raw, sig, false) {
return true
}
}
return false
}

View File

@@ -1,4 +1,4 @@
## 176 Supported MIME types
## 178 Supported MIME types
This file is automatically generated when running tests. Do not edit manually.
Extension | MIME type | Aliases
@@ -11,6 +11,7 @@ Extension | MIME type | Aliases
**.docx** | application/vnd.openxmlformats-officedocument.wordprocessingml.document | -
**.pptx** | application/vnd.openxmlformats-officedocument.presentationml.presentation | -
**.epub** | application/epub+zip | -
**.apk** | application/vnd.android.package-archive | -
**.jar** | application/jar | -
**.odt** | application/vnd.oasis.opendocument.text | application/x-vnd.oasis.opendocument.text
**.ott** | application/vnd.oasis.opendocument.text-template | application/x-vnd.oasis.opendocument.text-template
@@ -79,7 +80,7 @@ Extension | MIME type | Aliases
**.avif** | image/avif | -
**.3gp** | video/3gpp | video/3gp, audio/3gpp
**.3g2** | video/3gpp2 | video/3g2, audio/3gpp2
**.mp4** | audio/mp4 | audio/x-m4a, audio/x-mp4a
**.mp4** | audio/mp4 | audio/x-mp4a
**.mqv** | video/quicktime | -
**.m4a** | audio/x-m4a | -
**.m4v** | video/x-m4v | -
@@ -118,6 +119,7 @@ Extension | MIME type | Aliases
**.mobi** | application/x-mobipocket-ebook | -
**.lit** | application/x-ms-reader | -
**.bpg** | image/bpg | -
**.cbor** | application/cbor | -
**.sqlite** | application/vnd.sqlite3 | application/x-sqlite3
**.dwg** | image/vnd.dwg | image/x-dwg, application/acad, application/x-acad, application/autocad_dwg, application/dwg, application/x-dwg, application/x-autocad, drawing/dwg
**.nes** | application/vnd.nintendo.snes.rom | -
@@ -162,7 +164,7 @@ Extension | MIME type | Aliases
**.xfdf** | application/vnd.adobe.xfdf | -
**.owl** | application/owl+xml | -
**.php** | text/x-php | -
**.js** | application/javascript | application/x-javascript, text/javascript
**.js** | text/javascript | application/x-javascript, application/javascript
**.lua** | text/x-lua | -
**.pl** | text/x-perl | -
**.py** | text/x-python | text/x-script.python, application/x-python

View File

@@ -21,7 +21,7 @@ var root = newMIME("application/octet-stream", "",
jpm, jxs, gif, webp, exe, elf, ar, tar, xar, bz2, fits, tiff, bmp, ico, mp3,
flac, midi, ape, musePack, amr, wav, aiff, au, mpeg, quickTime, mp4, webM,
avi, flv, mkv, asf, aac, voc, m3u, rmvb, gzip, class, swf, crx, ttf, woff,
woff2, otf, ttc, eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg,
woff2, otf, ttc, eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg, cbor,
sqlite3, dwg, nes, lnk, macho, qcp, icns, hdr, mrc, mdb, accdb, zstd, cab,
rpm, xz, lzip, torrent, cpio, tzif, xcf, pat, gbr, glb, cabIS, jxr, parquet,
// Keep text last because it is the slowest check.
@@ -44,7 +44,11 @@ var (
"application/gzip-compressed", "application/x-gzip-compressed",
"gzip/document")
sevenZ = newMIME("application/x-7z-compressed", ".7z", magic.SevenZ)
zip = newMIME("application/zip", ".zip", magic.Zip, xlsx, docx, pptx, epub, jar, odt, ods, odp, odg, odf, odc, sxc).
// APK must be checked before JAR because APK is a subset of JAR.
// This means APK should be a child of JAR detector, but in practice,
// the decisive signature for JAR might be located at the end of the file
// and not reachable because of library readLimit.
zip = newMIME("application/zip", ".zip", magic.Zip, xlsx, docx, pptx, epub, apk, jar, odt, ods, odp, odg, odf, odc, sxc).
alias("application/x-zip", "application/x-zip-compressed")
tar = newMIME("application/x-tar", ".tar", magic.Tar)
xar = newMIME("application/x-xar", ".xar", magic.Xar)
@@ -57,6 +61,7 @@ var (
pptx = newMIME("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx", magic.Pptx)
epub = newMIME("application/epub+zip", ".epub", magic.Epub)
jar = newMIME("application/jar", ".jar", magic.Jar)
apk = newMIME("application/vnd.android.package-archive", ".apk", magic.APK)
ole = newMIME("application/x-ole-storage", "", magic.Ole, msi, aaf, msg, xls, pub, ppt, doc)
msi = newMIME("application/x-ms-installer", ".msi", magic.Msi).
alias("application/x-windows-installer", "application/x-msi")
@@ -87,8 +92,8 @@ var (
html = newMIME("text/html", ".html", magic.HTML)
php = newMIME("text/x-php", ".php", magic.Php)
rtf = newMIME("text/rtf", ".rtf", magic.Rtf).alias("application/rtf")
js = newMIME("application/javascript", ".js", magic.Js).
alias("application/x-javascript", "text/javascript")
js = newMIME("text/javascript", ".js", magic.Js).
alias("application/x-javascript", "application/javascript")
srt = newMIME("application/x-subrip", ".srt", magic.Srt).
alias("application/x-srt", "text/x-srt")
vtt = newMIME("text/vtt", ".vtt", magic.Vtt)
@@ -156,7 +161,7 @@ var (
aac = newMIME("audio/aac", ".aac", magic.AAC)
voc = newMIME("audio/x-unknown", ".voc", magic.Voc)
aMp4 = newMIME("audio/mp4", ".mp4", magic.AMp4).
alias("audio/x-m4a", "audio/x-mp4a")
alias("audio/x-mp4a")
m4a = newMIME("audio/x-m4a", ".m4a", magic.M4a)
m3u = newMIME("application/vnd.apple.mpegurl", ".m3u", magic.M3u).
alias("audio/mpegurl")
@@ -261,4 +266,5 @@ var (
jxr = newMIME("image/jxr", ".jxr", magic.Jxr).alias("image/vnd.ms-photo")
parquet = newMIME("application/vnd.apache.parquet", ".parquet", magic.Par1).
alias("application/x-parquet")
cbor = newMIME("application/cbor", ".cbor", magic.CBOR)
)

View File

@@ -1,7 +1,7 @@
Package validator
=================
<img align="right" src="logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-10.22.1-green.svg)
![Project status](https://img.shields.io/badge/version-10.24.0-green.svg)
[![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
@@ -22,6 +22,11 @@ It has the following **unique** features:
- Customizable i18n aware error messages.
- Default validator for the [gin](https://github.com/gin-gonic/gin) web framework; upgrading from v8 to v9 in gin see [here](https://github.com/go-playground/validator/tree/master/_examples/gin-upgrading-overriding)
A Call for Maintainers
----------------------
Please read the discussiong started [here](https://github.com/go-playground/validator/discussions/1330) if you are interested in contributing/helping maintain this package.
Installation
------------
@@ -266,74 +271,75 @@ validate := validator.New(validator.WithRequiredStructEnabled())
Benchmarks
------
###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64
###### Run on MacBook Pro Max M3
```go
go version go1.21.0 darwin/arm64
go version go1.23.3 darwin/arm64
goos: darwin
goarch: arm64
cpu: Apple M3 Max
pkg: github.com/go-playground/validator/v10
BenchmarkFieldSuccess-8 33142266 35.94 ns/op 0 B/op 0 allocs/op
BenchmarkFieldSuccessParallel-8 200816191 6.568 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-8 6779707 175.1 ns/op 200 B/op 4 allocs/op
BenchmarkFieldFailureParallel-8 11044147 108.4 ns/op 200 B/op 4 allocs/op
BenchmarkFieldArrayDiveSuccess-8 6054232 194.4 ns/op 97 B/op 5 allocs/op
BenchmarkFieldArrayDiveSuccessParallel-8 12523388 94.07 ns/op 97 B/op 5 allocs/op
BenchmarkFieldArrayDiveFailure-8 3587043 334.3 ns/op 300 B/op 10 allocs/op
BenchmarkFieldArrayDiveFailureParallel-8 5816665 200.8 ns/op 300 B/op 10 allocs/op
BenchmarkFieldMapDiveSuccess-8 2217910 540.1 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveSuccessParallel-8 4446698 258.7 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveFailure-8 2392759 504.6 ns/op 376 B/op 13 allocs/op
BenchmarkFieldMapDiveFailureParallel-8 4244199 286.9 ns/op 376 B/op 13 allocs/op
BenchmarkFieldMapDiveWithKeysSuccess-8 2005857 592.1 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveWithKeysSuccessParallel-8 4400850 296.9 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveWithKeysFailure-8 1850227 643.8 ns/op 553 B/op 16 allocs/op
BenchmarkFieldMapDiveWithKeysFailureParallel-8 3293233 375.1 ns/op 553 B/op 16 allocs/op
BenchmarkFieldCustomTypeSuccess-8 12174412 98.25 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeSuccessParallel-8 34389907 35.49 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-8 7582524 156.6 ns/op 184 B/op 3 allocs/op
BenchmarkFieldCustomTypeFailureParallel-8 13019902 92.79 ns/op 184 B/op 3 allocs/op
BenchmarkFieldOrTagSuccess-8 3427260 349.4 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagSuccessParallel-8 15144128 81.25 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-8 5913546 201.9 ns/op 216 B/op 5 allocs/op
BenchmarkFieldOrTagFailureParallel-8 9810212 113.7 ns/op 216 B/op 5 allocs/op
BenchmarkStructLevelValidationSuccess-8 13456327 87.66 ns/op 16 B/op 1 allocs/op
BenchmarkStructLevelValidationSuccessParallel-8 41818888 27.77 ns/op 16 B/op 1 allocs/op
BenchmarkStructLevelValidationFailure-8 4166284 272.6 ns/op 264 B/op 7 allocs/op
BenchmarkStructLevelValidationFailureParallel-8 7594581 152.1 ns/op 264 B/op 7 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-8 6508082 182.6 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeSuccessParallel-8 23078605 54.78 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeFailure-8 3118352 381.0 ns/op 416 B/op 9 allocs/op
BenchmarkStructSimpleCustomTypeFailureParallel-8 5300738 224.1 ns/op 432 B/op 10 allocs/op
BenchmarkStructFilteredSuccess-8 4761807 251.1 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredSuccessParallel-8 8792598 128.6 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredFailure-8 5202573 232.1 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredFailureParallel-8 9591267 121.4 ns/op 216 B/op 5 allocs/op
BenchmarkStructPartialSuccess-8 5188512 231.6 ns/op 224 B/op 4 allocs/op
BenchmarkStructPartialSuccessParallel-8 9179776 123.1 ns/op 224 B/op 4 allocs/op
BenchmarkStructPartialFailure-8 3071212 392.5 ns/op 440 B/op 9 allocs/op
BenchmarkStructPartialFailureParallel-8 5344261 223.7 ns/op 440 B/op 9 allocs/op
BenchmarkStructExceptSuccess-8 3184230 375.0 ns/op 424 B/op 8 allocs/op
BenchmarkStructExceptSuccessParallel-8 10090130 108.9 ns/op 208 B/op 3 allocs/op
BenchmarkStructExceptFailure-8 3347226 357.7 ns/op 424 B/op 8 allocs/op
BenchmarkStructExceptFailureParallel-8 5654923 209.5 ns/op 424 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-8 5232265 229.1 ns/op 56 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldSuccessParallel-8 17436674 64.75 ns/op 56 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldFailure-8 3128613 383.6 ns/op 272 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldFailureParallel-8 6994113 168.8 ns/op 272 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3506487 340.9 ns/op 64 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 13431300 91.77 ns/op 64 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2410566 500.9 ns/op 288 B/op 9 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 6344510 188.2 ns/op 288 B/op 9 allocs/op
BenchmarkStructSimpleSuccess-8 8922726 133.8 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleSuccessParallel-8 55291153 23.63 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleFailure-8 3171553 378.4 ns/op 416 B/op 9 allocs/op
BenchmarkStructSimpleFailureParallel-8 5571692 212.0 ns/op 416 B/op 9 allocs/op
BenchmarkStructComplexSuccess-8 1683750 714.5 ns/op 224 B/op 5 allocs/op
BenchmarkStructComplexSuccessParallel-8 4578046 257.0 ns/op 224 B/op 5 allocs/op
BenchmarkStructComplexFailure-8 481585 2547 ns/op 3041 B/op 48 allocs/op
BenchmarkStructComplexFailureParallel-8 965764 1577 ns/op 3040 B/op 48 allocs/op
BenchmarkOneof-8 17380881 68.50 ns/op 0 B/op 0 allocs/op
BenchmarkOneofParallel-8 8084733 153.5 ns/op 0 B/op 0 allocs/op
BenchmarkFieldSuccess-16 42461943 27.88 ns/op 0 B/op 0 allocs/op
BenchmarkFieldSuccessParallel-16 486632887 2.289 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-16 9566167 121.3 ns/op 200 B/op 4 allocs/op
BenchmarkFieldFailureParallel-16 17551471 83.68 ns/op 200 B/op 4 allocs/op
BenchmarkFieldArrayDiveSuccess-16 7602306 155.6 ns/op 97 B/op 5 allocs/op
BenchmarkFieldArrayDiveSuccessParallel-16 20664610 59.80 ns/op 97 B/op 5 allocs/op
BenchmarkFieldArrayDiveFailure-16 4659756 252.9 ns/op 301 B/op 10 allocs/op
BenchmarkFieldArrayDiveFailureParallel-16 8010116 152.9 ns/op 301 B/op 10 allocs/op
BenchmarkFieldMapDiveSuccess-16 2834575 421.2 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveSuccessParallel-16 7179700 171.8 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveFailure-16 3081728 384.4 ns/op 376 B/op 13 allocs/op
BenchmarkFieldMapDiveFailureParallel-16 6058137 204.0 ns/op 377 B/op 13 allocs/op
BenchmarkFieldMapDiveWithKeysSuccess-16 2544975 464.8 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveWithKeysSuccessParallel-16 6661954 181.4 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveWithKeysFailure-16 2435484 490.7 ns/op 553 B/op 16 allocs/op
BenchmarkFieldMapDiveWithKeysFailureParallel-16 4249617 282.0 ns/op 554 B/op 16 allocs/op
BenchmarkFieldCustomTypeSuccess-16 14943525 77.35 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeSuccessParallel-16 64051954 20.61 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-16 10721384 107.1 ns/op 184 B/op 3 allocs/op
BenchmarkFieldCustomTypeFailureParallel-16 18714495 69.77 ns/op 184 B/op 3 allocs/op
BenchmarkFieldOrTagSuccess-16 4063124 294.3 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagSuccessParallel-16 31903756 41.22 ns/op 18 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-16 7748558 146.8 ns/op 216 B/op 5 allocs/op
BenchmarkFieldOrTagFailureParallel-16 13139854 92.05 ns/op 216 B/op 5 allocs/op
BenchmarkStructLevelValidationSuccess-16 16808389 70.25 ns/op 16 B/op 1 allocs/op
BenchmarkStructLevelValidationSuccessParallel-16 90686955 14.47 ns/op 16 B/op 1 allocs/op
BenchmarkStructLevelValidationFailure-16 5818791 200.2 ns/op 264 B/op 7 allocs/op
BenchmarkStructLevelValidationFailureParallel-16 11115874 107.5 ns/op 264 B/op 7 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-16 7764956 151.9 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeSuccessParallel-16 52316265 30.37 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeFailure-16 4195429 277.2 ns/op 416 B/op 9 allocs/op
BenchmarkStructSimpleCustomTypeFailureParallel-16 7305661 164.6 ns/op 432 B/op 10 allocs/op
BenchmarkStructFilteredSuccess-16 6312625 186.1 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredSuccessParallel-16 13684459 93.42 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredFailure-16 6751482 171.2 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredFailureParallel-16 14146070 86.93 ns/op 216 B/op 5 allocs/op
BenchmarkStructPartialSuccess-16 6544448 177.3 ns/op 224 B/op 4 allocs/op
BenchmarkStructPartialSuccessParallel-16 13951946 88.73 ns/op 224 B/op 4 allocs/op
BenchmarkStructPartialFailure-16 4075833 287.5 ns/op 440 B/op 9 allocs/op
BenchmarkStructPartialFailureParallel-16 7490805 161.3 ns/op 440 B/op 9 allocs/op
BenchmarkStructExceptSuccess-16 4107187 281.4 ns/op 424 B/op 8 allocs/op
BenchmarkStructExceptSuccessParallel-16 15979173 80.86 ns/op 208 B/op 3 allocs/op
BenchmarkStructExceptFailure-16 4434372 264.3 ns/op 424 B/op 8 allocs/op
BenchmarkStructExceptFailureParallel-16 8081367 154.1 ns/op 424 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-16 6459542 183.4 ns/op 56 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldSuccessParallel-16 41013781 37.95 ns/op 56 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldFailure-16 4034998 292.1 ns/op 272 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldFailureParallel-16 11348446 115.3 ns/op 272 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-16 4448528 267.7 ns/op 64 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-16 26813619 48.33 ns/op 64 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-16 3090646 384.5 ns/op 288 B/op 9 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-16 9870906 129.5 ns/op 288 B/op 9 allocs/op
BenchmarkStructSimpleSuccess-16 10675562 109.5 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleSuccessParallel-16 131159784 8.932 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleFailure-16 4094979 286.6 ns/op 416 B/op 9 allocs/op
BenchmarkStructSimpleFailureParallel-16 7606663 157.9 ns/op 416 B/op 9 allocs/op
BenchmarkStructComplexSuccess-16 2073470 576.0 ns/op 224 B/op 5 allocs/op
BenchmarkStructComplexSuccessParallel-16 7821831 161.3 ns/op 224 B/op 5 allocs/op
BenchmarkStructComplexFailure-16 576358 2001 ns/op 3042 B/op 48 allocs/op
BenchmarkStructComplexFailureParallel-16 1000000 1171 ns/op 3041 B/op 48 allocs/op
BenchmarkOneof-16 22503973 52.82 ns/op 0 B/op 0 allocs/op
BenchmarkOneofParallel-16 8538474 140.4 ns/op 0 B/op 0 allocs/op
```
Complementary Software
@@ -349,6 +355,20 @@ How to Contribute
Make a pull request...
Maintenance and support for SDK major versions
----------------------------------------------
See prior discussion [here](https://github.com/go-playground/validator/discussions/1342) for more details.
This package is aligned with the [Go release policy](https://go.dev/doc/devel/release) in that support is guaranteed for
the two most recent major versions.
This does not mean the package will not work with older versions of Go, only that we reserve the right to increase the
MSGV(Minimum Supported Go Version) when the need arises to address Security issues/patches, OS issues & support or newly
introduced functionality that would greatly benefit the maintenance and/or usage of this package.
If and when the MSGV is increased it will be done so in a minimum of a `Minor` release bump.
License
-------
Distributed under MIT License, please see license file within the code for more details.

View File

@@ -205,6 +205,7 @@ var (
"fqdn": isFQDN,
"unique": isUnique,
"oneof": isOneOf,
"oneofci": isOneOfCI,
"html": isHTML,
"html_encoded": isHTMLEncoded,
"url_encoded": isURLEncoded,
@@ -213,6 +214,7 @@ var (
"json": isJSON,
"jwt": isJWT,
"hostname_port": isHostnamePort,
"port": isPort,
"lowercase": isLowercase,
"uppercase": isUppercase,
"datetime": isDatetime,
@@ -299,6 +301,23 @@ func isOneOf(fl FieldLevel) bool {
return false
}
// isOneOfCI is the validation function for validating if the current field's value is one of the provided string values (case insensitive).
func isOneOfCI(fl FieldLevel) bool {
vals := parseOneOfParam2(fl.Param())
field := fl.Field()
if field.Kind() != reflect.String {
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
v := field.String()
for _, val := range vals {
if strings.EqualFold(val, v) {
return true
}
}
return false
}
// isUnique is the validation function for validating if each array|slice|map value is unique
func isUnique(fl FieldLevel) bool {
field := fl.Field()
@@ -2711,6 +2730,13 @@ func isHostnamePort(fl FieldLevel) bool {
return true
}
// IsPort validates if the current field's value represents a valid port
func isPort(fl FieldLevel) bool {
val := fl.Field().Uint()
return val >= 1 && val <= 65535
}
// isLowercase is the validation function for validating if the current field's value is a lowercase string.
func isLowercase(fl FieldLevel) bool {
field := fl.Field()

View File

@@ -489,12 +489,19 @@ For strings, ints, and uints, oneof will ensure that the value
is one of the values in the parameter. The parameter should be
a list of values separated by whitespace. Values may be
strings or numbers. To match strings with spaces in them, include
the target string between single quotes.
the target string between single quotes. Kind of like an 'enum'.
Usage: oneof=red green
oneof='red green' 'blue yellow'
oneof=5 7 9
# One Of Case Insensitive
Works the same as oneof but is case insensitive and therefore only accepts strings.
Usage: oneofci=red green
oneofci='red green' 'blue yellow'
# Greater Than
For numbers, this will ensure that the value is greater than the

View File

@@ -73,7 +73,7 @@ const (
cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html
mongodbIdRegexString = "^[a-f\\d]{24}$"
mongodbConnStringRegexString = "^mongodb(\\+srv)?:\\/\\/(([a-zA-Z\\d]+):([a-zA-Z\\d$:\\/?#\\[\\]@]+)@)?(([a-z\\d.-]+)(:[\\d]+)?)((,(([a-z\\d.-]+)(:(\\d+))?))*)?(\\/[a-zA-Z-_]{1,64})?(\\?(([a-zA-Z]+)=([a-zA-Z\\d]+))(&(([a-zA-Z\\d]+)=([a-zA-Z\\d]+))?)*)?$"
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|((\*|\d+)(\/|-)\d+)|\d+|\*) ?){5,7})`
spicedbIDRegexString = `^(([a-zA-Z0-9/_|\-=+]{1,})|\*)$`
spicedbPermissionRegexString = "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$"
spicedbTypeRegexString = "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)?[a-z][a-z0-9_]{1,62}[a-z0-9]$"

View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.go]
indent_style = tab
[{Makefile,*.mk}]
indent_style = tab
[*.nix]
indent_size = 2

4
vendor/github.com/go-viper/mapstructure/v2/.envrc generated vendored Normal file
View File

@@ -0,0 +1,4 @@
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
fi
use flake . --impure

View File

@@ -0,0 +1,6 @@
/.devenv/
/.direnv/
/.pre-commit-config.yaml
/bin/
/build/
/var/

View File

@@ -0,0 +1,23 @@
run:
timeout: 5m
linters-settings:
gci:
sections:
- standard
- default
- prefix(github.com/go-viper/mapstructure)
golint:
min-confidence: 0
goimports:
local-prefixes: github.com/go-viper/maptstructure
linters:
disable-all: true
enable:
- gci
- gofmt
- gofumpt
- goimports
- staticcheck
# - stylecheck

View File

@@ -1,3 +1,11 @@
> [!WARNING]
> As of v2 of this library, change log can be found in GitHub releases.
## 1.5.1
* Wrap errors so they're compatible with `errors.Is` and `errors.As` [GH-282]
* Fix map of slices not decoding properly in certain cases. [GH-266]
## 1.5.0
* New option `IgnoreUntaggedFields` to ignore decoding to any fields

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