Update dependencies

This commit is contained in:
Ingo Oppermann 2024-11-06 14:21:47 +01:00
parent 0f3dfe4e67
commit e2e8838fc2
No known key found for this signature in database
GPG Key ID: 2AB32426E9DD229E
683 changed files with 72786 additions and 23376 deletions

81
go.mod
View File

@ -1,17 +1,17 @@
module github.com/datarhei/core/v16
go 1.21.0
go 1.22.5
toolchain go1.22.1
toolchain go1.23.2
require (
github.com/99designs/gqlgen v0.17.47
github.com/Masterminds/semver/v3 v3.2.1
github.com/99designs/gqlgen v0.17.55
github.com/Masterminds/semver/v3 v3.3.0
github.com/atrox/haikunatorgo/v2 v2.0.1
github.com/caddyserver/certmagic v0.21.2
github.com/datarhei/gosrt v0.6.0
github.com/caddyserver/certmagic v0.21.4
github.com/datarhei/gosrt v0.7.0
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e
github.com/go-playground/validator/v10 v10.20.0
github.com/go-playground/validator/v10 v10.22.1
github.com/gobwas/glob v0.2.3
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
@ -21,23 +21,23 @@ require (
github.com/labstack/echo/v4 v4.12.0
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/mattn/go-isatty v0.0.20
github.com/minio/minio-go/v7 v7.0.70
github.com/minio/minio-go/v7 v7.0.80
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
github.com/prometheus/client_golang v1.19.1
github.com/puzpuzpuz/xsync/v3 v3.1.0
github.com/shirou/gopsutil/v3 v3.24.4
github.com/prometheus/client_golang v1.20.5
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/shirou/gopsutil/v3 v3.24.5
github.com/stretchr/testify v1.9.0
github.com/swaggo/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.3
github.com/vektah/gqlparser/v2 v2.5.12
github.com/swaggo/swag v1.16.4
github.com/vektah/gqlparser/v2 v2.5.18
github.com/xeipuuv/gojsonschema v1.2.0
go.uber.org/zap v1.27.0
golang.org/x/mod v0.17.0
golang.org/x/mod v0.21.0
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/agnivade/levenshtein v1.2.0 // indirect
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
@ -45,8 +45,9 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
@ -56,53 +57,53 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/iancoleman/orderedmap v0.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/libdns/libdns v0.2.2 // indirect
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mholt/acmez/v2 v2.0.1 // indirect
github.com/miekg/dns v1.1.59 // indirect
github.com/mholt/acmez/v2 v2.0.3 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/swaggo/files/v2 v2.0.1 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/urfave/cli/v2 v2.27.2 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect
github.com/urfave/cli/v2 v2.27.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

182
go.sum
View File

@ -1,13 +1,13 @@
github.com/99designs/gqlgen v0.17.47 h1:M9DTK8X3+3ATNBfZlHBwMwNngn4hhZWDxNmTiuQU5tQ=
github.com/99designs/gqlgen v0.17.47/go.mod h1:ejVkldSdtmuudqmtfaiqjwlGXWAhIv0DKXGXFY25F04=
github.com/99designs/gqlgen v0.17.55 h1:3vzrNWYyzSZjGDFo68e5j9sSauLxfKvLp+6ioRokVtM=
github.com/99designs/gqlgen v0.17.55/go.mod h1:3Bq768f8hgVPGZxL8aY9MaYmbxa6llPM/qu1IGH1EJo=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0=
github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U=
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/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
@ -20,29 +20,31 @@ github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYh
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
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/caddyserver/certmagic v0.21.2 h1:O18LtaYBGDooyy257cYePnhp4lPfz6TaJELil6Q1fDg=
github.com/caddyserver/certmagic v0.21.2/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/datarhei/gosrt v0.6.0 h1:HrrXAw90V78ok4WMIhX6se1aTHPCn82Sg2hj+PhdmGc=
github.com/datarhei/gosrt v0.6.0/go.mod h1:fsOWdLSHUHShHjgi/46h6wjtdQrtnSdAQFnlas8ONxs=
github.com/datarhei/gosrt v0.7.0 h1:1/IY66HVVgqGA9zkmL5l6jUFuI8t/76WkuamSkJqHqs=
github.com/datarhei/gosrt v0.7.0/go.mod h1:wTDoyog1z4au8Fd/QJBQAndzvccuxjqUL/qMm0EyJxE=
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e h1:Qc/0D4xvXrazFkoi/4UGqO15yQ1JN5I8h7RwdzCLgTY=
github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@ -60,27 +62,25 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/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/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
@ -92,16 +92,17 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo-jwt v0.0.0-20221127215225-c84d41a71003 h1:FyalHKl9hnJvhNbrABJXXjC2hG7gvIF0ioW9i0xHNQU=
github.com/labstack/echo-jwt v0.0.0-20221127215225-c84d41a71003/go.mod h1:ovRFgyKvi73jQIFCWz9ByQwzhIyohkzY0MFAlPGyr8Q=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
@ -114,9 +115,8 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -124,44 +124,45 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=
github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
github.com/minio/minio-go/v7 v7.0.80 h1:2mdUHXEykRdY/BigLt3Iuu1otL0JTogT0Nmltg0wujk=
github.com/minio/minio-go/v7 v7.0.80/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 h1:Y7qCvg282QmlyrVQuL2fgGwebuw7zvfnRym09r+dUGc=
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3/go.mod h1:0ZE5gcyWKS151WBDIpmLshHY0l+3edpuKnBUWVVbWKk=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek=
github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk=
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@ -169,36 +170,28 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk=
github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.5.12 h1:COMhVVnql6RoaF7+aTBWiTADdpLGyZWU3K/NwW0ph98=
github.com/vektah/gqlparser/v2 v2.5.12/go.mod h1:WQQjFc+I1YIzoPvZBhUQX7waZgg3pMLi0r8KymvAE2w=
github.com/vektah/gqlparser/v2 v2.5.18 h1:zSND3GtutylAQ1JpWnTHcqtaRZjl+y3NROeW8vuNo6Y=
github.com/vektah/gqlparser/v2 v2.5.18/go.mod h1:6HLzf7JKv9Fi3APymudztFQNmLXR5qJeEo6BOFcXVfc=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@ -206,14 +199,14 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -222,41 +215,34 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Binary file not shown.

View File

@ -1,25 +1,74 @@
run:
tests: true
timeout: 5m
linters-settings:
gocritic:
enabled-checks:
- emptyStringTest
- equalFold
- httpNoBody
- nilValReturn
- paramTypeCombine
- preferFprint
- yodaStyleExpr
errcheck:
exclude-functions:
- (io.Writer).Write
- io.Copy
- io.WriteString
perfsprint:
int-conversion: false
err-error: false
errorf: true
sprintf1: false
strconcat: false
revive:
enable-all-rules: false
rules:
- name: empty-lines
- name: use-any
- name: struct-tag
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: error-return
- name: error-naming
- name: exported
disabled: true
- name: if-return
- name: increment-decrement
- name: var-declaration
- name: package-comments
disabled: true
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: superfluous-else
- name: unused-parameter
disabled: true
- name: unreachable-code
- name: redefines-builtin-id
testifylint:
disable-all: true
enable:
- blank-import
- bool-compare
- compares
- empty
- error-is-as
- error-nil
- expected-actual
- float-compare
- go-require
- len
- negative-positive
- nil-compare
- require-error
- useless-assert
linters:
disable-all: true
@ -35,6 +84,7 @@ linters:
- ineffassign
- misspell
- nakedret
- perfsprint
- prealloc
- revive
- staticcheck
@ -52,3 +102,26 @@ issues:
linters:
- dupl
- errcheck
# It's autogenerated code.
- path: codegen/testserver/.*/resolver\.go
linters:
- gocritic
# The interfaces are autogenerated and don't conform to the paramTypeCombine rule
- path: _examples/federation/products/graph/entity.resolvers.go
linters:
- gocritic
# Disable revive.use-any for backwards compatibility
- path: graphql/map.go
text: "use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
- path: codegen/testserver/followschema/resolver.go
text: "use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
- path: codegen/testserver/singlefile/resolver.go
text: "use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
- path: codegen/testserver/generated_test.go
linters:
- staticcheck
text: SA1019
- path: plugin/modelgen/models_test.go
linters:
- staticcheck
text: SA1019

File diff suppressed because it is too large Load Diff

View File

@ -45,7 +45,11 @@ func Generate(cfg *config.Config, option ...Option) error {
}
}
}
plugins = append([]plugin.Plugin{federation.New(cfg.Federation.Version)}, plugins...)
federationPlugin, err := federation.New(cfg.Federation.Version, cfg)
if err != nil {
return fmt.Errorf("failed to construct the Federation plugin: %w", err)
}
plugins = append([]plugin.Plugin{federationPlugin}, plugins...)
}
for _, o := range option {
@ -58,6 +62,13 @@ func Generate(cfg *config.Config, option ...Option) error {
cfg.Sources = append(cfg.Sources, s)
}
}
if inj, ok := p.(plugin.EarlySourcesInjector); ok {
s, err := inj.InjectSourcesEarly()
if err != nil {
return fmt.Errorf("%s: %w", p.Name(), err)
}
cfg.Sources = append(cfg.Sources, s...)
}
}
if err := cfg.LoadSchema(); err != nil {
@ -70,6 +81,13 @@ func Generate(cfg *config.Config, option ...Option) error {
cfg.Sources = append(cfg.Sources, s)
}
}
if inj, ok := p.(plugin.LateSourcesInjector); ok {
s, err := inj.InjectSourcesLate(cfg.Schema)
if err != nil {
return fmt.Errorf("%s: %w", p.Name(), err)
}
cfg.Sources = append(cfg.Sources, s...)
}
}
// LoadSchema again now we have everything
@ -90,11 +108,11 @@ func Generate(cfg *config.Config, option ...Option) error {
}
}
// Merge again now that the generated models have been injected into the typemap
data_plugins := make([]interface{}, len(plugins))
dataPlugins := make([]any, len(plugins))
for index := range plugins {
data_plugins[index] = plugins[index]
dataPlugins[index] = plugins[index]
}
data, err := codegen.BuildData(cfg, data_plugins...)
data, err := codegen.BuildData(cfg, dataPlugins...)
if err != nil {
return fmt.Errorf("merging type systems failed: %w", err)
}

View File

@ -29,19 +29,20 @@ func PrependPlugin(p plugin.Plugin) Option {
// ReplacePlugin replaces any existing plugin with a matching plugin name
func ReplacePlugin(p plugin.Plugin) Option {
return func(cfg *config.Config, plugins *[]plugin.Plugin) {
if plugins != nil {
found := false
ps := *plugins
for i, o := range ps {
if p.Name() == o.Name() {
ps[i] = p
found = true
}
}
if !found {
ps = append(ps, p)
}
*plugins = ps
if plugins == nil {
return
}
found := false
ps := *plugins
for i, o := range ps {
if p.Name() == o.Name() {
ps[i] = p
found = true
}
}
if !found {
ps = append(ps, p)
}
*plugins = ps
}
}

View File

@ -18,19 +18,20 @@ type ArgSet struct {
type FieldArgument struct {
*ast.ArgumentDefinition
TypeReference *config.TypeReference
VarName string // The name of the var in go
Object *Object // A link back to the parent object
Default interface{} // The default value
Directives []*Directive
Value interface{} // value set in Data
TypeReference *config.TypeReference
VarName string // The name of the var in go
Object *Object // A link back to the parent object
Default any // The default value
Directives []*Directive
Value any // value set in Data
CallArgumentDirectivesWithNull bool
}
// ImplDirectives get not Builtin and location ARGUMENT_DEFINITION directive
// ImplDirectives get not SkipRuntime and location ARGUMENT_DEFINITION directive
func (f *FieldArgument) ImplDirectives() []*Directive {
d := make([]*Directive, 0)
for i := range f.Directives {
if !f.Directives[i].Builtin && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) {
if !f.Directives[i].SkipRuntime && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) {
d = append(d, f.Directives[i])
}
}
@ -57,11 +58,12 @@ func (b *builder) buildArg(obj *Object, arg *ast.ArgumentDefinition) (*FieldArgu
return nil, err
}
newArg := FieldArgument{
ArgumentDefinition: arg,
TypeReference: tr,
Object: obj,
VarName: templates.ToGoPrivate(arg.Name),
Directives: argDirs,
ArgumentDefinition: arg,
TypeReference: tr,
Object: obj,
VarName: templates.ToGoPrivate(arg.Name),
Directives: argDirs,
CallArgumentDirectivesWithNull: b.Config.CallArgumentDirectivesWithNull,
}
if arg.DefaultValue != nil {

View File

@ -2,35 +2,67 @@
func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
{{- range $i, $arg := . }}
var arg{{$i}} {{ $arg.TypeReference.GO | ref}}
if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField({{$arg.Name|quote}}))
{{- if $arg.ImplDirectives }}
directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) }
{{ template "implDirectives" $arg }}
tmp, err = directive{{$arg.ImplDirectives|len}}(ctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if data, ok := tmp.({{ $arg.TypeReference.GO | ref }}) ; ok {
arg{{$i}} = data
{{- if $arg.TypeReference.IsNilable }}
} else if tmp == nil {
arg{{$i}} = nil
{{- end }}
} else {
return nil, graphql.ErrorOnPath(ctx, fmt.Errorf(`unexpected type %T from directive, should be {{ $arg.TypeReference.GO }}`, tmp))
}
{{- else }}
arg{{$i}}, err = ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp)
if err != nil {
return nil, err
}
{{- end }}
arg{{$i}}, err := ec.{{ $name }}{{$arg.Name | go}}(ctx, rawArgs)
if err != nil {
return nil, err
}
args[{{$arg.Name|quote}}] = arg{{$i}}
{{- end }}
return args, nil
}
{{- range $i, $arg := . }}
func (ec *executionContext) {{ $name }}{{$arg.Name | go}}(
ctx context.Context,
rawArgs map[string]interface{},
) ({{ $arg.TypeReference.GO | ref}}, error) {
{{- if not .CallArgumentDirectivesWithNull}}
// We won't call the directive if the argument is null.
// Set call_argument_directives_with_null to true to call directives
// even if the argument is null.
_, ok := rawArgs[{{$arg.Name|quote}}]
if !ok {
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil
}
{{end}}
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField({{$arg.Name|quote}}))
{{- if $arg.ImplDirectives }}
directive0 := func(ctx context.Context) (interface{}, error) {
tmp, ok := rawArgs[{{$arg.Name|quote}}]
if !ok {
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil
}
return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp)
}
{{ template "implDirectives" $arg }}
tmp, err := directive{{$arg.ImplDirectives|len}}(ctx)
if err != nil {
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, graphql.ErrorOnPath(ctx, err)
}
if data, ok := tmp.({{ $arg.TypeReference.GO | ref }}) ; ok {
return data, nil
{{- if $arg.TypeReference.IsNilable }}
} else if tmp == nil {
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil
{{- end }}
} else {
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, graphql.ErrorOnPath(ctx, fmt.Errorf(`unexpected type %T from directive, should be {{ $arg.TypeReference.GO }}`, tmp))
}
{{- else }}
if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok {
return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp)
}
var zeroVal {{ $arg.TypeReference.GO | ref}}
return zeroVal, nil
{{- end }}
}
{{end}}
{{ end }}

View File

