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 toolchain go1.23.1
require ( require (
github.com/99designs/gqlgen v0.17.55 github.com/99designs/gqlgen v0.17.63
github.com/Masterminds/semver/v3 v3.3.0 github.com/Masterminds/semver/v3 v3.3.1
github.com/adhocore/gronx v1.19.3 github.com/adhocore/gronx v1.19.5
github.com/andybalholm/brotli v1.1.1 github.com/andybalholm/brotli v1.1.1
github.com/atrox/haikunatorgo/v2 v2.0.1 github.com/atrox/haikunatorgo/v2 v2.0.1
github.com/caddyserver/certmagic v0.21.4 github.com/caddyserver/certmagic v0.21.7
github.com/datarhei/gosrt v0.7.0 github.com/datarhei/gosrt v0.8.0
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e
github.com/dolthub/swiss v0.2.1 github.com/dolthub/swiss v0.2.1
github.com/fujiwara/shapeio v1.0.0 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/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/golang-jwt/jwt/v5 v5.2.1
github.com/google/gops v0.3.28 github.com/google/gops v0.3.28
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/raft v1.7.1 github.com/hashicorp/raft v1.7.2
github.com/hashicorp/raft-boltdb/v2 v2.3.0 github.com/hashicorp/raft-boltdb/v2 v2.3.1
github.com/invopop/jsonschema v0.4.0 github.com/invopop/jsonschema v0.4.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/klauspost/compress v1.17.11 github.com/klauspost/compress v1.17.11
github.com/klauspost/cpuid/v2 v2.2.8 github.com/klauspost/cpuid/v2 v2.2.9
github.com/labstack/echo/v4 v4.12.0 github.com/labstack/echo/v4 v4.13.3
github.com/lestrrat-go/strftime v1.1.0 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/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/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/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/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.4 github.com/swaggo/swag v1.16.4
github.com/tklauser/go-sysconf v0.3.14 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 github.com/xeipuuv/gojsonschema v1.2.0
go.etcd.io/bbolt v1.3.11 go.etcd.io/bbolt v1.3.11
go.uber.org/automaxprocs v1.6.0 go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.32.0
golang.org/x/mod v0.21.0 golang.org/x/mod v0.22.0
) )
require ( require (
@@ -58,12 +58,12 @@ require (
github.com/boltdb/bolt v1.3.1 // indirect github.com/boltdb/bolt v1.3.1 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // 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/davecgh/go-spew v1.1.1 // indirect
github.com/dolthub/maphash v0.1.0 // indirect github.com/dolthub/maphash v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // 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/ghodss/yaml v1.0.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect github.com/go-ini/ini v1.67.0 // indirect
github.com/go-ole/go-ole v1.3.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-openapi/swag v0.23.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/goccy/go-json v0.10.4 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // 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/go-msgpack/v2 v2.1.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // 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/leodido/go-urn v1.4.0 // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v0.2.2 // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mholt/acmez/v2 v2.0.3 // indirect github.com/mholt/acmez/v3 v3.0.1 // indirect
github.com/miekg/dns v1.1.62 // indirect github.com/miekg/dns v1.1.62 // indirect
github.com/minio/md5-simd v1.1.2 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.1 // 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/prometheus/procfs v0.15.1 // indirect
github.com/rs/xid v1.6.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sosodev/duration v1.3.1 // 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/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/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // 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/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.30.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/time v0.9.0 // indirect
google.golang.org/protobuf v1.35.1 // 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.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // 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= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/99designs/gqlgen v0.17.55/go.mod h1:3Bq768f8hgVPGZxL8aY9MaYmbxa6llPM/qu1IGH1EJo= 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/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 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 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.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 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 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0=
github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U= 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.5 h1:cwIG4nT1v9DvadxtHBe6MzE+FZ1JDvAUC45U2fl4eSQ=
github.com/adhocore/gronx v1.19.3/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= 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 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= 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-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/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-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-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 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 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/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 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= 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.7 h1:66KJioPFJwttL43KYSWk7ErSmE6LfaJgCQuhm8Sg6fg=
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE= 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 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= 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= 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/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/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/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.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/datarhei/gosrt v0.7.0 h1:1/IY66HVVgqGA9zkmL5l6jUFuI8t/76WkuamSkJqHqs= github.com/datarhei/gosrt v0.8.0 h1:fna/FFRbVN7LvwAt2cR6pxwFz7rm979vdRzGfh9zbNM=
github.com/datarhei/gosrt v0.7.0/go.mod h1:wTDoyog1z4au8Fd/QJBQAndzvccuxjqUL/qMm0EyJxE= 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 h1:Qc/0D4xvXrazFkoi/4UGqO15yQ1JN5I8h7RwdzCLgTY=
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw= 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= 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/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 h1:xG5D9oNqCSUUbryZ/jQV3cqe1v2suEjwPIcEg1gKM8M=
github.com/fujiwara/shapeio v1.0.0/go.mod h1:LmEmu6L/8jetyj1oewewFb7bZCNRwE7wLCUNzDLaLVA= 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.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= 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 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.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.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.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.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 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 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.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 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-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 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 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.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 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/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/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
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/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 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-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.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.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.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.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.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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/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 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark=
github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= 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.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 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 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 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 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= 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 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 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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.2 h1:pyvxhfJ4R8VIAlHKvLoKQWElZspsCVT6YWuxVxsPAgc=
github.com/hashicorp/raft v1.7.1/go.mod h1:hUeiEwQQR/Nk2iKDD0dkEhklSsu3jcAcqvPzPoZSAEM= 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 h1:RLKEcCuKcZ+qp2VlaaZsYZfLOmIiuJNpEi48Rl8u9cQ=
github.com/hashicorp/raft-boltdb v0.0.0-20230125174641-2a8082862702/go.mod h1:nTakvJ4XYq45UXtn0DbwR4aU9ZdjlnIenpbs6Cd+FM0= 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.1 h1:ackhdCNPKblmOhjEU9+4lHSJYFkJd6Jqyvj6eW9pwkc=
github.com/hashicorp/raft-boltdb/v2 v2.3.0/go.mod h1:YHukhB04ChJsLHLJEUD6vjFyLX2L3dsX3wPBZcX4tmc= 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.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 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= 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/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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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.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.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.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 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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.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.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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.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/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 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/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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= 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 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 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= 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/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 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= 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.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= 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 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= 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.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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.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.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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.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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/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/v3 v3.0.1 h1:4PcjKjaySlgXK857aTfDuRbmnM5gb3Ruz3tvoSJAUp8=
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw= 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 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= 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 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= 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.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E=
github.com/minio/minio-go/v7 v7.0.80/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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-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 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 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.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.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 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 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= 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/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.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= 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.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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 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 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 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.1 h1:wWXLKXwzpsduC3kUSahiL45MWxkGb+AQG0dsri4iftA=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= 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 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= 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/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.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= 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= 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.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.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.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= 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.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= 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 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= 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 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= 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/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.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= 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 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 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 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 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.21 h1:Zw1rG2dr1pRR4wqwbVq4d6+xk2f4ut/yo+hwr4QjE08=
github.com/vektah/gqlparser/v2 v2.5.18/go.mod h1:6HLzf7JKv9Fi3APymudztFQNmLXR5qJeEo6BOFcXVfc= 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-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 h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 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/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 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 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-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.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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-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.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.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 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-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-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.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-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-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-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-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-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-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-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-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-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-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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 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= 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/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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.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.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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -12,6 +12,12 @@ linters-settings:
- paramTypeCombine - paramTypeCombine
- preferFprint - preferFprint
- yodaStyleExpr - yodaStyleExpr
govet:
enable-all: true
disable:
- fieldalignment
- shadow
- unusedwrite # TODO: fix and enable
errcheck: errcheck:
exclude-functions: exclude-functions:
- (io.Writer).Write - (io.Writer).Write
@@ -59,6 +65,7 @@ linters-settings:
- bool-compare - bool-compare
- compares - compares
- empty - empty
- encoded-compare
- error-is-as - error-is-as
- error-nil - error-nil
- expected-actual - expected-actual
@@ -74,7 +81,9 @@ linters:
disable-all: true disable-all: true
enable: enable:
- bodyclose - bodyclose
- copyloopvar
- dupl - dupl
- dupword
- errcheck - errcheck
- gocritic - gocritic
- gofmt - gofmt
@@ -84,6 +93,7 @@ linters:
- ineffassign - ineffassign
- misspell - misspell
- nakedret - nakedret
- nolintlint
- perfsprint - perfsprint
- prealloc - prealloc
- revive - 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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: 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 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: 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 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 ### 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) 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 { for _, p := range plugins {
if mut, ok := p.(plugin.CodeGenerator); ok { if mut, ok := p.(plugin.CodeGenerator); ok {
err := mut.GenerateCode(data) err := mut.GenerateCode(data)
@@ -140,6 +130,11 @@ func Generate(cfg *config.Config, option ...Option) error {
return fmt.Errorf("generating core failed: %w", err) 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 !cfg.SkipValidation {
if err := validate(cfg); err != nil { if err := validate(cfg); err != nil {
return fmt.Errorf("validation failed: %w", err) return fmt.Errorf("validation failed: %w", err)

View File

@@ -1,10 +1,20 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{ range $name, $args := .Args }} {{ 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 var err error
args := map[string]interface{}{} args := map[string]any{}
{{- range $i, $arg := . }} {{- 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) arg{{$i}}, err := ec.{{ $name }}{{$arg.Name | go}}(ctx, rawArgs)
{{- end }}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -14,31 +24,44 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]
} }
{{- range $i, $arg := . }} {{- 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}}( func (ec *executionContext) {{ $name }}{{$arg.Name | go}}(
ctx context.Context, ctx context.Context,
rawArgs map[string]interface{}, rawArgs map[string]any,
) ({{ $arg.TypeReference.GO | ref}}, error) { ) ({{ $arg.TypeReference.GO | ref}}, error) {
{{- end }}
{{- if not .CallArgumentDirectivesWithNull}} {{- 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 We won't call the directive if the argument is null.
// even if the argument is null. Set call_argument_directives_with_null to true to call directives
_, ok := rawArgs[{{$arg.Name|quote}}] even if the argument is null.
if !ok { */ -}}
if _, ok := rawArgs[{{$arg.Name|quote}}]; !ok {
var zeroVal {{ $arg.TypeReference.GO | ref}} var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil return zeroVal, nil
} }
{{end}} {{end}}
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField({{$arg.Name|quote}})) ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField({{$arg.Name|quote}}))
{{- if $arg.ImplDirectives }} {{- if $arg.ImplDirectives }}
directive0 := func(ctx context.Context) (interface{}, error) { directive0 := func(ctx context.Context) (any, error) {
tmp, ok := rawArgs[{{$arg.Name|quote}}] tmp, ok := rawArgs[{{$arg.Name|quote}}]
if !ok { if !ok {
var zeroVal {{ $arg.TypeReference.GO | ref}} var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil return zeroVal, nil
} }
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $arg.TypeReference.UnmarshalFunc }}(ctx, ec, tmp)
{{- else -}}
return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) 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) tmp, err := directive{{$arg.ImplDirectives|len}}(ctx)
if err != nil { if err != nil {
var zeroVal {{ $arg.TypeReference.GO | ref}} var zeroVal {{ $arg.TypeReference.GO | ref}}
@@ -57,7 +80,11 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]
} }
{{- else }} {{- else }}
if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok { 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) return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp)
{{- end }}
} }
var zeroVal {{ $arg.TypeReference.GO | ref}} 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) { func (b *Binder) FindType(pkgName, typeName string) (types.Type, error) {
if pkgName == "" { if pkgName == "" {
if typeName == "map[string]interface{}" { if typeName == "map[string]any" || typeName == "map[string]interface{}" {
return MapType, nil return MapType, nil
} }
if typeName == "interface{}" { if typeName == "any" || typeName == "interface{}" {
return InterfaceType, nil 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) 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 return MapType, nil
} }
if models[0] == "interface{}" { if models[0] == "any" || models[0] == "interface{}" {
return InterfaceType, nil 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) { func (b *Binder) FindObject(pkgName, typeName string) (types.Object, error) {
if pkgName == "" { 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) pkg := b.pkgs.LoadWithTypes(pkgName)
@@ -258,7 +258,7 @@ func (ref *TypeReference) IsPtrToSlice() bool {
func (ref *TypeReference) IsPtrToIntf() bool { func (ref *TypeReference) IsPtrToIntf() bool {
if ref.IsPtr() { if ref.IsPtr() {
_, isPointerToInterface := ref.GO.(*types.Pointer).Elem().(*types.Interface) _, isPointerToInterface := types.Unalias(ref.GO.(*types.Pointer).Elem()).(*types.Interface)
return isPointerToInterface return isPointerToInterface
} }
return false return false
@@ -344,7 +344,7 @@ func isIntf(t types.Type) bool {
if t == nil { if t == nil {
return true return true
} }
_, ok := t.(*types.Interface) _, ok := types.Unalias(t).(*types.Interface)
return ok 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 { 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) { if !isMap(bindTarget) {
continue continue
} }
@@ -410,7 +410,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
}, nil }, nil
} }
if model == "interface{}" { if model == "any" || model == "interface{}" {
if !isIntf(bindTarget) { if !isIntf(bindTarget) {
continue continue
} }
@@ -499,6 +499,7 @@ func isValid(t types.Type) bool {
} }
func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type { func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {
base = types.Unalias(base)
if t.Elem != nil { if t.Elem != nil {
child := b.CopyModifiersFromAst(t.Elem, base) child := b.CopyModifiersFromAst(t.Elem, base)
if _, isStruct := child.Underlying().(*types.Struct); isStruct && !b.cfg.OmitSliceElementPointers { if _, isStruct := child.Underlying().(*types.Struct); isStruct && !b.cfg.OmitSliceElementPointers {
@@ -528,11 +529,11 @@ func IsNilable(t types.Type) bool {
return IsNilable(namedType.Underlying()) return IsNilable(namedType.Underlying())
} }
_, isPtr := t.(*types.Pointer) _, isPtr := t.(*types.Pointer)
_, isMap := t.(*types.Map) _, isNilableMap := t.(*types.Map)
_, isInterface := t.(*types.Interface) _, isInterface := t.(*types.Interface)
_, isSlice := t.(*types.Slice) _, isSlice := t.(*types.Slice)
_, isChan := t.(*types.Chan) _, isChan := t.(*types.Chan)
return isPtr || isMap || isInterface || isSlice || isChan return isPtr || isNilableMap || isInterface || isSlice || isChan
} }
func hasMethod(it types.Type, name string) bool { 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 { func basicUnderlying(it types.Type) *types.Basic {
it = types.Unalias(it)
if ptr, isPtr := it.(*types.Pointer); isPtr { if ptr, isPtr := it.(*types.Pointer); isPtr {
it = ptr.Elem() it = types.Unalias(ptr.Elem())
} }
namedType, ok := it.(*types.Named) namedType, ok := it.(*types.Named)
if !ok { if !ok {

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ package codegen
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/ast"
@@ -143,7 +142,7 @@ func (d *Directive) CallArgs() string {
args := []string{"ctx", "obj", "n"} args := []string{"ctx", "obj", "n"}
for _, arg := range d.Args { 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, ", ") return strings.Join(args, ", ")
@@ -169,13 +168,13 @@ func (d *Directive) CallName() string {
} }
func (d *Directive) Declaration() 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 { for _, arg := range d.Args {
res += fmt.Sprintf(", %s %s", templates.ToGoPrivate(arg.Name), templates.CurrentImports.LookupType(arg.TypeReference.GO)) 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 return res
} }

View File

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

View File

@@ -30,7 +30,7 @@ type Field struct {
Args []*FieldArgument // A list of arguments to be passed to this field 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 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 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 Object *Object // A link back to the parent object
Default any // The default value Default any // The default value
Stream bool // does this field return a channel? 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() { if iface, ok := arg.TypeReference.GO.(*types.Interface); ok && iface.Empty() {
tmp = fmt.Sprintf(` tmp = fmt.Sprintf(`
func () interface{} { func () any {
if fc.Args["%s"] == nil { if fc.Args["%s"] == nil {
return nil return nil
} }
return fc.Args["%s"].(interface{}) return fc.Args["%s"].(any)
}()`, arg.Name, arg.Name, }()`, arg.Name, arg.Name,
) )
} }

View File

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

View File

@@ -4,9 +4,7 @@ import (
"embed" "embed"
"errors" "errors"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"github.com/vektah/gqlparser/v2/ast" "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 // Root file contains top-level definitions that should not be duplicated across the generated
// files for each schema file. // files for each schema file.
func generateRootFile(data *Data) error { func generateRootFile(data *Data) error {
dir := data.Config.Exec.DirName dir := data.Config.Exec.DirName
path := filepath.Join(dir, "root_.generated.go") 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{ return templates.Render(templates.Options{
PackageName: data.Config.Exec.Package, PackageName: data.Config.Exec.Package,
Template: template, Template: rootTemplate,
Filename: path, Filename: path,
Data: data, Data: data,
RegionTags: false, RegionTags: false,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -205,6 +205,7 @@ func Funcs() template.FuncMap {
"obj": obj, "obj": obj,
"ts": TypeIdentifier, "ts": TypeIdentifier,
"call": Call, "call": Call,
"dict": dict,
"prefixLines": prefixLines, "prefixLines": prefixLines,
"notNil": notNil, "notNil": notNil,
"strSplit": StrSplit, "strSplit": StrSplit,
@@ -247,7 +248,13 @@ func isDelimiter(c rune) bool {
} }
func ref(p types.Type) string { 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 { func obj(obj types.Object) string {
@@ -274,6 +281,21 @@ func Call(p *types.Func) string {
return pkg + p.Name() 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() { func resetModelNames() {
modelNamesMu.Lock() modelNamesMu.Lock()
defer modelNamesMu.Unlock() defer modelNamesMu.Unlock()
@@ -606,10 +628,10 @@ func Dump(val any) string {
for _, part := range val { for _, part := range val {
parts = append(parts, Dump(part)) parts = append(parts, Dump(part))
} }
return "[]interface{}{" + strings.Join(parts, ",") + "}" return "[]any{" + strings.Join(parts, ",") + "}"
case map[string]any: case map[string]any:
buf := bytes.Buffer{} buf := bytes.Buffer{}
buf.WriteString("map[string]interface{}{") buf.WriteString("map[string]any{")
var keys []string var keys []string
for key := range val { for key := range val {
keys = append(keys, key) keys = append(keys, key)

View File

@@ -1,14 +1,23 @@
{{ $useFunctionSyntaxForExecutionContext := .Config.UseFunctionSyntaxForExecutionContext }}
{{- range $type := .ReferencedTypes }} {{- range $type := .ReferencedTypes }}
{{ with $type.UnmarshalFunc }} {{ 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 and $type.IsNilable (not $type.GQL.NonNull) (not $type.IsPtrToPtr) }}
if v == nil { return nil, nil } if v == nil { return nil, nil }
{{- end }} {{- end }}
{{- if or $type.IsPtrToSlice $type.IsPtrToIntf }} {{- 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) res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v)
{{- end }}
return &res, graphql.ErrorOnPath(ctx, err) return &res, graphql.ErrorOnPath(ctx, err)
{{- else if $type.IsSlice }} {{- else if $type.IsSlice }}
var vSlice []interface{} var vSlice []any
if v != nil { if v != nil {
vSlice = graphql.CoerceList(v) vSlice = graphql.CoerceList(v)
} }
@@ -16,7 +25,11 @@
res := make([]{{$type.GO.Elem | ref}}, len(vSlice)) res := make([]{{$type.GO.Elem | ref}}, len(vSlice))
for i := range vSlice { for i := range vSlice {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) 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]) res[i], err = ec.{{ $type.Elem.UnmarshalFunc }}(ctx, vSlice[i])
{{- end }}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -25,7 +38,11 @@
{{- else if and $type.IsPtrToPtr (not $type.Unmarshaler) (not $type.IsMarshaler) }} {{- else if and $type.IsPtrToPtr (not $type.Unmarshaler) (not $type.IsMarshaler) }}
var pres {{ $type.Elem.GO | ref }} var pres {{ $type.Elem.GO | ref }}
if v != nil { if v != nil {
{{ if $useFunctionSyntaxForExecutionContext -}}
res, err := {{ $type.Elem.UnmarshalFunc }}(ctx, ec, v)
{{- else -}}
res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v) res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v)
{{- end }}
if err != nil { if err != nil {
return nil, graphql.ErrorOnPath(ctx, err) return nil, graphql.ErrorOnPath(ctx, err)
} }
@@ -75,7 +92,11 @@
{{- end }} {{- end }}
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
{{- else }} {{- else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
res, err := unmarshalInput{{ $type.GQL.Name }}(ctx, ec, v)
{{- else -}}
res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v) res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v)
{{- end }}
{{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUnmarshalInput) }} {{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUnmarshalInput) }}
return &res, graphql.ErrorOnPath(ctx, err) return &res, graphql.ErrorOnPath(ctx, err)
{{- else if and (not $type.IsNilable) $type.PointersInUnmarshalInput }} {{- else if and (not $type.IsNilable) $type.PointersInUnmarshalInput }}
@@ -89,9 +110,17 @@
{{- end }} {{- end }}
{{ with $type.MarshalFunc }} {{ 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 { func (ec *executionContext) {{ . }}(ctx context.Context, sel ast.SelectionSet, v {{ $type.GO | ref }}) graphql.Marshaler {
{{- end -}}
{{- if or $type.IsPtrToSlice $type.IsPtrToIntf }} {{- 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) return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v)
{{- end }}
{{- else if $type.IsSlice }} {{- else if $type.IsSlice }}
{{- if not $type.GQL.NonNull }} {{- if not $type.GQL.NonNull }}
if v == nil { if v == nil {
@@ -101,6 +130,9 @@
ret := make(graphql.Array, len(v)) ret := make(graphql.Array, len(v))
{{- if not $type.IsScalar }} {{- if not $type.IsScalar }}
var wg sync.WaitGroup var wg sync.WaitGroup
{{- if gt $.Config.Exec.WorkerLimit 0 }}
sm := semaphore.NewWeighted({{ $.Config.Exec.WorkerLimit }})
{{- end }}
isLen1 := len(v) == 1 isLen1 := len(v) == 1
if !isLen1 { if !isLen1 {
wg.Add(len(v)) wg.Add(len(v))
@@ -124,17 +156,40 @@
}() }()
{{- end }} {{- end }}
if !isLen1 { 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]) ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
{{- end }}
} }
if isLen1 { if isLen1 {
f(i) f(i)
} else { } 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 }} {{ else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
ret[i] = {{ $type.Elem.MarshalFunc }}(ctx, ec, sel, v[i])
{{- else -}}
ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i]) ret[i] = ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, v[i])
{{- end }}
{{- end }} {{- end }}
} }
{{ if not $type.IsScalar }} wg.Wait() {{ end }} {{ if not $type.IsScalar }} wg.Wait() {{ end }}
@@ -150,7 +205,11 @@
if v == nil { if v == nil {
return graphql.Null return graphql.Null
} }
{{ if $useFunctionSyntaxForExecutionContext -}}
return {{ $type.Elem.MarshalFunc }}(ctx, ec, sel, *v)
{{- else -}}
return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v) return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v)
{{- end }}
{{- else }} {{- else }}
{{- if $type.IsNilable }} {{- if $type.IsNilable }}
if v == nil { if v == nil {
@@ -195,13 +254,25 @@
{{- end }} {{- end }}
{{- else if $type.IsRoot }} {{- else if $type.IsRoot }}
{{- if eq $type.Definition.Name "Subscription" }} {{- if eq $type.Definition.Name "Subscription" }}
{{ if $useFunctionSyntaxForExecutionContext -}}
res := _{{$type.Definition.Name}}(ctx, ec, sel)
{{- else -}}
res := ec._{{$type.Definition.Name}}(ctx, sel) res := ec._{{$type.Definition.Name}}(ctx, sel)
{{- end }}
return res(ctx) return res(ctx)
{{- else }} {{- else }}
{{ if $useFunctionSyntaxForExecutionContext -}}
return _{{$type.Definition.Name}}(ctx, ec, sel)
{{- else -}}
return ec._{{$type.Definition.Name}}(ctx, sel) return ec._{{$type.Definition.Name}}(ctx, sel)
{{- end }}
{{- end }} {{- end }}
{{- else }} {{- 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) return ec._{{$type.Definition.Name}}(ctx, sel, {{ if not $type.IsNilable}}&{{end}} v)
{{- end }}
{{- end }} {{- 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. // Add adds a value to the cache.
func (m MapCache[T]) Add(_ context.Context, key string, value T) { m[key] = value } 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]) Get(_ context.Context, _ string) (value T, ok bool) {
func (n NoCache[T, T2]) Add(_ context.Context, _ string, _ T2) {} 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 // Note that, the returned child FieldContext represents the context as it was
// before the execution of the field resolver. For example: // 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) // fc := graphql.GetFieldContext(ctx)
// op := graphql.GetOperationContext(ctx) // opCtx := graphql.GetOperationContext(ctx)
// collected := graphql.CollectFields(opCtx, fc.Field.Selections, []string{"User"}) // collected := graphql.CollectFields(opCtx, fc.Field.Selections, []string{"User"})
// //
// child, err := fc.Child(ctx, collected[0]) // child, err := fc.Child(ctx, collected[0])
// if err != nil { // if err != nil {
// return nil, err // 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) // return next(ctx)
// }) // })

View File

@@ -65,8 +65,8 @@ func GetOperationContext(ctx context.Context) *OperationContext {
panic("missing operation context") panic("missing operation context")
} }
func WithOperationContext(ctx context.Context, rc *OperationContext) context.Context { func WithOperationContext(ctx context.Context, opCtx *OperationContext) context.Context {
return context.WithValue(ctx, operationCtx, rc) return context.WithValue(ctx, operationCtx, opCtx)
} }
// HasOperationContext checks if the given context is part of an ongoing operation // 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 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 { func CollectFieldsCtx(ctx context.Context, satisfies []string) []CollectedField {
resctx := GetFieldContext(ctx) resctx := GetFieldContext(ctx)
return CollectFields(GetOperationContext(ctx), resctx.Field.Selections, satisfies) 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. // AddError sends an error to the client, first passing it through the error presenter.
func AddError(ctx context.Context, err error) { func AddError(ctx context.Context, err error) {
if err == nil {
return
}
c := getResponseContext(ctx) c := getResponseContext(ctx)
presentedError := c.errorPresenter(ctx, ErrorOnPath(ctx, err)) presentedError := c.errorPresenter(ctx, ErrorOnPath(ctx, err))

View File

@@ -10,6 +10,9 @@ import (
type ErrorPresenterFunc func(ctx context.Context, err error) *gqlerror.Error type ErrorPresenterFunc func(ctx context.Context, err error) *gqlerror.Error
func DefaultErrorPresenter(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 var gqlErr *gqlerror.Error
if errors.As(err, &gqlErr) { if errors.As(err, &gqlErr) {
return 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 // 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 // passed through satisfies. Providing an empty slice for satisfies will collect all fields regardless of fragment type conditions.
// type conditions.
func CollectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string) []CollectedField { func CollectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string) []CollectedField {
return collectFields(reqCtx, selSet, satisfies, map[string]bool{}) 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...) f.Selections = append(f.Selections, sel.SelectionSet...)
case *ast.InlineFragment: case *ast.InlineFragment:
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) { // To allow simplified "collect all" types behavior, pass an empty list
continue // of types that the type condition must satisfy: we will apply the
} // fragment regardless of type condition.
if len(satisfies) > 0 && !instanceOf(sel.TypeCondition, satisfies) { //
// 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 continue
} }
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
shouldDefer, label := deferrable(sel.Directives, reqCtx.Variables) shouldDefer, label := deferrable(sel.Directives, reqCtx.Variables)
for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) { for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) {
@@ -61,9 +69,6 @@ func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies
} }
case *ast.FragmentSpread: case *ast.FragmentSpread:
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
fragmentName := sel.Name fragmentName := sel.Name
if _, seen := visited[fragmentName]; seen { if _, seen := visited[fragmentName]; seen {
continue continue
@@ -80,6 +85,9 @@ func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies
continue continue
} }
if !shouldIncludeNode(sel.Directives, reqCtx.Variables) {
continue
}
shouldDefer, label := deferrable(sel.Directives, reqCtx.Variables) shouldDefer, label := deferrable(sel.Directives, reqCtx.Variables)
for _, childField := range collectFields(reqCtx, fragment.SelectionSet, satisfies, visited) { 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/gqlerror"
"github.com/vektah/gqlparser/v2/parser" "github.com/vektah/gqlparser/v2/parser"
"github.com/vektah/gqlparser/v2/validator" "github.com/vektah/gqlparser/v2/validator"
"github.com/vektah/gqlparser/v2/validator/rules"
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/errcode" "github.com/99designs/gqlgen/graphql/errcode"
@@ -24,7 +25,8 @@ type Executor struct {
recoverFunc graphql.RecoverFunc recoverFunc graphql.RecoverFunc
queryCache graphql.Cache[*ast.QueryDocument] queryCache graphql.Cache[*ast.QueryDocument]
parserTokenLimit int parserTokenLimit int
disableSuggestion bool
} }
var _ graphql.GraphExecutor = &Executor{} var _ graphql.GraphExecutor = &Executor{}
@@ -36,7 +38,7 @@ func New(es graphql.ExecutableSchema) *Executor {
es: es, es: es,
errorPresenter: graphql.DefaultErrorPresenter, errorPresenter: graphql.DefaultErrorPresenter,
recoverFunc: graphql.DefaultRecover, recoverFunc: graphql.DefaultRecover,
queryCache: graphql.NoCache[ast.QueryDocument, *ast.QueryDocument]{}, queryCache: graphql.NoCache[*ast.QueryDocument]{},
ext: processExtensions(nil), ext: processExtensions(nil),
parserTokenLimit: parserTokenNoLimit, parserTokenLimit: parserTokenNoLimit,
} }
@@ -47,7 +49,7 @@ func (e *Executor) CreateOperationContext(
ctx context.Context, ctx context.Context,
params *graphql.RawParams, params *graphql.RawParams,
) (*graphql.OperationContext, gqlerror.List) { ) (*graphql.OperationContext, gqlerror.List) {
rc := &graphql.OperationContext{ opCtx := &graphql.OperationContext{
DisableIntrospection: true, DisableIntrospection: true,
RecoverFunc: e.recoverFunc, RecoverFunc: e.recoverFunc,
ResolverMiddleware: e.ext.fieldMiddleware, ResolverMiddleware: e.ext.fieldMiddleware,
@@ -57,56 +59,56 @@ func (e *Executor) CreateOperationContext(
OperationStart: graphql.GetStartTime(ctx), OperationStart: graphql.GetStartTime(ctx),
}, },
} }
ctx = graphql.WithOperationContext(ctx, rc) ctx = graphql.WithOperationContext(ctx, opCtx)
for _, p := range e.ext.operationParameterMutators { for _, p := range e.ext.operationParameterMutators {
if err := p.MutateOperationParameters(ctx, params); err != nil { if err := p.MutateOperationParameters(ctx, params); err != nil {
return rc, gqlerror.List{err} return opCtx, gqlerror.List{err}
} }
} }
rc.RawQuery = params.Query opCtx.RawQuery = params.Query
rc.OperationName = params.OperationName opCtx.OperationName = params.OperationName
rc.Headers = params.Headers opCtx.Headers = params.Headers
var listErr gqlerror.List 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 { if len(listErr) != 0 {
return rc, listErr return opCtx, listErr
} }
rc.Operation = rc.Doc.Operations.ForName(params.OperationName) opCtx.Operation = opCtx.Doc.Operations.ForName(params.OperationName)
if rc.Operation == nil { if opCtx.Operation == nil {
err := gqlerror.Errorf("operation %s not found", params.OperationName) err := gqlerror.Errorf("operation %s not found", params.OperationName)
errcode.Set(err, errcode.ValidationFailed) errcode.Set(err, errcode.ValidationFailed)
return rc, gqlerror.List{err} return opCtx, gqlerror.List{err}
} }
var err error 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 { if err != nil {
gqlErr, ok := err.(*gqlerror.Error) gqlErr, ok := err.(*gqlerror.Error)
if ok { if ok {
errcode.Set(gqlErr, errcode.ValidationFailed) 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 { for _, p := range e.ext.operationContextMutators {
if err := p.MutateOperationContext(ctx, rc); err != nil { if err := p.MutateOperationContext(ctx, opCtx); err != nil {
return rc, gqlerror.List{err} return opCtx, gqlerror.List{err}
} }
} }
return rc, nil return opCtx, nil
} }
func (e *Executor) DispatchOperation( func (e *Executor) DispatchOperation(
ctx context.Context, ctx context.Context,
rc *graphql.OperationContext, opCtx *graphql.OperationContext,
) (graphql.ResponseHandler, context.Context) { ) (graphql.ResponseHandler, context.Context) {
ctx = graphql.WithOperationContext(ctx, rc) ctx = graphql.WithOperationContext(ctx, opCtx)
var innerCtx context.Context var innerCtx context.Context
res := e.ext.operationMiddleware(ctx, func(ctx context.Context) graphql.ResponseHandler { res := e.ext.operationMiddleware(ctx, func(ctx context.Context) graphql.ResponseHandler {
@@ -177,6 +179,10 @@ func (e *Executor) SetParserTokenLimit(limit int) {
e.parserTokenLimit = limit 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. // 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 // 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} 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) listErr := validator.Validate(e.es.Schema(), doc)
if len(listErr) != 0 { if len(listErr) != 0 {
for _, e := range listErr { for _, e := range listErr {

View File

@@ -34,7 +34,7 @@ type (
GraphExecutor interface { GraphExecutor interface {
CreateOperationContext(ctx context.Context, params *RawParams) (*OperationContext, gqlerror.List) 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 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 is called after creating the request context, but before executing the root resolver.
OperationContextMutator interface { 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, // 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" "encoding/hex"
"errors" "errors"
"github.com/mitchellh/mapstructure" "github.com/go-viper/mapstructure/v2"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
@@ -98,12 +98,12 @@ func (a AutomaticPersistedQuery) MutateOperationParameters(ctx context.Context,
} }
func GetApqStats(ctx context.Context) *ApqStats { func GetApqStats(ctx context.Context) *ApqStats {
rc := graphql.GetOperationContext(ctx) opCtx := graphql.GetOperationContext(ctx)
if rc == nil { if opCtx == nil {
return nil return nil
} }
s, _ := rc.Stats.GetExtension(apqExtension).(*ApqStats) s, _ := opCtx.Stats.GetExtension(apqExtension).(*ApqStats)
return s 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. // If a query is submitted that exceeds the limit, a 422 status code will be returned.
type ComplexityLimit struct { type ComplexityLimit struct {
Func func(ctx context.Context, rc *graphql.OperationContext) int Func func(ctx context.Context, opCtx *graphql.OperationContext) int
es graphql.ExecutableSchema es graphql.ExecutableSchema
} }
@@ -40,7 +40,7 @@ type ComplexityStats struct {
// FixedComplexityLimit sets a complexity limit that does not change // FixedComplexityLimit sets a complexity limit that does not change
func FixedComplexityLimit(limit int) *ComplexityLimit { func FixedComplexityLimit(limit int) *ComplexityLimit {
return &ComplexityLimit{ return &ComplexityLimit{
Func: func(ctx context.Context, rc *graphql.OperationContext) int { Func: func(ctx context.Context, opCtx *graphql.OperationContext) int {
return limit return limit
}, },
} }
@@ -58,13 +58,13 @@ func (c *ComplexityLimit) Validate(schema graphql.ExecutableSchema) error {
return nil return nil
} }
func (c ComplexityLimit) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error { func (c ComplexityLimit) MutateOperationContext(ctx context.Context, opCtx *graphql.OperationContext) *gqlerror.Error {
op := rc.Doc.Operations.ForName(rc.OperationName) op := opCtx.Doc.Operations.ForName(opCtx.OperationName)
complexityCalcs := complexity.Calculate(c.es, op, rc.Variables) 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, Complexity: complexityCalcs,
ComplexityLimit: limit, ComplexityLimit: limit,
}) })
@@ -79,11 +79,11 @@ func (c ComplexityLimit) MutateOperationContext(ctx context.Context, rc *graphql
} }
func GetComplexityStats(ctx context.Context) *ComplexityStats { func GetComplexityStats(ctx context.Context) *ComplexityStats {
rc := graphql.GetOperationContext(ctx) opCtx := graphql.GetOperationContext(ctx)
if rc == nil { if opCtx == nil {
return nil return nil
} }
s, _ := rc.Stats.GetExtension(complexityExtension).(*ComplexityStats) s, _ := opCtx.Stats.GetExtension(complexityExtension).(*ComplexityStats)
return s return s
} }

View File

@@ -24,7 +24,7 @@ func (c Introspection) Validate(schema graphql.ExecutableSchema) error {
return nil return nil
} }
func (c Introspection) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error { func (c Introspection) MutateOperationContext(ctx context.Context, opCtx *graphql.OperationContext) *gqlerror.Error {
rc.DisableIntrospection = false opCtx.DisableIntrospection = false
return nil 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 { func NewDefaultServer(es graphql.ExecutableSchema) *Server {
srv := New(es) srv := New(es)
@@ -72,6 +87,10 @@ func (s *Server) SetParserTokenLimit(limit int) {
s.exec.SetParserTokenLimit(limit) s.exec.SetParserTokenLimit(limit)
} }
func (s *Server) SetDisableSuggestion(value bool) {
s.exec.SetDisableSuggestion(value)
}
func (s *Server) Use(extension graphql.HandlerExtension) { func (s *Server) Use(extension graphql.HandlerExtension) {
s.exec.Use(extension) 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() raw.ReadTime.End = graphql.Now()
rc, gqlError := exec.CreateOperationContext(r.Context(), raw) opCtx, gqlError := exec.CreateOperationContext(r.Context(), raw)
if gqlError != nil { if gqlError != nil {
w.WriteHeader(statusFor(gqlError)) 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) writeJson(w, resp)
return return
} }
op := rc.Doc.Operations.ForName(rc.OperationName) op := opCtx.Doc.Operations.ForName(opCtx.OperationName)
if op.Operation != ast.Query { if op.Operation != ast.Query {
w.WriteHeader(http.StatusNotAcceptable) w.WriteHeader(http.StatusNotAcceptable)
writeJsonError(w, "GET requests only allow query operations") writeJsonError(w, "GET requests only allow query operations")
return return
} }
responses, ctx := exec.DispatchOperation(r.Context(), rc) responses, ctx := exec.DispatchOperation(r.Context(), opCtx)
writeJson(w, responses(ctx)) 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 package transport
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
"strings" "sync"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
@@ -46,32 +47,49 @@ func getRequestBody(r *http.Request) (string, error) {
return string(body), nil 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) { func (h POST) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) {
ctx := r.Context() ctx := r.Context()
writeHeaders(w, h.ResponseHeaders) writeHeaders(w, h.ResponseHeaders)
params := &graphql.RawParams{} params := pool.Get().(*graphql.RawParams)
start := graphql.Now() 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 params.Headers = r.Header
start := graphql.Now()
params.ReadTime = graphql.TraceTiming{ params.ReadTime = graphql.TraceTiming{
Start: start, Start: start,
End: graphql.Now(), End: graphql.Now(),
} }
bodyString, err := getRequestBody(r) bodyBytes, err := io.ReadAll(r.Body)
if err != nil { 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}) resp := exec.DispatchError(ctx, gqlerror.List{gqlErr})
writeJson(w, resp) writeJson(w, resp)
return return
} }
bodyReader := io.NopCloser(strings.NewReader(bodyString)) bodyReader := bytes.NewReader(bodyBytes)
if err = jsonDecode(bodyReader, &params); err != nil { if err := jsonDecode(bodyReader, &params); err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
gqlErr := gqlerror.Errorf( gqlErr := gqlerror.Errorf(
"json request body could not be decoded: %+v body:%s", "json request body could not be decoded: %+v body:%s",
err, err,
bodyString, string(bodyBytes),
) )
resp := exec.DispatchError(ctx, gqlerror.List{gqlErr}) resp := exec.DispatchError(ctx, gqlerror.List{gqlErr})
writeJson(w, resp) writeJson(w, resp)

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"math"
"strconv" "strconv"
) )
@@ -62,24 +63,62 @@ func MarshalInt32(i int32) Marshaler {
func UnmarshalInt32(v any) (int32, error) { func UnmarshalInt32(v any) (int32, error) {
switch v := v.(type) { switch v := v.(type) {
case string: case string:
iv, err := strconv.ParseInt(v, 10, 32) iv, err := strconv.ParseInt(v, 10, 64)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return int32(iv), nil return safeCastInt32(iv)
case int: case int:
return int32(v), nil return safeCastInt32(int64(v))
case int64: case int64:
return int32(v), nil return safeCastInt32(v)
case json.Number: case json.Number:
iv, err := strconv.ParseInt(string(v), 10, 32) iv, err := strconv.ParseInt(string(v), 10, 64)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return int32(iv), nil return safeCastInt32(iv)
case nil: case nil:
return 0, nil return 0, nil
default: default:
return 0, fmt.Errorf("%T is not an int", v) 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" "io"
) )
func MarshalMap(val map[string]interface{}) Marshaler { func MarshalMap(val map[string]any) Marshaler {
return WriterFunc(func(w io.Writer) { return WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(val) err := json.NewEncoder(w).Encode(val)
if err != nil { if err != nil {
@@ -15,8 +15,8 @@ func MarshalMap(val map[string]interface{}) Marshaler {
}) })
} }
func UnmarshalMap(v interface{}) (map[string]interface{}, error) { func UnmarshalMap(v any) (map[string]any, error) {
if m, ok := v.(map[string]interface{}); ok { if m, ok := v.(map[string]any); ok {
return m, nil return m, nil
} }

View File

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

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"strconv" "strconv"
) )
@@ -18,21 +19,33 @@ func UnmarshalUint(v any) (uint, error) {
switch v := v.(type) { switch v := v.(type) {
case string: case string:
u64, err := strconv.ParseUint(v, 10, 64) 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 return uint(u64), err
case int: case int:
if v < 0 { 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 return uint(v), nil
case int64: case int64:
if v < 0 { if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint") return 0, newUintSignError(strconv.FormatInt(v, 10))
} }
return uint(v), nil return uint(v), nil
case json.Number: case json.Number:
u64, err := strconv.ParseUint(string(v), 10, 64) 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 return uint(u64), err
case nil: case nil:
return 0, nil return 0, nil
@@ -50,21 +63,35 @@ func MarshalUint64(i uint64) Marshaler {
func UnmarshalUint64(v any) (uint64, error) { func UnmarshalUint64(v any) (uint64, error) {
switch v := v.(type) { switch v := v.(type) {
case string: 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: case int:
if v < 0 { 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 return uint64(v), nil
case int64: case int64:
if v < 0 { if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint64") return 0, newUintSignError(strconv.FormatInt(v, 10))
} }
return uint64(v), nil return uint64(v), nil
case json.Number: 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: case nil:
return 0, nil return 0, nil
default: default:
@@ -81,32 +108,92 @@ func MarshalUint32(i uint32) Marshaler {
func UnmarshalUint32(v any) (uint32, error) { func UnmarshalUint32(v any) (uint32, error) {
switch v := v.(type) { switch v := v.(type) {
case string: case string:
iv, err := strconv.ParseUint(v, 10, 32) iv, err := strconv.ParseUint(v, 10, 64)
if err != nil { if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(v) {
return 0, newUintSignError(v)
}
return 0, err return 0, err
} }
return uint32(iv), nil return safeCastUint32(iv)
case int: case int:
if v < 0 { if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint32") return 0, newUintSignError(strconv.FormatInt(int64(v), 10))
} }
return safeCastUint32(uint64(v))
return uint32(v), nil
case int64: case int64:
if v < 0 { if v < 0 {
return 0, errors.New("cannot convert negative numbers to uint32") return 0, newUintSignError(strconv.FormatInt(v, 10))
} }
return safeCastUint32(uint64(v))
return uint32(v), nil
case json.Number: case json.Number:
iv, err := strconv.ParseUint(string(v), 10, 32) iv, err := strconv.ParseUint(string(v), 10, 64)
if err != nil { if err != nil {
var strconvErr *strconv.NumError
if errors.As(err, &strconvErr) && isSignedInteger(string(v)) {
return 0, newUintSignError(string(v))
}
return 0, err return 0, err
} }
return uint32(iv), nil return safeCastUint32(iv)
case nil: case nil:
return 0, nil return 0, nil
default: default:
return 0, fmt.Errorf("%T is not an uint", v) 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 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? # Where should the generated server code go?
exec: exec:
filename: graph/generated.go
package: graph 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 # Uncomment to enable federation
# federation: # federation:
# filename: graph/federation.go # filename: graph/federation.go
# package: graph # package: graph
# version: 2 # version: 2
# options # options:
# computed_requires: true # computed_requires: true
# Where should any generated models go? # Where should any generated models go?
@@ -20,14 +30,27 @@ model:
filename: graph/model/models_gen.go filename: graph/model/models_gen.go
package: model 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? # Where should the resolver implementations go?
resolver: resolver:
layout: follow-schema
dir: graph
package: 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" filename_template: "{name}.resolvers.go"
# Optional: turn on to not generate template comments above resolvers # Optional: turn on to not generate template comments above resolvers
# omit_template_comment: false # 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 # Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
# struct_tag: json # struct_tag: json
@@ -36,7 +59,7 @@ resolver:
# omit_slice_element_pointers: false # omit_slice_element_pointers: false
# Optional: turn on to omit Is<Name>() methods to interface and unions # 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 # Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function
# omit_complexity: false # 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. # 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 # 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 # Optional: turn off to make struct-type struct fields not use pointers
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing } # e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
# struct_fields_always_pointers: true # struct_fields_always_pointers: true
@@ -73,6 +102,18 @@ resolver:
# argument values but to set them even if they're null. # argument values but to set them even if they're null.
call_argument_directives_with_null: true 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 # 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. # if they match it will use them, otherwise it will generate them.
autobind: autobind:
@@ -90,8 +131,25 @@ models:
- github.com/99designs/gqlgen/graphql.Int - github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64 - github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32 - 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: Int:
model:
- github.com/99designs/gqlgen/graphql.Int32
Int64:
model: model:
- github.com/99designs/gqlgen/graphql.Int - github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64 - github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32

View File

@@ -1,5 +1,3 @@
//go:build !go1.23
package code package code
import ( import (
@@ -7,7 +5,13 @@ import (
) )
// Unalias unwraps an alias type // Unalias unwraps an alias type
// TODO: Drop this function when we drop support for go1.22
func Unalias(t types.Type) types.Type { 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"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime/debug"
"strings" "strings"
"sync"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
) )
var (
once = sync.Once{}
modInfo *debug.BuildInfo
)
var mode = packages.NeedName | var mode = packages.NeedName |
packages.NeedFiles | packages.NeedFiles |
packages.NeedTypes | packages.NeedTypes |
@@ -30,10 +23,11 @@ type (
// Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages // 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. // that can be invalidated as writes are made and packages are known to change.
Packages struct { Packages struct {
packages map[string]*packages.Package packages map[string]*packages.Package
importToName map[string]string importToName map[string]string
loadErrors []error loadErrors []error
buildFlags []string buildFlags []string
packagesToCachePrefix string
numLoadCalls int // stupid test steam. ignore. numLoadCalls int // stupid test steam. ignore.
numNameCalls int // stupid test steam. ignore. numNameCalls int // stupid test steam. ignore.
@@ -42,13 +36,21 @@ type (
Option func(p *Packages) 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) { func WithBuildTags(tags ...string) func(p *Packages) {
return func(p *Packages) { return func(p *Packages) {
p.buildFlags = append(p.buildFlags, "-tags", strings.Join(tags, ",")) 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 // 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 // It will load all packages in the current module, and any packages that are passed to Load or LoadAll
func NewPackages(opts ...Option) *Packages { func NewPackages(opts ...Option) *Packages {
@@ -60,27 +62,23 @@ func NewPackages(opts ...Option) *Packages {
} }
func (p *Packages) CleanupUserPackages() { func (p *Packages) CleanupUserPackages() {
once.Do(func() { if p.packagesToCachePrefix == "" {
var ok bool // Cleanup all packages if we don't know which ones to keep
modInfo, ok = debug.ReadBuildInfo() p.packages = nil
if !ok { } else {
modInfo = nil // 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
// Don't cleanup github.com/99designs/gqlgen prefixed packages, // the prefix using PackagePrefixToCache
// they haven't changed and do not need to be reloaded
if modInfo != nil {
var toRemove []string var toRemove []string
for k := range p.packages { for k := range p.packages {
if !strings.HasPrefix(k, modInfo.Main.Path) { if !strings.HasPrefix(k, p.packagesToCachePrefix) {
toRemove = append(toRemove, k) toRemove = append(toRemove, k)
} }
} }
for _, k := range toRemove { for _, k := range toRemove {
delete(p.packages, k) 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. // 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 { func (p *Packages) Errors() PkgErrors {
var res []error //nolint:prealloc res := append([]error{}, p.loadErrors...)
res = append(res, p.loadErrors...)
for _, pkg := range p.packages { for _, pkg := range p.packages {
for _, err := range pkg.Errors { for _, err := range pkg.Errors {
res = append(res, err) res = append(res, err)

View File

@@ -1,8 +1,9 @@
package federation package federation
import ( import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/vektah/gqlparser/v2/ast" "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. // 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(), ".") typeString := strings.Split(obj.Type.String(), ".")
requiresImports[strings.Join(typeString[:len(typeString)-1], ".")] = true requiresImports[strings.Join(typeString[:len(typeString)-1], ".")] = true
if containsUnionField(reqField) {
continue
}
cgField := reqField.Field.TypeReference(obj, data.Objects) cgField := reqField.Field.TypeReference(obj, data.Objects)
reqField.Type = cgField.TypeReference reqField.Type = cgField.TypeReference
} }
// add type info to entity // add type info to entity
e.Type = obj.Type e.Type = obj.Type
} }
@@ -321,14 +324,24 @@ func (f *Federation) GenerateCode(data *codegen.Data) error {
Filename: data.Config.Federation.Filename, Filename: data.Config.Federation.Filename,
Data: struct { Data: struct {
Federation Federation
UsePointers bool UsePointers bool
}{*f, data.Config.ResolversAlwaysReturnPointers}, UseFunctionSyntaxForExecutionContext bool
}{*f, data.Config.ResolversAlwaysReturnPointers, data.Config.UseFunctionSyntaxForExecutionContext},
GeneratedHeader: true, GeneratedHeader: true,
Packages: data.Config.Packages, Packages: data.Config.Packages,
Template: federationTemplate, 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 // Fill in types for key fields
func populateKeyFieldTypes( func populateKeyFieldTypes(
resolver *EntityResolver, resolver *EntityResolver,

View File

@@ -8,6 +8,8 @@
{{ $options := .PackageOptions }} {{ $options := .PackageOptions }}
{{ $usePointers := .UsePointers }} {{ $usePointers := .UsePointers }}
{{ $useFunctionSyntaxForExecutionContext := .UseFunctionSyntaxForExecutionContext }}
var ( var (
ErrUnknownType = errors.New("unknown type") ErrUnknownType = errors.New("unknown type")
ErrTypeNotFound = errors.New("type not found") ErrTypeNotFound = errors.New("type not found")
@@ -33,7 +35,7 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.
} }
{{if .Entities}} {{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)) list := make([]fedruntime.Entity, len(representations))
repsMap := ec.buildRepresentationGroups(ctx, representations) repsMap := ec.buildRepresentationGroups(ctx, representations)
@@ -169,7 +171,11 @@ func (ec *executionContext) resolveEntity(
{{ range $i, $resolver := .Resolvers }} {{ range $i, $resolver := .Resolvers }}
case "{{.ResolverName}}": case "{{.ResolverName}}":
{{- range $j, $keyField := .KeyFields }} {{- 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 { if err != nil {
return nil, fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err) return nil, fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err)
} }
@@ -187,7 +193,11 @@ func (ec *executionContext) resolveEntity(
} }
{{- else }} {{- else }}
{{ range $entity.Requires }} {{ 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 { if err != nil {
return nil, err return nil, err
} }
@@ -231,7 +241,11 @@ func (ec *executionContext) resolveManyEntities(
for i, rep := range reps { for i, rep := range reps {
{{ range $i, $keyField := .KeyFields -}} {{ 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 { if err != nil {
return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}")) 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 { for i, entity := range entities {
{{- range $entity.Requires }} {{- 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 { if err != nil {
return err return err
} }
@@ -276,11 +294,14 @@ func (ec *executionContext) resolveManyEntities(
{{- if .Resolvers }} {{- if .Resolvers }}
func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep EntityRepresentation) (string, error) { 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 }} {{- range .Resolvers }}
for { for {
var ( var (
m EntityRepresentation m EntityRepresentation
val interface{} val any
ok bool ok bool
) )
_ = val _ = val
@@ -292,10 +313,15 @@ func (ec *executionContext) resolveManyEntities(
{{- range $i, $field := .Field }} {{- range $i, $field := .Field }}
val, ok = m["{{.}}"] val, ok = m["{{.}}"]
if !ok { if !ok {
entityResolverErrs = append(entityResolverErrs,
fmt.Errorf("%w due to missing Key Field \"{{.}}\" for {{$entity.Name}}", ErrTypeNotFound))
break break
} }
{{- if (ne $i $keyField.Field.LastIndex ) }} {{- 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 break
} }
{{- else}} {{- else}}
@@ -306,12 +332,15 @@ func (ec *executionContext) resolveManyEntities(
{{- end}} {{- end}}
{{- end }} {{- end }}
if allNull { if allNull {
entityResolverErrs = append(entityResolverErrs,
fmt.Errorf("%w due to all null value KeyFields for {{$entity.Name}}", ErrTypeNotFound))
break break
} }
return "{{.ResolverName}}", nil return "{{.ResolverName}}", nil
} }
{{- end }} {{- 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 }}
{{- end }} {{- end }}

View File

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

View File

@@ -12,7 +12,7 @@
{{- else -}} {{- else -}}
// {{.FuncName}} is the requires populator for the {{.Entity.Def.Name}} entity. // {{.FuncName}} is the requires populator for the {{.Entity.Def.Name}} entity.
{{- end }} {{- 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}} {{.Implementation}}
} }
{{ end }} {{ end }}

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -45,6 +44,7 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error {
case config.LayoutSingleFile: case config.LayoutSingleFile:
return m.generateSingleFile(data) return m.generateSingleFile(data)
case config.LayoutFollowSchema: case config.LayoutFollowSchema:
return m.generatePerSchema(data) return m.generatePerSchema(data)
} }
@@ -53,6 +53,14 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error {
func (m *Plugin) generateSingleFile(data *codegen.Data) error { func (m *Plugin) generateSingleFile(data *codegen.Data) error {
file := File{} 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()) rewriter, err := rewrite.New(data.Config.Resolver.Dir())
if err != nil { if err != nil {
return err 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.name = data.Config.Resolver.Filename
file.imports = rewriter.ExistingImports(file.name) file.imports = rewriter.ExistingImports(file.name)
file.RemainingSource = rewriter.RemainingSource(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) 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{ return templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package, 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, Filename: data.Config.Resolver.Filename,
Data: resolverBuild, Data: resolverBuild,
Packages: data.Config.Packages, Packages: data.Config.Packages,
@@ -146,6 +159,7 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error {
continue continue
} }
structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type) 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), `\`)) comment := strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment(structName, f.GoFieldName), `\`))
implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName)) implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName))
resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation, nil} 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 { 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{ resolverBuild := &ResolverBuild{
File: file, File: file,
PackageName: data.Config.Resolver.Package, 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{ err := templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package, PackageName: data.Config.Resolver.Package,
FileNotice: ` FileNotice: `
@@ -304,3 +323,10 @@ func readResolverTemplate(customResolverTemplate string) string {
} }
return string(contentBytes) 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 -}} {{ range $resolver := .Resolvers -}}
{{ if $resolver.Comment -}} {{ if $resolver.Comment -}}
// {{ $resolver.Comment }} {{with $resolver.Comment}}{{.|prefixLines "// "}}{{end}}
{{- else if not $.OmitTemplateComment -}} {{- else if not $.OmitTemplateComment -}}
// {{ $resolver.Field.GoFieldName }} is the resolver for the {{ $resolver.Field.Name }} field. // {{ $resolver.Field.GoFieldName }} is the resolver for the {{ $resolver.Field.Name }} field.
{{- end }} {{- end }}

View File

@@ -2,8 +2,12 @@
{{ reserveImport "log" }} {{ reserveImport "log" }}
{{ reserveImport "net/http" }} {{ reserveImport "net/http" }}
{{ reserveImport "os" }} {{ reserveImport "os" }}
{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/playground" }} {{ reserveImport "github.com/99designs/gqlgen/graphql/playground" }}
{{ reserveImport "github.com/99designs/gqlgen/graphql/handler" }} {{ 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" const defaultPort = "8080"
@@ -13,7 +17,18 @@ func main() {
port = defaultPort 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("/", playground.Handler("GraphQL playground", "/query"))
http.Handle("/query", srv) http.Handle("/query", srv)

View File

@@ -39,9 +39,11 @@ var (
) )
// semVerRegex is the regular expression used to parse a semantic version. // semVerRegex is the regular expression used to parse a semantic version.
const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + // This is not the official regex from the semver spec. It has been modified to allow for loose handling
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + // where versions like 2.1 are detected.
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` 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. // Version represents a single semantic version.
type Version struct { type Version struct {
@@ -146,8 +148,8 @@ func NewVersion(v string) (*Version, error) {
} }
sv := &Version{ sv := &Version{
metadata: m[8], metadata: m[5],
pre: m[5], pre: m[4],
original: v, original: v,
} }
@@ -158,7 +160,7 @@ func NewVersion(v string) (*Version, error) {
} }
if m[2] != "" { 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 { if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err) return nil, fmt.Errorf("Error parsing version segment: %s", err)
} }
@@ -167,7 +169,7 @@ func NewVersion(v string) (*Version, error) {
} }
if m[3] != "" { 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 { if err != nil {
return nil, fmt.Errorf("Error parsing version segment: %s", err) 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 { func validatePrerelease(p string) error {
eparts := strings.Split(p, ".") eparts := strings.Split(p, ".")
for _, p := range eparts { for _, p := range eparts {
if containsOnly(p, num) { if p == "" {
return ErrInvalidMetadata
} else if containsOnly(p, num) {
if len(p) > 1 && p[0] == '0' { if len(p) > 1 && p[0] == '0' {
return ErrSegmentStartsZero return ErrSegmentStartsZero
} }
@@ -631,7 +635,9 @@ func validatePrerelease(p string) error {
func validateMetadata(m string) error { func validateMetadata(m string) error {
eparts := strings.Split(m, ".") eparts := strings.Split(m, ".")
for _, p := range eparts { for _, p := range eparts {
if !containsOnly(p, allowed) { if p == "" {
return ErrInvalidMetadata
} else if !containsOnly(p, allowed) {
return ErrInvalidMetadata return ErrInvalidMetadata
} }
} }

View File

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

View File

@@ -19,7 +19,7 @@ func PrevTickBefore(expr string, start time.Time, inclRefTime bool) (time.Time,
} }
segments, _ := Segments(expr) 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]) 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 - Exponential backoff with carefully-tuned intervals
- Retries with optional test/staging CA endpoint instead of production, to avoid rate limits - Retries with optional test/staging CA endpoint instead of production, to avoid rate limits
- Written in Go, a language with memory-safety guarantees - 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 - All [libdns](https://github.com/libdns) DNS providers work out-of-the-box
- Pluggable storage backends (default: file system) - Pluggable storage backends (default: file system)
- Pluggable key sources - Pluggable key sources
@@ -567,7 +567,7 @@ We welcome your contributions! Please see our **[contributing guidelines](https:
## Project History ## 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. 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. 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" "strings"
"sync" "sync"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
) )

View File

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

View File

@@ -28,8 +28,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/mholt/acmez/v2" "github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -69,6 +69,15 @@ type ACMEIssuer struct {
// with this ACME account // with this ACME account
ExternalAccount *acme.EAB 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 // Optionally specify the validity period of
// the certificate(s) here as offsets from the // the certificate(s) here as offsets from the
// approximate time of certificate issuance, // approximate time of certificate issuance,
@@ -282,7 +291,7 @@ func NewACMEIssuer(cfg *Config, template ACMEIssuer) *ACMEIssuer {
} }
// IssuerKey returns the unique issuer key for the // IssuerKey returns the unique issuer key for the
// confgured CA endpoint. // configured CA endpoint.
func (am *ACMEIssuer) IssuerKey() string { func (am *ACMEIssuer) IssuerKey() string {
return am.issuerKey(am.CA) return am.issuerKey(am.CA)
} }
@@ -450,6 +459,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
if am.NotAfter != 0 { if am.NotAfter != 0 {
params.NotAfter = time.Now().Add(am.NotAfter) 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), // Notify the ACME server we are replacing a certificate (if the caller says we are),
// only if the following conditions are met: // 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.String("account_id", client.account.Location),
zap.Strings("account_contact", client.account.Contact), zap.Strings("account_contact", client.account.Contact),
zap.String("key_location", am.storageKeyUserPrivateKey(client.acmeClient.Directory, am.getEmail())), 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; // 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 // we could use the same key pair, but this is a good opportunity to rotate keys

View File

@@ -26,7 +26,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/crypto/ocsp" "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 // 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. // 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 { 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) expiration := expiresAt(leaf)
var logger *zap.Logger 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 // (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 // 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 // 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) // be useful)
selectedTime := ari.SelectedTime 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 // possibility of a bug in ARI compromising a site's uptime: we should always always
// always give heed to actual validity period // always give heed to actual validity period
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) { 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.Duration("remaining", time.Until(expiration)),
zap.Time("renewal_cutoff", cutoff)) zap.Time("renewal_cutoff", cutoff))
return true return true
@@ -188,6 +196,14 @@ func (cert Certificate) Expired() bool {
return time.Now().After(expiresAt(cert.Leaf)) 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 // currentlyInRenewalWindow returns true if the current time is within
// (or after) the renewal window, according to the given start/end // (or after) the renewal window, according to the given start/end
// dates and the ratio of the renewal window. If true is returned, // 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 // 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 // and TLS-ALPN challenges yourself (unless you disabled them or use the
// DNS challenge) by using the provided Config.GetCertificate function // 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. // handler.
// //
// See the package's README for more instruction. // 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 // containing the names passed into those functions if
// no DecisionFunc is set. This ensures some degree of // no DecisionFunc is set. This ensures some degree of
// control by default to avoid certificate operations for // 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, // manually specify a DecisionFunc. To impose rate limits,
// specify your own DecisionFunc. // specify your own DecisionFunc.
type OnDemandConfig struct { type OnDemandConfig struct {

View File

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

View File

@@ -25,7 +25,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/mholt/acmez/v2" "github.com/mholt/acmez/v3"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/crypto/ocsp" "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 returns an expired certificate that the client supports,
// otherwise it just returns the first certificate in the list of choices. // otherwise it just returns the first certificate in the list of choices.
func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificate) (Certificate, error) { 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 { if len(choices) == 0 {
return Certificate{}, fmt.Errorf("no certificates available") return Certificate{}, fmt.Errorf("no certificates available")
} }
// Slow path: There are choices, so we need to check each of them.
now := time.Now() now := time.Now()
best := choices[0] best := choices[0]
for _, choice := range choices { 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. // This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) { 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 // Check OCSP staple validity
if cert.ocsp != nil && !freshOCSP(cert.ocsp) { if cert.ocsp != nil && !freshOCSP(cert.ocsp) {
logger.Debug("OCSP response needs refreshing", logger.Debug("OCSP response needs refreshing",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status), zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate), zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate)) zap.Time("next_update", cert.ocsp.NextUpdate))
@@ -563,13 +589,9 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
if err != nil { if err != nil {
// An error with OCSP stapling is not the end of the world, and in fact, is // 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. // quite common considering not all certs have issuer URLs that support it.
logger.Warn("stapling OCSP", logger.Warn("stapling OCSP", zap.Error(err))
zap.String("server_name", hello.ServerName),
zap.Strings("sans", cert.Names),
zap.Error(err))
} else { } else {
logger.Debug("successfully stapled new OCSP response", logger.Debug("successfully stapled new OCSP response",
zap.Strings("identifiers", cert.Names),
zap.Int("ocsp_status", cert.ocsp.Status), zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate), zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate)) 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() cfg.certCache.mu.Unlock()
} }
// Check ARI status // 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() { if !cfg.DisableARI && cert.ari.NeedsRefresh() && time.Now().Before(cert.Leaf.NotAfter) {
// we ignore the second return value here because we go on to check renewal status below regardless // update ARI in a goroutine to avoid blocking an active handshake, since the results of
var err error // this do not strictly affect the handshake; even though the cert may be updated with
cert, _, err = cfg.updateARI(ctx, cert, logger) // the new ARI, it is also updated in the cache and in storage, so future handshakes
if err != nil { // will utilize it
logger.Error("updated ARI", zap.Error(err)) 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. // We attempt to replace any certificates that were revoked.
// Crucially, this happens OUTSIDE a lock on the certCache. // Crucially, this happens OUTSIDE a lock on the certCache.
if certShouldBeForceRenewed(cert) { if certShouldBeForceRenewed(cert) {
logger.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew", 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.Int("ocsp_status", cert.ocsp.Status),
zap.Time("revoked_at", cert.ocsp.RevokedAt), zap.Time("revoked_at", cert.ocsp.RevokedAt),
zap.Time("this_update", cert.ocsp.ThisUpdate), 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) return cfg.renewDynamicCertificate(ctx, hello, cert)
} }
// Check cert expiration // Since renewal conditions may have changed, do a renewal if necessary
if cfg.certNeedsRenewal(cert.Leaf, cert.ari, true) { return renewIfNecessary(ctx, hello, cert)
// 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
} }
// renewDynamicCertificate renews the certificate for name using cfg. It returns the // 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 // 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, // 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. // 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. // allow a certificate. Instead, the ClientHello can be useful for logging, etc.
const ClientHelloInfoCtxKey helloInfoCtxKey = "certmagic:ClientHelloInfo" const ClientHelloInfoCtxKey helloInfoCtxKey = "certmagic:ClientHelloInfo"

View File

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

View File

@@ -27,7 +27,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/crypto/ocsp" "golang.org/x/crypto/ocsp"
) )
@@ -507,7 +507,19 @@ func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.
if err == nil && gotNewARI { if err == nil && gotNewARI {
// great, storage has a newer one we can use // great, storage has a newer one we can use
cfg.certCache.mu.Lock() 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 updatedCert.ari = newARI
cfg.certCache.cache[cert.hash] = updatedCert cfg.certCache.cache[cert.hash] = updatedCert
cfg.certCache.mu.Unlock() 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 // of the issuers configured, hopefully one of them is the ACME CA we got the cert from
for _, iss := range cfg.Issuers { 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 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 { if err != nil {
// could be anything, but a common error might simply be the "wrong" ACME CA // 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 // be sure we get the cert from the cache while inside a lock to avoid logical races
cfg.certCache.mu.Lock() 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 updatedCert.ari = newARI
cfg.certCache.cache[cert.hash] = updatedCert cfg.certCache.cache[cert.hash] = updatedCert
cfg.certCache.mu.Unlock() cfg.certCache.mu.Unlock()
@@ -581,7 +607,7 @@ func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.
return return
} }
logger.Info("updated ACME renewal information", logger.Info("updated and stored ACME renewal information",
zap.Time("selected_time", newARI.SelectedTime), zap.Time("selected_time", newARI.SelectedTime),
zap.Timep("next_update", newARI.RetryAfter), zap.Timep("next_update", newARI.RetryAfter),
zap.String("explanation_url", newARI.ExplanationURL)) 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 // then we need to request it from the OCSP responder
if ocspResp == nil || len(ocspBytes) == 0 { if ocspResp == nil || len(ocspBytes) == 0 {
ocspBytes, ocspResp, ocspErr = getOCSPForCert(ocspConfig, pemBundle) 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 { if ocspErr != nil {
// An error here is not a problem because a certificate may simply // For short-lived certificates, this is fine and we can ignore
// not contain a link to an OCSP server. But we should log it anyway. // 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, // 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) return fmt.Errorf("no OCSP stapling for %v: %w", cert.Names, ocspErr)
} }
gotNewOCSP = true gotNewOCSP = true

View File

@@ -30,8 +30,8 @@ import (
"time" "time"
"github.com/libdns/libdns" "github.com/libdns/libdns"
"github.com/mholt/acmez/v2" "github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"github.com/miekg/dns" "github.com/miekg/dns"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -547,14 +547,15 @@ func (s *DNSManager) getDNSPresentMemory(dnsName, recType, value string) (dnsPre
defer s.recordsMu.Unlock() defer s.recordsMu.Unlock()
var memory dnsPresentMemory var memory dnsPresentMemory
var found bool
for _, mem := range s.records[dnsName] { for _, mem := range s.records[dnsName] {
if mem.zoneRec.record.Type == recType && mem.zoneRec.record.Value == value { if mem.zoneRec.record.Type == recType && mem.zoneRec.record.Value == value {
memory = mem memory = mem
found = true
break break
} }
} }
if !found {
if memory.zoneRec.record.Name == "" {
return dnsPresentMemory{}, fmt.Errorf("no memory of presenting a DNS record for %q (usually OK if presenting also failed)", dnsName) 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" "time"
"github.com/caddyserver/zerossl" "github.com/caddyserver/zerossl"
"github.com/mholt/acmez/v2" "github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v2/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -62,6 +62,9 @@ type ZeroSSLIssuer struct {
// validation, set this field. // validation, set this field.
CNAMEValidation *DNSManager CNAMEValidation *DNSManager
// Delay between poll attempts.
PollInterval time.Duration
// An optional (but highly recommended) logger. // An optional (but highly recommended) logger.
Logger *zap.Logger Logger *zap.Logger
} }
@@ -203,8 +206,8 @@ func (iss *ZeroSSLIssuer) Issue(ctx context.Context, csr *x509.CertificateReques
}, nil }, nil
} }
func (*ZeroSSLIssuer) waitForCertToBeIssued(ctx context.Context, client zerossl.Client, cert zerossl.CertificateObject) (zerossl.CertificateObject, error) { func (iss *ZeroSSLIssuer) waitForCertToBeIssued(ctx context.Context, client zerossl.Client, cert zerossl.CertificateObject) (zerossl.CertificateObject, error) {
ticker := time.NewTicker(5 * time.Second) ticker := time.NewTicker(iss.pollInterval())
defer ticker.Stop() defer ticker.Stop()
for { 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 { func (iss *ZeroSSLIssuer) getClient() zerossl.Client {
return zerossl.Client{AccessKey: iss.APIKey} return zerossl.Client{AccessKey: iss.APIKey}
} }
@@ -299,9 +309,8 @@ func (iss *ZeroSSLIssuer) getDistributedValidationInfo(ctx context.Context, iden
} }
const ( const (
zerosslAPIBase = "https://" + zerossl.BaseURL + "/acme" zerosslIssuerKey = "zerossl"
zerosslValidationPathPrefix = "/.well-known/pki-validation/" defaultPollInterval = 5 * time.Second
zerosslIssuerKey = "zerossl"
) )
// Interface guards // 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 package md2man
import ( import (
"os"
"strconv"
"github.com/russross/blackfriday/v2" "github.com/russross/blackfriday/v2"
) )
// Render converts a markdown document into a roff formatted document. // Render converts a markdown document into a roff formatted document.
func Render(doc []byte) []byte { func Render(doc []byte) []byte {
renderer := NewRoffRenderer() renderer := NewRoffRenderer()
var r blackfriday.Renderer = renderer
if v, _ := strconv.ParseBool(os.Getenv("MD2MAN_DEBUG")); v {
r = &debugDecorator{Renderer: r}
}
return blackfriday.Run(doc, return blackfriday.Run(doc,
[]blackfriday.Option{ []blackfriday.Option{
blackfriday.WithRenderer(renderer), blackfriday.WithRenderer(r),
blackfriday.WithExtensions(renderer.GetExtensions()), blackfriday.WithExtensions(renderer.GetExtensions()),
}...) }...)
} }

View File

@@ -14,10 +14,8 @@ import (
// roffRenderer implements the blackfriday.Renderer interface for creating // roffRenderer implements the blackfriday.Renderer interface for creating
// roff format (manpages) from markdown text // roff format (manpages) from markdown text
type roffRenderer struct { type roffRenderer struct {
extensions blackfriday.Extensions
listCounters []int listCounters []int
firstHeader bool firstHeader bool
firstDD bool
listDepth int listDepth int
} }
@@ -43,7 +41,7 @@ const (
quoteTag = "\n.PP\n.RS\n" quoteTag = "\n.PP\n.RS\n"
quoteCloseTag = "\n.RE\n" quoteCloseTag = "\n.RE\n"
listTag = "\n.RS\n" listTag = "\n.RS\n"
listCloseTag = "\n.RE\n" listCloseTag = ".RE\n"
dtTag = "\n.TP\n" dtTag = "\n.TP\n"
dd2Tag = "\n" dd2Tag = "\n"
tableStart = "\n.TS\nallbox;\n" tableStart = "\n.TS\nallbox;\n"
@@ -56,23 +54,18 @@ const (
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents // NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown // from markdown
func NewRoffRenderer() *roffRenderer { // nolint: golint func NewRoffRenderer() *roffRenderer { // nolint: golint
var extensions blackfriday.Extensions return &roffRenderer{}
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,
}
} }
// GetExtensions returns the list of extensions used by this renderer implementation // GetExtensions returns the list of extensions used by this renderer implementation
func (r *roffRenderer) GetExtensions() blackfriday.Extensions { func (*roffRenderer) GetExtensions() blackfriday.Extensions {
return r.extensions return blackfriday.NoIntraEmphasis |
blackfriday.Tables |
blackfriday.FencedCode |
blackfriday.SpaceHeadings |
blackfriday.Footnotes |
blackfriday.Titleblock |
blackfriday.DefinitionLists
} }
// RenderHeader handles outputting the header at document start // 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 { switch node.Type {
case blackfriday.Text: 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: case blackfriday.Softbreak:
out(w, crTag) out(w, crTag)
case blackfriday.Hardbreak: case blackfriday.Hardbreak:
@@ -141,14 +150,25 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
case blackfriday.Document: case blackfriday.Document:
break break
case blackfriday.Paragraph: case blackfriday.Paragraph:
// roff .PP markers break lists
if r.listDepth > 0 {
return blackfriday.GoToNext
}
if entering { 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 { } else {
out(w, crTag) if node.Next == nil || node.Next.Type != blackfriday.List {
out(w, crTag)
}
} }
case blackfriday.BlockQuote: case blackfriday.BlockQuote:
if entering { 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) { func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
openTag := listTag openTag := listTag
closeTag := listCloseTag closeTag := listCloseTag
if (entering && r.listDepth == 0) || (!entering && r.listDepth == 1) {
openTag = crTag
closeTag = ""
}
if node.ListFlags&blackfriday.ListTypeDefinition != 0 { if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// tags for definition lists handled within Item node // tags for definition lists handled within Item node
openTag = "" openTag = ""
@@ -239,23 +263,25 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 { } else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
// DT (definition term): line just before DD (see below). // DT (definition term): line just before DD (see below).
out(w, dtTag) out(w, dtTag)
r.firstDD = true
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// DD (definition description): line that starts with ": ". // DD (definition description): line that starts with ": ".
// //
// We have to distinguish between the first DD and the // We have to distinguish between the first DD and the
// subsequent ones, as there should be no vertical // subsequent ones, as there should be no vertical
// whitespace between the DT and the first DD. // whitespace between the DT and the first DD.
if r.firstDD { if node.Prev != nil && node.Prev.ListFlags&(blackfriday.ListTypeTerm|blackfriday.ListTypeDefinition) == blackfriday.ListTypeDefinition {
r.firstDD = false if node.Prev.Type == blackfriday.Item &&
} else { node.Prev.LastChild != nil &&
out(w, dd2Tag) node.Prev.LastChild.Type == blackfriday.List &&
node.Prev.LastChild.ListFlags&blackfriday.ListTypeDefinition == 0 {
out(w, ".IP\n")
} else {
out(w, dd2Tag)
}
} }
} else { } else {
out(w, ".IP \\(bu 2\n") 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 MIN_PAYLOAD_SIZE = MIN_MSS_SIZE - UDP_HEADER_SIZE - SRT_HEADER_SIZE
MAX_PAYLOAD_SIZE = MAX_MSS_SIZE - UDP_HEADER_SIZE - SRT_HEADER_SIZE MAX_PAYLOAD_SIZE = MAX_MSS_SIZE - UDP_HEADER_SIZE - SRT_HEADER_SIZE
MIN_PASSPHRASE_SIZE = 10 MIN_PASSPHRASE_SIZE = 10
MAX_PASSPHRASE_SIZE = 79 MAX_PASSPHRASE_SIZE = 80
MAX_STREAMID_SIZE = 512 MAX_STREAMID_SIZE = 512
SRT_VERSION = 0x010401 SRT_VERSION = 0x010401
) )
@@ -169,6 +169,9 @@ type Config struct {
// An implementation of the Logger interface // An implementation of the Logger interface
Logger Logger 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 // DefaultConfig is the default configuration for a SRT connection
@@ -209,6 +212,7 @@ var defaultConfig Config = Config{
TooLatePacketDrop: true, TooLatePacketDrop: true,
TransmissionType: "live", TransmissionType: "live",
TSBPDMode: true, TSBPDMode: true,
AllowPeerIpChange: false,
} }
// DefaultConfig returns the default configuration for Dial and Listen. // 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) minPktTsbpdTime, maxPktTsbpdTime := uint64(0), uint64(0)
ackSequenceNumber := r.lastACKSequenceNumber
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.
e := r.packetList.Front() e := r.packetList.Front()
if e != nil { if e != nil {
@@ -277,49 +273,42 @@ func (r *receiver) periodicACK(now uint64) (ok bool, sequenceNumber circular.Num
minPktTsbpdTime = p.Header().PktTsbpdTime minPktTsbpdTime = p.Header().PktTsbpdTime
maxPktTsbpdTime = p.Header().PktTsbpdTime maxPktTsbpdTime = p.Header().PktTsbpdTime
}
// If there are packets that should be delivered by now, move foward. // Find the sequence number up until we have all in a row.
if p.Header().PktTsbpdTime <= now { // Where the first gap is (or at the end of the list) is where we can ACK to.
for e = e.Next(); e != nil; e = e.Next() {
p = e.Value.(packet.Packet)
if p.Header().PktTsbpdTime > now { for e := r.packetList.Front(); e != nil; e = e.Next() {
break p := e.Value.(packet.Packet)
}
}
ackSequenceNumber = p.Header().PacketSequenceNumber // Skip packets that we already ACK'd.
maxPktTsbpdTime = p.Header().PktTsbpdTime if p.Header().PacketSequenceNumber.Lte(ackSequenceNumber) {
continue
if e != nil {
if e = e.Next(); e != nil {
p = e.Value.(packet.Packet)
}
}
} }
// 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()) { if p.Header().PacketSequenceNumber.Equals(ackSequenceNumber.Inc()) {
ackSequenceNumber = p.Header().PacketSequenceNumber ackSequenceNumber = p.Header().PacketSequenceNumber
maxPktTsbpdTime = p.Header().PktTsbpdTime
for e = e.Next(); e != nil; e = e.Next() { continue
p = e.Value.(packet.Packet)
if !p.Header().PacketSequenceNumber.Equals(ackSequenceNumber.Inc()) {
break
}
ackSequenceNumber = p.Header().PacketSequenceNumber
maxPktTsbpdTime = p.Header().PktTsbpdTime
}
} }
ok = true break
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
} }
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.lastPeriodicACK = now
r.nPackets = 0 r.nPackets = 0
@@ -338,13 +327,19 @@ func (r *receiver) periodicNAK(now uint64) (ok bool, from, to circular.Number) {
// Send a periodic NAK // Send a periodic NAK
ackSequenceNumber := r.lastDeliveredSequenceNumber ackSequenceNumber := r.lastACKSequenceNumber
// Send a NAK only for the first gap. // 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. // 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() { for e := r.packetList.Front(); e != nil; e = e.Next() {
p := e.Value.(packet.Packet) 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()) { if !p.Header().PacketSequenceNumber.Equals(ackSequenceNumber.Inc()) {
nackSequenceNumber := ackSequenceNumber.Inc() nackSequenceNumber := ackSequenceNumber.Inc()

View File

@@ -7,6 +7,7 @@ import (
"github.com/datarhei/gosrt/crypto" "github.com/datarhei/gosrt/crypto"
"github.com/datarhei/gosrt/packet" "github.com/datarhei/gosrt/packet"
"github.com/datarhei/gosrt/rand"
) )
// ConnRequest is an incoming connection request // ConnRequest is an incoming connection request
@@ -206,6 +207,18 @@ func newConnRequest(ln *listener, p packet.Packet) *connRequest {
return nil 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 { } else {
cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) 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) }) 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) 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) { func (req *connRequest) Accept() (Conn, error) {
if req.crypto != nil && len(req.passphrase) == 0 { if req.crypto != nil && len(req.passphrase) == 0 {
req.Reject(REJ_BADSECRET) req.Reject(REJ_BADSECRET)
@@ -342,7 +372,10 @@ func (req *connRequest) Accept() (Conn, error) {
} }
// Create a new socket ID // 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 // Select the largest TSBPD delay advertised by the caller, but at least 120ms
recvTsbpdDelay := uint16(req.config.ReceiverLatency.Milliseconds()) recvTsbpdDelay := uint16(req.config.ReceiverLatency.Milliseconds())

View File

@@ -69,6 +69,51 @@ type Conn interface {
Version() uint32 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 { type connStats struct {
headerSize uint64 headerSize uint64
pktSentACK uint64 pktSentACK uint64
@@ -108,19 +153,16 @@ type srtConn struct {
config Config config Config
cryptoLock sync.Mutex
crypto crypto.Crypto crypto crypto.Crypto
keyBaseEncryption packet.PacketEncryption keyBaseEncryption packet.PacketEncryption
kmPreAnnounceCountdown uint64 kmPreAnnounceCountdown uint64
kmRefreshCountdown uint64 kmRefreshCountdown uint64
kmConfirmed bool kmConfirmed bool
cryptoLock sync.Mutex
peerIdleTimeout *time.Timer peerIdleTimeout *time.Timer
rtt float64 // microseconds rtt rtt // microseconds
rttVar float64 // microseconds
nakInterval float64
ackLock sync.RWMutex ackLock sync.RWMutex
ackNumbers map[uint32]time.Time ackNumbers map[uint32]time.Time
@@ -232,10 +274,10 @@ func newSRTConn(config srtConnConfig) *srtConn {
c.kmRefreshCountdown = c.config.KMRefreshRate c.kmRefreshCountdown = c.config.KMRefreshRate
// 4.10. Round-Trip Time Estimation // 4.10. Round-Trip Time Estimation
c.rtt = float64((100 * time.Millisecond).Microseconds()) c.rtt = rtt{
c.rttVar = float64((50 * time.Millisecond).Microseconds()) rtt: float64((100 * time.Millisecond).Microseconds()),
rttVar: float64((50 * time.Millisecond).Microseconds()),
c.nakInterval = float64((20 * time.Millisecond).Microseconds()) }
c.networkQueue = make(chan packet.Packet, 1024) 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. // handleACKACK updates the RTT and NAK interval for the congestion control.
func (c *srtConn) handleACKACK(p packet.Packet) { func (c *srtConn) handleACKACK(p packet.Packet) {
c.ackLock.RLock() c.ackLock.Lock()
c.statisticsLock.Lock() c.statisticsLock.Lock()
c.statistics.pktRecvACKACK++ 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(uint64(c.rtt.NAKInterval()))
c.recv.SetNAKInterval(nakInterval)
} }
// recalculateRTT recalculates the RTT based on a full ACK exchange // recalculateRTT recalculates the RTT based on a full ACK exchange
func (c *srtConn) recalculateRTT(rtt time.Duration) { func (c *srtConn) recalculateRTT(rtt time.Duration) {
// 4.10. Round-Trip Time Estimation c.rtt.Recalculate(rtt)
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.log("connection:rtt", func() string { 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 { } else {
pps, bps, capacity := c.recv.PacketRate() pps, bps, capacity := c.recv.PacketRate()
cif.RTT = uint32(c.rtt) cif.RTT = uint32(c.rtt.RTT())
cif.RTTVar = uint32(c.rttVar) cif.RTTVar = uint32(c.rtt.RTTVar())
cif.AvailableBufferSize = c.config.FC // TODO: available buffer size (packets) cif.AvailableBufferSize = c.config.FC // TODO: available buffer size (packets)
cif.PacketsReceivingRate = uint32(pps) // packets receiving rate (packets/s) cif.PacketsReceivingRate = uint32(pps) // packets receiving rate (packets/s)
cif.EstimatedLinkCapacity = uint32(capacity) // estimated link capacity (packets/s), not relevant for live mode 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, UsPktSendPeriod: send.UsPktSndPeriod,
PktFlowWindow: uint64(c.config.FC), PktFlowWindow: uint64(c.config.FC),
PktFlightSize: send.PktFlightSize, PktFlightSize: send.PktFlightSize,
MsRTT: c.rtt / 1000, MsRTT: c.rtt.RTT() / 1000,
MbpsSentRate: send.MbpsEstimatedSentBandwidth, MbpsSentRate: send.MbpsEstimatedSentBandwidth,
MbpsRecvRate: recv.MbpsEstimatedRecvBandwidth, MbpsRecvRate: recv.MbpsEstimatedRecvBandwidth,
MbpsLinkCapacity: recv.MbpsEstimatedLinkCapacity, MbpsLinkCapacity: recv.MbpsEstimatedLinkCapacity,

View File

@@ -407,6 +407,14 @@ func (ln *listener) reader(ctx context.Context) {
break 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) conn.push(p)
} }
} }

View File

@@ -33,7 +33,7 @@ const (
CTRLTYPE_SHUTDOWN CtrlType = 0x0005 CTRLTYPE_SHUTDOWN CtrlType = 0x0005
CTRLTYPE_ACKACK CtrlType = 0x0006 CTRLTYPE_ACKACK CtrlType = 0x0006
CRTLTYPE_DROPREQ CtrlType = 0x0007 // unimplemented, sender->receiver 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 CTRLTYPE_USER CtrlType = 0x7FFF
) )
@@ -141,8 +141,8 @@ const (
EXTTYPE_KMRSP CtrlSubType = 4 EXTTYPE_KMRSP CtrlSubType = 4
EXTTYPE_SID CtrlSubType = 5 EXTTYPE_SID CtrlSubType = 5
EXTTYPE_CONGESTION CtrlSubType = 6 EXTTYPE_CONGESTION CtrlSubType = 6
EXTTYPE_FILTER CtrlSubType = 7 EXTTYPE_FILTER CtrlSubType = 7 // unimplemented
EXTTYPE_GROUP CtrlSubType = 8 EXTTYPE_GROUP CtrlSubType = 8 // unimplemented
) )
func (h CtrlSubType) String() string { 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. 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. 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. 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. 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). 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. 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. 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 HasHS bool
HasKM bool HasKM bool
HasSID bool HasSID bool
HasCongestionCtl bool
// 3.2.1.1. Handshake Extension Message // 3.2.1.1. Handshake Extension Message
SRTHS *CIFHandshakeExtension SRTHS *CIFHandshakeExtension
@@ -505,6 +506,9 @@ type CIFHandshake struct {
// 3.2.1.3. Stream ID Extension Message // 3.2.1.3. Stream ID Extension Message
StreamId string StreamId string
// ??? Congestion Control Extension message (handshake.md #### Congestion controller)
CongestionCtl string
} }
func (c CIFHandshake) String() 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, " streamId : %s\n", c.StreamId)
fmt.Fprintf(&b, "--- /SIDExt ---\n") 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 ---") fmt.Fprintf(&b, "--- /handshake ---")
@@ -599,7 +609,7 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
if extensionType == EXTTYPE_HSREQ || extensionType == EXTTYPE_HSRSP { if extensionType == EXTTYPE_HSREQ || extensionType == EXTTYPE_HSRSP {
// 3.2.1.1. Handshake Extension Message // 3.2.1.1. Handshake Extension Message
if extensionLength != 12 || len(pivot) < extensionLength { 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 c.HasHS = true
@@ -612,7 +622,7 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
} else if extensionType == EXTTYPE_KMREQ || extensionType == EXTTYPE_KMRSP { } else if extensionType == EXTTYPE_KMREQ || extensionType == EXTTYPE_KMRSP {
// 3.2.1.2. Key Material Extension Message // 3.2.1.2. Key Material Extension Message
if len(pivot) < extensionLength { 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 c.HasKM = true
@@ -638,7 +648,7 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
} else if extensionType == EXTTYPE_SID { } else if extensionType == EXTTYPE_SID {
// 3.2.1.3. Stream ID Extension Message // 3.2.1.3. Stream ID Extension Message
if extensionLength > 512 || len(pivot) < extensionLength { 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 c.HasSID = true
@@ -653,8 +663,30 @@ func (c *CIFHandshake) Unmarshal(data []byte) error {
} }
c.StreamId = strings.TrimRight(b.String(), "\x00") 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 { } else {
return fmt.Errorf("unimplemented extension (%d)", extensionType) return fmt.Errorf("unknown extension (%d)", extensionType)
} }
if len(pivot) > extensionLength { if len(pivot) > extensionLength {
@@ -695,6 +727,10 @@ func (c *CIFHandshake) Marshal(w io.Writer) {
if c.HasSID { if c.HasSID {
c.ExtensionField = c.ExtensionField | 4 c.ExtensionField = c.ExtensionField | 4
} }
if c.HasCongestionCtl {
c.ExtensionField = c.ExtensionField | 4
}
} else { } else {
c.EncryptionField = 0 c.EncryptionField = 0
c.ExtensionField = 2 c.ExtensionField = 2
@@ -773,6 +809,33 @@ func (c *CIFHandshake) Marshal(w io.Writer) {
w.Write(buffer[:4]) 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 // 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 { if err != nil {
return "", err return "", err
} }
b[i] = charset[int(j)] b[i] = charset[j]
} }
return string(b), nil return string(b), nil

View File

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

View File

@@ -110,3 +110,22 @@ func zipContains(raw, sig []byte, msoCheck bool) bool {
} }
return false 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. This file is automatically generated when running tests. Do not edit manually.
Extension | MIME type | Aliases Extension | MIME type | Aliases
@@ -11,6 +11,7 @@ Extension | MIME type | Aliases
**.docx** | application/vnd.openxmlformats-officedocument.wordprocessingml.document | - **.docx** | application/vnd.openxmlformats-officedocument.wordprocessingml.document | -
**.pptx** | application/vnd.openxmlformats-officedocument.presentationml.presentation | - **.pptx** | application/vnd.openxmlformats-officedocument.presentationml.presentation | -
**.epub** | application/epub+zip | - **.epub** | application/epub+zip | -
**.apk** | application/vnd.android.package-archive | -
**.jar** | application/jar | - **.jar** | application/jar | -
**.odt** | application/vnd.oasis.opendocument.text | application/x-vnd.oasis.opendocument.text **.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 **.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 | - **.avif** | image/avif | -
**.3gp** | video/3gpp | video/3gp, audio/3gpp **.3gp** | video/3gpp | video/3gp, audio/3gpp
**.3g2** | video/3gpp2 | video/3g2, audio/3gpp2 **.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 | - **.mqv** | video/quicktime | -
**.m4a** | audio/x-m4a | - **.m4a** | audio/x-m4a | -
**.m4v** | video/x-m4v | - **.m4v** | video/x-m4v | -
@@ -118,6 +119,7 @@ Extension | MIME type | Aliases
**.mobi** | application/x-mobipocket-ebook | - **.mobi** | application/x-mobipocket-ebook | -
**.lit** | application/x-ms-reader | - **.lit** | application/x-ms-reader | -
**.bpg** | image/bpg | - **.bpg** | image/bpg | -
**.cbor** | application/cbor | -
**.sqlite** | application/vnd.sqlite3 | application/x-sqlite3 **.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 **.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 | - **.nes** | application/vnd.nintendo.snes.rom | -
@@ -162,7 +164,7 @@ Extension | MIME type | Aliases
**.xfdf** | application/vnd.adobe.xfdf | - **.xfdf** | application/vnd.adobe.xfdf | -
**.owl** | application/owl+xml | - **.owl** | application/owl+xml | -
**.php** | text/x-php | - **.php** | text/x-php | -
**.js** | application/javascript | application/x-javascript, text/javascript **.js** | text/javascript | application/x-javascript, application/javascript
**.lua** | text/x-lua | - **.lua** | text/x-lua | -
**.pl** | text/x-perl | - **.pl** | text/x-perl | -
**.py** | text/x-python | text/x-script.python, application/x-python **.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, 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, 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, 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, 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, rpm, xz, lzip, torrent, cpio, tzif, xcf, pat, gbr, glb, cabIS, jxr, parquet,
// Keep text last because it is the slowest check. // Keep text last because it is the slowest check.
@@ -44,7 +44,11 @@ var (
"application/gzip-compressed", "application/x-gzip-compressed", "application/gzip-compressed", "application/x-gzip-compressed",
"gzip/document") "gzip/document")
sevenZ = newMIME("application/x-7z-compressed", ".7z", magic.SevenZ) 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") alias("application/x-zip", "application/x-zip-compressed")
tar = newMIME("application/x-tar", ".tar", magic.Tar) tar = newMIME("application/x-tar", ".tar", magic.Tar)
xar = newMIME("application/x-xar", ".xar", magic.Xar) xar = newMIME("application/x-xar", ".xar", magic.Xar)
@@ -57,6 +61,7 @@ var (
pptx = newMIME("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx", magic.Pptx) pptx = newMIME("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx", magic.Pptx)
epub = newMIME("application/epub+zip", ".epub", magic.Epub) epub = newMIME("application/epub+zip", ".epub", magic.Epub)
jar = newMIME("application/jar", ".jar", magic.Jar) 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) ole = newMIME("application/x-ole-storage", "", magic.Ole, msi, aaf, msg, xls, pub, ppt, doc)
msi = newMIME("application/x-ms-installer", ".msi", magic.Msi). msi = newMIME("application/x-ms-installer", ".msi", magic.Msi).
alias("application/x-windows-installer", "application/x-msi") alias("application/x-windows-installer", "application/x-msi")
@@ -87,8 +92,8 @@ var (
html = newMIME("text/html", ".html", magic.HTML) html = newMIME("text/html", ".html", magic.HTML)
php = newMIME("text/x-php", ".php", magic.Php) php = newMIME("text/x-php", ".php", magic.Php)
rtf = newMIME("text/rtf", ".rtf", magic.Rtf).alias("application/rtf") rtf = newMIME("text/rtf", ".rtf", magic.Rtf).alias("application/rtf")
js = newMIME("application/javascript", ".js", magic.Js). js = newMIME("text/javascript", ".js", magic.Js).
alias("application/x-javascript", "text/javascript") alias("application/x-javascript", "application/javascript")
srt = newMIME("application/x-subrip", ".srt", magic.Srt). srt = newMIME("application/x-subrip", ".srt", magic.Srt).
alias("application/x-srt", "text/x-srt") alias("application/x-srt", "text/x-srt")
vtt = newMIME("text/vtt", ".vtt", magic.Vtt) vtt = newMIME("text/vtt", ".vtt", magic.Vtt)
@@ -156,7 +161,7 @@ var (
aac = newMIME("audio/aac", ".aac", magic.AAC) aac = newMIME("audio/aac", ".aac", magic.AAC)
voc = newMIME("audio/x-unknown", ".voc", magic.Voc) voc = newMIME("audio/x-unknown", ".voc", magic.Voc)
aMp4 = newMIME("audio/mp4", ".mp4", magic.AMp4). 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) m4a = newMIME("audio/x-m4a", ".m4a", magic.M4a)
m3u = newMIME("application/vnd.apple.mpegurl", ".m3u", magic.M3u). m3u = newMIME("application/vnd.apple.mpegurl", ".m3u", magic.M3u).
alias("audio/mpegurl") alias("audio/mpegurl")
@@ -261,4 +266,5 @@ var (
jxr = newMIME("image/jxr", ".jxr", magic.Jxr).alias("image/vnd.ms-photo") jxr = newMIME("image/jxr", ".jxr", magic.Jxr).alias("image/vnd.ms-photo")
parquet = newMIME("application/vnd.apache.parquet", ".parquet", magic.Par1). parquet = newMIME("application/vnd.apache.parquet", ".parquet", magic.Par1).
alias("application/x-parquet") alias("application/x-parquet")
cbor = newMIME("application/cbor", ".cbor", magic.CBOR)
) )

View File

@@ -1,7 +1,7 @@
Package validator 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) <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) [![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) [![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) [![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. - 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) - 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 Installation
------------ ------------
@@ -266,74 +271,75 @@ validate := validator.New(validator.WithRequiredStructEnabled())
Benchmarks Benchmarks
------ ------
###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64 ###### Run on MacBook Pro Max M3
```go ```go
go version go1.21.0 darwin/arm64 go version go1.23.3 darwin/arm64
goos: darwin goos: darwin
goarch: arm64 goarch: arm64
cpu: Apple M3 Max
pkg: github.com/go-playground/validator/v10 pkg: github.com/go-playground/validator/v10
BenchmarkFieldSuccess-8 33142266 35.94 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccess-16 42461943 27.88 ns/op 0 B/op 0 allocs/op
BenchmarkFieldSuccessParallel-8 200816191 6.568 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccessParallel-16 486632887 2.289 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-8 6779707 175.1 ns/op 200 B/op 4 allocs/op BenchmarkFieldFailure-16 9566167 121.3 ns/op 200 B/op 4 allocs/op
BenchmarkFieldFailureParallel-8 11044147 108.4 ns/op 200 B/op 4 allocs/op BenchmarkFieldFailureParallel-16 17551471 83.68 ns/op 200 B/op 4 allocs/op
BenchmarkFieldArrayDiveSuccess-8 6054232 194.4 ns/op 97 B/op 5 allocs/op BenchmarkFieldArrayDiveSuccess-16 7602306 155.6 ns/op 97 B/op 5 allocs/op
BenchmarkFieldArrayDiveSuccessParallel-8 12523388 94.07 ns/op 97 B/op 5 allocs/op BenchmarkFieldArrayDiveSuccessParallel-16 20664610 59.80 ns/op 97 B/op 5 allocs/op
BenchmarkFieldArrayDiveFailure-8 3587043 334.3 ns/op 300 B/op 10 allocs/op BenchmarkFieldArrayDiveFailure-16 4659756 252.9 ns/op 301 B/op 10 allocs/op
BenchmarkFieldArrayDiveFailureParallel-8 5816665 200.8 ns/op 300 B/op 10 allocs/op BenchmarkFieldArrayDiveFailureParallel-16 8010116 152.9 ns/op 301 B/op 10 allocs/op
BenchmarkFieldMapDiveSuccess-8 2217910 540.1 ns/op 288 B/op 14 allocs/op BenchmarkFieldMapDiveSuccess-16 2834575 421.2 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveSuccessParallel-8 4446698 258.7 ns/op 288 B/op 14 allocs/op BenchmarkFieldMapDiveSuccessParallel-16 7179700 171.8 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveFailure-8 2392759 504.6 ns/op 376 B/op 13 allocs/op BenchmarkFieldMapDiveFailure-16 3081728 384.4 ns/op 376 B/op 13 allocs/op
BenchmarkFieldMapDiveFailureParallel-8 4244199 286.9 ns/op 376 B/op 13 allocs/op BenchmarkFieldMapDiveFailureParallel-16 6058137 204.0 ns/op 377 B/op 13 allocs/op
BenchmarkFieldMapDiveWithKeysSuccess-8 2005857 592.1 ns/op 288 B/op 14 allocs/op BenchmarkFieldMapDiveWithKeysSuccess-16 2544975 464.8 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveWithKeysSuccessParallel-8 4400850 296.9 ns/op 288 B/op 14 allocs/op BenchmarkFieldMapDiveWithKeysSuccessParallel-16 6661954 181.4 ns/op 288 B/op 14 allocs/op
BenchmarkFieldMapDiveWithKeysFailure-8 1850227 643.8 ns/op 553 B/op 16 allocs/op BenchmarkFieldMapDiveWithKeysFailure-16 2435484 490.7 ns/op 553 B/op 16 allocs/op
BenchmarkFieldMapDiveWithKeysFailureParallel-8 3293233 375.1 ns/op 553 B/op 16 allocs/op BenchmarkFieldMapDiveWithKeysFailureParallel-16 4249617 282.0 ns/op 554 B/op 16 allocs/op
BenchmarkFieldCustomTypeSuccess-8 12174412 98.25 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccess-16 14943525 77.35 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeSuccessParallel-8 34389907 35.49 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccessParallel-16 64051954 20.61 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-8 7582524 156.6 ns/op 184 B/op 3 allocs/op BenchmarkFieldCustomTypeFailure-16 10721384 107.1 ns/op 184 B/op 3 allocs/op
BenchmarkFieldCustomTypeFailureParallel-8 13019902 92.79 ns/op 184 B/op 3 allocs/op BenchmarkFieldCustomTypeFailureParallel-16 18714495 69.77 ns/op 184 B/op 3 allocs/op
BenchmarkFieldOrTagSuccess-8 3427260 349.4 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccess-16 4063124 294.3 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagSuccessParallel-8 15144128 81.25 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccessParallel-16 31903756 41.22 ns/op 18 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-8 5913546 201.9 ns/op 216 B/op 5 allocs/op BenchmarkFieldOrTagFailure-16 7748558 146.8 ns/op 216 B/op 5 allocs/op
BenchmarkFieldOrTagFailureParallel-8 9810212 113.7 ns/op 216 B/op 5 allocs/op BenchmarkFieldOrTagFailureParallel-16 13139854 92.05 ns/op 216 B/op 5 allocs/op
BenchmarkStructLevelValidationSuccess-8 13456327 87.66 ns/op 16 B/op 1 allocs/op BenchmarkStructLevelValidationSuccess-16 16808389 70.25 ns/op 16 B/op 1 allocs/op
BenchmarkStructLevelValidationSuccessParallel-8 41818888 27.77 ns/op 16 B/op 1 allocs/op BenchmarkStructLevelValidationSuccessParallel-16 90686955 14.47 ns/op 16 B/op 1 allocs/op
BenchmarkStructLevelValidationFailure-8 4166284 272.6 ns/op 264 B/op 7 allocs/op BenchmarkStructLevelValidationFailure-16 5818791 200.2 ns/op 264 B/op 7 allocs/op
BenchmarkStructLevelValidationFailureParallel-8 7594581 152.1 ns/op 264 B/op 7 allocs/op BenchmarkStructLevelValidationFailureParallel-16 11115874 107.5 ns/op 264 B/op 7 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-8 6508082 182.6 ns/op 32 B/op 2 allocs/op BenchmarkStructSimpleCustomTypeSuccess-16 7764956 151.9 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeSuccessParallel-8 23078605 54.78 ns/op 32 B/op 2 allocs/op BenchmarkStructSimpleCustomTypeSuccessParallel-16 52316265 30.37 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeFailure-8 3118352 381.0 ns/op 416 B/op 9 allocs/op BenchmarkStructSimpleCustomTypeFailure-16 4195429 277.2 ns/op 416 B/op 9 allocs/op
BenchmarkStructSimpleCustomTypeFailureParallel-8 5300738 224.1 ns/op 432 B/op 10 allocs/op BenchmarkStructSimpleCustomTypeFailureParallel-16 7305661 164.6 ns/op 432 B/op 10 allocs/op
BenchmarkStructFilteredSuccess-8 4761807 251.1 ns/op 216 B/op 5 allocs/op BenchmarkStructFilteredSuccess-16 6312625 186.1 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredSuccessParallel-8 8792598 128.6 ns/op 216 B/op 5 allocs/op BenchmarkStructFilteredSuccessParallel-16 13684459 93.42 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredFailure-8 5202573 232.1 ns/op 216 B/op 5 allocs/op BenchmarkStructFilteredFailure-16 6751482 171.2 ns/op 216 B/op 5 allocs/op
BenchmarkStructFilteredFailureParallel-8 9591267 121.4 ns/op 216 B/op 5 allocs/op BenchmarkStructFilteredFailureParallel-16 14146070 86.93 ns/op 216 B/op 5 allocs/op
BenchmarkStructPartialSuccess-8 5188512 231.6 ns/op 224 B/op 4 allocs/op BenchmarkStructPartialSuccess-16 6544448 177.3 ns/op 224 B/op 4 allocs/op
BenchmarkStructPartialSuccessParallel-8 9179776 123.1 ns/op 224 B/op 4 allocs/op BenchmarkStructPartialSuccessParallel-16 13951946 88.73 ns/op 224 B/op 4 allocs/op
BenchmarkStructPartialFailure-8 3071212 392.5 ns/op 440 B/op 9 allocs/op BenchmarkStructPartialFailure-16 4075833 287.5 ns/op 440 B/op 9 allocs/op
BenchmarkStructPartialFailureParallel-8 5344261 223.7 ns/op 440 B/op 9 allocs/op BenchmarkStructPartialFailureParallel-16 7490805 161.3 ns/op 440 B/op 9 allocs/op
BenchmarkStructExceptSuccess-8 3184230 375.0 ns/op 424 B/op 8 allocs/op BenchmarkStructExceptSuccess-16 4107187 281.4 ns/op 424 B/op 8 allocs/op
BenchmarkStructExceptSuccessParallel-8 10090130 108.9 ns/op 208 B/op 3 allocs/op BenchmarkStructExceptSuccessParallel-16 15979173 80.86 ns/op 208 B/op 3 allocs/op
BenchmarkStructExceptFailure-8 3347226 357.7 ns/op 424 B/op 8 allocs/op BenchmarkStructExceptFailure-16 4434372 264.3 ns/op 424 B/op 8 allocs/op
BenchmarkStructExceptFailureParallel-8 5654923 209.5 ns/op 424 B/op 8 allocs/op BenchmarkStructExceptFailureParallel-16 8081367 154.1 ns/op 424 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-8 5232265 229.1 ns/op 56 B/op 3 allocs/op BenchmarkStructSimpleCrossFieldSuccess-16 6459542 183.4 ns/op 56 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldSuccessParallel-8 17436674 64.75 ns/op 56 B/op 3 allocs/op BenchmarkStructSimpleCrossFieldSuccessParallel-16 41013781 37.95 ns/op 56 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldFailure-8 3128613 383.6 ns/op 272 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldFailure-16 4034998 292.1 ns/op 272 B/op 8 allocs/op
BenchmarkStructSimpleCrossFieldFailureParallel-8 6994113 168.8 ns/op 272 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldFailureParallel-16 11348446 115.3 ns/op 272 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 3506487 340.9 ns/op 64 B/op 4 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccess-16 4448528 267.7 ns/op 64 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 13431300 91.77 ns/op 64 B/op 4 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-16 26813619 48.33 ns/op 64 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2410566 500.9 ns/op 288 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailure-16 3090646 384.5 ns/op 288 B/op 9 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 6344510 188.2 ns/op 288 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-16 9870906 129.5 ns/op 288 B/op 9 allocs/op
BenchmarkStructSimpleSuccess-8 8922726 133.8 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleSuccess-16 10675562 109.5 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleSuccessParallel-8 55291153 23.63 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleSuccessParallel-16 131159784 8.932 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleFailure-8 3171553 378.4 ns/op 416 B/op 9 allocs/op BenchmarkStructSimpleFailure-16 4094979 286.6 ns/op 416 B/op 9 allocs/op
BenchmarkStructSimpleFailureParallel-8 5571692 212.0 ns/op 416 B/op 9 allocs/op BenchmarkStructSimpleFailureParallel-16 7606663 157.9 ns/op 416 B/op 9 allocs/op
BenchmarkStructComplexSuccess-8 1683750 714.5 ns/op 224 B/op 5 allocs/op BenchmarkStructComplexSuccess-16 2073470 576.0 ns/op 224 B/op 5 allocs/op
BenchmarkStructComplexSuccessParallel-8 4578046 257.0 ns/op 224 B/op 5 allocs/op BenchmarkStructComplexSuccessParallel-16 7821831 161.3 ns/op 224 B/op 5 allocs/op
BenchmarkStructComplexFailure-8 481585 2547 ns/op 3041 B/op 48 allocs/op BenchmarkStructComplexFailure-16 576358 2001 ns/op 3042 B/op 48 allocs/op
BenchmarkStructComplexFailureParallel-8 965764 1577 ns/op 3040 B/op 48 allocs/op BenchmarkStructComplexFailureParallel-16 1000000 1171 ns/op 3041 B/op 48 allocs/op
BenchmarkOneof-8 17380881 68.50 ns/op 0 B/op 0 allocs/op BenchmarkOneof-16 22503973 52.82 ns/op 0 B/op 0 allocs/op
BenchmarkOneofParallel-8 8084733 153.5 ns/op 0 B/op 0 allocs/op BenchmarkOneofParallel-16 8538474 140.4 ns/op 0 B/op 0 allocs/op
``` ```
Complementary Software Complementary Software
@@ -349,6 +355,20 @@ How to Contribute
Make a pull request... 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 License
------- -------
Distributed under MIT License, please see license file within the code for more details. Distributed under MIT License, please see license file within the code for more details.

View File

@@ -205,6 +205,7 @@ var (
"fqdn": isFQDN, "fqdn": isFQDN,
"unique": isUnique, "unique": isUnique,
"oneof": isOneOf, "oneof": isOneOf,
"oneofci": isOneOfCI,
"html": isHTML, "html": isHTML,
"html_encoded": isHTMLEncoded, "html_encoded": isHTMLEncoded,
"url_encoded": isURLEncoded, "url_encoded": isURLEncoded,
@@ -213,6 +214,7 @@ var (
"json": isJSON, "json": isJSON,
"jwt": isJWT, "jwt": isJWT,
"hostname_port": isHostnamePort, "hostname_port": isHostnamePort,
"port": isPort,
"lowercase": isLowercase, "lowercase": isLowercase,
"uppercase": isUppercase, "uppercase": isUppercase,
"datetime": isDatetime, "datetime": isDatetime,
@@ -299,6 +301,23 @@ func isOneOf(fl FieldLevel) bool {
return false 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 // isUnique is the validation function for validating if each array|slice|map value is unique
func isUnique(fl FieldLevel) bool { func isUnique(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
@@ -2711,6 +2730,13 @@ func isHostnamePort(fl FieldLevel) bool {
return true 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. // isLowercase is the validation function for validating if the current field's value is a lowercase string.
func isLowercase(fl FieldLevel) bool { func isLowercase(fl FieldLevel) bool {
field := fl.Field() 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 is one of the values in the parameter. The parameter should be
a list of values separated by whitespace. Values may be a list of values separated by whitespace. Values may be
strings or numbers. To match strings with spaces in them, include 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 Usage: oneof=red green
oneof='red green' 'blue yellow' oneof='red green' 'blue yellow'
oneof=5 7 9 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 # Greater Than
For numbers, this will ensure that the value is greater than the 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 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}$" 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]+))?)*)?$" 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,})|\*)$` spicedbIDRegexString = `^(([a-zA-Z0-9/_|\-=+]{1,})|\*)$`
spicedbPermissionRegexString = "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$" 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]$" 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 ## 1.5.0
* New option `IgnoreUntaggedFields` to ignore decoding to any fields * 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