@ -36,7 +36,7 @@ func (c *Config) NewBinder() *Binder {
}
func (b *Binder) TypePosition(typ types.Type) token.Position {
named, isNamed := typ.(*types.Named)
named, isNamed := code.Unalias(typ).(*types.Named)
if !isNamed {
return token.Position{
Filename: "unknown",
@ -61,7 +61,7 @@ func (b *Binder) FindTypeFromName(name string) (types.Type, error) {
return b.FindType(pkgName, typeName)
}
func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
func (b *Binder) FindType(pkgName, typeName string) (types.Type, error) {
if pkgName == "" {
if typeName == "map[string]interface{}" {
return MapType, nil
@ -77,10 +77,11 @@ func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) {
return nil, err
}
if fun, isFunc := obj.(*types.Func); isFunc {
return fun.Type().(*types.Signature).Params().At(0).Type(), nil
t := code.Unalias(obj.Type())
if _, isFunc := obj.(*types.Func); isFunc {
return code.Unalias(t.(*types.Signature).Params().At(0).Type()), nil
}
return obj.Type(), nil
return t, nil
}
func (b *Binder) InstantiateType(orig types.Type, targs []types.Type) (types.Type, error) {
@ -99,7 +100,7 @@ var (
func (b *Binder) DefaultUserObject(name string) (types.Type, error) {
models := b.cfg.Models[name].Model
if len(models) == 0 {
return nil, fmt.Errorf(name + " not found in typemap")
return nil, fmt.Errorf("%s not found in typemap", name)
}
if models[0] == "map[string]interface{}" {
@ -120,12 +121,12 @@ func (b *Binder) DefaultUserObject(name string) (types.Type, error) {
return nil, err
}
return obj.Type(), nil
return code.Unalias(obj.Type()), nil
}
func (b *Binder) FindObject(pkgName string, typeName string) (types.Object, error) {
func (b *Binder) FindObject(pkgName, typeName string) (types.Object, error) {
if pkgName == "" {
return nil, fmt.Errorf("package cannot be nil")
return nil, errors.New("package cannot be nil")
}
pkg := b.pkgs.LoadWithTypes(pkgName)
@ -193,19 +194,19 @@ func (b *Binder) PointerTo(ref *TypeReference) *TypeReference {
// TypeReference is used by args and field types. The Definition can refer to both input and output types.
type TypeReference struct {
Definition *ast.Definition
GQL *ast.Type
GO types.Type // Type of the field being bound. Could be a pointer or a value type of Target.
Target types.Type // The actual type that we know how to bind to. May require pointer juggling when traversing to fields.
CastType types.Type // Before calling marshalling functions cast from/to this base type
Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function
Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function
IsMarshaler bool // Does the type implement graphql.Marshaler and graphql.Unmarshaler
IsOmittable bool // Is the type wrapped with Omittable
IsContext bool // Is the Marshaler/Unmarshaller the context version; applies to either the method or interface variety.
PointersInUmarshalInput bool // Inverse values and pointers in return.
IsRoot bool // Is the type a root level definition such as Query, Mutation or Subscription
EnumValues []EnumValueReference
Definition *ast.Definition
GQL *ast.Type
GO types.Type // Type of the field being bound. Could be a pointer or a value type of Target.
Target types.Type // The actual type that we know how to bind to. May require pointer juggling when traversing to fields.
CastType types.Type // Before calling marshalling functions cast from/to this base type
Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function
Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function
IsMarshaler bool // Does the type implement graphql.Marshaler and graphql.Unmarshaler
IsOmittable bool // Is the type wrapped with Omittable
IsContext bool // Is the Marshaler/Unmarshaller the context version; applies to either the method or interface variety.
PointersInUnmarshalInput bool // Inverse values and pointers in return.
IsRoot bool // Is the type a root level definition such as Query, Mutation or Subscription
EnumValues []EnumValueReference
}
func (ref *TypeReference) Elem() *TypeReference {
@ -264,13 +265,13 @@ func (ref *TypeReference) IsPtrToIntf() bool {
}
func (ref *TypeReference) IsNamed() bool {
_, isSlice := ref.GO.(*types.Named)
return isSlice
_, ok := ref.GO.(*types.Named)
return ok
}
func (ref *TypeReference) IsStruct() bool {
_, isStruct := ref.GO.Underlying().(*types.Struct)
return isStruct
_, ok := ref.GO.Underlying().(*types.Struct)
return ok
}
func (ref *TypeReference) IsScalar() bool {
@ -349,7 +350,7 @@ func isIntf(t types.Type) bool {
func unwrapOmittable(t types.Type) (types.Type, bool) {
if t == nil {
return t, false
return nil, false
}
named, ok := t.(*types.Named)
if !ok {
@ -362,6 +363,9 @@ func unwrapOmittable(t types.Type) (types.Type, bool) {
}
func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret *TypeReference, err error) {
if bindTarget != nil {
bindTarget = code.Unalias(bindTarget)
}
if innerType, ok := unwrapOmittable(bindTarget); ok {
if schemaType.NonNull {
return nil, fmt.Errorf("%s is wrapped with Omittable but non-null", schemaType.Name())
@ -433,28 +437,28 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
if err != nil {
return nil, err
}
t := code.Unalias(obj.Type())
if values := b.enumValues(def); len(values) > 0 {
err = b.enumReference(ref, obj, values)
if err != nil {
return nil, err
}
} else if fun, isFunc := obj.(*types.Func); isFunc {
ref.GO = fun.Type().(*types.Signature).Params().At(0).Type()
ref.IsContext = fun.Type().(*types.Signature).Results().At(0).Type().String() == "github.com/99designs/gqlgen/graphql.ContextMarshaler"
ref.GO = code.Unalias(t.(*types.Signature).Params().At(0).Type())
ref.IsContext = code.Unalias(t.(*types.Signature).Results().At(0).Type()).String() == "github.com/99designs/gqlgen/graphql.ContextMarshaler"
ref.Marshaler = fun
ref.Unmarshaler = types.NewFunc(0, fun.Pkg(), "Unmarshal"+typeName, nil)
} else if hasMethod(obj.Type(), "MarshalGQLContext") && hasMethod(obj.Type(), "UnmarshalGQLContext") {
ref.GO = obj.Type()
} else if hasMethod(t, "MarshalGQLContext") && hasMethod(t, "UnmarshalGQLContext") {
ref.GO = t
ref.IsContext = true
ref.IsMarshaler = true
} else if hasMethod(obj.Type(), "MarshalGQL") && hasMethod(obj.Type(), "UnmarshalGQL") {
ref.GO = obj.Type()
} else if hasMethod(t, "MarshalGQL") && hasMethod(t, "UnmarshalGQL") {
ref.GO = t
ref.IsMarshaler = true
} else if underlying := basicUnderlying(obj.Type()); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String {
} else if underlying := basicUnderlying(t); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String {
// TODO delete before v1. Backwards compatibility case for named types wrapping strings (see #595)
ref.GO = obj.Type()
ref.GO = t
ref.CastType = underlying
underlyingRef, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil)
@ -465,7 +469,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
ref.Marshaler = underlyingRef.Marshaler
ref.Unmarshaler = underlyingRef.Unmarshaler
} else {
ref.GO = obj.Type()
ref.GO = t
}
ref.Target = ref.GO
@ -478,7 +482,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret
ref.GO = bindTarget
}
ref.PointersInUmarshalInput = b.cfg.ReturnPointersInUmarshalInput
ref.PointersInUnmarshalInput = b.cfg.ReturnPointersInUnmarshalInput
return ref, nil
}
@ -516,6 +520,10 @@ func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type {
}
func IsNilable(t types.Type) bool {
// Note that we use types.Unalias rather than code.Unalias here
// because we want to always check the underlying type.
// code.Unalias only unwraps aliases in Go 1.23
t = types.Unalias(t)
if namedType, isNamed := t.(*types.Named); isNamed {
return IsNilable(namedType.Underlying())
}
@ -587,10 +595,11 @@ func (b *Binder) enumReference(ref *TypeReference, obj types.Object, values map[
return fmt.Errorf("not all enum values are binded for %v", ref.Definition.Name)
}
if fn, ok := obj.Type().(*types.Signature); ok {
ref.GO = fn.Params().At(0).Type()
t := code.Unalias(obj.Type())
if fn, ok := t.(*types.Signature); ok {
ref.GO = code.Unalias(fn.Params().At(0).Type())
} else {
ref.GO = obj.Type()
ref.GO = t
}
str, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil)
@ -618,9 +627,10 @@ func (b *Binder) enumReference(ref *TypeReference, obj types.Object, values map[
return err
}
if !types.AssignableTo(valueObj.Type(), ref.GO) {
valueTyp := code.Unalias(valueObj.Type())
if !types.AssignableTo(valueTyp, ref.GO) {
return fmt.Errorf("wrong type: %v, for enum value: %v, expected type: %v, of enum: %v",
valueObj.Type(), value.Name, ref.GO, ref.Definition.Name)
valueTyp, value.Name, ref.GO, ref.Definition.Name)
}
switch valueObj.(type) {

View File

@ -2,6 +2,7 @@ package config
import (
"bytes"
"errors"
"fmt"
"go/types"
"io"
@ -40,16 +41,23 @@ type Config struct {
OmitGQLGenVersionInFileNotice bool `yaml:"omit_gqlgen_version_in_file_notice,omitempty"`
OmitRootModels bool `yaml:"omit_root_models,omitempty"`
OmitResolverFields bool `yaml:"omit_resolver_fields,omitempty"`
StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"`
ReturnPointersInUmarshalInput bool `yaml:"return_pointers_in_unmarshalinput,omitempty"`
ResolversAlwaysReturnPointers bool `yaml:"resolvers_always_return_pointers,omitempty"`
NullableInputOmittable bool `yaml:"nullable_input_omittable,omitempty"`
EnableModelJsonOmitemptyTag *bool `yaml:"enable_model_json_omitempty_tag,omitempty"`
SkipValidation bool `yaml:"skip_validation,omitempty"`
SkipModTidy bool `yaml:"skip_mod_tidy,omitempty"`
Sources []*ast.Source `yaml:"-"`
Packages *code.Packages `yaml:"-"`
Schema *ast.Schema `yaml:"-"`
OmitPanicHandler bool `yaml:"omit_panic_handler,omitempty"`
// If this is set to true, argument directives that
// decorate a field with a null value will still be called.
//
// This enables argumment directives to not just mutate
// argument values but to set them even if they're null.
CallArgumentDirectivesWithNull bool `yaml:"call_argument_directives_with_null,omitempty"`
StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"`
ReturnPointersInUnmarshalInput bool `yaml:"return_pointers_in_unmarshalinput,omitempty"`
ResolversAlwaysReturnPointers bool `yaml:"resolvers_always_return_pointers,omitempty"`
NullableInputOmittable bool `yaml:"nullable_input_omittable,omitempty"`
EnableModelJsonOmitemptyTag *bool `yaml:"enable_model_json_omitempty_tag,omitempty"`
SkipValidation bool `yaml:"skip_validation,omitempty"`
SkipModTidy bool `yaml:"skip_mod_tidy,omitempty"`
Sources []*ast.Source `yaml:"-"`
Packages *code.Packages `yaml:"-"`
Schema *ast.Schema `yaml:"-"`
// Deprecated: use Federation instead. Will be removed next release
Federated bool `yaml:"federated,omitempty"`
@ -60,15 +68,15 @@ var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"}
// DefaultConfig creates a copy of the default config
func DefaultConfig() *Config {
return &Config{
SchemaFilename: StringList{"schema.graphql"},
Model: PackageConfig{Filename: "models_gen.go"},
Exec: ExecConfig{Filename: "generated.go"},
Directives: map[string]DirectiveConfig{},
Models: TypeMap{},
StructFieldsAlwaysPointers: true,
ReturnPointersInUmarshalInput: false,
ResolversAlwaysReturnPointers: true,
NullableInputOmittable: false,
SchemaFilename: StringList{"schema.graphql"},
Model: PackageConfig{Filename: "models_gen.go"},
Exec: ExecConfig{Filename: "generated.go"},
Directives: map[string]DirectiveConfig{},
Models: TypeMap{},
StructFieldsAlwaysPointers: true,
ReturnPointersInUnmarshalInput: false,
ResolversAlwaysReturnPointers: true,
NullableInputOmittable: false,
}
}
@ -305,7 +313,7 @@ func (c *Config) injectTypesFromSchema() error {
if ma := bd.Arguments.ForName("models"); ma != nil {
if mvs, err := ma.Value.Value(nil); err == nil {
for _, mv := range mvs.([]interface{}) {
for _, mv := range mvs.([]any) {
c.Models.Add(schemaType.Name, mv.(string))
}
}
@ -318,24 +326,33 @@ func (c *Config) injectTypesFromSchema() error {
}
}
if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject {
if schemaType.Kind == ast.Object ||
schemaType.Kind == ast.InputObject ||
schemaType.Kind == ast.Interface {
for _, field := range schemaType.Fields {
if fd := field.Directives.ForName("goField"); fd != nil {
forceResolver := c.Models[schemaType.Name].Fields[field.Name].Resolver
fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName
if ra := fd.Arguments.ForName("forceResolver"); ra != nil {
if fr, err := ra.Value.Value(nil); err == nil {
forceResolver = fr.(bool)
}
}
fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName
if na := fd.Arguments.ForName("name"); na != nil {
if fr, err := na.Value.Value(nil); err == nil {
fieldName = fr.(string)
}
}
omittable := c.Models[schemaType.Name].Fields[field.Name].Omittable
if arg := fd.Arguments.ForName("omittable"); arg != nil {
if k, err := arg.Value.Value(nil); err == nil {
val := k.(bool)
omittable = &val
}
}
if c.Models[schemaType.Name].Fields == nil {
c.Models[schemaType.Name] = TypeMapEntry{
Model: c.Models[schemaType.Name].Model,
@ -347,31 +364,18 @@ func (c *Config) injectTypesFromSchema() error {
c.Models[schemaType.Name].Fields[field.Name] = TypeMapField{
FieldName: fieldName,
Resolver: forceResolver,
Omittable: omittable,
}
}
}
if efds := schemaType.Directives.ForNames("goExtraField"); len(efds) != 0 {
for _, efd := range efds {
if fn := efd.Arguments.ForName("name"); fn != nil {
extraFieldName := ""
if fnv, err := fn.Value.Value(nil); err == nil {
extraFieldName = fnv.(string)
}
if extraFieldName == "" {
return fmt.Errorf(
"argument 'name' for directive @goExtraField (src: %s, line: %d) cannot by empty",
efd.Position.Src.Name,
efd.Position.Line,
)
}
if t := efd.Arguments.ForName("type"); t != nil {
extraField := ModelExtraField{}
if t := efd.Arguments.ForName("type"); t != nil {
if tv, err := t.Value.Value(nil); err == nil {
extraField.Type = tv.(string)
}
if tv, err := t.Value.Value(nil); err == nil {
extraField.Type = tv.(string)
}
if extraField.Type == "" {
@ -394,13 +398,28 @@ func (c *Config) injectTypesFromSchema() error {
}
}
typeMapEntry := c.Models[schemaType.Name]
if typeMapEntry.ExtraFields == nil {
typeMapEntry.ExtraFields = make(map[string]ModelExtraField)
extraFieldName := ""
if fn := efd.Arguments.ForName("name"); fn != nil {
if fnv, err := fn.Value.Value(nil); err == nil {
extraFieldName = fnv.(string)
}
}
c.Models[schemaType.Name] = typeMapEntry
c.Models[schemaType.Name].ExtraFields[extraFieldName] = extraField
if extraFieldName == "" {
// Embeddable fields
typeMapEntry := c.Models[schemaType.Name]
typeMapEntry.EmbedExtraFields = append(typeMapEntry.EmbedExtraFields, extraField)
c.Models[schemaType.Name] = typeMapEntry
} else {
// Regular fields
typeMapEntry := c.Models[schemaType.Name]
if typeMapEntry.ExtraFields == nil {
typeMapEntry.ExtraFields = make(map[string]ModelExtraField)
}
c.Models[schemaType.Name] = typeMapEntry
c.Models[schemaType.Name].ExtraFields[extraFieldName] = extraField
}
}
}
}
@ -439,12 +458,14 @@ type TypeMapEntry struct {
EnumValues map[string]EnumValue `yaml:"enum_values,omitempty"`
// Key is the Go name of the field.
ExtraFields map[string]ModelExtraField `yaml:"extraFields,omitempty"`
ExtraFields map[string]ModelExtraField `yaml:"extraFields,omitempty"`
EmbedExtraFields []ModelExtraField `yaml:"embedExtraFields,omitempty"`
}
type TypeMapField struct {
Resolver bool `yaml:"resolver"`
FieldName string `yaml:"fieldName"`
Omittable *bool `yaml:"omittable"`
GeneratedMethod string `yaml:"-"`
}
@ -480,7 +501,7 @@ type ModelExtraField struct {
type StringList []string
func (a *StringList) UnmarshalYAML(unmarshal func(interface{}) error) error {
func (a *StringList) UnmarshalYAML(unmarshal func(any) error) error {
var single string
err := unmarshal(&single)
if err == nil {
@ -562,11 +583,11 @@ func (c *Config) check() error {
Declaree: "federation",
})
if c.Federation.ImportPath() != c.Exec.ImportPath() {
return fmt.Errorf("federation and exec must be in the same package")
return errors.New("federation and exec must be in the same package")
}
}
if c.Federated {
return fmt.Errorf("federated has been removed, instead use\nfederation:\n filename: path/to/federated.go")
return errors.New("federated has been removed, instead use\nfederation:\n filename: path/to/federated.go")
}
for importPath, pkg := range fileList {
@ -641,7 +662,7 @@ func (tm TypeMap) ReferencedPackages() []string {
return pkgs
}
func (tm TypeMap) Add(name string, goType string) {
func (tm TypeMap) Add(name, goType string) {
modelCfg := tm[name]
modelCfg.Model = append(modelCfg.Model, goType)
tm[name] = modelCfg
@ -655,6 +676,16 @@ func (tm TypeMap) ForceGenerate(name string, forceGenerate bool) {
type DirectiveConfig struct {
SkipRuntime bool `yaml:"skip_runtime"`
// If the directive implementation is statically defined, don't provide a hook for it
// in the generated server. This is useful for directives that are implemented
// by plugins or the runtime itself.
//
// The function implemmentation should be provided here as a string.
//
// The function should have the following signature:
// func(ctx context.Context, obj any, next graphql.Resolver[, directive arguments if any]) (res any, err error)
Implementation *string
}
func inStrSlice(haystack []string, needle string) bool {

View File

@ -1,6 +1,7 @@
package config
import (
"errors"
"fmt"
"go/types"
"path/filepath"
@ -38,15 +39,15 @@ func (r *ExecConfig) Check() error {
switch r.Layout {
case ExecLayoutSingleFile:
if r.Filename == "" {
return fmt.Errorf("filename must be specified when using single-file layout")
return errors.New("filename must be specified when using single-file layout")
}
if !strings.HasSuffix(r.Filename, ".go") {
return fmt.Errorf("filename should be path to a go source file when using single-file layout")
return errors.New("filename should be path to a go source file when using single-file layout")
}
r.Filename = abs(r.Filename)
case ExecLayoutFollowSchema:
if r.DirName == "" {
return fmt.Errorf("dir must be specified when using follow-schema layout")
return errors.New("dir must be specified when using follow-schema layout")
}
r.DirName = abs(r.DirName)
default:
@ -54,7 +55,7 @@ func (r *ExecConfig) Check() error {
}
if strings.ContainsAny(r.Package, "./\\") {
return fmt.Errorf("package should be the output package name only, do not include the output filename")
return errors.New("package should be the output package name only, do not include the output filename")
}
if r.Package == "" && r.Dir() != "" {

View File

@ -14,7 +14,7 @@ type GoInitialismsConfig struct {
Initialisms []string `yaml:"initialisms"`
}
// setInitialisms adjustes GetInitialisms based on its settings.
// setInitialisms adjusts GetInitialisms based on its settings.
func (i GoInitialismsConfig) setInitialisms() {
toUse := i.determineGoInitialisms()
templates.GetInitialisms = func() map[string]bool {
@ -22,7 +22,7 @@ func (i GoInitialismsConfig) setInitialisms() {
}
}
// determineGoInitialisms returns the Go initialims to be used, based on its settings.
// determineGoInitialisms returns the Go initialisms to be used, based on its settings.
func (i GoInitialismsConfig) determineGoInitialisms() (initialismsToUse map[string]bool) {
if i.ReplaceDefaults {
initialismsToUse = make(map[string]bool, len(i.Initialisms))

View File

@ -1,7 +1,7 @@
package config
import (
"fmt"
"errors"
"go/types"
"path/filepath"
"strings"
@ -44,13 +44,13 @@ func (c *PackageConfig) IsDefined() bool {
func (c *PackageConfig) Check() error {
if strings.ContainsAny(c.Package, "./\\") {
return fmt.Errorf("package should be the output package name only, do not include the output filename")
return errors.New("package should be the output package name only, do not include the output filename")
}
if c.Filename == "" {
return fmt.Errorf("filename must be specified")
return errors.New("filename must be specified")
}
if !strings.HasSuffix(c.Filename, ".go") {
return fmt.Errorf("filename should be path to a go source file")
return errors.New("filename should be path to a go source file")
}
c.Filename = abs(c.Filename)

View File

@ -1,6 +1,7 @@
package config
import (
"errors"
"fmt"
"go/types"
"path/filepath"
@ -59,7 +60,7 @@ func (r *ResolverConfig) Check() error {
}
if strings.ContainsAny(r.Package, "./\\") {
return fmt.Errorf("package should be the output package name only, do not include the output filename")
return errors.New("package should be the output package name only, do not include the output filename")
}
if r.Package == "" && r.Dir() != "" {

View File

@ -1,6 +1,7 @@
package codegen
import (
"errors"
"fmt"
"os"
"path/filepath"
@ -34,7 +35,7 @@ type Data struct {
MutationRoot *Object
SubscriptionRoot *Object
AugmentedSources []AugmentedSource
Plugins []interface{}
Plugins []any
}
func (d *Data) HasEmbeddableSources() bool {
@ -63,6 +64,30 @@ type builder struct {
Directives map[string]*Directive
}
// Get only the directives which should have a user provided definition on server instantiation
func (d *Data) UserDirectives() DirectiveList {
res := DirectiveList{}
directives := d.Directives()
for k, directive := range directives {
if directive.Implementation == nil {
res[k] = directive
}
}
return res
}
// Get only the directives which should have a statically provided definition
func (d *Data) BuiltInDirectives() DirectiveList {
res := DirectiveList{}
directives := d.Directives()
for k, directive := range directives {
if directive.Implementation != nil {
res[k] = directive
}
}
return res
}
// Get only the directives which are defined in the config's sources.
func (d *Data) Directives() DirectiveList {
res := DirectiveList{}
@ -77,7 +102,7 @@ func (d *Data) Directives() DirectiveList {
return res
}
func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
func BuildData(cfg *config.Config, plugins ...any) (*Data, error) {
// We reload all packages to allow packages to be compared correctly.
cfg.ReloadAllPackages()
@ -96,7 +121,7 @@ func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
dataDirectives := make(map[string]*Directive)
for name, d := range b.Directives {
if !d.Builtin {
if !d.SkipRuntime {
dataDirectives[name] = d
}
}
@ -137,7 +162,7 @@ func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
if s.Schema.Query != nil {
s.QueryRoot = s.Objects.ByName(s.Schema.Query.Name)
} else {
return nil, fmt.Errorf("query entry point missing")
return nil, errors.New("query entry point missing")
}
if s.Schema.Mutation != nil {
@ -170,7 +195,7 @@ func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
}
// otherwise show a generic error message
return nil, fmt.Errorf("invalid types were encountered while traversing the go source code, this probably means the invalid code generated isnt correct. add try adding -v to debug")
return nil, errors.New("invalid types were encountered while traversing the go source code, this probably means the invalid code generated isnt correct. add try adding -v to debug")
}
aSources := []AugmentedSource{}
for _, s := range cfg.Sources {
@ -204,7 +229,7 @@ func BuildData(cfg *config.Config, plugins ...interface{}) (*Data, error) {
func (b *builder) injectIntrospectionRoots(s *Data) error {
obj := s.Objects.ByName(b.Schema.Query.Name)
if obj == nil {
return fmt.Errorf("root query type must be defined")
return errors.New("root query type must be defined")
}
__type, err := b.buildField(obj, &ast.FieldDefinition{

View File

@ -7,6 +7,7 @@ import (
"github.com/vektah/gqlparser/v2/ast"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
)
@ -19,9 +20,10 @@ func (dl DirectiveList) LocationDirectives(location string) DirectiveList {
type Directive struct {
*ast.DirectiveDefinition
Name string
Args []*FieldArgument
Builtin bool
Name string
Args []*FieldArgument
config.DirectiveConfig
}
// IsLocation check location directive
@ -82,7 +84,7 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) {
DirectiveDefinition: dir,
Name: name,
Args: args,
Builtin: b.Config.Directives[name].SkipRuntime,
DirectiveConfig: b.Config.Directives[name],
}
}
@ -92,7 +94,7 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) {
func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) {
dirs := make([]*Directive, len(list))
for i, d := range list {
argValues := make(map[string]interface{}, len(d.Arguments))
argValues := make(map[string]any, len(d.Arguments))
for _, da := range d.Arguments {
val, err := da.Value.Value(nil)
if err != nil {
@ -122,7 +124,7 @@ func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) {
Name: d.Name,
Args: args,
DirectiveDefinition: list[i].Definition,
Builtin: b.Config.Directives[d.Name].SkipRuntime,
DirectiveConfig: b.Config.Directives[d.Name],
}
}
@ -162,8 +164,12 @@ func (d *Directive) ResolveArgs(obj string, next int) string {
return strings.Join(args, ", ")
}
func (d *Directive) CallName() string {
return ucFirst(d.Name)
}
func (d *Directive) Declaration() string {
res := ucFirst(d.Name) + " func(ctx context.Context, obj interface{}, next graphql.Resolver"
res := d.CallName() + " func(ctx context.Context, obj interface{}, next graphql.Resolver"
for _, arg := range d.Args {
res += fmt.Sprintf(", %s %s", templates.ToGoPrivate(arg.Name), templates.CurrentImports.LookupType(arg.TypeReference.GO))
@ -172,3 +178,23 @@ func (d *Directive) Declaration() string {
res += ") (res interface{}, err error)"
return res
}
func (d *Directive) IsBuiltIn() bool {
return d.Implementation != nil
}
func (d *Directive) CallPath() string {
if d.IsBuiltIn() {
return "builtInDirective" + d.CallName()
}
return "ec.directives." + d.CallName()
}
func (d *Directive) FunctionImpl() string {
if d.Implementation == nil {
return ""
}
return d.CallPath() + " = " + *d.Implementation
}

View File

@ -1,23 +1,29 @@
{{ define "implDirectives" }}{{ $in := .DirectiveObjName }}
{{ $zeroVal := .TypeReference.GO | ref}}
{{- range $i, $directive := .ImplDirectives -}}
directive{{add $i 1}} := func(ctx context.Context) (interface{}, error) {
{{- range $arg := $directive.Args }}
{{- if notNil "Value" $arg }}
{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Value | dump }})
if err != nil{
return nil, err
var zeroVal {{$zeroVal}}
return zeroVal, err
}
{{- else if notNil "Default" $arg }}
{{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Default | dump }})
if err != nil{
return nil, err
var zeroVal {{$zeroVal}}
return zeroVal, err
}
{{- end }}
{{- end }}
if ec.directives.{{$directive.Name|ucFirst}} == nil {
return nil, errors.New("directive {{$directive.Name}} is not implemented")
}
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs $in $i }})
{{- if not $directive.IsBuiltIn}}
if {{$directive.CallPath}} == nil {
var zeroVal {{$zeroVal}}
return zeroVal, errors.New("directive {{$directive.Name}} is not implemented")
}
{{- end}}
return {{$directive.CallPath}}({{$directive.ResolveArgs $in $i }})
}
{{ end -}}
{{ end }}
@ -37,10 +43,7 @@
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
if ec.directives.{{$directive.Name|ucFirst}} == nil {
return nil, errors.New("directive {{$directive.Name}} is not implemented")
}
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
{{- template "callDirective" $directive -}}
}
{{- end }}
}
@ -57,6 +60,15 @@
return graphql.Null
{{end}}
{{define "callDirective"}}
{{- if not .IsBuiltIn}}
if {{.CallPath}} == nil {
return nil, errors.New("directive {{.Name}} is not implemented")
}
{{- end}}
return {{.CallPath}}({{.CallArgs}})
{{end}}
{{ if .Directives.LocationDirectives "QUERY" }}
func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler {
{{ template "queryDirectives" .Directives.LocationDirectives "QUERY" }}
@ -87,10 +99,7 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
if ec.directives.{{$directive.Name|ucFirst}} == nil {
return nil, errors.New("directive {{$directive.Name}} is not implemented")
}
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
{{- template "callDirective" $directive -}}
}
{{- end }}
}
@ -130,10 +139,7 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as
{{- end }}
n := next
next = func(ctx context.Context) (interface{}, error) {
if ec.directives.{{$directive.Name|ucFirst}} == nil {
return nil, errors.New("directive {{$directive.Name}} is not implemented")
}
return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}})
{{- template "callDirective" $directive -}}
}
{{- end }}
}

View File

@ -16,6 +16,7 @@ import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/code"
)
type Field struct {
@ -31,7 +32,7 @@ type Field struct {
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)
Object *Object // A link back to the parent object
Default interface{} // The default value
Default any // The default value
Stream bool // does this field return a channel?
Directives []*Directive
}
@ -144,7 +145,7 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) {
f.GoFieldName = b.Config.Models[obj.Name].Fields[f.Name].FieldName
}
target, err := b.findBindTarget(obj.Type.(*types.Named), f.GoFieldName)
target, err := b.findBindTarget(obj.Type, f.GoFieldName)
if err != nil {
return err
}
@ -174,7 +175,7 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) {
} else if s := sig.Results(); s.Len() == 2 && s.At(1).Type().String() == "bool" {
f.VOkFunc = true
} else if sig.Results().Len() != 2 {
return fmt.Errorf("method has wrong number of args")
return errors.New("method has wrong number of args")
}
params := sig.Params()
// If the first argument is the context, remove it from the comparison and set
@ -229,7 +230,7 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) {
}
// findBindTarget attempts to match the name to a field or method on a Type
// with the following priorites:
// with the following priorities:
// 1. Any Fields with a struct tag (see config.StructTag). Errors if more than one match is found
// 2. Any method or field with a matching name. Errors if more than one match is found
// 3. Same logic again for embedded fields
@ -380,7 +381,7 @@ func (b *builder) findBindStructEmbedsTarget(strukt *types.Struct, name string)
continue
}
fieldType := field.Type()
fieldType := code.Unalias(field.Type())
if ptr, ok := fieldType.(*types.Pointer); ok {
fieldType = ptr.Elem()
}
@ -442,7 +443,7 @@ func (f *Field) ImplDirectives() []*Directive {
loc = ast.LocationInputFieldDefinition
}
for i := range f.Directives {
if !f.Directives[i].Builtin &&
if !f.Directives[i].SkipRuntime &&
(f.Directives[i].IsLocation(loc, ast.LocationObject) || f.Directives[i].IsLocation(loc, ast.LocationInputObject)) {
d = append(d, f.Directives[i])
}

View File

@ -10,12 +10,14 @@ func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Contex
return {{ $null }}
}
ctx = graphql.WithFieldContext(ctx, fc)
{{- if not $.Config.OmitPanicHandler }}
defer func () {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = {{ $null }}
}
}()
{{- end }}
{{- if $field.TypeReference.IsRoot }}
{{- if $field.TypeReference.IsPtr }}
res := &{{ $field.TypeReference.Elem.GO | ref }}{}
@ -95,12 +97,14 @@ func (ec *executionContext) {{ $field.FieldContextFunc }}({{ if not $field.Args
},
}
{{- if $field.Args }}
{{- if not $.Config.OmitPanicHandler }}
defer func () {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
{{- end }}
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.{{ $field.ArgsFunc }}(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)

View File

@ -20,7 +20,7 @@ var codegenTemplates embed.FS
func GenerateCode(data *Data) error {
if !data.Config.Exec.IsDefined() {
return fmt.Errorf("missing exec config")
return errors.New("missing exec config")
}
switch data.Config.Exec.Layout {

View File

@ -1,3 +1,4 @@
{{/* Context object: codegen.Data */}}
{{ reserveImport "context" }}
{{ reserveImport "fmt" }}
{{ reserveImport "io" }}
@ -46,7 +47,7 @@
}
type DirectiveRoot struct {
{{ range $directive := .Directives }}
{{ range $directive := .UserDirectives }}
{{- $directive.Declaration }}
{{ end }}
}
@ -93,6 +94,12 @@
{{- end }}
{{- end }}
{{ range $directive := .BuiltInDirectives }}
var (
{{- $directive.FunctionImpl }}
)
{{ end }}
{{ if eq .Config.Exec.Layout "single-file" }}
type executableSchema struct {
schema *ast.Schema

View File

@ -1,10 +1,10 @@
{{- range $input := .Inputs }}
{{- if not .HasUnmarshal }}
{{- $it := "it" }}
{{- if .PointersInUmarshalInput }}
{{- if .PointersInUnmarshalInput }}
{{- $it = "&it" }}
{{- end }}
func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{ if .PointersInUmarshalInput }}*{{ end }}{{.Type | ref}}, error) {
func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{ if .PointersInUnmarshalInput }}*{{ end }}{{.Type | ref}}, error) {
{{- if $input.IsMap }}
it := make(map[string]interface{}, len(obj.(map[string]interface{})))
{{- else }}

View File

@ -26,15 +26,15 @@ const (
type Object struct {
*ast.Definition
Type types.Type
ResolverInterface types.Type
Root bool
Fields []*Field
Implements []*ast.Definition
DisableConcurrency bool
Stream bool
Directives []*Directive
PointersInUmarshalInput bool
Type types.Type
ResolverInterface types.Type
Root bool
Fields []*Field
Implements []*ast.Definition
DisableConcurrency bool
Stream bool
Directives []*Directive
PointersInUnmarshalInput bool
}
func (b *builder) buildObject(typ *ast.Definition) (*Object, error) {
@ -44,12 +44,12 @@ func (b *builder) buildObject(typ *ast.Definition) (*Object, error) {
}
caser := cases.Title(language.English, cases.NoLower)
obj := &Object{
Definition: typ,
Root: b.Config.IsRoot(typ),
DisableConcurrency: typ == b.Schema.Mutation,
Stream: typ == b.Schema.Subscription,
Directives: dirs,
PointersInUmarshalInput: b.Config.ReturnPointersInUmarshalInput,
Definition: typ,
Root: b.Config.IsRoot(typ),
DisableConcurrency: typ == b.Schema.Mutation,
Stream: typ == b.Schema.Subscription,
Directives: dirs,
PointersInUnmarshalInput: b.Config.ReturnPointersInUnmarshalInput,
ResolverInterface: types.NewNamed(
types.NewTypeName(0, b.Config.Exec.Pkg(), caser.String(typ.Name)+"Resolver", nil),
nil,

View File

@ -49,11 +49,13 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec
field := field
innerFunc := func(ctx context.Context, {{ if $field.TypeReference.GQL.NonNull }}fs{{ else }}_{{ end }} *graphql.FieldSet) (res graphql.Marshaler) {
{{- if not $.Config.OmitPanicHandler }}
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
{{- end }}
res = ec._{{$object.Name}}_{{$field.Name}}(ctx, field{{if not $object.Root}}, obj{{end}})
{{- if $field.TypeReference.GQL.NonNull }}
if res == graphql.Null {

View File

@ -1,3 +1,4 @@
{{/* Context object: codegen.Data */}}
{{ reserveImport "context" }}
{{ reserveImport "fmt" }}
{{ reserveImport "io" }}
@ -45,7 +46,7 @@ type ResolverRoot interface {
}
type DirectiveRoot struct {
{{ range $directive := .Directives }}
{{ range $directive := .UserDirectives }}
{{- $directive.Declaration }}
{{ end }}
}
@ -67,6 +68,12 @@ type ComplexityRoot struct {
{{- end }}
}
{{ range $directive := .BuiltInDirectives }}
var (
{{- $directive.FunctionImpl }}
)
{{ end }}
type executableSchema struct {
schema *ast.Schema
resolvers ResolverRoot

View File

@ -1,6 +1,7 @@
package templates
import (
"errors"
"fmt"
"go/types"
"strconv"
@ -62,11 +63,11 @@ func (s *Imports) Reserve(path string, aliases ...string) (string, error) {
if existing.Alias == alias {
return "", nil
}
return "", fmt.Errorf("ambient import already exists")
return "", errors.New("ambient import already exists")
}
if alias := s.findByAlias(alias); alias != nil {
return "", fmt.Errorf("ambient import collides on an alias")
return "", errors.New("ambient import collides on an alias")
}
s.imports = append(s.imports, &Import{

View File

@ -2,6 +2,7 @@ package templates
import (
"bytes"
"errors"
"fmt"
"go/types"
"io/fs"
@ -52,7 +53,7 @@ type Options struct {
// FileNotice is notice written below the package line
FileNotice string
// Data will be passed to the template execution.
Data interface{}
Data any
Funcs template.FuncMap
// Packages cache, you can find me on config.Config
@ -71,7 +72,7 @@ var (
// files inside the directory where you wrote the plugin.
func Render(cfg Options) error {
if CurrentImports != nil {
panic(fmt.Errorf("recursive or concurrent call to RenderToFile detected"))
panic(errors.New("recursive or concurrent call to RenderToFile detected"))
}
CurrentImports = &Imports{packages: cfg.Packages, destDir: filepath.Dir(cfg.Filename)}
@ -184,7 +185,7 @@ func parseTemplates(cfg Options, t *template.Template) (*template.Template, erro
return t, nil
}
func center(width int, pad string, s string) string {
func center(width int, pad, s string) string {
if len(s)+2 > width {
return s
}
@ -206,6 +207,7 @@ func Funcs() template.FuncMap {
"call": Call,
"prefixLines": prefixLines,
"notNil": notNil,
"strSplit": StrSplit,
"reserveImport": CurrentImports.Reserve,
"lookupImport": CurrentImports.Lookup,
"go": ToGo,
@ -215,7 +217,7 @@ func Funcs() template.FuncMap {
"add": func(a, b int) int {
return a + b
},
"render": func(filename string, tpldata interface{}) (*bytes.Buffer, error) {
"render": func(filename string, tpldata any) (*bytes.Buffer, error) {
return render(resolveName(filename, 0), tpldata)
},
}
@ -493,18 +495,18 @@ func wordWalker(str string, f func(*wordInfo)) {
if initialisms[upperWord] {
// If the uppercase word (string(runes[w:i]) is "ID" or "IP"
// AND
// the word is the first two characters of the str
// the word is the first two characters of the current word
// AND
// that is not the end of the word
// AND
// the length of the string is greater than 3
// the length of the remaining string is greater than 3
// AND
// the third rune is an uppercase one
// THEN
// do NOT count this as an initialism.
switch upperWord {
case "ID", "IP":
if word == str[:2] && !eow && len(str) > 3 && unicode.IsUpper(runes[3]) {
if remainingRunes := runes[w:]; word == string(remainingRunes[:2]) && !eow && len(remainingRunes) > 3 && unicode.IsUpper(remainingRunes[3]) {
continue
}
}
@ -567,7 +569,7 @@ func rawQuote(s string) string {
return "`" + strings.ReplaceAll(s, "`", "`+\"`\"+`") + "`"
}
func notNil(field string, data interface{}) bool {
func notNil(field string, data any) bool {
v := reflect.ValueOf(data)
if v.Kind() == reflect.Ptr {
@ -581,12 +583,16 @@ func notNil(field string, data interface{}) bool {
return val.IsValid() && !val.IsNil()
}
func Dump(val interface{}) string {
func StrSplit(s, sep string) []string {
return strings.Split(s, sep)
}
func Dump(val any) string {
switch val := val.(type) {
case int:
return strconv.Itoa(val)
case int64:
return fmt.Sprintf("%d", val)
return strconv.FormatInt(val, 10)
case float64:
return fmt.Sprintf("%f", val)
case string:
@ -595,13 +601,13 @@ func Dump(val interface{}) string {
return strconv.FormatBool(val)
case nil:
return "nil"
case []interface{}:
case []any:
var parts []string
for _, part := range val {
parts = append(parts, Dump(part))
}
return "[]interface{}{" + strings.Join(parts, ",") + "}"
case map[string]interface{}:
case map[string]any:
buf := bytes.Buffer{}
buf.WriteString("map[string]interface{}{")
var keys []string
@ -641,7 +647,7 @@ func resolveName(name string, skip int) string {
return filepath.Join(filepath.Dir(callerFile), name)
}
func render(filename string, tpldata interface{}) (*bytes.Buffer, error) {
func render(filename string, tpldata any) (*bytes.Buffer, error) {
t := template.New("").Funcs(Funcs())
b, err := os.ReadFile(filename)
@ -688,7 +694,7 @@ var pkgReplacer = strings.NewReplacer(
func TypeIdentifier(t types.Type) string {
res := ""
for {
switch it := t.(type) {
switch it := code.Unalias(t).(type) {
case *types.Pointer:
t.Underlying()
res += "ᚖ"
@ -765,6 +771,8 @@ var CommonInitialisms = map[string]bool{
"XMPP": true,
"XSRF": true,
"XSS": true,
"AWS": true,
"GCP": true,
}
// GetInitialisms returns the initialisms to capitalize in Go names. If unchanged, default initialisms will be returned

View File

@ -76,9 +76,9 @@
return res, graphql.ErrorOnPath(ctx, err)
{{- else }}
res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v)
{{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUmarshalInput) }}
{{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUnmarshalInput) }}
return &res, graphql.ErrorOnPath(ctx, err)
{{- else if and (not $type.IsNilable) $type.PointersInUmarshalInput }}
{{- else if and (not $type.IsNilable) $type.PointersInUnmarshalInput }}
return *res, graphql.ErrorOnPath(ctx, err)
{{- else }}
return res, graphql.ErrorOnPath(ctx, err)
@ -115,12 +115,14 @@
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
{{- if not $.Config.OmitPanicHandler }}
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
{{- end }}
if !isLen1 {
defer wg.Done()
}

View File

@ -6,7 +6,7 @@ import (
"github.com/99designs/gqlgen/graphql"
)
func Calculate(es graphql.ExecutableSchema, op *ast.OperationDefinition, vars map[string]interface{}) int {
func Calculate(es graphql.ExecutableSchema, op *ast.OperationDefinition, vars map[string]any) int {
walker := complexityWalker{
es: es,
schema: es.Schema(),
@ -18,7 +18,7 @@ func Calculate(es graphql.ExecutableSchema, op *ast.OperationDefinition, vars ma
type complexityWalker struct {
es graphql.ExecutableSchema
schema *ast.Schema
vars map[string]interface{}
vars map[string]any
}
func (cw complexityWalker) selectionSetComplexity(selectionSet ast.SelectionSet) int {
@ -57,7 +57,7 @@ func (cw complexityWalker) selectionSetComplexity(selectionSet ast.SelectionSet)
return complexity
}
func (cw complexityWalker) interfaceFieldComplexity(def *ast.Definition, field string, childComplexity int, args map[string]interface{}) int {
func (cw complexityWalker) interfaceFieldComplexity(def *ast.Definition, field string, childComplexity int, args map[string]any) int {
// Interfaces don't have their own separate field costs, so they have to assume the worst case.
// We iterate over all implementors and choose the most expensive one.
maxComplexity := 0
@ -71,7 +71,7 @@ func (cw complexityWalker) interfaceFieldComplexity(def *ast.Definition, field s
return maxComplexity
}
func (cw complexityWalker) fieldComplexity(object, field string, childComplexity int, args map[string]interface{}) int {
func (cw complexityWalker) fieldComplexity(object, field string, childComplexity int, args map[string]any) int {
if customComplexity, ok := cw.es.Complexity(object, field, childComplexity, args); ok && customComplexity >= childComplexity {
return customComplexity
}

View File

@ -5,7 +5,7 @@ import (
"io"
)
func MarshalAny(v interface{}) Marshaler {
func MarshalAny(v any) Marshaler {
return WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(v)
if err != nil {
@ -14,6 +14,6 @@ func MarshalAny(v interface{}) Marshaler {
})
}
func UnmarshalAny(v interface{}) (interface{}, error) {
func UnmarshalAny(v any) (any, error) {
return v, nil
}

View File

@ -3,24 +3,25 @@ package graphql
import (
"fmt"
"io"
"strconv"
"strings"
)
func MarshalBoolean(b bool) Marshaler {
if b {
return WriterFunc(func(w io.Writer) { w.Write(trueLit) })
}
return WriterFunc(func(w io.Writer) { w.Write(falseLit) })
str := strconv.FormatBool(b)
return WriterFunc(func(w io.Writer) { w.Write([]byte(str)) })
}
func UnmarshalBoolean(v interface{}) (bool, error) {
func UnmarshalBoolean(v any) (bool, error) {
switch v := v.(type) {
case string:
return strings.ToLower(v) == "true", nil
return strings.EqualFold(v, "true"), nil
case int:
return v != 0, nil
case bool:
return v, nil
case nil:
return false, nil
default:
return false, fmt.Errorf("%T is not a bool", v)
}

View File

@ -3,27 +3,29 @@ package graphql
import "context"
// Cache is a shared store for APQ and query AST caching
type Cache interface {
type Cache[T any] interface {
// Get looks up a key's value from the cache.
Get(ctx context.Context, key string) (value interface{}, ok bool)
Get(ctx context.Context, key string) (value T, ok bool)
// Add adds a value to the cache.
Add(ctx context.Context, key string, value interface{})
Add(ctx context.Context, key string, value T)
}
// MapCache is the simplest implementation of a cache, because it can not evict it should only be used in tests
type MapCache map[string]interface{}
type MapCache[T any] map[string]T
// Get looks up a key's value from the cache.
func (m MapCache) Get(_ context.Context, key string) (value interface{}, ok bool) {
func (m MapCache[T]) Get(_ context.Context, key string) (value T, ok bool) {
v, ok := m[key]
return v, ok
}
// Add adds a value to the cache.
func (m MapCache) Add(_ context.Context, key string, value interface{}) { m[key] = value }
func (m MapCache[T]) Add(_ context.Context, key string, value T) { m[key] = value }
type NoCache struct{}
type NoCache[T any, T2 *T] struct{}
func (n NoCache) Get(_ context.Context, _ string) (value interface{}, ok bool) { return nil, false }
func (n NoCache) Add(_ context.Context, _ string, _ interface{}) {}
var _ Cache[*string] = (*NoCache[string, *string])(nil)
func (n NoCache[T, T2]) Get(_ context.Context, _ string) (value T2, ok bool) { return nil, false }
func (n NoCache[T, T2]) Add(_ context.Context, _ string, _ T2) {}

View File

@ -5,51 +5,51 @@ import (
)
// CoerceList applies coercion from a single value to a list.
func CoerceList(v interface{}) []interface{} {
var vSlice []interface{}
func CoerceList(v any) []any {
var vSlice []any
if v != nil {
switch v := v.(type) {
case []interface{}:
case []any:
// already a slice no coercion required
vSlice = v
case []string:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []json.Number:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []bool:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []map[string]interface{}:
case []map[string]any:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []float64:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []float32:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []int:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []int32:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
case []int64:
if len(v) > 0 {
vSlice = []interface{}{v[0]}
vSlice = []any{v[0]}
}
default:
vSlice = []interface{}{v}
vSlice = []any{v}
}
}
return vSlice

View File

@ -19,13 +19,13 @@ type FieldContext struct {
// The name of the type this field belongs to
Object string
// These are the args after processing, they can be mutated in middleware to change what the resolver will get.
Args map[string]interface{}
Args map[string]any
// The raw field
Field CollectedField
// The index of array in path.
Index *int
// The result object of resolver
Result interface{}
Result any
// IsMethod indicates if the resolver is a method
IsMethod bool
// IsResolver indicates if the field has a user-specified resolver
@ -98,7 +98,7 @@ func WithFieldContext(ctx context.Context, rc *FieldContext) context.Context {
return context.WithValue(ctx, resolverCtx, rc)
}
func equalPath(a ast.Path, b ast.Path) bool {
func equalPath(a, b ast.Path) bool {
if len(a) != len(b) {
return false
}

View File

@ -14,7 +14,7 @@ type RequestContext = OperationContext
type OperationContext struct {
RawQuery string
Variables map[string]interface{}
Variables map[string]any
OperationName string
Doc *ast.QueryDocument
Headers http.Header
@ -36,7 +36,7 @@ func (c *OperationContext) Validate(ctx context.Context) error {
return errors.New("field 'RawQuery' is required")
}
if c.Variables == nil {
c.Variables = make(map[string]interface{})
c.Variables = make(map[string]any)
}
if c.ResolverMiddleware == nil {
return errors.New("field 'ResolverMiddleware' is required")
@ -103,7 +103,7 @@ Next:
// Errorf sends an error string to the client, passing it through the formatter.
// Deprecated: use graphql.AddErrorf(ctx, err) instead
func (c *OperationContext) Errorf(ctx context.Context, format string, args ...interface{}) {
func (c *OperationContext) Errorf(ctx context.Context, format string, args ...any) {
AddErrorf(ctx, format, args...)
}
@ -120,6 +120,6 @@ func (c *OperationContext) Error(ctx context.Context, err error) {
AddError(ctx, err)
}
func (c *OperationContext) Recover(ctx context.Context, err interface{}) error {
func (c *OperationContext) Recover(ctx context.Context, err any) error {
return ErrorOnPath(ctx, c.RecoverFunc(ctx, err))
}

View File

@ -15,7 +15,7 @@ type responseContext struct {
errors gqlerror.List
errorsMu sync.Mutex
extensions map[string]interface{}
extensions map[string]any
extensionsMu sync.Mutex
}
@ -45,7 +45,7 @@ func WithFreshResponseContext(ctx context.Context) context.Context {
}
// AddErrorf writes a formatted error to the client, first passing it through the error presenter.
func AddErrorf(ctx context.Context, format string, args ...interface{}) {
func AddErrorf(ctx context.Context, format string, args ...any) {
AddError(ctx, fmt.Errorf(format, args...))
}
@ -60,7 +60,7 @@ func AddError(ctx context.Context, err error) {
c.errors = append(c.errors, presentedError)
}
func Recover(ctx context.Context, err interface{}) (userMessage error) {
func Recover(ctx context.Context, err any) (userMessage error) {
c := getResponseContext(ctx)
return ErrorOnPath(ctx, c.recover(ctx, err))
}
@ -125,13 +125,13 @@ func GetErrors(ctx context.Context) gqlerror.List {
}
// RegisterExtension allows you to add a new extension into the graphql response
func RegisterExtension(ctx context.Context, key string, value interface{}) {
func RegisterExtension(ctx context.Context, key string, value any) {
c := getResponseContext(ctx)
c.extensionsMu.Lock()
defer c.extensionsMu.Unlock()
if c.extensions == nil {
c.extensions = make(map[string]interface{})
c.extensions = make(map[string]any)
}
if _, ok := c.extensions[key]; ok {
@ -142,16 +142,16 @@ func RegisterExtension(ctx context.Context, key string, value interface{}) {
}
// GetExtensions returns any extensions registered in the current result context
func GetExtensions(ctx context.Context) map[string]interface{} {
func GetExtensions(ctx context.Context) map[string]any {
ext := getResponseContext(ctx).extensions
if ext == nil {
return map[string]interface{}{}
return map[string]any{}
}
return ext
}
func GetExtension(ctx context.Context, name string) interface{} {
func GetExtension(ctx context.Context, name string) any {
ext := getResponseContext(ctx).extensions
if ext == nil {
return nil

View File

@ -1,17 +1,17 @@
package graphql
import (
"fmt"
"errors"
"time"
dur "github.com/sosodev/duration"
)
// UnmarshalDuration returns the duration from a string in ISO8601 format
func UnmarshalDuration(v interface{}) (time.Duration, error) {
func UnmarshalDuration(v any) (time.Duration, error) {
input, ok := v.(string)
if !ok {
return 0, fmt.Errorf("input must be a string")
return 0, errors.New("input must be a string")
}
d2, err := dur.Parse(input)

View File

@ -40,7 +40,7 @@ func Set(err error, value string) {
}
if gqlErr.Extensions == nil {
gqlErr.Extensions = map[string]interface{}{}
gqlErr.Extensions = map[string]any{}
}
gqlErr.Extensions["code"] = value

View File

@ -12,7 +12,7 @@ import (
type ExecutableSchema interface {
Schema() *ast.Schema
Complexity(typeName, fieldName string, childComplexity int, args map[string]interface{}) (int, bool)
Complexity(typeName, fieldName string, childComplexity int, args map[string]any) (int, bool)
Exec(ctx context.Context) ResponseHandler
}
@ -116,7 +116,7 @@ func instanceOf(val string, satisfies []string) bool {
return false
}
func getOrCreateAndAppendField(c *[]CollectedField, name string, alias string, objectDefinition *ast.Definition, creator func() CollectedField) *CollectedField {
func getOrCreateAndAppendField(c *[]CollectedField, name, alias string, objectDefinition *ast.Definition, creator func() CollectedField) *CollectedField {
for i, cf := range *c {
if cf.Name == name && cf.Alias == alias {
if cf.ObjectDefinition == objectDefinition {
@ -150,7 +150,7 @@ func getOrCreateAndAppendField(c *[]CollectedField, name string, alias string, o
return &(*c)[len(*c)-1]
}
func shouldIncludeNode(directives ast.DirectiveList, variables map[string]interface{}) bool {
func shouldIncludeNode(directives ast.DirectiveList, variables map[string]any) bool {
if len(directives) == 0 {
return true
}
@ -168,7 +168,7 @@ func shouldIncludeNode(directives ast.DirectiveList, variables map[string]interf
return !skip && include
}
func deferrable(directives ast.DirectiveList, variables map[string]interface{}) (shouldDefer bool, label string) {
func deferrable(directives ast.DirectiveList, variables map[string]any) (shouldDefer bool, label string) {
d := directives.ForName("defer")
if d == nil {
return false, ""
@ -194,7 +194,7 @@ func deferrable(directives ast.DirectiveList, variables map[string]interface{})
return shouldDefer, label
}
func resolveIfArgument(d *ast.Directive, variables map[string]interface{}) bool {
func resolveIfArgument(d *ast.Directive, variables map[string]any) bool {
arg := d.Arguments.ForName("if")
if arg == nil {
panic(fmt.Sprintf("%s: argument 'if' not defined", d.Name))

View File

@ -19,7 +19,7 @@ var _ ExecutableSchema = &ExecutableSchemaMock{}
//
// // make and configure a mocked ExecutableSchema
// mockedExecutableSchema := &ExecutableSchemaMock{
// ComplexityFunc: func(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (int, bool) {
// ComplexityFunc: func(typeName string, fieldName string, childComplexity int, args map[string]any) (int, bool) {
// panic("mock out the Complexity method")
// },
// ExecFunc: func(ctx context.Context) ResponseHandler {
@ -36,7 +36,7 @@ var _ ExecutableSchema = &ExecutableSchemaMock{}
// }
type ExecutableSchemaMock struct {
// ComplexityFunc mocks the Complexity method.
ComplexityFunc func(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (int, bool)
ComplexityFunc func(typeName string, fieldName string, childComplexity int, args map[string]any) (int, bool)
// ExecFunc mocks the Exec method.
ExecFunc func(ctx context.Context) ResponseHandler
@ -55,7 +55,7 @@ type ExecutableSchemaMock struct {
// ChildComplexity is the childComplexity argument value.
ChildComplexity int
// Args is the args argument value.
Args map[string]interface{}
Args map[string]any
}
// Exec holds details about calls to the Exec method.
Exec []struct {
@ -72,7 +72,7 @@ type ExecutableSchemaMock struct {
}
// Complexity calls ComplexityFunc.
func (mock *ExecutableSchemaMock) Complexity(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (int, bool) {
func (mock *ExecutableSchemaMock) Complexity(typeName string, fieldName string, childComplexity int, args map[string]any) (int, bool) {
if mock.ComplexityFunc == nil {
panic("ExecutableSchemaMock.ComplexityFunc: method is nil but ExecutableSchema.Complexity was just called")
}
@ -80,7 +80,7 @@ func (mock *ExecutableSchemaMock) Complexity(typeName string, fieldName string,
TypeName string
FieldName string
ChildComplexity int
Args map[string]interface{}
Args map[string]any
}{
TypeName: typeName,
FieldName: fieldName,
@ -101,13 +101,13 @@ func (mock *ExecutableSchemaMock) ComplexityCalls() []struct {
TypeName string
FieldName string
ChildComplexity int
Args map[string]interface{}
Args map[string]any
} {
var calls []struct {
TypeName string
FieldName string
ChildComplexity int
Args map[string]interface{}
Args map[string]any
}
mock.lockComplexity.RLock()
calls = mock.calls.Complexity

View File

@ -12,6 +12,8 @@ import (
"github.com/99designs/gqlgen/graphql/errcode"
)
const parserTokenNoLimit = 0
// Executor executes graphql queries against a schema.
type Executor struct {
es graphql.ExecutableSchema
@ -20,7 +22,9 @@ type Executor struct {
errorPresenter graphql.ErrorPresenterFunc
recoverFunc graphql.RecoverFunc
queryCache graphql.Cache
queryCache graphql.Cache[*ast.QueryDocument]
parserTokenLimit int
}
var _ graphql.GraphExecutor = &Executor{}
@ -29,11 +33,12 @@ var _ graphql.GraphExecutor = &Executor{}
// recovery callbacks, and no query cache or extensions.
func New(es graphql.ExecutableSchema) *Executor {
e := &Executor{
es: es,
errorPresenter: graphql.DefaultErrorPresenter,
recoverFunc: graphql.DefaultRecover,
queryCache: graphql.NoCache{},
ext: processExtensions(nil),
es: es,
errorPresenter: graphql.DefaultErrorPresenter,
recoverFunc: graphql.DefaultRecover,
queryCache: graphql.NoCache[ast.QueryDocument, *ast.QueryDocument]{},
ext: processExtensions(nil),
parserTokenLimit: parserTokenNoLimit,
}
return e
}
@ -79,7 +84,6 @@ func (e *Executor) CreateOperationContext(
var err error
rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables)
if err != nil {
gqlErr, ok := err.(*gqlerror.Error)
if ok {
@ -153,11 +157,11 @@ func (e *Executor) DispatchError(ctx context.Context, list gqlerror.List) *graph
return resp
}
func (e *Executor) PresentRecoveredError(ctx context.Context, err interface{}) error {
func (e *Executor) PresentRecoveredError(ctx context.Context, err any) error {
return e.errorPresenter(ctx, e.recoverFunc(ctx, err))
}
func (e *Executor) SetQueryCache(cache graphql.Cache) {
func (e *Executor) SetQueryCache(cache graphql.Cache[*ast.QueryDocument]) {
e.queryCache = cache
}
@ -169,6 +173,10 @@ func (e *Executor) SetRecoverFunc(f graphql.RecoverFunc) {
e.recoverFunc = f
}
func (e *Executor) SetParserTokenLimit(limit int) {
e.parserTokenLimit = limit
}
// 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
@ -186,10 +194,10 @@ func (e *Executor) parseQuery(
stats.Parsing.End = now
stats.Validation.Start = now
return doc.(*ast.QueryDocument), nil
return doc, nil
}
doc, err := parser.ParseQuery(&ast.Source{Input: query})
doc, err := parser.ParseQueryWithTokenLimit(&ast.Source{Input: query}, e.parserTokenLimit)
if err != nil {
gqlErr, ok := err.(*gqlerror.Error)
if ok {

View File

@ -2,6 +2,7 @@ package executor
import (
"context"
"errors"
"fmt"
"github.com/99designs/gqlgen/graphql"
@ -68,7 +69,7 @@ func processExtensions(exts []graphql.HandlerExtension) extensions {
rootFieldMiddleware: func(ctx context.Context, next graphql.RootResolver) graphql.Marshaler {
return next(ctx)
},
fieldMiddleware: func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
fieldMiddleware: func(ctx context.Context, next graphql.Resolver) (res any, err error) {
return next(ctx)
},
}
@ -105,8 +106,8 @@ func processExtensions(exts []graphql.HandlerExtension) extensions {
if p, ok := p.(graphql.FieldInterceptor); ok {
previous := e.fieldMiddleware
e.fieldMiddleware = func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
return p.InterceptField(ctx, func(ctx context.Context) (res interface{}, err error) {
e.fieldMiddleware = func(ctx context.Context, next graphql.Resolver) (res any, err error) {
return p.InterceptField(ctx, func(ctx context.Context) (res any, err error) {
return previous(ctx, next)
})
}
@ -134,7 +135,7 @@ func (r aroundOpFunc) ExtensionName() string {
func (r aroundOpFunc) Validate(schema graphql.ExecutableSchema) error {
if r == nil {
return fmt.Errorf("OperationFunc can not be nil")
return errors.New("OperationFunc can not be nil")
}
return nil
}
@ -151,7 +152,7 @@ func (r aroundRespFunc) ExtensionName() string {
func (r aroundRespFunc) Validate(schema graphql.ExecutableSchema) error {
if r == nil {
return fmt.Errorf("ResponseFunc can not be nil")
return errors.New("ResponseFunc can not be nil")
}
return nil
}
@ -160,7 +161,7 @@ func (r aroundRespFunc) InterceptResponse(ctx context.Context, next graphql.Resp
return r(ctx, next)
}
type aroundFieldFunc func(ctx context.Context, next graphql.Resolver) (res interface{}, err error)
type aroundFieldFunc func(ctx context.Context, next graphql.Resolver) (res any, err error)
func (f aroundFieldFunc) ExtensionName() string {
return "InlineFieldFunc"
@ -168,12 +169,12 @@ func (f aroundFieldFunc) ExtensionName() string {
func (f aroundFieldFunc) Validate(schema graphql.ExecutableSchema) error {
if f == nil {
return fmt.Errorf("FieldFunc can not be nil")
return errors.New("FieldFunc can not be nil")
}
return nil
}
func (f aroundFieldFunc) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
func (f aroundFieldFunc) InterceptField(ctx context.Context, next graphql.Resolver) (res any, err error) {
return f(ctx, next)
}
@ -185,7 +186,7 @@ func (f aroundRootFieldFunc) ExtensionName() string {
func (f aroundRootFieldFunc) Validate(schema graphql.ExecutableSchema) error {
if f == nil {
return fmt.Errorf("RootFieldFunc can not be nil")
return errors.New("RootFieldFunc can not be nil")
}
return nil
}

View File

@ -3,6 +3,7 @@ package graphql
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math"
@ -11,11 +12,11 @@ import (
func MarshalFloat(f float64) Marshaler {
return WriterFunc(func(w io.Writer) {
io.WriteString(w, fmt.Sprintf("%g", f))
fmt.Fprintf(w, "%g", f)
})
}
func UnmarshalFloat(v interface{}) (float64, error) {
func UnmarshalFloat(v any) (float64, error) {
switch v := v.(type) {
case string:
return strconv.ParseFloat(v, 64)
@ -27,6 +28,8 @@ func UnmarshalFloat(v interface{}) (float64, error) {
return v, nil
case json.Number:
return strconv.ParseFloat(string(v), 64)
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an float", v)
}
@ -35,13 +38,13 @@ func UnmarshalFloat(v interface{}) (float64, error) {
func MarshalFloatContext(f float64) ContextMarshaler {
return ContextWriterFunc(func(ctx context.Context, w io.Writer) error {
if math.IsInf(f, 0) || math.IsNaN(f) {
return fmt.Errorf("cannot marshal infinite no NaN float values")
return errors.New("cannot marshal infinite no NaN float values")
}
io.WriteString(w, fmt.Sprintf("%g", f))
fmt.Fprintf(w, "%g", f)
return nil
})
}
func UnmarshalFloatContext(ctx context.Context, v interface{}) (float64, error) {
func UnmarshalFloatContext(ctx context.Context, v any) (float64, error) {
return UnmarshalFloat(v)
}

View File

@ -16,18 +16,18 @@ type (
ResponseHandler func(ctx context.Context) *Response
ResponseMiddleware func(ctx context.Context, next ResponseHandler) *Response
Resolver func(ctx context.Context) (res interface{}, err error)
FieldMiddleware func(ctx context.Context, next Resolver) (res interface{}, err error)
Resolver func(ctx context.Context) (res any, err error)
FieldMiddleware func(ctx context.Context, next Resolver) (res any, err error)
RootResolver func(ctx context.Context) Marshaler
RootFieldMiddleware func(ctx context.Context, next RootResolver) Marshaler
RawParams struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
Extensions map[string]interface{} `json:"extensions"`
Headers http.Header `json:"headers"`
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]any `json:"variables"`
Extensions map[string]any `json:"extensions"`
Headers http.Header `json:"headers"`
ReadTime TraceTiming `json:"-"`
}
@ -86,7 +86,7 @@ type (
// FieldInterceptor called around each field
FieldInterceptor interface {
InterceptField(ctx context.Context, next Resolver) (res interface{}, err error)
InterceptField(ctx context.Context, next Resolver) (res any, err error)
}
// Transport provides support for different wire level encodings of graphql requests, eg Form, Get, Post, Websocket
@ -103,7 +103,7 @@ func (p *RawParams) AddUpload(upload Upload, key, path string) *gqlerror.Error {
return gqlerror.Errorf("invalid operations paths for key %s", key)
}
var ptr interface{} = p.Variables
var ptr any = p.Variables
parts := strings.Split(path, ".")
// skip the first part (variables) because we started there
@ -114,15 +114,15 @@ func (p *RawParams) AddUpload(upload Upload, key, path string) *gqlerror.Error {
}
if index, parseNbrErr := strconv.Atoi(p); parseNbrErr == nil {
if last {
ptr.([]interface{})[index] = upload
ptr.([]any)[index] = upload
} else {
ptr = ptr.([]interface{})[index]
ptr = ptr.([]any)[index]
}
} else {
if last {
ptr.(map[string]interface{})[p] = upload
ptr.(map[string]any)[p] = upload
} else {
ptr = ptr.(map[string]interface{})[p]
ptr = ptr.(map[string]any)[p]
}
}
}

View File

@ -4,7 +4,7 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"errors"
"github.com/mitchellh/mapstructure"
"github.com/vektah/gqlparser/v2/gqlerror"
@ -23,7 +23,7 @@ const (
// hash in the next request.
// see https://github.com/apollographql/apollo-link-persisted-queries
type AutomaticPersistedQuery struct {
Cache graphql.Cache
Cache graphql.Cache[string]
}
type ApqStats struct {
@ -47,7 +47,7 @@ func (a AutomaticPersistedQuery) ExtensionName() string {
func (a AutomaticPersistedQuery) Validate(schema graphql.ExecutableSchema) error {
if a.Cache == nil {
return fmt.Errorf("AutomaticPersistedQuery.Cache can not be nil")
return errors.New("AutomaticPersistedQuery.Cache can not be nil")
}
return nil
}
@ -72,14 +72,14 @@ func (a AutomaticPersistedQuery) MutateOperationParameters(ctx context.Context,
fullQuery := false
if rawParams.Query == "" {
var ok bool
// client sent optimistic query hash without query string, get it from the cache
query, ok := a.Cache.Get(ctx, extension.Sha256)
rawParams.Query, ok = a.Cache.Get(ctx, extension.Sha256)
if !ok {
err := gqlerror.Errorf(errPersistedQueryNotFound)
errcode.Set(err, errPersistedQueryNotFoundCode)
return err
}
rawParams.Query = query.(string)
} else {
// client sent optimistic query hash with query string, verify and store it
if computeQueryHash(rawParams.Query) != extension.Sha256 {

View File

@ -2,7 +2,7 @@ package extension
import (
"context"
"fmt"
"errors"
"github.com/vektah/gqlparser/v2/gqlerror"
@ -52,7 +52,7 @@ func (c ComplexityLimit) ExtensionName() string {
func (c *ComplexityLimit) Validate(schema graphql.ExecutableSchema) error {
if c.Func == nil {
return fmt.Errorf("ComplexityLimit func can not be nil")
return errors.New("ComplexityLimit func can not be nil")
}
c.es = schema
return nil

View File

@ -8,26 +8,26 @@ import (
"github.com/99designs/gqlgen/graphql"
)
type LRU struct {
lru *lru.Cache[string, any]
type LRU[T any] struct {
lru *lru.Cache[string, T]
}
var _ graphql.Cache = &LRU{}
var _ graphql.Cache[any] = &LRU[any]{}
func New(size int) *LRU {
cache, err := lru.New[string, any](size)
func New[T any](size int) *LRU[T] {
cache, err := lru.New[string, T](size)
if err != nil {
// An error is only returned for non-positive cache size
// and we already checked for that.
panic("unexpected error creating cache: " + err.Error())
}
return &LRU{cache}
return &LRU[T]{cache}
}
func (l LRU) Get(ctx context.Context, key string) (value interface{}, ok bool) {
func (l LRU[T]) Get(ctx context.Context, key string) (value T, ok bool) {
return l.lru.Get(key)
}
func (l LRU) Add(ctx context.Context, key string, value interface{}) {
func (l LRU[T]) Add(ctx context.Context, key string, value T) {
l.lru.Add(key, value)
}

View File

@ -3,10 +3,12 @@ package handler
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/gqlerror"
"github.com/99designs/gqlgen/graphql"
@ -40,11 +42,11 @@ func NewDefaultServer(es graphql.ExecutableSchema) *Server {
srv.AddTransport(transport.POST{})
srv.AddTransport(transport.MultipartForm{})
srv.SetQueryCache(lru.New(1000))
srv.SetQueryCache(lru.New[*ast.QueryDocument](1000))
srv.Use(extension.Introspection{})
srv.Use(extension.AutomaticPersistedQuery{
Cache: lru.New(100),
Cache: lru.New[string](100),
})
return srv
@ -62,10 +64,14 @@ func (s *Server) SetRecoverFunc(f graphql.RecoverFunc) {
s.exec.SetRecoverFunc(f)
}
func (s *Server) SetQueryCache(cache graphql.Cache) {
func (s *Server) SetQueryCache(cache graphql.Cache[*ast.QueryDocument]) {
s.exec.SetQueryCache(cache)
}
func (s *Server) SetParserTokenLimit(limit int) {
s.exec.SetParserTokenLimit(limit)
}
func (s *Server) Use(extension graphql.HandlerExtension) {
s.exec.Use(extension)
}
@ -131,7 +137,7 @@ func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
_, _ = w.Write(b)
}
func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
func sendErrorf(w http.ResponseWriter, code int, format string, args ...any) {
sendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)})
}
@ -143,7 +149,7 @@ func (r OperationFunc) ExtensionName() string {
func (r OperationFunc) Validate(schema graphql.ExecutableSchema) error {
if r == nil {
return fmt.Errorf("OperationFunc can not be nil")
return errors.New("OperationFunc can not be nil")
}
return nil
}
@ -160,7 +166,7 @@ func (r ResponseFunc) ExtensionName() string {
func (r ResponseFunc) Validate(schema graphql.ExecutableSchema) error {
if r == nil {
return fmt.Errorf("ResponseFunc can not be nil")
return errors.New("ResponseFunc can not be nil")
}
return nil
}
@ -169,7 +175,7 @@ func (r ResponseFunc) InterceptResponse(ctx context.Context, next graphql.Respon
return r(ctx, next)
}
type FieldFunc func(ctx context.Context, next graphql.Resolver) (res interface{}, err error)
type FieldFunc func(ctx context.Context, next graphql.Resolver) (res any, err error)
func (f FieldFunc) ExtensionName() string {
return "InlineFieldFunc"
@ -177,11 +183,11 @@ func (f FieldFunc) ExtensionName() string {
func (f FieldFunc) Validate(schema graphql.ExecutableSchema) error {
if f == nil {
return fmt.Errorf("FieldFunc can not be nil")
return errors.New("FieldFunc can not be nil")
}
return nil
}
func (f FieldFunc) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) {
func (f FieldFunc) InterceptField(ctx context.Context, next graphql.Resolver) (res any, err error) {
return f(ctx, next)
}

View File

@ -22,6 +22,6 @@ func SendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) {
}
// SendErrorf wraps SendError to add formatted messages
func SendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) {
func SendErrorf(w http.ResponseWriter, code int, format string, args ...any) {
SendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)})
}

View File

@ -63,10 +63,10 @@ func (h UrlEncodedForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.
return
}
rc, OpErr := exec.CreateOperationContext(ctx, params)
if OpErr != nil {
w.WriteHeader(statusFor(OpErr))
resp := exec.DispatchError(graphql.WithOperationContext(ctx, rc), OpErr)
rc, opErr := exec.CreateOperationContext(ctx, params)
if opErr != nil {
w.WriteHeader(statusFor(opErr))
resp := exec.DispatchError(graphql.WithOperationContext(ctx, rc), opErr)
writeJson(w, resp)
return
}

View File

@ -84,7 +84,7 @@ func (h GET) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecut
writeJson(w, responses(ctx))
}
func jsonDecode(r io.Reader, val interface{}) error {
func jsonDecode(r io.Reader, val any) error {
dec := json.NewDecoder(r)
dec.UseNumber()
return dec.Decode(val)

View File

@ -64,10 +64,10 @@ func (h GRAPHQL) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphEx
return
}
rc, OpErr := exec.CreateOperationContext(ctx, params)
if OpErr != nil {
w.WriteHeader(statusFor(OpErr))
resp := exec.DispatchError(graphql.WithOperationContext(ctx, rc), OpErr)
rc, opErr := exec.CreateOperationContext(ctx, params)
if opErr != nil {
w.WriteHeader(statusFor(opErr))
resp := exec.DispatchError(graphql.WithOperationContext(ctx, rc), opErr)
writeJson(w, resp)
return
}
@ -88,7 +88,6 @@ func cleanupBody(body string) (out string, err error) {
// is where query starts. If it is, query is url encoded.
if strings.HasPrefix(body, "%7B") {
body, err = url.QueryUnescape(body)
if err != nil {
return body, err
}

View File

@ -78,10 +78,10 @@ func (h POST) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecu
return
}
rc, OpErr := exec.CreateOperationContext(ctx, params)
if OpErr != nil {
w.WriteHeader(statusFor(OpErr))
resp := exec.DispatchError(graphql.WithOperationContext(ctx, rc), OpErr)
rc, opErr := exec.CreateOperationContext(ctx, params)
if opErr != nil {
w.WriteHeader(statusFor(opErr))
resp := exec.DispatchError(graphql.WithOperationContext(ctx, rc), opErr)
writeJson(w, resp)
return
}

View File

@ -22,7 +22,7 @@ func writeJsonError(w io.Writer, msg string) {
writeJson(w, &graphql.Response{Errors: gqlerror.List{{Message: msg}}})
}
func writeJsonErrorf(w io.Writer, format string, args ...interface{}) {
func writeJsonErrorf(w io.Writer, format string, args ...any) {
writeJson(w, &graphql.Response{Errors: gqlerror.List{{Message: fmt.Sprintf(format, args...)}}})
}

View File

@ -193,12 +193,12 @@ func (c *wsConnection) init() bool {
}
}
var initAckPayload *InitPayload = nil
var initAckPayload *InitPayload
if c.InitFunc != nil {
var ctx context.Context
ctx, initAckPayload, err = c.InitFunc(c.ctx, c.initPayload)
if err != nil {
c.sendConnectionError(err.Error())
c.sendConnectionError("%s", err.Error())
c.close(websocket.CloseNormalClosure, "terminated")
return false
}
@ -239,7 +239,6 @@ func (c *wsConnection) run() {
ctx, cancel := context.WithCancel(c.ctx)
defer func() {
cancel()
c.close(websocket.CloseAbnormalClosure, "unexpected closure")
}()
// If we're running in graphql-ws mode, create a timer that will trigger a
@ -369,7 +368,7 @@ func (c *wsConnection) closeOnCancel(ctx context.Context) {
<-ctx.Done()
if r := closeReasonForContext(ctx); r != "" {
c.sendConnectionError(r)
c.sendConnectionError("%s", r)
}
c.close(websocket.CloseNormalClosure, "terminated")
}
@ -480,7 +479,7 @@ func (c *wsConnection) sendError(id string, errors ...*gqlerror.Error) {
c.write(&message{t: errorMessageType, id: id, payload: b})
}
func (c *wsConnection) sendConnectionError(format string, args ...interface{}) {
func (c *wsConnection) sendConnectionError(format string, args ...any) {
b, err := json.Marshal(&gqlerror.Error{Message: fmt.Sprintf(format, args...)})
if err != nil {
panic(err)

View File

@ -10,7 +10,7 @@ const (
// InitPayload is a structure that is parsed from the websocket init message payload. TO use
// request headers for non-websocket, instead wrap the graphql handler in a middleware.
type InitPayload map[string]interface{}
type InitPayload map[string]any
// GetString safely gets a string value from the payload. It returns an empty string if the
// payload is nil or the value isn't set.

View File

@ -11,7 +11,7 @@ func MarshalID(s string) Marshaler {
return MarshalString(s)
}
func UnmarshalID(v interface{}) (string, error) {
func UnmarshalID(v any) (string, error) {
switch v := v.(type) {
case string:
return v, nil
@ -22,13 +22,9 @@ func UnmarshalID(v interface{}) (string, error) {
case int64:
return strconv.FormatInt(v, 10), nil
case float64:
return fmt.Sprintf("%f", v), nil
return strconv.FormatFloat(v, 'f', 6, 64), nil
case bool:
if v {
return "true", nil
} else {
return "false", nil
}
return strconv.FormatBool(v), nil
case nil:
return "null", nil
default:
@ -42,7 +38,7 @@ func MarshalIntID(i int) Marshaler {
})
}
func UnmarshalIntID(v interface{}) (int, error) {
func UnmarshalIntID(v any) (int, error) {
switch v := v.(type) {
case string:
return strconv.Atoi(v)
@ -63,7 +59,7 @@ func MarshalUintID(i uint) Marshaler {
})
}
func UnmarshalUintID(v interface{}) (uint, error) {
func UnmarshalUintID(v any) (uint, error) {
switch v := v.(type) {
case string:
result, err := strconv.ParseUint(v, 10, 64)

View File

@ -10,7 +10,7 @@ const unmarshalInputCtx key = "unmarshal_input_context"
// BuildUnmarshalerMap returns a map of unmarshal functions of the ExecutableContext
// to use with the WithUnmarshalerMap function.
func BuildUnmarshalerMap(unmarshaler ...interface{}) map[reflect.Type]reflect.Value {
func BuildUnmarshalerMap(unmarshaler ...any) map[reflect.Type]reflect.Value {
maps := make(map[reflect.Type]reflect.Value)
for _, v := range unmarshaler {
ft := reflect.TypeOf(v)
@ -28,7 +28,7 @@ func WithUnmarshalerMap(ctx context.Context, maps map[reflect.Type]reflect.Value
}
// UnmarshalInputFromContext allows unmarshaling input object from a context.
func UnmarshalInputFromContext(ctx context.Context, raw, v interface{}) error {
func UnmarshalInputFromContext(ctx context.Context, raw, v any) error {
m, ok := ctx.Value(unmarshalInputCtx).(map[reflect.Type]reflect.Value)
if m == nil || !ok {
return errors.New("graphql: the input context is empty")

View File

@ -13,7 +13,7 @@ func MarshalInt(i int) Marshaler {
})
}
func UnmarshalInt(v interface{}) (int, error) {
func UnmarshalInt(v any) (int, error) {
switch v := v.(type) {
case string:
return strconv.Atoi(v)
@ -23,6 +23,8 @@ func UnmarshalInt(v interface{}) (int, error) {
return int(v), nil
case json.Number:
return strconv.Atoi(string(v))
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an int", v)
}
@ -34,7 +36,7 @@ func MarshalInt64(i int64) Marshaler {
})
}
func UnmarshalInt64(v interface{}) (int64, error) {
func UnmarshalInt64(v any) (int64, error) {
switch v := v.(type) {
case string:
return strconv.ParseInt(v, 10, 64)
@ -44,6 +46,8 @@ func UnmarshalInt64(v interface{}) (int64, error) {
return v, nil
case json.Number:
return strconv.ParseInt(string(v), 10, 64)
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an int", v)
}
@ -55,7 +59,7 @@ func MarshalInt32(i int32) Marshaler {
})
}
func UnmarshalInt32(v interface{}) (int32, error) {
func UnmarshalInt32(v any) (int32, error) {
switch v := v.(type) {
case string:
iv, err := strconv.ParseInt(v, 10, 32)
@ -73,6 +77,8 @@ func UnmarshalInt32(v interface{}) (int32, error) {
return 0, err
}
return int32(iv), nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an int", v)
}

View File

@ -28,7 +28,7 @@ type Marshaler interface {
}
type Unmarshaler interface {
UnmarshalGQL(v interface{}) error
UnmarshalGQL(v any) error
}
type ContextMarshaler interface {
@ -36,7 +36,7 @@ type ContextMarshaler interface {
}
type ContextUnmarshaler interface {
UnmarshalGQLContext(ctx context.Context, v interface{}) error
UnmarshalGQLContext(ctx context.Context, v any) error
}
type contextMarshalerAdapter struct {

View File

@ -66,7 +66,7 @@ var altairPage = template.Must(template.New("altair").Parse(`<!doctype html>
// AltairHandler responsible for setting up the altair playground
func AltairHandler(title, endpoint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
err := altairPage.Execute(w, map[string]interface{}{
err := altairPage.Execute(w, map[string]any{
"title": title,
"endpoint": endpoint,
"endpointIsAbsolute": endpointHasScheme(endpoint),

View File

@ -64,7 +64,7 @@ func ApolloSandboxHandler(title, endpoint string, opts ...ApolloSandboxOption) h
}
return func(w http.ResponseWriter, r *http.Request) {
err := apolloSandboxPage.Execute(w, map[string]interface{}{
err := apolloSandboxPage.Execute(w, map[string]any{
"title": title,
"endpoint": endpoint,
"endpointIsAbsolute": endpointHasScheme(endpoint),

View File

@ -85,17 +85,17 @@ var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
`))
// Handler responsible for setting up the playground
func Handler(title string, endpoint string) http.HandlerFunc {
func Handler(title, endpoint string) http.HandlerFunc {
return HandlerWithHeaders(title, endpoint, nil, nil)
}
// HandlerWithHeaders sets up the playground.
// fetcherHeaders are used by the playground's fetcher instance and will not be visible in the UI.
// uiHeaders are default headers that will show up in the UI headers editor.
func HandlerWithHeaders(title string, endpoint string, fetcherHeaders map[string]string, uiHeaders map[string]string) http.HandlerFunc {
func HandlerWithHeaders(title, endpoint string, fetcherHeaders, uiHeaders map[string]string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html; charset=UTF-8")
err := page.Execute(w, map[string]interface{}{
err := page.Execute(w, map[string]any{
"title": title,
"endpoint": endpoint,
"fetcherHeaders": fetcherHeaders,

View File

@ -9,9 +9,9 @@ import (
"github.com/vektah/gqlparser/v2/gqlerror"
)
type RecoverFunc func(ctx context.Context, err interface{}) (userMessage error)
type RecoverFunc func(ctx context.Context, err any) (userMessage error)
func DefaultRecover(ctx context.Context, err interface{}) error {
func DefaultRecover(ctx context.Context, err any) error {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr)
debug.PrintStack()

View File

@ -13,15 +13,15 @@ import (
// https://github.com/facebook/graphql/commit/7b40390d48680b15cb93e02d46ac5eb249689876#diff-757cea6edf0288677a9eea4cfc801d87R107
// and https://github.com/facebook/graphql/pull/384
type Response struct {
Errors gqlerror.List `json:"errors,omitempty"`
Data json.RawMessage `json:"data"`
Label string `json:"label,omitempty"`
Path ast.Path `json:"path,omitempty"`
HasNext *bool `json:"hasNext,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
Errors gqlerror.List `json:"errors,omitempty"`
Data json.RawMessage `json:"data"`
Label string `json:"label,omitempty"`
Path ast.Path `json:"path,omitempty"`
HasNext *bool `json:"hasNext,omitempty"`
Extensions map[string]any `json:"extensions,omitempty"`
}
func ErrorResponse(ctx context.Context, messagef string, args ...interface{}) *Response {
func ErrorResponse(ctx context.Context, messagef string, args ...any) *Response {
return &Response{
Errors: gqlerror.List{{Message: fmt.Sprintf(messagef, args...)}},
}

View File

@ -14,7 +14,7 @@ type Stats struct {
// Stats collected by handler extensions. Don't use directly, the extension should provide a type safe way to
// access this.
extension map[string]interface{}
extension map[string]any
}
type TraceTiming struct {
@ -42,14 +42,14 @@ func GetStartTime(ctx context.Context) time.Time {
return t
}
func (c *Stats) SetExtension(name string, data interface{}) {
func (c *Stats) SetExtension(name string, data any) {
if c.extension == nil {
c.extension = map[string]interface{}{}
c.extension = map[string]any{}
}
c.extension[name] = data
}
func (c *Stats) GetExtension(name string) interface{} {
func (c *Stats) GetExtension(name string) any {
if c.extension == nil {
return nil
}

View File

@ -47,7 +47,7 @@ func writeQuotedString(w io.Writer, s string) {
io.WriteString(w, `"`)
}
func UnmarshalString(v interface{}) (string, error) {
func UnmarshalString(v any) (string, error) {
switch v := v.(type) {
case string:
return v, nil
@ -60,13 +60,9 @@ func UnmarshalString(v interface{}) (string, error) {
case json.Number:
return string(v), nil
case bool:
if v {
return "true", nil
} else {
return "false", nil
}
return strconv.FormatBool(v), nil
case nil:
return "null", nil
return "", nil
default:
return "", fmt.Errorf("%T is not a string", v)
}

View File

@ -17,7 +17,7 @@ func MarshalTime(t time.Time) Marshaler {
})
}
func UnmarshalTime(v interface{}) (time.Time, error) {
func UnmarshalTime(v any) (time.Time, error) {
if tmpStr, ok := v.(string); ok {
return time.Parse(time.RFC3339Nano, tmpStr)
}

View File

@ -14,7 +14,7 @@ func MarshalUint(i uint) Marshaler {
})
}
func UnmarshalUint(v interface{}) (uint, error) {
func UnmarshalUint(v any) (uint, error) {
switch v := v.(type) {
case string:
u64, err := strconv.ParseUint(v, 10, 64)
@ -34,6 +34,8 @@ func UnmarshalUint(v interface{}) (uint, error) {
case json.Number:
u64, err := strconv.ParseUint(string(v), 10, 64)
return uint(u64), err
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an uint", v)
}
@ -45,7 +47,7 @@ func MarshalUint64(i uint64) Marshaler {
})
}
func UnmarshalUint64(v interface{}) (uint64, error) {
func UnmarshalUint64(v any) (uint64, error) {
switch v := v.(type) {
case string:
return strconv.ParseUint(v, 10, 64)
@ -63,6 +65,8 @@ func UnmarshalUint64(v interface{}) (uint64, error) {
return uint64(v), nil
case json.Number:
return strconv.ParseUint(string(v), 10, 64)
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an uint", v)
}
@ -74,7 +78,7 @@ func MarshalUint32(i uint32) Marshaler {
})
}
func UnmarshalUint32(v interface{}) (uint32, error) {
func UnmarshalUint32(v any) (uint32, error) {
switch v := v.(type) {
case string:
iv, err := strconv.ParseUint(v, 10, 32)
@ -100,6 +104,8 @@ func UnmarshalUint32(v interface{}) (uint32, error) {
return 0, err
}
return uint32(iv), nil
case nil:
return 0, nil
default:
return 0, fmt.Errorf("%T is not an uint", v)
}

View File

@ -18,7 +18,7 @@ func MarshalUpload(f Upload) Marshaler {
})
}
func UnmarshalUpload(v interface{}) (Upload, error) {
func UnmarshalUpload(v any) (Upload, error) {
upload, ok := v.(Upload)
if !ok {
return Upload{}, fmt.Errorf("%T is not an Upload", v)

View File

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

View File

@ -11,6 +11,9 @@ exec:
# federation:
# filename: graph/federation.go
# package: graph
# version: 2
# options
# computed_requires: true
# Where should any generated models go?
model:
@ -63,6 +66,13 @@ resolver:
# Optional: set to skip running `go mod tidy` when generating server code
# skip_mod_tidy: true
# Optional: if this is set to true, argument directives that
# decorate a field with a null value will still be called.
#
# This enables argumment directives to not just mutate
# argument values but to set them even if they're null.
call_argument_directives_with_null: true
# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:

View File

@ -0,0 +1,13 @@
//go:build !go1.23
package code
import (
"go/types"
)
// Unalias unwraps an alias type
// TODO: Drop this function when we drop support for go1.22
func Unalias(t types.Type) types.Type {
return t // No-op
}

View File

@ -0,0 +1,19 @@
//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

@ -1,12 +1,15 @@
package code
import (
"errors"
"fmt"
"go/types"
)
// CompatibleTypes isnt a strict comparison, it allows for pointer differences
func CompatibleTypes(expected, actual types.Type) error {
// Unwrap any aliases
expected, actual = Unalias(expected), Unalias(actual)
// Special case to deal with pointer mismatches
{
expectedPtr, expectedIsPtr := expected.(*types.Pointer)
@ -32,7 +35,7 @@ func CompatibleTypes(expected, actual types.Type) error {
case *types.Array:
if actual, ok := actual.(*types.Array); ok {
if expected.Len() != actual.Len() {
return fmt.Errorf("array length differs")
return errors.New("array length differs")
}
return CompatibleTypes(expected.Elem(), actual.Elem())
@ -50,7 +53,7 @@ func CompatibleTypes(expected, actual types.Type) error {
case *types.Struct:
if actual, ok := actual.(*types.Struct); ok {
if expected.NumFields() != actual.NumFields() {
return fmt.Errorf("number of struct fields differ")
return errors.New("number of struct fields differ")
}
for i := 0; i < expected.NumFields(); i++ {

View File

@ -102,21 +102,21 @@ func goModuleRoot(dir string) (string, bool) {
// go.mod is not found in the tree, so the same sentinel value fits all the directories in a tree
goModuleRootCache[d] = result
} else {
if relPath, err := filepath.Rel(result.goModPath, d); err != nil {
relPath, err := filepath.Rel(result.goModPath, d)
if err != nil {
panic(err)
} else {
path := result.moduleName
relPath := filepath.ToSlash(relPath)
if !strings.HasSuffix(relPath, "/") {
path += "/"
}
path += relPath
}
path := result.moduleName
relPath = filepath.ToSlash(relPath)
if !strings.HasSuffix(relPath, "/") {
path += "/"
}
path += relPath
goModuleRootCache[d] = goModuleSearchResult{
path: path,
goModPath: result.goModPath,
moduleName: result.moduleName,
}
goModuleRootCache[d] = goModuleSearchResult{
path: path,
goModPath: result.goModPath,
moduleName: result.moduleName,
}
}
}

View File

@ -100,17 +100,17 @@ var initCmd = &cli.Command{
cwd, err := os.Getwd()
if err != nil {
log.Println(err)
return fmt.Errorf("unable to determine current directory:%w", err)
return fmt.Errorf("unable to determine current directory: %w", err)
}
pkgName := code.ImportPathForDir(cwd)
if pkgName == "" {
return fmt.Errorf(
return errors.New(
"unable to determine import path for current directory, you probably need to run 'go mod init' first",
)
}
modRoot := findModuleRoot(cwd)
if modRoot == "" {
return fmt.Errorf("go.mod is missing. Please, do 'go mod init' first\n")
return errors.New("go.mod is missing. Please, do 'go mod init' first\n")
}
// check schema and config don't already exist
@ -121,7 +121,7 @@ var initCmd = &cli.Command{
}
_, err = config.LoadConfigFromDefaultLocations()
if err == nil {
return fmt.Errorf("gqlgen.yml already exists in a parent directory\n")
return errors.New("gqlgen.yml already exists in a parent directory\n")
}
// create config
@ -187,10 +187,7 @@ var generateCmd = &cli.Command{
}
}
if err = api.Generate(cfg); err != nil {
return err
}
return nil
return api.Generate(cfg)
},
}

View File

@ -0,0 +1,185 @@
package federation
import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/vektah/gqlparser/v2/ast"
)
// The name of the field argument that is injected into the resolver to support @requires.
const fieldArgRequires = "_federationRequires"
// The name of the scalar type used in the injected field argument to support @requires.
const mapTypeName = "_RequiresMap"
// The @key directive that defines the key fields for an entity.
const dirNameKey = "key"
// The @requires directive that defines the required fields for an entity to be resolved.
const dirNameRequires = "requires"
// The @entityResolver directive allows users to specify entity resolvers as batch lookups
const dirNameEntityResolver = "entityResolver"
const dirNamePopulateFromRepresentations = "populateFromRepresentations"
var populateFromRepresentationsImplementation = `func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) {
fc := graphql.GetFieldContext(ctx)
// We get the Federation representations argument from the _entities resolver
representations, ok := fc.Parent.Parent.Args["representations"].([]map[string]any)
if !ok {
return nil, errors.New("must be called from within _entities")
}
// Get the index of the current entity in the representations list. This is
// set by the execution context after the _entities resolver is called.
index := fc.Parent.Index
if index == nil {
return nil, errors.New("couldn't find input index for entity")
}
if len(representations) < *index {
return nil, errors.New("representation not found")
}
return representations[*index], nil
}`
const DirNameEntityReference = "entityReference"
// The fields arguments must be provided to both key and requires directives.
const DirArgFields = "fields"
// Tells the code generator what type the directive is referencing
const DirArgType = "type"
// The file name for Federation directives
const dirGraphQLQFile = "federation/directives.graphql"
// The file name for Federation entities
const entityGraphQLQFile = "federation/entity.graphql"
const federationVersion1Schema = `
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
directive @external on FIELD_DEFINITION
scalar _Any
scalar _FieldSet
`
const federationVersion2Schema = `
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
directive @composeDirective(name: String!) repeatable on SCHEMA
directive @extends on OBJECT | INTERFACE
directive @external on OBJECT | FIELD_DEFINITION
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @inaccessible on
| ARGUMENT_DEFINITION
| ENUM
| ENUM_VALUE
| FIELD_DEFINITION
| INPUT_FIELD_DEFINITION
| INPUT_OBJECT
| INTERFACE
| OBJECT
| SCALAR
| UNION
directive @interfaceObject on OBJECT
directive @link(import: [String!], url: String!) repeatable on SCHEMA
directive @override(from: String!, label: String) on FIELD_DEFINITION
directive @policy(policies: [[federation__Policy!]!]!) on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| SCALAR
| ENUM
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @requiresScopes(scopes: [[federation__Scope!]!]!) on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| SCALAR
| ENUM
directive @shareable repeatable on FIELD_DEFINITION | OBJECT
directive @tag(name: String!) repeatable on
| ARGUMENT_DEFINITION
| ENUM
| ENUM_VALUE
| FIELD_DEFINITION
| INPUT_FIELD_DEFINITION
| INPUT_OBJECT
| INTERFACE
| OBJECT
| SCALAR
| UNION
scalar _Any
scalar FieldSet
scalar federation__Policy
scalar federation__Scope
`
var builtins = config.TypeMap{
"_Service": {
Model: config.StringList{
"github.com/99designs/gqlgen/plugin/federation/fedruntime.Service",
},
},
"_Entity": {
Model: config.StringList{
"github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity",
},
},
"Entity": {
Model: config.StringList{
"github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity",
},
},
"_Any": {
Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"},
},
"federation__Scope": {
Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"},
},
"federation__Policy": {
Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"},
},
}
var dirPopulateFromRepresentations = &ast.DirectiveDefinition{
Name: dirNamePopulateFromRepresentations,
IsRepeatable: false,
Description: `This is a runtime directive used to implement @requires. It's automatically placed
on the generated _federationRequires argument, and the implementation of it extracts the
correct value from the input representations list.`,
Locations: []ast.DirectiveLocation{ast.LocationArgumentDefinition},
Position: &ast.Position{Src: &ast.Source{
Name: dirGraphQLQFile,
}},
}
var dirEntityReference = &ast.DirectiveDefinition{
Name: DirNameEntityReference,
IsRepeatable: false,
Description: `This is a compile-time directive used to implement @requires.
It tells the code generator how to generate the model for the scalar.`,
Locations: []ast.DirectiveLocation{ast.LocationScalar},
Arguments: ast.ArgumentDefinitionList{
{
Name: DirArgType,
Type: ast.NonNullNamedType("String", nil),
Description: `The name of the entity that the fields selection
set should be validated against.`,
},
{
Name: DirArgFields,
Type: ast.NonNullNamedType("FieldSet", nil),
Description: "The selection that the scalar should generate into.",
},
},
Position: &ast.Position{Src: &ast.Source{
Name: dirGraphQLQFile,
}},
}

View File

@ -22,10 +22,12 @@ type Entity struct {
}
type EntityResolver struct {
ResolverName string // The resolver name, such as FindUserByID
KeyFields []*KeyField // The fields declared in @key.
InputType types.Type // The Go generated input type for multi entity resolvers
InputTypeName string
ResolverName string // The resolver name, such as FindUserByID
KeyFields []*KeyField // The fields declared in @key.
InputType types.Type // The Go generated input type for multi entity resolvers
InputTypeName string
ReturnType types.Type // The Go generated return type for the entity
ReturnTypeName string
}
func (e *EntityResolver) LookupInputType() string {
@ -60,7 +62,7 @@ func (e *Entity) isFieldImplicitlyExternal(field *ast.FieldDefinition, federatio
if federationVersion != 2 {
return false
}
// TODO: From the spec, it seems like if an entity is not resolvable then it should not only not have a resolver, but should not appear in the _Entitiy union.
// TODO: From the spec, it seems like if an entity is not resolvable then it should not only not have a resolver, but should not appear in the _Entity union.
// The current implementation is a less drastic departure from the previous behavior, but should probably be reviewed.
// See https://www.apollographql.com/docs/federation/subgraph-spec/
if e.isResolvable() {
@ -76,7 +78,7 @@ func (e *Entity) isFieldImplicitlyExternal(field *ast.FieldDefinition, federatio
// Determine if the entity is resolvable.
func (e *Entity) isResolvable() bool {
key := e.Def.Directives.ForName("key")
key := e.Def.Directives.ForName(dirNameKey)
if key == nil {
// If there is no key directive, the entity is resolvable.
return true
@ -102,11 +104,11 @@ func (e *Entity) isKeyField(field *ast.FieldDefinition) bool {
// Get the key fields for this entity.
func (e *Entity) keyFields() []string {
key := e.Def.Directives.ForName("key")
key := e.Def.Directives.ForName(dirNameKey)
if key == nil {
return []string{}
}
fields := key.Arguments.ForName("fields")
fields := key.Arguments.ForName(DirArgFields)
if fields == nil {
return []string{}
}

View File

@ -2,6 +2,7 @@ package federation
import (
_ "embed"
"errors"
"fmt"
"sort"
"strings"
@ -12,7 +13,6 @@ import (
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/rewrite"
"github.com/99designs/gqlgen/plugin"
"github.com/99designs/gqlgen/plugin/federation/fieldset"
)
@ -22,56 +22,81 @@ var federationTemplate string
//go:embed requires.gotpl
var explicitRequiresTemplate string
type federation struct {
type Federation struct {
Entities []*Entity
Version int
PackageOptions map[string]bool
PackageOptions PackageOptions
version int
// true if @requires is used in the schema
usesRequires bool
}
type PackageOptions struct {
// ExplicitRequires will generate a function in the execution context
// to populate fields using the @required directive into the entity.
//
// You can only set one of ExplicitRequires or ComputedRequires to true.
ExplicitRequires bool
// ComputedRequires generates resolver functions to compute values for
// fields using the @required directive.
ComputedRequires bool
}
// New returns a federation plugin that injects
// federated directives and types into the schema
func New(version int) plugin.Plugin {
func New(version int, cfg *config.Config) (*Federation, error) {
if version == 0 {
version = 1
}
return &federation{Version: version}
options, err := buildPackageOptions(cfg)
if err != nil {
return nil, fmt.Errorf("invalid federation package options: %w", err)
}
return &Federation{
version: version,
PackageOptions: options,
}, nil
}
func buildPackageOptions(cfg *config.Config) (PackageOptions, error) {
packageOptions := cfg.Federation.Options
explicitRequires := packageOptions["explicit_requires"]
computedRequires := packageOptions["computed_requires"]
if explicitRequires && computedRequires {
return PackageOptions{}, errors.New("only one of explicit_requires or computed_requires can be set to true")
}
if computedRequires {
if cfg.Federation.Version != 2 {
return PackageOptions{}, errors.New("when using federation.options.computed_requires you must be using Federation 2")
}
// We rely on injecting a null argument with a directives for fields with @requires, so we need to ensure
// our directive is always called.
if !cfg.CallArgumentDirectivesWithNull {
return PackageOptions{}, errors.New("when using federation.options.computed_requires, call_argument_directives_with_null must be set to true")
}
}
// We rely on injecting a null argument with a directives for fields with @requires, so we need to ensure
// our directive is always called.
return PackageOptions{
ExplicitRequires: explicitRequires,
ComputedRequires: computedRequires,
}, nil
}
// Name returns the plugin name
func (f *federation) Name() string {
func (f *Federation) Name() string {
return "federation"
}
// MutateConfig mutates the configuration
func (f *federation) MutateConfig(cfg *config.Config) error {
builtins := config.TypeMap{
"_Service": {
Model: config.StringList{
"github.com/99designs/gqlgen/plugin/federation/fedruntime.Service",
},
},
"_Entity": {
Model: config.StringList{
"github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity",
},
},
"Entity": {
Model: config.StringList{
"github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity",
},
},
"_Any": {
Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"},
},
"federation__Scope": {
Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"},
},
"federation__Policy": {
Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"},
},
}
func (f *Federation) MutateConfig(cfg *config.Config) error {
for typeName, entry := range builtins {
if cfg.Models.Exists(typeName) {
return fmt.Errorf("%v already exists which must be reserved when Federation is enabled", typeName)
@ -79,13 +104,14 @@ func (f *federation) MutateConfig(cfg *config.Config) error {
cfg.Models[typeName] = entry
}
cfg.Directives["external"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["requires"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives[dirNameRequires] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["provides"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["key"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives[dirNameKey] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["extends"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives[dirNameEntityResolver] = config.DirectiveConfig{SkipRuntime: true}
// Federation 2 specific directives
if f.Version == 2 {
if f.version == 2 {
cfg.Directives["shareable"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["link"] = config.DirectiveConfig{SkipRuntime: true}
cfg.Directives["tag"] = config.DirectiveConfig{SkipRuntime: true}
@ -98,95 +124,48 @@ func (f *federation) MutateConfig(cfg *config.Config) error {
cfg.Directives["composeDirective"] = config.DirectiveConfig{SkipRuntime: true}
}
if f.usesRequires && f.PackageOptions.ComputedRequires {
cfg.Schema.Directives[dirPopulateFromRepresentations.Name] = dirPopulateFromRepresentations
cfg.Directives[dirPopulateFromRepresentations.Name] = config.DirectiveConfig{Implementation: &populateFromRepresentationsImplementation}
cfg.Schema.Directives[dirEntityReference.Name] = dirEntityReference
cfg.Directives[dirEntityReference.Name] = config.DirectiveConfig{SkipRuntime: true}
f.addMapType(cfg)
f.mutateSchemaForRequires(cfg.Schema, cfg)
}
return nil
}
func (f *federation) InjectSourceEarly() *ast.Source {
func (f *Federation) InjectSourcesEarly() ([]*ast.Source, error) {
input := ``
// add version-specific changes on key directive, as well as adding the new directives for federation 2
if f.Version == 1 {
input += `
directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE
directive @requires(fields: _FieldSet!) on FIELD_DEFINITION
directive @provides(fields: _FieldSet!) on FIELD_DEFINITION
directive @extends on OBJECT | INTERFACE
directive @external on FIELD_DEFINITION
scalar _Any
scalar _FieldSet
`
} else if f.Version == 2 {
input += `
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
directive @composeDirective(name: String!) repeatable on SCHEMA
directive @extends on OBJECT | INTERFACE
directive @external on OBJECT | FIELD_DEFINITION
directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @inaccessible on
| ARGUMENT_DEFINITION
| ENUM
| ENUM_VALUE
| FIELD_DEFINITION
| INPUT_FIELD_DEFINITION
| INPUT_OBJECT
| INTERFACE
| OBJECT
| SCALAR
| UNION
directive @interfaceObject on OBJECT
directive @link(import: [String!], url: String!) repeatable on SCHEMA
directive @override(from: String!, label: String) on FIELD_DEFINITION
directive @policy(policies: [[federation__Policy!]!]!) on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| SCALAR
| ENUM
directive @provides(fields: FieldSet!) on FIELD_DEFINITION
directive @requires(fields: FieldSet!) on FIELD_DEFINITION
directive @requiresScopes(scopes: [[federation__Scope!]!]!) on
| FIELD_DEFINITION
| OBJECT
| INTERFACE
| SCALAR
| ENUM
directive @shareable repeatable on FIELD_DEFINITION | OBJECT
directive @tag(name: String!) repeatable on
| ARGUMENT_DEFINITION
| ENUM
| ENUM_VALUE
| FIELD_DEFINITION
| INPUT_FIELD_DEFINITION
| INPUT_OBJECT
| INTERFACE
| OBJECT
| SCALAR
| UNION
scalar _Any
scalar FieldSet
scalar federation__Policy
scalar federation__Scope
`
if f.version == 1 {
input += federationVersion1Schema
} else if f.version == 2 {
input += federationVersion2Schema
}
return &ast.Source{
Name: "federation/directives.graphql",
return []*ast.Source{{
Name: dirGraphQLQFile,
Input: input,
BuiltIn: true,
}
}}, nil
}
// InjectSourceLate creates a GraphQL Entity type with all
// the fields that had the @key directive
func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source {
f.setEntities(schema)
func (f *Federation) InjectSourcesLate(schema *ast.Schema) ([]*ast.Source, error) {
f.Entities = f.buildEntities(schema, f.version)
var entities, resolvers, entityResolverInputDefinitions string
entities := make([]string, 0)
resolvers := make([]string, 0)
entityResolverInputDefinitions := make([]string, 0)
for _, e := range f.Entities {
if e.Def.Kind != ast.Interface {
if entities != "" {
entities += " | "
}
entities += e.Name
entities = append(entities, e.Name)
} else if len(schema.GetPossibleTypes(e.Def)) == 0 {
fmt.Println(
"skipping @key field on interface " + e.Def.Name + " as no types implement it",
@ -194,48 +173,33 @@ func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source {
}
for _, r := range e.Resolvers {
if e.Multi {
if entityResolverInputDefinitions != "" {
entityResolverInputDefinitions += "\n\n"
}
entityResolverInputDefinitions += "input " + r.InputTypeName + " {\n"
for _, keyField := range r.KeyFields {
entityResolverInputDefinitions += fmt.Sprintf(
"\t%s: %s\n",
keyField.Field.ToGo(),
keyField.Definition.Type.String(),
)
}
entityResolverInputDefinitions += "}"
resolvers += fmt.Sprintf("\t%s(reps: [%s]!): [%s]\n", r.ResolverName, r.InputTypeName, e.Name)
} else {
resolverArgs := ""
for _, keyField := range r.KeyFields {
resolverArgs += fmt.Sprintf("%s: %s,", keyField.Field.ToGoPrivate(), keyField.Definition.Type.String())
}
resolvers += fmt.Sprintf("\t%s(%s): %s!\n", r.ResolverName, resolverArgs, e.Name)
resolverSDL, entityResolverInputSDL := buildResolverSDL(r, e.Multi)
resolvers = append(resolvers, resolverSDL)
if entityResolverInputSDL != "" {
entityResolverInputDefinitions = append(entityResolverInputDefinitions, entityResolverInputSDL)
}
}
}
var blocks []string
if entities != "" {
entities = `# a union of all types that use the @key directive
union _Entity = ` + entities
blocks = append(blocks, entities)
if len(entities) > 0 {
entitiesSDL := `# a union of all types that use the @key directive
union _Entity = ` + strings.Join(entities, " | ")
blocks = append(blocks, entitiesSDL)
}
// resolvers can be empty if a service defines only "empty
// extend" types. This should be rare.
if resolvers != "" {
if entityResolverInputDefinitions != "" {
blocks = append(blocks, entityResolverInputDefinitions)
if len(resolvers) > 0 {
if len(entityResolverInputDefinitions) > 0 {
inputSDL := strings.Join(entityResolverInputDefinitions, "\n\n")
blocks = append(blocks, inputSDL)
}
resolvers = `# fake type to build resolver interfaces for users to implement
resolversSDL := `# fake type to build resolver interfaces for users to implement
type Entity {
` + resolvers + `
` + strings.Join(resolvers, "\n") + `
}`
blocks = append(blocks, resolvers)
blocks = append(blocks, resolversSDL)
}
_serviceTypeDef := `type _Service {
@ -259,14 +223,14 @@ type Entity {
}`
blocks = append(blocks, extendTypeQueryDef)
return &ast.Source{
Name: "federation/entity.graphql",
return []*ast.Source{{
Name: entityGraphQLQFile,
BuiltIn: true,
Input: "\n" + strings.Join(blocks, "\n\n") + "\n",
}
}}, nil
}
func (f *federation) GenerateCode(data *codegen.Data) error {
func (f *Federation) GenerateCode(data *codegen.Data) error {
// requires imports
requiresImports := make(map[string]bool, 0)
requiresImports["context"] = true
@ -275,7 +239,11 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
requiresEntities := make(map[string]*Entity, 0)
// Save package options on f for template use
f.PackageOptions = data.Config.Federation.Options
packageOptions, err := buildPackageOptions(data.Config)
if err != nil {
return fmt.Errorf("invalid federation package options: %w", err)
}
f.PackageOptions = packageOptions
if len(f.Entities) > 0 {
if data.Objects.ByName("Entity") != nil {
@ -295,18 +263,7 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
}
for _, r := range e.Resolvers {
// fill in types for key fields
//
for _, keyField := range r.KeyFields {
if len(keyField.Field) == 0 {
fmt.Println(
"skipping @key field " + keyField.Definition.Name + " in " + r.ResolverName + " in " + e.Def.Name,
)
continue
}
cgField := keyField.Field.TypeReference(obj, data.Objects)
keyField.Type = cgField.TypeReference
}
populateKeyFieldTypes(r, obj, data.Objects, e.Def.Name)
}
// fill in types for requires fields
@ -348,69 +305,12 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
}
}
if data.Config.Federation.Options["explicit_requires"] && len(requiresEntities) > 0 {
// check for existing requires functions
type Populator struct {
FuncName string
Exists bool
Comment string
Implementation string
Entity *Entity
}
populators := make([]Populator, 0)
rewriter, err := rewrite.New(data.Config.Federation.Dir())
if err != nil {
return err
}
for name, entity := range requiresEntities {
populator := Populator{
FuncName: fmt.Sprintf("Populate%sRequires", name),
Entity: entity,
}
populator.Comment = strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment("executionContext", populator.FuncName), `\`))
populator.Implementation = strings.TrimSpace(rewriter.GetMethodBody("executionContext", populator.FuncName))
if populator.Implementation == "" {
populator.Exists = false
populator.Implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v\"))", populator.FuncName)
}
populators = append(populators, populator)
}
sort.Slice(populators, func(i, j int) bool {
return populators[i].FuncName < populators[j].FuncName
})
requiresFile := data.Config.Federation.Dir() + "/federation.requires.go"
existingImports := rewriter.ExistingImports(requiresFile)
for _, imp := range existingImports {
if imp.Alias == "" {
// import exists in both places, remove
delete(requiresImports, imp.ImportPath)
}
}
for k := range requiresImports {
existingImports = append(existingImports, rewrite.Import{ImportPath: k})
}
// render requires populators
err = templates.Render(templates.Options{
PackageName: data.Config.Federation.Package,
Filename: requiresFile,
Data: struct {
federation
ExistingImports []rewrite.Import
Populators []Populator
OriginalSource string
}{*f, existingImports, populators, ""},
GeneratedHeader: false,
Packages: data.Config.Packages,
Template: explicitRequiresTemplate,
})
if f.PackageOptions.ExplicitRequires && len(requiresEntities) > 0 {
err := f.generateExplicitRequires(
data,
requiresEntities,
requiresImports,
)
if err != nil {
return err
}
@ -420,7 +320,7 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
PackageName: data.Config.Federation.Package,
Filename: data.Config.Federation.Filename,
Data: struct {
federation
Federation
UsePointers bool
}{*f, data.Config.ResolversAlwaysReturnPointers},
GeneratedHeader: true,
@ -429,137 +329,227 @@ func (f *federation) GenerateCode(data *codegen.Data) error {
})
}
func (f *federation) setEntities(schema *ast.Schema) {
// Fill in types for key fields
func populateKeyFieldTypes(
resolver *EntityResolver,
obj *codegen.Object,
allObjects codegen.Objects,
name string,
) {
for _, keyField := range resolver.KeyFields {
if len(keyField.Field) == 0 {
fmt.Println(
"skipping @key field " + keyField.Definition.Name + " in " + resolver.ResolverName + " in " + name,
)
continue
}
cgField := keyField.Field.TypeReference(obj, allObjects)
keyField.Type = cgField.TypeReference
}
}
func (f *Federation) buildEntities(schema *ast.Schema, version int) []*Entity {
entities := make([]*Entity, 0)
for _, schemaType := range schema.Types {
keys, ok := isFederatedEntity(schemaType)
if !ok {
continue
entity := f.buildEntity(schemaType, schema, version)
if entity != nil {
entities = append(entities, entity)
}
if (schemaType.Kind == ast.Interface) && (len(schema.GetPossibleTypes(schemaType)) == 0) {
fmt.Printf("@key directive found on unused \"interface %s\". Will be ignored.\n", schemaType.Name)
continue
}
e := &Entity{
Name: schemaType.Name,
Def: schemaType,
Resolvers: nil,
Requires: nil,
}
// Let's process custom entity resolver settings.
dir := schemaType.Directives.ForName("entityResolver")
if dir != nil {
if dirArg := dir.Arguments.ForName("multi"); dirArg != nil {
if dirVal, err := dirArg.Value.Value(nil); err == nil {
e.Multi = dirVal.(bool)
}
}
}
// If our schema has a field with a type defined in
// another service, then we need to define an "empty
// extend" of that type in this service, so this service
// knows what the type is like. But the graphql-server
// will never ask us to actually resolve this "empty
// extend", so we don't require a resolver function for
// it. (Well, it will never ask in practice; it's
// unclear whether the spec guarantees this. See
// https://github.com/apollographql/apollo-server/issues/3852
// ). Example:
// type MyType {
// myvar: TypeDefinedInOtherService
// }
// // Federation needs this type, but
// // it doesn't need a resolver for it!
// extend TypeDefinedInOtherService @key(fields: "id") {
// id: ID @external
// }
if !e.allFieldsAreExternal(f.Version) {
for _, dir := range keys {
if len(dir.Arguments) > 2 {
panic("More than two arguments provided for @key declaration.")
}
var arg *ast.Argument
// since keys are able to now have multiple arguments, we need to check both possible for a possible @key(fields="" fields="")
for _, a := range dir.Arguments {
if a.Name == "fields" {
if arg != nil {
panic("More than one `fields` provided for @key declaration.")
}
arg = a
}
}
keyFieldSet := fieldset.New(arg.Value.Raw, nil)
keyFields := make([]*KeyField, len(keyFieldSet))
resolverFields := []string{}
for i, field := range keyFieldSet {
def := field.FieldDefinition(schemaType, schema)
if def == nil {
panic(fmt.Sprintf("no field for %v", field))
}
keyFields[i] = &KeyField{Definition: def, Field: field}
resolverFields = append(resolverFields, keyFields[i].Field.ToGo())
}
resolverFieldsToGo := schemaType.Name + "By" + strings.Join(resolverFields, "And")
var resolverName string
if e.Multi {
resolverFieldsToGo += "s" // Pluralize for better API readability
resolverName = fmt.Sprintf("findMany%s", resolverFieldsToGo)
} else {
resolverName = fmt.Sprintf("find%s", resolverFieldsToGo)
}
e.Resolvers = append(e.Resolvers, &EntityResolver{
ResolverName: resolverName,
KeyFields: keyFields,
InputTypeName: resolverFieldsToGo + "Input",
})
}
e.Requires = []*Requires{}
for _, f := range schemaType.Fields {
dir := f.Directives.ForName("requires")
if dir == nil {
continue
}
if len(dir.Arguments) != 1 || dir.Arguments[0].Name != "fields" {
panic("Exactly one `fields` argument needed for @requires declaration.")
}
requiresFieldSet := fieldset.New(dir.Arguments[0].Value.Raw, nil)
for _, field := range requiresFieldSet {
e.Requires = append(e.Requires, &Requires{
Name: field.ToGoPrivate(),
Field: field,
})
}
}
}
f.Entities = append(f.Entities, e)
}
// make sure order remains stable across multiple builds
sort.Slice(f.Entities, func(i, j int) bool {
return f.Entities[i].Name < f.Entities[j].Name
sort.Slice(entities, func(i, j int) bool {
return entities[i].Name < entities[j].Name
})
return entities
}
func (f *Federation) buildEntity(
schemaType *ast.Definition,
schema *ast.Schema,
version int,
) *Entity {
keys, ok := isFederatedEntity(schemaType)
if !ok {
return nil
}
if (schemaType.Kind == ast.Interface) && (len(schema.GetPossibleTypes(schemaType)) == 0) {
fmt.Printf("@key directive found on unused \"interface %s\". Will be ignored.\n", schemaType.Name)
return nil
}
entity := &Entity{
Name: schemaType.Name,
Def: schemaType,
Resolvers: nil,
Requires: nil,
Multi: isMultiEntity(schemaType),
}
// If our schema has a field with a type defined in
// another service, then we need to define an "empty
// extend" of that type in this service, so this service
// knows what the type is like. But the graphql-server
// will never ask us to actually resolve this "empty
// extend", so we don't require a resolver function for
// it. (Well, it will never ask in practice; it's
// unclear whether the spec guarantees this. See
// https://github.com/apollographql/apollo-server/issues/3852
// ). Example:
// type MyType {
// myvar: TypeDefinedInOtherService
// }
// // Federation needs this type, but
// // it doesn't need a resolver for it!
// extend TypeDefinedInOtherService @key(fields: "id") {
// id: ID @external
// }
if entity.allFieldsAreExternal(version) {
return entity
}
entity.Resolvers = buildResolvers(schemaType, schema, keys, entity.Multi)
entity.Requires = buildRequires(schemaType)
if len(entity.Requires) > 0 {
f.usesRequires = true
}
return entity
}
func isMultiEntity(schemaType *ast.Definition) bool {
dir := schemaType.Directives.ForName(dirNameEntityResolver)
if dir == nil {
return false
}
if dirArg := dir.Arguments.ForName("multi"); dirArg != nil {
if dirVal, err := dirArg.Value.Value(nil); err == nil {
return dirVal.(bool)
}
}
return false
}
func buildResolvers(
schemaType *ast.Definition,
schema *ast.Schema,
keys []*ast.Directive,
multi bool,
) []*EntityResolver {
resolvers := make([]*EntityResolver, 0)
for _, dir := range keys {
if len(dir.Arguments) > 2 {
panic("More than two arguments provided for @key declaration.")
}
keyFields, resolverFields := buildKeyFields(
schemaType,
schema,
dir,
)
resolverFieldsToGo := schemaType.Name + "By" + strings.Join(resolverFields, "And")
var resolverName string
if multi {
resolverFieldsToGo += "s" // Pluralize for better API readability
resolverName = fmt.Sprintf("findMany%s", resolverFieldsToGo)
} else {
resolverName = fmt.Sprintf("find%s", resolverFieldsToGo)
}
resolvers = append(resolvers, &EntityResolver{
ResolverName: resolverName,
KeyFields: keyFields,
InputTypeName: resolverFieldsToGo + "Input",
ReturnTypeName: schemaType.Name,
})
}
return resolvers
}
func extractFields(
dir *ast.Directive,
) (string, error) {
var arg *ast.Argument
// since directives are able to now have multiple arguments, we need to check both possible for a possible @key(fields="" fields="")
for _, a := range dir.Arguments {
if a.Name == DirArgFields {
if arg != nil {
return "", errors.New("more than one \"fields\" argument provided for declaration")
}
arg = a
}
}
return arg.Value.Raw, nil
}
func buildKeyFields(
schemaType *ast.Definition,
schema *ast.Schema,
dir *ast.Directive,
) ([]*KeyField, []string) {
fieldsRaw, err := extractFields(dir)
if err != nil {
panic("More than one `fields` argument provided for declaration.")
}
keyFieldSet := fieldset.New(fieldsRaw, nil)
keyFields := make([]*KeyField, len(keyFieldSet))
resolverFields := []string{}
for i, field := range keyFieldSet {
def := field.FieldDefinition(schemaType, schema)
if def == nil {
panic(fmt.Sprintf("no field for %v", field))
}
keyFields[i] = &KeyField{Definition: def, Field: field}
resolverFields = append(resolverFields, keyFields[i].Field.ToGo())
}
return keyFields, resolverFields
}
func buildRequires(schemaType *ast.Definition) []*Requires {
requires := make([]*Requires, 0)
for _, f := range schemaType.Fields {
dir := f.Directives.ForName(dirNameRequires)
if dir == nil {
continue
}
fieldsRaw, err := extractFields(dir)
if err != nil {
panic("Exactly one `fields` argument needed for @requires declaration.")
}
requiresFieldSet := fieldset.New(fieldsRaw, nil)
for _, field := range requiresFieldSet {
requires = append(requires, &Requires{
Name: field.ToGoPrivate(),
Field: field,
})
}
}
return requires
}
func isFederatedEntity(schemaType *ast.Definition) ([]*ast.Directive, bool) {
switch schemaType.Kind {
case ast.Object:
keys := schemaType.Directives.ForNames("key")
keys := schemaType.Directives.ForNames(dirNameKey)
if len(keys) > 0 {
return keys, true
}
case ast.Interface:
keys := schemaType.Directives.ForNames("key")
keys := schemaType.Directives.ForNames(dirNameKey)
if len(keys) > 0 {
return keys, true
}
@ -577,3 +567,146 @@ func isFederatedEntity(schemaType *ast.Definition) ([]*ast.Directive, bool) {
}
return nil, false
}
func (f *Federation) generateExplicitRequires(
data *codegen.Data,
requiresEntities map[string]*Entity,
requiresImports map[string]bool,
) error {
// check for existing requires functions
type Populator struct {
FuncName string
Exists bool
Comment string
Implementation string
Entity *Entity
}
populators := make([]Populator, 0)
rewriter, err := rewrite.New(data.Config.Federation.Dir())
if err != nil {
return err
}
for name, entity := range requiresEntities {
populator := Populator{
FuncName: fmt.Sprintf("Populate%sRequires", name),
Entity: entity,
}
populator.Comment = strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment("executionContext", populator.FuncName), `\`))
populator.Implementation = strings.TrimSpace(rewriter.GetMethodBody("executionContext", populator.FuncName))
if populator.Implementation == "" {
populator.Exists = false
populator.Implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v\"))", populator.FuncName)
}
populators = append(populators, populator)
}
sort.Slice(populators, func(i, j int) bool {
return populators[i].FuncName < populators[j].FuncName
})
requiresFile := data.Config.Federation.Dir() + "/federation.requires.go"
existingImports := rewriter.ExistingImports(requiresFile)
for _, imp := range existingImports {
if imp.Alias == "" {
// import exists in both places, remove
delete(requiresImports, imp.ImportPath)
}
}
for k := range requiresImports {
existingImports = append(existingImports, rewrite.Import{ImportPath: k})
}
// render requires populators
return templates.Render(templates.Options{
PackageName: data.Config.Federation.Package,
Filename: requiresFile,
Data: struct {
Federation
ExistingImports []rewrite.Import
Populators []Populator
OriginalSource string
}{*f, existingImports, populators, ""},
GeneratedHeader: false,
Packages: data.Config.Packages,
Template: explicitRequiresTemplate,
})
}
func buildResolverSDL(
resolver *EntityResolver,
multi bool,
) (resolverSDL, entityResolverInputSDL string) {
if multi {
entityResolverInputSDL = buildEntityResolverInputDefinitionSDL(resolver)
resolverSDL := fmt.Sprintf("\t%s(reps: [%s]!): [%s]", resolver.ResolverName, resolver.InputTypeName, resolver.ReturnTypeName)
return resolverSDL, entityResolverInputSDL
}
resolverArgs := ""
for _, keyField := range resolver.KeyFields {
resolverArgs += fmt.Sprintf("%s: %s,", keyField.Field.ToGoPrivate(), keyField.Definition.Type.String())
}
resolverSDL = fmt.Sprintf("\t%s(%s): %s!", resolver.ResolverName, resolverArgs, resolver.ReturnTypeName)
return resolverSDL, ""
}
func buildEntityResolverInputDefinitionSDL(resolver *EntityResolver) string {
entityResolverInputDefinition := "input " + resolver.InputTypeName + " {\n"
for _, keyField := range resolver.KeyFields {
entityResolverInputDefinition += fmt.Sprintf(
"\t%s: %s\n",
keyField.Field.ToGo(),
keyField.Definition.Type.String(),
)
}
return entityResolverInputDefinition + "}"
}
func (f *Federation) addMapType(cfg *config.Config) {
cfg.Models[mapTypeName] = config.TypeMapEntry{
Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"},
}
cfg.Schema.Types[mapTypeName] = &ast.Definition{
Kind: ast.Scalar,
Name: mapTypeName,
Description: "Maps an arbitrary GraphQL value to a map[string]any Go type.",
}
}
func (f *Federation) mutateSchemaForRequires(
schema *ast.Schema,
cfg *config.Config,
) {
for _, schemaType := range schema.Types {
for _, field := range schemaType.Fields {
if dir := field.Directives.ForName(dirNameRequires); dir != nil {
// ensure we always generate a resolver for any @requires field
model := cfg.Models[schemaType.Name]
fieldConfig := model.Fields[field.Name]
fieldConfig.Resolver = true
if model.Fields == nil {
model.Fields = make(map[string]config.TypeMapField)
}
model.Fields[field.Name] = fieldConfig
cfg.Models[schemaType.Name] = model
requiresArgument := &ast.ArgumentDefinition{
Name: fieldArgRequires,
Type: ast.NamedType(mapTypeName, nil),
Directives: ast.DirectiveList{
{
Name: dirNamePopulateFromRepresentations,
Definition: dirPopulateFromRepresentations,
},
},
}
field.Arguments = append(field.Arguments, requiresArgument)
}
}
}
}

View File

@ -36,15 +36,50 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.
func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity {
list := make([]fedruntime.Entity, len(representations))
repsMap := map[string]struct {
i []int
r []map[string]interface{}
}{}
repsMap := ec.buildRepresentationGroups(ctx, representations)
switch len(repsMap) {
case 0:
return list
case 1:
for typeName, reps := range repsMap {
ec.resolveEntityGroup(ctx, typeName, reps, list)
}
return list
default:
var g sync.WaitGroup
g.Add(len(repsMap))
for typeName, reps := range repsMap {
go func(typeName string, reps []EntityWithIndex) {
ec.resolveEntityGroup(ctx, typeName, reps, list)
g.Done()
}(typeName, reps)
}
g.Wait()
return list
}
}
type EntityWithIndex struct {
// The index in the original representation array
index int
entity EntityRepresentation
}
// EntityRepresentation is the JSON representation of an entity sent by the Router
// used as the inputs for us to resolve.
//
// We make it a map because we know the top level JSON is always an object.
type EntityRepresentation map[string]any
// We group entities by typename so that we can parallelize their resolution.
// This is particularly helpful when there are entity groups in multi mode.
buildRepresentationGroups := func(reps []map[string]interface{}) {
for i, rep := range reps {
func (ec *executionContext) buildRepresentationGroups(
ctx context.Context,
representations []map[string]any,
) map[string][]EntityWithIndex {
repsMap := make(map[string][]EntityWithIndex)
for i, rep := range representations {
typeName, ok := rep["__typename"].(string)
if !ok {
// If there is no __typename, we just skip the representation;
@ -53,14 +88,48 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
continue
}
_r := repsMap[typeName]
_r.i = append(_r.i, i)
_r.r = append(_r.r, rep)
repsMap[typeName] = _r
}
repsMap[typeName] = append(repsMap[typeName], EntityWithIndex{
index: i,
entity: rep,
})
}
isMulti := func(typeName string) bool {
return repsMap
}
func (ec *executionContext) resolveEntityGroup(
ctx context.Context,
typeName string,
reps []EntityWithIndex,
list []fedruntime.Entity,
) {
if isMulti(typeName) {
err := ec.resolveManyEntities(ctx, typeName, reps, list)
if err != nil {
ec.Error(ctx, err)
}
} else {
// if there are multiple entities to resolve, parallelize (similar to
// graphql.FieldSet.Dispatch)
var e sync.WaitGroup
e.Add(len(reps))
for i, rep := range reps {
i, rep := i, rep
go func(i int, rep EntityWithIndex) {
entity, err := ec.resolveEntity(ctx, typeName, rep.entity)
if err != nil {
ec.Error(ctx, err)
} else {
list[rep.index] = entity
}
e.Done()
}(i, rep)
}
e.Wait()
}
}
func isMulti(typeName string) bool {
switch typeName {
{{- range .Entities -}}
{{- if .Resolvers -}}
@ -75,7 +144,11 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
}
}
resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) {
func (ec *executionContext) resolveEntity(
ctx context.Context,
typeName string,
rep EntityRepresentation,
) (e fedruntime.Entity, err error) {
// we need to do our own panic handling, because we may be called in a
// goroutine, where the usual panic handling can't catch us
defer func () {
@ -90,45 +163,51 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
case "{{.Def.Name}}":
resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, rep)
if err != nil {
return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err)
return nil, fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err)
}
switch resolverName {
{{ range $i, $resolver := .Resolvers }}
case "{{.ResolverName}}":
{{- range $j, $keyField := .KeyFields }}
id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
if err != nil {
return fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err)
return nil, fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err)
}
{{- end}}
entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, {{- range $j, $_ := .KeyFields -}} id{{$j}}, {{end}})
if err != nil {
return fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err)
return nil, fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err)
}
{{ if and (index $options "explicit_requires") $entity.Requires }}
{{- if $options.ComputedRequires }}
{{/* We don't do anything in this case, computed requires are handled by standard resolvers */}}
{{- else if and $options.ExplicitRequires $entity.Requires }}
err = ec.Populate{{$entity.Def.Name}}Requires(ctx, {{- if (not $usePointers) -}}&{{- end -}}entity, rep)
if err != nil {
return fmt.Errorf(`populating requires for Entity "{{$entity.Def.Name}}": %w`, err)
return nil, fmt.Errorf(`populating requires for Entity "{{$entity.Def.Name}}": %w`, err)
}
{{- else }}
{{ range $entity.Requires }}
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
if err != nil {
return err
return nil, err
}
{{- end }}
{{- end }}
list[idx[i]] = entity
return nil
return entity, nil
{{- end }}
}
{{ end }}
{{- end }}
}
return fmt.Errorf("%w: %s", ErrUnknownType, typeName)
return nil, fmt.Errorf("%w: %s", ErrUnknownType, typeName)
}
resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) {
func (ec *executionContext) resolveManyEntities(
ctx context.Context,
typeName string,
reps []EntityWithIndex,
list []fedruntime.Entity,
) (err error) {
// we need to do our own panic handling, because we may be called in a
// goroutine, where the usual panic handling can't catch us
defer func () {
@ -141,43 +220,43 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
{{ range $_, $entity := .Entities }}
{{ if and .Resolvers .Multi -}}
case "{{.Def.Name}}":
resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, reps[0])
resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, reps[0].entity)
if err != nil {
return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err)
}
switch resolverName {
{{ range $i, $resolver := .Resolvers }}
case "{{.ResolverName}}":
_reps := make([]*{{.LookupInputType}}, len(reps))
typedReps := make([]*{{.LookupInputType}}, len(reps))
for i, rep := range reps {
{{ range $i, $keyField := .KeyFields -}}
id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep.entity["{{.Field.Join `"].(map[string]interface{})["`}}"])
if err != nil {
return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}"))
}
{{end}}
_reps[i] = &{{.LookupInputType}} {
typedReps[i] = &{{.LookupInputType}} {
{{ range $i, $keyField := .KeyFields -}}
{{$keyField.Field.ToGo}}: id{{$i}},
{{end}}
}
}
entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, _reps)
entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, typedReps)
if err != nil {
return err
}
for i, entity := range entities {
{{- range $entity.Requires }}
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i]["{{.Field.Join `"].(map[string]interface{})["`}}"])
entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i].entity["{{.Field.Join `"].(map[string]interface{})["`}}"])
if err != nil {
return err
}
{{- end}}
list[idx[i]] = entity
list[reps[i].index] = entity
}
return nil
{{ end }}
@ -188,54 +267,6 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
{{- end }}
default:
return errors.New("unknown type: "+typeName)
}
}
resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) {
if isMulti(typeName) {
err := resolveManyEntities(ctx, typeName, reps, idx)
if err != nil {
ec.Error(ctx, err)
}
} else {
// if there are multiple entities to resolve, parallelize (similar to
// graphql.FieldSet.Dispatch)
var e sync.WaitGroup
e.Add(len(reps))
for i, rep := range reps {
i, rep := i, rep
go func(i int, rep map[string]interface{}) {
err := resolveEntity(ctx, typeName, rep, idx, i)
if err != nil {
ec.Error(ctx, err)
}
e.Done()
}(i, rep)
}
e.Wait()
}
}
buildRepresentationGroups(representations)
switch len(repsMap) {
case 0:
return list
case 1:
for typeName, reps := range repsMap {
resolveEntityGroup(typeName, reps.r, reps.i)
}
return list
default:
var g sync.WaitGroup
g.Add(len(repsMap))
for typeName, reps := range repsMap {
go func(typeName string, reps []map[string]interface{}, idx []int) {
resolveEntityGroup(typeName, reps, idx)
g.Done()
}(typeName, reps.r, reps.i)
}
g.Wait()
return list
}
}
@ -244,13 +275,13 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
{{ range $_, $entity := .Entities }}
{{- if .Resolvers }}
func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep map[string]interface{}) (string, error) {
func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep EntityRepresentation) (string, error) {
{{- range .Resolvers }}
for {
var (
m map[string]interface{}
m EntityRepresentation
val interface{}
ok bool
ok bool
)
_ = val
// if all of the KeyFields values for this resolver are null,

View File

@ -18,7 +18,7 @@ TODO(miguel): add details.
# Entity resolvers - GetMany entities
The federation plugin implements `GetMany` semantics in which entity resolvers get the entire list of representations that need to be resolved. This functionality is currently optin tho, and to enable it you need to specify the directive `@entityResolver` in the federated entity you want this feature for. E.g.
The federation plugin implements `GetMany` semantics in which entity resolvers get the entire list of representations that need to be resolved. This functionality is currently option tho, and to enable it you need to specify the directive `@entityResolver` in the federated entity you want this feature for. E.g.
```
directive @entityResolver(multi: Boolean) on OBJECT
@ -39,4 +39,4 @@ func (r *entityResolver) FindManyMultiHellosByName(ctx context.Context, reps []*
```
**Note:**
If you are using `omit_slice_element_pointers: true` option in your config yaml, your `GetMany` resolver will still generate in the example above the same signature `FindManyMultiHellosByName(ctx context.Context, reps []*generated.ManyMultiHellosByNameInput) ([]*generated.MultiHello, error)`. But all other instances will continue to honor `omit_slice_element_pointers: true`
If you are using `omit_slice_element_pointers: true` option in your config yaml, your `GetMany` resolver will still generate in the example above the same signature `FindManyMultiHellosByName(ctx context.Context, reps []*generated.ManyMultiHellosByNameInput) ([]*generated.MultiHello, error)`. But all other instances will continue to honor `omit_slice_element_pointers: true`

View File

@ -26,11 +26,6 @@ type (
// DefaultFieldMutateHook is the default hook for the Plugin which applies the GoFieldHook and GoTagFieldHook.
func DefaultFieldMutateHook(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error) {
var err error
f, err = GoFieldHook(td, fd, f)
if err != nil {
return f, err
}
return GoTagFieldHook(td, fd, f)
}
@ -292,18 +287,17 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
getter += "\treturn interfaceSlice\n"
getter += "}"
return getter
} else {
getter := fmt.Sprintf("func (this %s) Get%s() %s { return ", templates.ToGo(model.Name), field.GoName, goType)
if interfaceFieldTypeIsPointer && !structFieldTypeIsPointer {
getter += "&"
} else if !interfaceFieldTypeIsPointer && structFieldTypeIsPointer {
getter += "*"
}
getter += fmt.Sprintf("this.%s }", field.GoName)
return getter
}
getter := fmt.Sprintf("func (this %s) Get%s() %s { return ", templates.ToGo(model.Name), field.GoName, goType)
if interfaceFieldTypeIsPointer && !structFieldTypeIsPointer {
getter += "&"
} else if !interfaceFieldTypeIsPointer && structFieldTypeIsPointer {
getter += "*"
}
getter += fmt.Sprintf("this.%s }", field.GoName)
return getter
}
funcMap := template.FuncMap{
"getInterfaceByName": getInterfaceByName,
@ -338,146 +332,199 @@ func (m *Plugin) generateFields(cfg *config.Config, schemaType *ast.Definition)
binder := cfg.NewBinder()
fields := make([]*Field, 0)
var omittableType types.Type
for _, field := range schemaType.Fields {
var typ types.Type
fieldDef := cfg.Schema.Types[field.Type.Name()]
if cfg.Models.UserDefined(field.Type.Name()) {
var err error
typ, err = binder.FindTypeFromName(cfg.Models[field.Type.Name()].Model[0])
if err != nil {
return nil, err
}
} else {
switch fieldDef.Kind {
case ast.Scalar:
// no user defined model, referencing a default scalar
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), "string", nil),
nil,
nil,
)
case ast.Interface, ast.Union:
// no user defined model, referencing a generated interface type
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
types.NewInterfaceType([]*types.Func{}, []types.Type{}),
nil,
)
case ast.Enum:
// no user defined model, must reference a generated enum
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
nil,
nil,
)
case ast.Object, ast.InputObject:
// no user defined model, must reference a generated struct
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
types.NewStruct(nil, nil),
nil,
)
default:
panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind))
}
f, err := m.generateField(cfg, binder, schemaType, field)
if err != nil {
return nil, err
}
name := templates.ToGo(field.Name)
if nameOveride := cfg.Models[schemaType.Name].Fields[field.Name].FieldName; nameOveride != "" {
name = nameOveride
}
typ = binder.CopyModifiersFromAst(field.Type, typ)
if cfg.StructFieldsAlwaysPointers {
if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) {
typ = types.NewPointer(typ)
}
}
f := &Field{
Name: field.Name,
GoName: name,
Type: typ,
Description: field.Description,
Tag: getStructTagFromField(cfg, field),
Omittable: cfg.NullableInputOmittable && schemaType.Kind == ast.InputObject && !field.Type.NonNull,
}
if m.FieldHook != nil {
mf, err := m.FieldHook(schemaType, field, f)
if err != nil {
return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err)
}
f = mf
}
if f.IsResolver && cfg.OmitResolverFields {
if f == nil {
continue
}
if f.Omittable {
if schemaType.Kind != ast.InputObject || field.Type.NonNull {
return nil, fmt.Errorf("generror: field %v.%v: omittable is only applicable to nullable input fields", schemaType.Name, field.Name)
}
var err error
if omittableType == nil {
omittableType, err = binder.FindTypeFromName("github.com/99designs/gqlgen/graphql.Omittable")
if err != nil {
return nil, err
}
}
f.Type, err = binder.InstantiateType(omittableType, []types.Type{f.Type})
if err != nil {
return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err)
}
}
fields = append(fields, f)
}
// appending extra fields at the end of the fields list.
modelcfg := cfg.Models[schemaType.Name]
if len(modelcfg.ExtraFields) > 0 {
ff := make([]*Field, 0, len(modelcfg.ExtraFields))
for fname, fspec := range modelcfg.ExtraFields {
ftype := buildType(fspec.Type)
tag := `json:"-"`
if fspec.OverrideTags != "" {
tag = fspec.OverrideTags
}
ff = append(ff,
&Field{
Name: fname,
GoName: fname,
Type: ftype,
Description: fspec.Description,
Tag: tag,
})
}
sort.Slice(ff, func(i, j int) bool {
return ff[i].Name < ff[j].Name
})
fields = append(fields, ff...)
}
fields = append(fields, getExtraFields(cfg, schemaType.Name)...)
return fields, nil
}
func (m *Plugin) generateField(
cfg *config.Config,
binder *config.Binder,
schemaType *ast.Definition,
field *ast.FieldDefinition,
) (*Field, error) {
var omittableType types.Type
var typ types.Type
fieldDef := cfg.Schema.Types[field.Type.Name()]
if cfg.Models.UserDefined(field.Type.Name()) {
var err error
typ, err = binder.FindTypeFromName(cfg.Models[field.Type.Name()].Model[0])
if err != nil {
return nil, err
}
} else {
switch fieldDef.Kind {
case ast.Scalar:
// no user defined model, referencing a default scalar
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), "string", nil),
nil,
nil,
)
case ast.Interface, ast.Union:
// no user defined model, referencing a generated interface type
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
types.NewInterfaceType([]*types.Func{}, []types.Type{}),
nil,
)
case ast.Enum:
// no user defined model, must reference a generated enum
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
nil,
nil,
)
case ast.Object, ast.InputObject:
// no user defined model, must reference a generated struct
typ = types.NewNamed(
types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil),
types.NewStruct(nil, nil),
nil,
)
default:
panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind))
}
}
name := templates.ToGo(field.Name)
if nameOverride := cfg.Models[schemaType.Name].Fields[field.Name].FieldName; nameOverride != "" {
name = nameOverride
}
typ = binder.CopyModifiersFromAst(field.Type, typ)
if cfg.StructFieldsAlwaysPointers {
if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) {
typ = types.NewPointer(typ)
}
}
f := &Field{
Name: field.Name,
GoName: name,
Type: typ,
Description: field.Description,
Tag: getStructTagFromField(cfg, field),
Omittable: cfg.NullableInputOmittable && schemaType.Kind == ast.InputObject && !field.Type.NonNull,
IsResolver: cfg.Models[schemaType.Name].Fields[field.Name].Resolver,
}
if omittable := cfg.Models[schemaType.Name].Fields[field.Name].Omittable; omittable != nil {
f.Omittable = *omittable
}
if m.FieldHook != nil {
mf, err := m.FieldHook(schemaType, field, f)
if err != nil {
return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err)
}
f = mf
}
if f.IsResolver && cfg.OmitResolverFields {
return nil, nil
}
if f.Omittable {
if schemaType.Kind != ast.InputObject || field.Type.NonNull {
return nil, fmt.Errorf("generror: field %v.%v: omittable is only applicable to nullable input fields", schemaType.Name, field.Name)
}
var err error
if omittableType == nil {
omittableType, err = binder.FindTypeFromName("github.com/99designs/gqlgen/graphql.Omittable")
if err != nil {
return nil, err
}
}
f.Type, err = binder.InstantiateType(omittableType, []types.Type{f.Type})
if err != nil {
return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err)
}
}
return f, nil
}
func getExtraFields(cfg *config.Config, modelName string) []*Field {
modelcfg := cfg.Models[modelName]
extraFieldsCount := len(modelcfg.ExtraFields) + len(modelcfg.EmbedExtraFields)
if extraFieldsCount == 0 {
return nil
}
extraFields := make([]*Field, 0, extraFieldsCount)
makeExtraField := func(fname string, fspec config.ModelExtraField) *Field {
ftype := buildType(fspec.Type)
tag := `json:"-"`
if fspec.OverrideTags != "" {
tag = fspec.OverrideTags
}
return &Field{
Name: fname,
GoName: fname,
Type: ftype,
Description: fspec.Description,
Tag: tag,
}
}
if len(modelcfg.ExtraFields) > 0 {
for fname, fspec := range modelcfg.ExtraFields {
extraFields = append(extraFields, makeExtraField(fname, fspec))
}
}
if len(modelcfg.EmbedExtraFields) > 0 {
for _, fspec := range modelcfg.EmbedExtraFields {
extraFields = append(extraFields, makeExtraField("", fspec))
}
}
sort.Slice(extraFields, func(i, j int) bool {
if extraFields[i].Name == "" && extraFields[j].Name == "" {
return extraFields[i].Type.String() < extraFields[j].Type.String()
}
if extraFields[i].Name == "" {
return false
}
if extraFields[j].Name == "" {
return true
}
return extraFields[i].Name < extraFields[j].Name
})
return extraFields
}
func getStructTagFromField(cfg *config.Config, field *ast.FieldDefinition) string {
if !field.Type.NonNull && (cfg.EnableModelJsonOmitemptyTag == nil || *cfg.EnableModelJsonOmitemptyTag) {
return `json:"` + field.Name + `,omitempty"`
@ -591,7 +638,7 @@ func removeDuplicateTags(t string) string {
key := kv[0]
value := strings.Join(kv[1:], ":")
processed[key] = true
if len(returnTags) > 0 {
if returnTags != "" {
returnTags = " " + returnTags
}
@ -606,29 +653,9 @@ func removeDuplicateTags(t string) string {
return returnTags
}
// GoFieldHook applies the goField directive to the generated Field f.
// GoFieldHook is a noop
// TODO: This will be removed in the next breaking release
func GoFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error) {
args := make([]string, 0)
_ = args
for _, goField := range fd.Directives.ForNames("goField") {
if arg := goField.Arguments.ForName("name"); arg != nil {
if k, err := arg.Value.Value(nil); err == nil {
f.GoName = k.(string)
}
}
if arg := goField.Arguments.ForName("forceResolver"); arg != nil {
if k, err := arg.Value.Value(nil); err == nil {
f.IsResolver = k.(bool)
}
}
if arg := goField.Arguments.ForName("omittable"); arg != nil {
if k, err := arg.Value.Value(nil); err == nil {
f.Omittable = k.(bool)
}
}
}
return f, nil
}

View File

@ -22,11 +22,18 @@ type CodeGenerator interface {
}
// EarlySourceInjector is used to inject things that are required for user schema files to compile.
// Deprecated: Use EarlySourcesInjector instead
type EarlySourceInjector interface {
InjectSourceEarly() *ast.Source
}
// EarlySourcesInjector is used to inject things that are required for user schema files to compile.
type EarlySourcesInjector interface {
InjectSourcesEarly() ([]*ast.Source, error)
}
// LateSourceInjector is used to inject more sources, after we have loaded the users schema.
// Deprecated: Use LateSourcesInjector instead
type LateSourceInjector interface {
InjectSourceLate(schema *ast.Schema) *ast.Source
}
@ -35,3 +42,8 @@ type LateSourceInjector interface {
type ResolverImplementer interface {
Implement(prevImplementation string, field *codegen.Field) string
}
// LateSourcesInjector is used to inject more sources, after we have loaded the users schema.
type LateSourcesInjector interface {
InjectSourcesLate(schema *ast.Schema) ([]*ast.Source, error)
}

View File

@ -53,26 +53,44 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error {
func (m *Plugin) generateSingleFile(data *codegen.Data) error {
file := File{}
if _, err := os.Stat(data.Config.Resolver.Filename); err == nil {
// file already exists and we do not support updating resolvers with layout = single so just return
return nil
rewriter, err := rewrite.New(data.Config.Resolver.Dir())
if err != nil {
return err
}
for _, o := range data.Objects {
if o.HasResolvers() {
caser := cases.Title(language.English, cases.NoLower)
rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type))
rewriter.GetMethodBody(data.Config.Resolver.Type, caser.String(o.Name))
file.Objects = append(file.Objects, o)
}
for _, f := range o.Fields {
if !f.IsResolver {
continue
}
resolver := Resolver{o, f, nil, "", `panic("not implemented")`, nil}
file.Resolvers = append(file.Resolvers, &resolver)
structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)
comment := strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment(structName, f.GoFieldName), `\`))
implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName))
if implementation != "" {
resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation, nil}
file.Resolvers = append(file.Resolvers, &resolver)
} else {
resolver := Resolver{o, f, nil, "", `panic("not implemented")`, nil}
file.Resolvers = append(file.Resolvers, &resolver)
}
}
}
if _, err := os.Stat(data.Config.Resolver.Filename); err == nil {
file.name = data.Config.Resolver.Filename
file.imports = rewriter.ExistingImports(file.name)
file.RemainingSource = rewriter.RemainingSource(file.name)
}
resolverBuild := &ResolverBuild{
File: &file,
PackageName: data.Config.Resolver.Package,
@ -88,7 +106,7 @@ func (m *Plugin) generateSingleFile(data *codegen.Data) error {
return templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package,
FileNotice: `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`,
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.`,
Filename: data.Config.Resolver.Filename,
Data: resolverBuild,
Packages: data.Config.Packages,
@ -138,7 +156,7 @@ func (m *Plugin) generatePerSchema(data *codegen.Data) error {
continue
}
if implExists {
return fmt.Errorf("multiple plugins implement ResolverImplementer")
return errors.New("multiple plugins implement ResolverImplementer")
}
implExists = true
resolver.ImplementationRender = rImpl.Implement
@ -269,7 +287,7 @@ func (r *Resolver) Implementation() string {
return r.ImplementationStr
}
func gqlToResolverName(base string, gqlname, filenameTmpl string) string {
func gqlToResolverName(base, gqlname, filenameTmpl string) string {
gqlname = filepath.Base(gqlname)
ext := filepath.Ext(gqlname)
if filenameTmpl == "" {

View File

@ -48,5 +48,7 @@
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
/*
{{ .RemainingSource }}
*/
{{ end }}

View File

@ -1,5 +1,33 @@
# Changelog
## 3.3.0 (2024-08-27)
### Added
- #238: Add LessThanEqual and GreaterThanEqual functions (thanks @grosser)
- #213: nil version equality checking (thanks @KnutZuidema)
### Changed
- #241: Simplify StrictNewVersion parsing (thanks @grosser)
- Testing support up through Go 1.23
- Minimum version set to 1.21 as this is what's tested now
- Fuzz testing now supports caching
## 3.2.1 (2023-04-10)
### Changed
- #198: Improved testing around pre-release names
- #200: Improved code scanning with addition of CodeQL
- #201: Testing now includes Go 1.20. Go 1.17 has been dropped
- #202: Migrated Fuzz testing to Go built-in Fuzzing. CI runs daily
- #203: Docs updated for security details
### Fixed
- #199: Fixed issue with range transformations
## 3.2.0 (2022-11-28)
### Added

View File

@ -19,6 +19,7 @@ test-cover:
.PHONY: fuzz
fuzz:
@echo "==> Running Fuzz Tests"
go env GOCACHE
go test -fuzz=FuzzNewVersion -fuzztime=15s .
go test -fuzz=FuzzStrictNewVersion -fuzztime=15s .
go test -fuzz=FuzzNewConstraint -fuzztime=15s .
@ -27,4 +28,4 @@ $(GOLANGCI_LINT):
# Install golangci-lint. The configuration for it is in the .golangci.yml
# file in the root of the repository
echo ${GOPATH}
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.17.1
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.56.2

View File

@ -13,12 +13,9 @@ Active](https://masterminds.github.io/stability/active.svg)](https://masterminds
[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/semver/v3)
[![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver)
If you are looking for a command line tool for version comparisons please see
[vert](https://github.com/Masterminds/vert) which uses this library.
## Package Versions
Note, import `github.com/github.com/Masterminds/semver/v3` to use the latest version.
Note, import `github.com/Masterminds/semver/v3` to use the latest version.
There are three major versions fo the `semver` package.
@ -80,12 +77,12 @@ There are two methods for comparing versions. One uses comparison methods on
differences to notes between these two methods of comparison.
1. When two versions are compared using functions such as `Compare`, `LessThan`,
and others it will follow the specification and always include prereleases
and others it will follow the specification and always include pre-releases
within the comparison. It will provide an answer that is valid with the
comparison section of the spec at https://semver.org/#spec-item-11
2. When constraint checking is used for checks or validation it will follow a
different set of rules that are common for ranges with tools like npm/js
and Rust/Cargo. This includes considering prereleases to be invalid if the
and Rust/Cargo. This includes considering pre-releases to be invalid if the
ranges does not include one. If you want to have it include pre-releases a
simple solution is to include `-0` in your range.
3. Constraint ranges can have some complex rules including the shorthand use of
@ -113,7 +110,7 @@ v, err := semver.NewVersion("1.3")
if err != nil {
// Handle version not being parsable.
}
// Check if the version meets the constraints. The a variable will be true.
// Check if the version meets the constraints. The variable a will be true.
a := c.Check(v)
```
@ -137,20 +134,20 @@ The basic comparisons are:
### Working With Prerelease Versions
Pre-releases, for those not familiar with them, are used for software releases
prior to stable or generally available releases. Examples of prereleases include
development, alpha, beta, and release candidate releases. A prerelease may be
prior to stable or generally available releases. Examples of pre-releases include
development, alpha, beta, and release candidate releases. A pre-release may be
a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the
order of precedence, prereleases come before their associated releases. In this
order of precedence, pre-releases come before their associated releases. In this
example `1.2.3-beta.1 < 1.2.3`.
According to the Semantic Version specification prereleases may not be
According to the Semantic Version specification, pre-releases may not be
API compliant with their release counterpart. It says,
> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version.
SemVer comparisons using constraints without a prerelease comparator will skip
prerelease versions. For example, `>=1.2.3` will skip prereleases when looking
at a list of releases while `>=1.2.3-0` will evaluate and find prereleases.
SemVer's comparisons using constraints without a pre-release comparator will skip
pre-release versions. For example, `>=1.2.3` will skip pre-releases when looking
at a list of releases while `>=1.2.3-0` will evaluate and find pre-releases.
The reason for the `0` as a pre-release version in the example comparison is
because pre-releases can only contain ASCII alphanumerics and hyphens (along with
@ -171,6 +168,9 @@ These look like:
* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5`
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5`
Note that `1.2-1.4.5` without whitespace is parsed completely differently; it's
parsed as a single constraint `1.2.0` with _prerelease_ `1.4.5`.
### Wildcards In Comparisons
The `x`, `X`, and `*` characters can be used as a wildcard character. This works

View File

@ -83,22 +83,23 @@ func StrictNewVersion(v string) (*Version, error) {
original: v,
}
// check for prerelease or build metadata
var extra []string
if strings.ContainsAny(parts[2], "-+") {
// Start with the build metadata first as it needs to be on the right
extra = strings.SplitN(parts[2], "+", 2)
if len(extra) > 1 {
// build metadata found
sv.metadata = extra[1]
parts[2] = extra[0]
// Extract build metadata
if strings.Contains(parts[2], "+") {
extra := strings.SplitN(parts[2], "+", 2)
sv.metadata = extra[1]
parts[2] = extra[0]
if err := validateMetadata(sv.metadata); err != nil {
return nil, err
}
}
extra = strings.SplitN(parts[2], "-", 2)
if len(extra) > 1 {
// prerelease found
sv.pre = extra[1]
parts[2] = extra[0]
// Extract build prerelease
if strings.Contains(parts[2], "-") {
extra := strings.SplitN(parts[2], "-", 2)
sv.pre = extra[1]
parts[2] = extra[0]
if err := validatePrerelease(sv.pre); err != nil {
return nil, err
}
}
@ -114,7 +115,7 @@ func StrictNewVersion(v string) (*Version, error) {
}
}
// Extract the major, minor, and patch elements onto the returned Version
// Extract major, minor, and patch
var err error
sv.major, err = strconv.ParseUint(parts[0], 10, 64)
if err != nil {
@ -131,23 +132,6 @@ func StrictNewVersion(v string) (*Version, error) {
return nil, err
}
// No prerelease or build metadata found so returning now as a fastpath.
if sv.pre == "" && sv.metadata == "" {
return sv, nil
}
if sv.pre != "" {
if err = validatePrerelease(sv.pre); err != nil {
return nil, err
}
}
if sv.metadata != "" {
if err = validateMetadata(sv.metadata); err != nil {
return nil, err
}
}
return sv, nil
}
@ -381,15 +365,31 @@ func (v *Version) LessThan(o *Version) bool {
return v.Compare(o) < 0
}
// LessThanEqual tests if one version is less or equal than another one.
func (v *Version) LessThanEqual(o *Version) bool {
return v.Compare(o) <= 0
}
// GreaterThan tests if one version is greater than another one.
func (v *Version) GreaterThan(o *Version) bool {
return v.Compare(o) > 0
}
// GreaterThanEqual tests if one version is greater or equal than another one.
func (v *Version) GreaterThanEqual(o *Version) bool {
return v.Compare(o) >= 0
}
// Equal tests if two versions are equal to each other.
// Note, versions can be equal with different metadata since metadata
// is not considered part of the comparable version.
func (v *Version) Equal(o *Version) bool {
if v == o {
return true
}
if v == nil || o == nil {
return false
}
return v.Compare(o) == 0
}

View File

@ -1,23 +0,0 @@
language: go
# See https://travis-ci.community/t/goos-js-goarch-wasm-go-run-fails-panic-newosproc-not-implemented/1651
#addons:
# chrome: stable
before_install:
- export GO111MODULE=on
#install:
#- go get github.com/agnivade/wasmbrowsertest
#- mv $GOPATH/bin/wasmbrowsertest $GOPATH/bin/go_js_wasm_exec
#- export PATH=$GOPATH/bin:$PATH
go:
- 1.13.x
- 1.14.x
- 1.15.x
- tip
script:
#- GOOS=js GOARCH=wasm go test -v
- go test -v

View File

@ -4,12 +4,10 @@ install:
go install
lint:
gofmt -l -s -w . && go vet . && golint -set_exit_status=1 .
gofmt -l -s -w . && go vet .
test: # The first 2 go gets are to support older Go versions
go get github.com/arbovm/levenshtein
go get github.com/dgryski/trifles/leven
GO111MODULE=on go test -race -v -coverprofile=coverage.txt -covermode=atomic
test:
go test -race -v -coverprofile=coverage.txt -covermode=atomic
bench:
go test -run=XXX -bench=. -benchmem -count=5

View File

@ -1,4 +1,4 @@
levenshtein [![Build Status](https://travis-ci.org/agnivade/levenshtein.svg?branch=master)](https://travis-ci.org/agnivade/levenshtein) [![Go Report Card](https://goreportcard.com/badge/github.com/agnivade/levenshtein)](https://goreportcard.com/report/github.com/agnivade/levenshtein) [![PkgGoDev](https://pkg.go.dev/badge/github.com/agnivade/levenshtein)](https://pkg.go.dev/github.com/agnivade/levenshtein)
levenshtein ![Build Status](https://github.com/agnivade/levenshtein/actions/workflows/ci.yml/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/agnivade/levenshtein)](https://goreportcard.com/report/github.com/agnivade/levenshtein) [![PkgGoDev](https://pkg.go.dev/badge/github.com/agnivade/levenshtein)](https://pkg.go.dev/github.com/agnivade/levenshtein)
===========
[Go](http://golang.org) package to calculate the [Levenshtein Distance](http://en.wikipedia.org/wiki/Levenshtein_distance)

View File

@ -41,6 +41,25 @@ func ComputeDistance(a, b string) int {
if len(s1) > len(s2) {
s1, s2 = s2, s1
}
// remove trailing identical runes.
for i := 0; i < len(s1); i++ {
if s1[len(s1)-1-i] != s2[len(s2)-1-i] {
s1 = s1[:len(s1)-i]
s2 = s2[:len(s2)-i]
break
}
}
// Remove leading identical runes.
for i := 0; i < len(s1); i++ {
if s1[i] != s2[i] {
s1 = s1[i:]
s2 = s2[i:]
break
}
}
lenS1 := len(s1)
lenS2 := len(s2)
@ -71,7 +90,7 @@ func ComputeDistance(a, b string) int {
for j := 1; j <= lenS1; j++ {
current := x[j-1] // match
if s2[i-1] != s1[j-1] {
current = min(min(x[j-1]+1, prev+1), x[j]+1)
current = min(x[j-1]+1, prev+1, x[j]+1)
}
x[j-1] = prev
prev = current
@ -80,10 +99,3 @@ func ComputeDistance(a, b string) int {
}
return int(x[lenS1])
}
func min(a, b uint16) uint16 {
if a < b {
return a
}
return b
}

View File

@ -33,6 +33,7 @@ import (
"sync"
"github.com/mholt/acmez/v2/acme"
"go.uber.org/zap"
)
// getAccount either loads or creates a new account, depending on if
@ -40,8 +41,15 @@ import (
func (am *ACMEIssuer) getAccount(ctx context.Context, ca, email string) (acme.Account, error) {
acct, err := am.loadAccount(ctx, ca, email)
if errors.Is(err, fs.ErrNotExist) {
am.Logger.Info("creating new account because no account for configured email is known to us",
zap.String("email", email),
zap.String("ca", ca),
zap.Error(err))
return am.newAccount(email)
}
am.Logger.Debug("using existing ACME account because key found in storage associated with email",
zap.String("email", email),
zap.String("ca", ca))
return acct, err
}
@ -407,6 +415,15 @@ func (am *ACMEIssuer) mostRecentAccountEmail(ctx context.Context, caURL string)
return getPrimaryContact(account), true
}
func accountRegLockKey(acc acme.Account) string {
key := "register_acme_account"
if len(acc.Contact) == 0 {
return key
}
key += "_" + getPrimaryContact(acc)
return key
}
// getPrimaryContact returns the first contact on the account (if any)
// without the scheme. (I guess we assume an email address.)
func getPrimaryContact(account acme.Account) string {

View File

@ -50,77 +50,123 @@ func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA,
return nil, err
}
// look up or create the ACME account
var account acme.Account
if iss.AccountKeyPEM != "" {
account, err = iss.GetAccount(ctx, []byte(iss.AccountKeyPEM))
} else {
account, err = iss.getAccount(ctx, client.Directory, iss.getEmail())
// we try loading the account from storage before a potential
// lock, and after obtaining the lock as well, to ensure we don't
// repeat work done by another instance or goroutine
getAccount := func() (acme.Account, error) {
// look up or create the ACME account
var account acme.Account
if iss.AccountKeyPEM != "" {
iss.Logger.Info("using configured ACME account")
account, err = iss.GetAccount(ctx, []byte(iss.AccountKeyPEM))
} else {
account, err = iss.getAccount(ctx, client.Directory, iss.getEmail())
}
if err != nil {
return acme.Account{}, fmt.Errorf("getting ACME account: %v", err)
}
return account, nil
}
// first try getting the account
account, err := getAccount()
if err != nil {
return nil, fmt.Errorf("getting ACME account: %v", err)
return nil, err
}
// register account if it is new
if account.Status == "" {
if iss.NewAccountFunc != nil {
// obtain lock here, since NewAccountFunc calls happen concurrently and they typically read and change the issuer
iss.mu.Lock()
account, err = iss.NewAccountFunc(ctx, iss, account)
iss.mu.Unlock()
if err != nil {
return nil, fmt.Errorf("account pre-registration callback: %v", err)
iss.Logger.Info("ACME account has empty status; registering account with ACME server",
zap.Strings("contact", account.Contact),
zap.String("location", account.Location))
// synchronize this so the account is only created once
acctLockKey := accountRegLockKey(account)
err = acquireLock(ctx, iss.config.Storage, acctLockKey)
if err != nil {
return nil, fmt.Errorf("locking account registration: %v", err)
}
defer func() {
if err := releaseLock(ctx, iss.config.Storage, acctLockKey); err != nil {
iss.Logger.Error("failed to unlock account registration lock", zap.Error(err))
}
}()
// if we're not the only one waiting for this account, then by this point it should already be registered and in storage; reload it
account, err = getAccount()
if err != nil {
return nil, err
}
// agree to terms
if interactive {
if !iss.isAgreed() {
var termsURL string
dir, err := client.GetDirectory(ctx)
// if we are the only or first one waiting for this account, then proceed to register it while we have the lock
if account.Status == "" {
if iss.NewAccountFunc != nil {
// obtain lock here, since NewAccountFunc calls happen concurrently and they typically read and change the issuer
iss.mu.Lock()
account, err = iss.NewAccountFunc(ctx, iss, account)
iss.mu.Unlock()
if err != nil {
return nil, fmt.Errorf("getting directory: %w", err)
return nil, fmt.Errorf("account pre-registration callback: %v", err)
}
if dir.Meta != nil {
termsURL = dir.Meta.TermsOfService
}
if termsURL != "" {
agreed := iss.askUserAgreement(termsURL)
if !agreed {
return nil, fmt.Errorf("user must agree to CA terms")
}
// agree to terms
if interactive {
if !iss.isAgreed() {
var termsURL string
dir, err := client.GetDirectory(ctx)
if err != nil {
return nil, fmt.Errorf("getting directory: %w", err)
}
if dir.Meta != nil {
termsURL = dir.Meta.TermsOfService
}
if termsURL != "" {
agreed := iss.askUserAgreement(termsURL)
if !agreed {
return nil, fmt.Errorf("user must agree to CA terms")
}
iss.mu.Lock()
iss.agreed = agreed
iss.mu.Unlock()
}
iss.mu.Lock()
iss.agreed = agreed
iss.mu.Unlock()
}
} else {
// can't prompt a user who isn't there; they should
// have reviewed the terms beforehand
iss.mu.Lock()
iss.agreed = true
iss.mu.Unlock()
}
account.TermsOfServiceAgreed = iss.isAgreed()
// associate account with external binding, if configured
if iss.ExternalAccount != nil {
err := account.SetExternalAccountBinding(ctx, client.Client, *iss.ExternalAccount)
if err != nil {
return nil, err
}
}
// create account
account, err = client.NewAccount(ctx, account)
if err != nil {
return nil, fmt.Errorf("registering account %v with server: %w", account.Contact, err)
}
iss.Logger.Info("new ACME account registered",
zap.Strings("contact", account.Contact),
zap.String("status", account.Status))
// persist the account to storage
err = iss.saveAccount(ctx, client.Directory, account)
if err != nil {
return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err)
}
} else {
// can't prompt a user who isn't there; they should
// have reviewed the terms beforehand
iss.mu.Lock()
iss.agreed = true
iss.mu.Unlock()
}
account.TermsOfServiceAgreed = iss.isAgreed()
// associate account with external binding, if configured
if iss.ExternalAccount != nil {
err := account.SetExternalAccountBinding(ctx, client.Client, *iss.ExternalAccount)
if err != nil {
return nil, err
}
}
// create account
account, err = client.NewAccount(ctx, account)
if err != nil {
return nil, fmt.Errorf("registering account %v with server: %w", account.Contact, err)
}
// persist the account to storage
err = iss.saveAccount(ctx, client.Directory, account)
if err != nil {
return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err)
iss.Logger.Info("account has already been registered; reloaded",
zap.Strings("contact", account.Contact),
zap.String("status", account.Status),
zap.String("location", account.Location))
}
}
@ -235,7 +281,8 @@ func (iss *ACMEIssuer) newBasicACMEClient() (*acmez.Client, error) {
}, nil
}
func (iss *ACMEIssuer) getRenewalInfo(ctx context.Context, cert Certificate) (acme.RenewalInfo, error) {
// GetRenewalInfo gets the ACME Renewal Information (ARI) for the certificate.
func (iss *ACMEIssuer) GetRenewalInfo(ctx context.Context, cert Certificate) (acme.RenewalInfo, error) {
acmeClient, err := iss.newBasicACMEClient()
if err != nil {
return acme.RenewalInfo{}, err
@ -312,6 +359,15 @@ func buildUAString() string {
return ua
}
// RenewalInfoGetter is a type that can get ACME Renewal Information (ARI).
// Users of this package that wrap the ACMEIssuer or use any other issuer
// that supports ARI will need to implement this so that CertMagic can
// update ARI which happens outside the normal issuance flow and is thus
// not required by the Issuer interface (a type assertion is performed).
type RenewalInfoGetter interface {
GetRenewalInfo(context.Context, Certificate) (acme.RenewalInfo, error)
}
// These internal rate limits are designed to prevent accidentally
// firehosing a CA's ACME endpoints. They are not intended to
// replace or replicate the CA's actual rate limits.

View File

@ -461,7 +461,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
// between client and server or some sort of bookkeeping error with regards to the certID
// and the server is rejecting the ARI certID. In any case, an invalid certID may cause
// orders to fail. So try once without setting it.
if !usingTestCA && attempts != 2 {
if !am.config.DisableARI && !usingTestCA && attempts != 2 {
if replacing, ok := ctx.Value(ctxKeyARIReplaces).(*x509.Certificate); ok {
params.Replaces = replacing
}

View File

@ -103,53 +103,54 @@ func (cfg *Config) certNeedsRenewal(leaf *x509.Certificate, ari acme.RenewalInfo
logger = zap.NewNop()
}
// first check ARI: if it says it's time to renew, it's time to renew
// (notice that we don't strictly require an ARI window to also exist; we presume
// that if a time has been selected, a window does or did exist, even if it didn't
// get stored/encoded for some reason - but also: this allows administrators to
// manually or explicitly schedule a renewal time indepedently of ARI which could
// be useful)
selectedTime := ari.SelectedTime
if !cfg.DisableARI {
// first check ARI: if it says it's time to renew, it's time to renew
// (notice that we don't strictly require an ARI window to also exist; we presume
// that if a time has been selected, a window does or did exist, even if it didn't
// get stored/encoded for some reason - but also: this allows administrators to
// manually or explicitly schedule a renewal time indepedently of ARI which could
// be useful)
selectedTime := ari.SelectedTime
// if, for some reason a random time in the window hasn't been selected yet, but an ARI
// window does exist, we can always improvise one... even if this is called repeatedly,
// a random time is a random time, whether you generate it once or more :D
// (code borrowed from our acme package)
if selectedTime.IsZero() &&
(!ari.SuggestedWindow.Start.IsZero() && !ari.SuggestedWindow.End.IsZero()) {
start, end := ari.SuggestedWindow.Start.Unix()+1, ari.SuggestedWindow.End.Unix()
selectedTime = time.Unix(rand.Int63n(end-start)+start, 0).UTC()
logger.Warn("no renewal time had been selected with ARI; chose an ephemeral one for now",
zap.Time("ephemeral_selected_time", selectedTime))
}
// if a renewal time has been selected, start with that
if !selectedTime.IsZero() {
// ARI spec recommends an algorithm that renews after the randomly-selected
// time OR just before it if the next waking time would be after it; this
// cutoff can actually be before the start of the renewal window, but the spec
// author says that's OK: https://github.com/aarongable/draft-acme-ari/issues/71
cutoff := ari.SelectedTime.Add(-cfg.certCache.options.RenewCheckInterval)
if time.Now().After(cutoff) {
logger.Info("certificate needs renewal based on ARI window",
zap.Time("selected_time", selectedTime),
zap.Time("renewal_cutoff", cutoff))
return true
// if, for some reason a random time in the window hasn't been selected yet, but an ARI
// window does exist, we can always improvise one... even if this is called repeatedly,
// a random time is a random time, whether you generate it once or more :D
// (code borrowed from our acme package)
if selectedTime.IsZero() &&
(!ari.SuggestedWindow.Start.IsZero() && !ari.SuggestedWindow.End.IsZero()) {
start, end := ari.SuggestedWindow.Start.Unix()+1, ari.SuggestedWindow.End.Unix()
selectedTime = time.Unix(rand.Int63n(end-start)+start, 0).UTC()
logger.Warn("no renewal time had been selected with ARI; chose an ephemeral one for now",
zap.Time("ephemeral_selected_time", selectedTime))
}
// according to ARI, we are not ready to renew; however, we do not rely solely on
// ARI calculations... what if there is a bug in our implementation, or in the
// server's, or the stored metadata? for redundancy, give credence to the expiration
// date; ignore ARI if we are past a "dangerously close" limit, to avoid any
// possibility of a bug in ARI compromising a site's uptime: we should always always
// always give heed to actual validity period
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) {
logger.Warn("certificate is in emergency renewal window; superceding ARI",
zap.Duration("remaining", time.Until(expiration)),
zap.Time("renewal_cutoff", cutoff))
return true
}
// if a renewal time has been selected, start with that
if !selectedTime.IsZero() {
// ARI spec recommends an algorithm that renews after the randomly-selected
// time OR just before it if the next waking time would be after it; this
// cutoff can actually be before the start of the renewal window, but the spec
// author says that's OK: https://github.com/aarongable/draft-acme-ari/issues/71
cutoff := ari.SelectedTime.Add(-cfg.certCache.options.RenewCheckInterval)
if time.Now().After(cutoff) {
logger.Info("certificate needs renewal based on ARI window",
zap.Time("selected_time", selectedTime),
zap.Time("renewal_cutoff", cutoff))
return true
}
// according to ARI, we are not ready to renew; however, we do not rely solely on
// ARI calculations... what if there is a bug in our implementation, or in the
// server's, or the stored metadata? for redundancy, give credence to the expiration
// date; ignore ARI if we are past a "dangerously close" limit, to avoid any
// possibility of a bug in ARI compromising a site's uptime: we should always always
// always give heed to actual validity period
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) {
logger.Warn("certificate is in emergency renewal window; superceding ARI",
zap.Duration("remaining", time.Until(expiration)),
zap.Time("renewal_cutoff", cutoff))
return true
}
}
}
// the normal check, in the absence of ARI, is to determine if we're near enough (or past)
@ -552,6 +553,7 @@ func SubjectIsInternal(subj string) bool {
return subj == "localhost" ||
strings.HasSuffix(subj, ".localhost") ||
strings.HasSuffix(subj, ".local") ||
strings.HasSuffix(subj, ".internal") ||
strings.HasSuffix(subj, ".home.arpa") ||
isInternalIP(subj)
}

View File

@ -149,6 +149,10 @@ type Config struct {
// EXPERIMENTAL: Subject to change or removal.
SubjectTransformer func(ctx context.Context, domain string) string
// Disables both ARI fetching and the use of ARI for renewal decisions.
// TEMPORARY: Will likely be removed in the future.
DisableARI bool
// Set a logger to enable logging. If not set,
// a default logger will be created.
Logger *zap.Logger
@ -370,9 +374,11 @@ func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bo
}
for _, domainName := range domainNames {
domainName = normalizedName(domainName)
// if on-demand is configured, defer obtain and renew operations
if cfg.OnDemand != nil {
cfg.OnDemand.hostAllowlist[normalizedName(domainName)] = struct{}{}
cfg.OnDemand.hostAllowlist[domainName] = struct{}{}
continue
}
@ -449,7 +455,7 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool)
// ensure ARI is updated before we check whether the cert needs renewing
// (we ignore the second return value because we already check if needs renewing anyway)
if cert.ari.NeedsRefresh() {
if !cfg.DisableARI && cert.ari.NeedsRefresh() {
cert, _, err = cfg.updateARI(ctx, cert, cfg.Logger)
if err != nil {
cfg.Logger.Error("updating ARI upon managing", zap.Error(err))
@ -886,11 +892,13 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
// if we're renewing with the same ACME CA as before, have the ACME
// client tell the server we are replacing a certificate (but doing
// this on the wrong CA, or when the CA doesn't recognize the certID,
// can fail the order)
if acmeData, err := certRes.getACMEData(); err == nil && acmeData.CA != "" {
if acmeIss, ok := issuer.(*ACMEIssuer); ok {
if acmeIss.CA == acmeData.CA {
ctx = context.WithValue(ctx, ctxKeyARIReplaces, leaf)
// can fail the order) -- TODO: change this check to whether we're using the same ACME account, not CA
if !cfg.DisableARI {
if acmeData, err := certRes.getACMEData(); err == nil && acmeData.CA != "" {
if acmeIss, ok := issuer.(*ACMEIssuer); ok {
if acmeIss.CA == acmeData.CA {
ctx = context.WithValue(ctx, ctxKeyARIReplaces, leaf)
}
}
}
}
@ -982,23 +990,26 @@ func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string, useC
csrTemplate := new(x509.CertificateRequest)
for _, name := range sans {
// identifiers should be converted to punycode before going into the CSR
// (convert IDNs to ASCII according to RFC 5280 section 7)
normalizedName, err := idna.ToASCII(name)
if err != nil {
return nil, fmt.Errorf("converting identifier '%s' to ASCII: %v", name, err)
}
// TODO: This is a temporary hack to support ZeroSSL API...
if useCN && csrTemplate.Subject.CommonName == "" && len(name) <= 64 {
csrTemplate.Subject.CommonName = name
if useCN && csrTemplate.Subject.CommonName == "" && len(normalizedName) <= 64 {
csrTemplate.Subject.CommonName = normalizedName
continue
}
if ip := net.ParseIP(name); ip != nil {
if ip := net.ParseIP(normalizedName); ip != nil {
csrTemplate.IPAddresses = append(csrTemplate.IPAddresses, ip)
} else if strings.Contains(name, "@") {
csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, name)
} else if u, err := url.Parse(name); err == nil && strings.Contains(name, "/") {
} else if strings.Contains(normalizedName, "@") {
csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, normalizedName)
} else if u, err := url.Parse(normalizedName); err == nil && strings.Contains(normalizedName, "/") {
csrTemplate.URIs = append(csrTemplate.URIs, u)
} else {
// convert IDNs to ASCII according to RFC 5280 section 7
normalizedName, err := idna.ToASCII(name)
if err != nil {
return nil, fmt.Errorf("converting identifier '%s' to ASCII: %v", name, err)
}
csrTemplate.DNSNames = append(csrTemplate.DNSNames, normalizedName)
}
}
@ -1007,6 +1018,16 @@ func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string, useC
csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, mustStapleExtension)
}
// IP addresses aren't printed here because I'm too lazy to marshal them as strings, but
// we at least print the incoming SANs so it should be obvious what became IPs
cfg.Logger.Debug("created CSR",
zap.Strings("identifiers", sans),
zap.Strings("san_dns_names", csrTemplate.DNSNames),
zap.Strings("san_emails", csrTemplate.EmailAddresses),
zap.String("common_name", csrTemplate.Subject.CommonName),
zap.Int("extra_extensions", len(csrTemplate.ExtraExtensions)),
)
csrDER, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privateKey)
if err != nil {
return nil, err
@ -1244,8 +1265,10 @@ func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource, emitLogs
return 0, nil, true
}
var ari acme.RenewalInfo
if ariPtr, err := certRes.getARI(); err == nil && ariPtr != nil {
ari = *ariPtr
if !cfg.DisableARI {
if ariPtr, err := certRes.getARI(); err == nil && ariPtr != nil {
ari = *ariPtr
}
}
remaining := time.Until(expiresAt(certChain[0]))
return remaining, certChain[0], cfg.certNeedsRenewal(certChain[0], ari, emitLogs)

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