diff --git a/go.mod b/go.mod index bf852a7b85..27d3d2c9c2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/wundergraph/graphql-go-tools go 1.18 require ( - github.com/99designs/gqlgen v0.17.13 + github.com/99designs/gqlgen v0.17.22 github.com/buger/jsonparser v1.1.1 github.com/cespare/xxhash/v2 v2.1.2 github.com/dave/jennifer v1.4.0 @@ -31,7 +31,7 @@ require ( github.com/stretchr/testify v1.7.1 github.com/tidwall/gjson v1.11.0 github.com/tidwall/sjson v1.0.4 - github.com/vektah/gqlparser/v2 v2.4.6 + github.com/vektah/gqlparser/v2 v2.5.1 go.uber.org/atomic v1.9.0 go.uber.org/zap v1.18.1 golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9 @@ -41,7 +41,7 @@ require ( ) require ( - github.com/Masterminds/goutils v1.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect github.com/agnivade/levenshtein v1.1.1 // indirect @@ -57,10 +57,10 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/logrusorgru/aurora/v3 v3.0.0 // indirect github.com/magiconair/properties v1.8.0 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/nats-io/nats-server/v2 v2.8.2 // indirect github.com/nats-io/nkeys v0.3.0 // indirect @@ -77,12 +77,12 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/net v0.5.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1b4dfdd401..dfa97cedee 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,11 @@ -github.com/99designs/gqlgen v0.17.13 h1:ETUEqvRg5Zvr1lXtpoRdj026fzVay0ZlJPwI33qXLIw= -github.com/99designs/gqlgen v0.17.13/go.mod h1:w1brbeOdqVyNJI553BGwtwdVcYu1LKeYE1opLWN9RgQ= +github.com/99designs/gqlgen v0.17.22 h1:TOcrF8t0T3I0za9JD3CB6ehq7dDEMjR9Onikf8Lc/04= +github.com/99designs/gqlgen v0.17.22/go.mod h1:BMhYIhe4bp7OlCo5I2PnowSK/Wimpv/YlxfNkqZGwLo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= @@ -124,20 +125,19 @@ github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxA github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= @@ -212,11 +212,12 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY= -github.com/vektah/gqlparser/v2 v2.4.6 h1:Yjzp66g6oVq93Jihbi0qhGnf/6zIWjcm8H6gA27zstE= -github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= +github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4= +github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= @@ -234,30 +235,31 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9 h1:frX3nT9RkKybPnjyI+yvZh6ZucTZatCCEm9D47sZ2zo= golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -268,19 +270,23 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -292,8 +298,8 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -317,8 +323,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= diff --git a/pkg/engine/datasource/graphql_datasource/graphql_datasource.go b/pkg/engine/datasource/graphql_datasource/graphql_datasource.go index d3f6437df7..e6a0f1e64a 100644 --- a/pkg/engine/datasource/graphql_datasource/graphql_datasource.go +++ b/pkg/engine/datasource/graphql_datasource/graphql_datasource.go @@ -396,14 +396,8 @@ func (p *Planner) EnterSelectionSet(ref int) { p.upstreamOperation.InlineFragments[parent.Ref].SelectionSet = set.Ref } p.nodes = append(p.nodes, set) - // Abstract meaning interface or union if p.visitor.Walker.EnclosingTypeDefinition.Kind.IsAbstractType() { - // Always include __typename in abstract type selection sets. This is - // done because child fields may be federated and __typename will be - // needed for representations. While it would be possible to determine - // exactly when __typename is needed, there's no harm in just always - // including it. - p.addTypenameToSelectionSet(set.Ref) + // Adding the typename to abstract (unions and interfaces) types is handled elsewhere return } diff --git a/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go b/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go index 23ff43e0a2..713ca538ea 100644 --- a/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go +++ b/pkg/engine/datasource/graphql_datasource/graphql_datasource_test.go @@ -118,7 +118,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$1$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($id: ID!){droid(id: $id){name aliased: name friends {__typename name} primaryFunction} hero {__typename name} stringList nestedStringList}","variables":{"id":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$1$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($id: ID!){droid(id: $id){name aliased: name friends {name} primaryFunction} hero {name} stringList nestedStringList}","variables":{"id":$$0$$}}}`, Variables: resolve.NewVariables( &resolve.ContextVariable{ Path: []string{"id"}, @@ -295,7 +295,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {__typename id displayName}}"}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, }, @@ -370,7 +370,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {__typename id displayName @skip(if: $skip)}}","variables":{"skip":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {id displayName @skip(if: $skip)}}","variables":{"skip":$$0$$}}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, Variables: resolve.NewVariables( @@ -455,7 +455,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {__typename id displayName __typename @skip(if: $skip)}}","variables":{"skip":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {id displayName __typename @skip(if: $skip)}}","variables":{"skip":$$0$$}}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), Variables: resolve.NewVariables(&resolve.ContextVariable{ Path: []string{"skip"}, @@ -554,7 +554,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {__typename ... @skip(if: $skip){__typename id displayName}}}","variables":{"skip":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($skip: Boolean!){user {... @skip(if: $skip){id displayName}}}","variables":{"skip":$$0$$}}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, Variables: resolve.NewVariables( @@ -578,6 +578,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"id"}, }, + OnTypeNames: [][]byte{[]byte("RegisteredUser")}, SkipDirectiveDefined: true, SkipVariableName: "skip", }, @@ -586,6 +587,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"displayName"}, }, + OnTypeNames: [][]byte{[]byte("RegisteredUser")}, SkipDirectiveDefined: true, SkipVariableName: "skip", }, @@ -641,7 +643,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {__typename ... @include(if: $include){__typename id displayName}}}","variables":{"include":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {... @include(if: $include){id displayName}}}","variables":{"include":$$0$$}}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, Variables: resolve.NewVariables( @@ -665,6 +667,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"id"}, }, + OnTypeNames: [][]byte{[]byte("RegisteredUser")}, IncludeDirectiveDefined: true, IncludeVariableName: "include", }, @@ -673,6 +676,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"displayName"}, }, + OnTypeNames: [][]byte{[]byte("RegisteredUser")}, IncludeDirectiveDefined: true, IncludeVariableName: "include", }, @@ -726,7 +730,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {__typename id}}"}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id}}"}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, }, @@ -795,7 +799,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {__typename id displayName}}"}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, }, @@ -870,7 +874,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {__typename id displayName @include(if: $include)}}","variables":{"include":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($include: Boolean!){user {id displayName @include(if: $include)}}","variables":{"include":$$0$$}}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, Variables: resolve.NewVariables( @@ -953,7 +957,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {__typename id displayName}}"}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName}}"}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, }, @@ -1027,7 +1031,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {__typename id}}"}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id}}"}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, }, @@ -1099,7 +1103,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {__typename id displayName ... on RegisteredUser {hasVerifiedEmail}}}"}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"{user {id displayName __typename ... on RegisteredUser {hasVerifiedEmail}}}"}}`, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, }, @@ -1129,7 +1133,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.Boolean{ Path: []string{"hasVerifiedEmail"}, }, - OnTypeName: []byte("RegisteredUser"), + OnTypeNames: [][]byte{[]byte("RegisteredUser")}, }, }, }, @@ -1276,7 +1280,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($heroId: ID!){droid(id: $heroId){name} hero {__typename id}}","variables":{"heroId":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($heroId: ID!){droid(id: $heroId){name} hero {id}}","variables":{"heroId":$$0$$}}}`, Variables: resolve.NewVariables( &resolve.ContextVariable{ Path: []string{"heroId"}, @@ -1400,7 +1404,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$2$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($id: ID!, $heroName: String!){droid(id: $id){name aliased: name friends {__typename name} primaryFunction} hero {__typename name} search(name: $heroName){__typename ... on Droid {primaryFunction}} stringList nestedStringList}","variables":{"heroName":$$1$$,"id":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$2$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($id: ID!, $heroName: String!){droid(id: $id){name aliased: name friends {name} primaryFunction} hero {name} search(name: $heroName){__typename ... on Droid {primaryFunction}} stringList nestedStringList}","variables":{"heroName":$$1$$,"id":$$0$$}}}`, Variables: resolve.NewVariables( &resolve.ContextVariable{ Path: []string{"id"}, @@ -1499,7 +1503,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"primaryFunction"}, }, - OnTypeName: []byte("Droid"), + OnTypeNames: [][]byte{[]byte("Droid")}, }, }, }, @@ -1644,7 +1648,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$4$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($id: ID!, $a: String! @onVariable, $input: SearchInput!, $options: JSON)@onOperation {api_droid: droid(id: $id){name @format aliased: name friends {__typename name} primaryFunction} api_hero: hero {__typename name ... on Human {height}} api_stringList: stringList renamed: nestedStringList api_search: search(name: $a){__typename ... on Droid {primaryFunction}} api_searchWithInput: searchWithInput(input: $input){__typename ... on Droid {primaryFunction}} withOptions: searchWithInput(input: {options: $options}){__typename ... on Droid {primaryFunction}}}","variables":{"options":$$3$$,"input":$$2$$,"a":$$1$$,"id":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$4$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($id: ID!, $a: String! @onVariable, $input: SearchInput!, $options: JSON)@onOperation {api_droid: droid(id: $id){name @format aliased: name friends {name} primaryFunction} api_hero: hero {name __typename ... on Human {height}} api_stringList: stringList renamed: nestedStringList api_search: search(name: $a){__typename ... on Droid {primaryFunction}} api_searchWithInput: searchWithInput(input: $input){__typename ... on Droid {primaryFunction}} withOptions: searchWithInput(input: {options: $options}){__typename ... on Droid {primaryFunction}}}","variables":{"options":$$3$$,"input":$$2$$,"a":$$1$$,"id":$$0$$}}}`, Variables: resolve.NewVariables( &resolve.ContextVariable{ Path: []string{"id"}, @@ -1736,7 +1740,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"height"}, }, - OnTypeName: []byte("Human"), + OnTypeNames: [][]byte{[]byte("Human")}, }, }, }, @@ -1778,7 +1782,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"primaryFunction"}, }, - OnTypeName: []byte("Droid"), + OnTypeNames: [][]byte{[]byte("Droid")}, }, }, }, @@ -1796,7 +1800,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"primaryFunction"}, }, - OnTypeName: []byte("Droid"), + OnTypeNames: [][]byte{[]byte("Droid")}, }, }, }, @@ -1814,7 +1818,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"primaryFunction"}, }, - OnTypeName: []byte("Droid"), + OnTypeNames: [][]byte{[]byte("Droid")}, }, }, }, @@ -2037,7 +2041,7 @@ func TestGraphQLDataSource(t *testing.T) { }, }, }, - OnTypeName: []byte("Product"), + OnTypeNames: [][]byte{[]byte("Product")}, }, }, }, @@ -2244,7 +2248,7 @@ func TestGraphQLDataSource(t *testing.T) { Data: &resolve.Object{ Fetch: &resolve.SingleFetch{ BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($birthdate: Date!){heroByBirthdate(birthdate: $birthdate){__typename name}}","variables":{"birthdate":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","body":{"query":"query($birthdate: Date!){heroByBirthdate(birthdate: $birthdate){name}}","variables":{"birthdate":$$0$$}}}`, DataSource: &Source{}, Variables: resolve.NewVariables( &resolve.ContextVariable{ @@ -3447,8 +3451,8 @@ func TestGraphQLDataSource(t *testing.T) { }, }, { - OnTypeName: []byte("NamespaceCreated"), - Name: []byte("namespace"), + OnTypeNames: [][]byte{[]byte("NamespaceCreated")}, + Name: []byte("namespace"), Value: &resolve.Object{ Path: []string{"namespace"}, Fields: []*resolve.Field{ @@ -3470,15 +3474,15 @@ func TestGraphQLDataSource(t *testing.T) { }, }, { - OnTypeName: []byte("Error"), - Name: []byte("code"), + OnTypeNames: [][]byte{[]byte("Error")}, + Name: []byte("code"), Value: &resolve.String{ Path: []string{"code"}, }, }, { - OnTypeName: []byte("Error"), - Name: []byte("message"), + OnTypeNames: [][]byte{[]byte("Error")}, + Name: []byte("message"), Value: &resolve.String{ Path: []string{"message"}, }, @@ -4156,7 +4160,7 @@ func TestGraphQLDataSource(t *testing.T) { Data: &resolve.Object{ Fetch: &resolve.SingleFetch{ BufferId: 0, - Input: `{"method":"POST","url":"http://user.service","body":{"query":"query($a: ID!){user(id: $a){id name {first last} username birthDate __typename ssn}}","variables":{"a":$$0$$}}}`, + Input: `{"method":"POST","url":"http://user.service","body":{"query":"query($a: ID!){user(id: $a){id name {first last} username birthDate __typename account {__typename ... on PasswordAccount {email} ... on SMSAccount {number}} metadata {name address description} ssn}}","variables":{"a":$$0$$}}}`, DataSource: &Source{}, Variables: resolve.NewVariables( &resolve.ObjectVariable{ @@ -4176,7 +4180,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.BatchFetch{ Fetch: &resolve.SingleFetch{ BufferId: 1, - Input: `{"method":"POST","url":"http://product.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {vehicle {__typename id description price}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, + Input: `{"method":"POST","url":"http://product.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {vehicle {id description price}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, Variables: resolve.NewVariables( &resolve.ObjectVariable{ Path: []string{"id"}, @@ -4288,7 +4292,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"email"}, }, - OnTypeName: []byte("PasswordAccount"), + OnTypeNames: [][]byte{[]byte("PasswordAccount")}, }, { Name: []byte("number"), @@ -4296,7 +4300,7 @@ func TestGraphQLDataSource(t *testing.T) { Nullable: true, Path: []string{"number"}, }, - OnTypeName: []byte("SMSAccount"), + OnTypeNames: [][]byte{[]byte("SMSAccount")}, }, }, }, @@ -4360,7 +4364,7 @@ func TestGraphQLDataSource(t *testing.T) { ChildNodes: []plan.TypeField{ { TypeName: "User", - FieldNames: []string{"id", "name", "username", "birthDate", "metaData", "ssn"}, + FieldNames: []string{"id", "name", "username", "birthDate", "metadata", "ssn"}, }, { TypeName: "UserMetadata", @@ -4473,7 +4477,7 @@ func TestGraphQLDataSource(t *testing.T) { Data: &resolve.Object{ Fetch: &resolve.SingleFetch{ BufferId: 0, - Input: `{"method":"POST","url":"http://user.service","body":{"query":"query($a: ID!){user(id: $a){id name {first last} username birthDate __typename ssn}}","variables":{"a":$$0$$}}}`, + Input: `{"method":"POST","url":"http://user.service","body":{"query":"query($a: ID!){user(id: $a){id name {first last} username birthDate account {__typename ... on PasswordAccount {email} ... on SMSAccount {number}} metadata {name address description} __typename ssn}}","variables":{"a":$$0$$}}}`, DataSource: &Source{}, Variables: resolve.NewVariables( &resolve.ObjectVariable{ @@ -4493,7 +4497,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.BatchFetch{ Fetch: &resolve.SingleFetch{ BufferId: 1, - Input: `{"method":"POST","url":"http://product.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {vehicle {__typename id description price}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, + Input: `{"method":"POST","url":"http://product.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {vehicle {id description price}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, Variables: resolve.NewVariables( &resolve.ObjectVariable{ Path: []string{"id"}, @@ -4567,7 +4571,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"email"}, }, - OnTypeName: []byte("PasswordAccount"), + OnTypeNames: [][]byte{[]byte("PasswordAccount")}, }, { Name: []byte("number"), @@ -4575,7 +4579,7 @@ func TestGraphQLDataSource(t *testing.T) { Nullable: true, Path: []string{"number"}, }, - OnTypeName: []byte("SMSAccount"), + OnTypeNames: [][]byte{[]byte("SMSAccount")}, }, }, }, @@ -4677,7 +4681,7 @@ func TestGraphQLDataSource(t *testing.T) { ChildNodes: []plan.TypeField{ { TypeName: "User", - FieldNames: []string{"id", "name", "username", "birthDate", "metaData", "ssn"}, + FieldNames: []string{"id", "name", "username", "birthDate", "metadata", "ssn"}, }, { TypeName: "UserMetadata", @@ -5845,7 +5849,7 @@ func TestGraphQLDataSource(t *testing.T) { Data: &resolve.Object{ Fetch: &resolve.SingleFetch{ BufferId: 0, - Input: `{"method":"POST","url":"http://user.service","body":{"query":"{self {__typename id ... on User {uid: id username id}}}"}}`, + Input: `{"method":"POST","url":"http://user.service","body":{"query":"{self {id __typename ... on User {uid: id username}}}"}}`, DataSource: &Source{}, DataSourceIdentifier: []byte("graphql_datasource.Source"), ProcessResponseConfig: resolve.ProcessResponseConfig{ExtractGraphqlResponse: true}, @@ -5883,13 +5887,12 @@ func TestGraphQLDataSource(t *testing.T) { Path: []string{"self"}, Nullable: true, Fields: []*resolve.Field{ - // there is a bug with interfaces that consumes this - //{ - // Name: []byte("id"), - // Value: &resolve.String{ - // Path: []string{"id"}, - // }, - //}, + { + Name: []byte("id"), + Value: &resolve.String{ + Path: []string{"id"}, + }, + }, { Name: []byte("__typename"), Value: &resolve.String{ @@ -5903,14 +5906,14 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"uid"}, }, - OnTypeName: []byte("User"), + OnTypeNames: [][]byte{[]byte("User")}, }, { Name: []byte("username"), Value: &resolve.String{ Path: []string{"username"}, }, - OnTypeName: []byte("User"), + OnTypeNames: [][]byte{[]byte("User")}, }, { HasBuffer: true, @@ -5931,7 +5934,7 @@ func TestGraphQLDataSource(t *testing.T) { }, }, }, - OnTypeName: []byte("User"), + OnTypeNames: [][]byte{[]byte("User")}, }, }, }, @@ -6104,14 +6107,14 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"extension"}, }, - OnTypeName: []byte("Image"), + OnTypeNames: [][]byte{[]byte("Image")}, }, { Name: []byte("length"), Value: &resolve.String{ Path: []string{"length"}, }, - OnTypeName: []byte("Video"), + OnTypeNames: [][]byte{[]byte("Video")}, }, }, }, @@ -6119,7 +6122,7 @@ func TestGraphQLDataSource(t *testing.T) { }, }, }, - OnTypeName: []byte("User"), + OnTypeNames: [][]byte{[]byte("User")}, }, }, }, @@ -6276,11 +6279,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.BatchFetch{ Fetch: &resolve.SingleFetch{ BufferId: 1, - // Note: __typename is included in the Cat and Dog inline fragments - // because the field were originally themselves in inline fragments - // that were inlined. The additional __typename selections are - // harmless. - Input: `{"method":"POST","url":"http://pet.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {pets {__typename name ... on Cat {catField details {age}} ... on Dog {dogField species} details {hasOwner}}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, + Input: `{"method":"POST","url":"http://pet.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {pets {name __typename ... on Cat {catField details {age}} ... on Dog {dogField species} details {hasOwner}}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, Variables: resolve.NewVariables( &resolve.ObjectVariable{ Path: []string{"id"}, @@ -6327,7 +6326,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"catField"}, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("details"), @@ -6342,21 +6341,21 @@ func TestGraphQLDataSource(t *testing.T) { }, }, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("dogField"), Value: &resolve.String{ Path: []string{"dogField"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { Name: []byte("species"), Value: &resolve.String{ Path: []string{"species"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { Name: []byte("details"), @@ -6484,28 +6483,28 @@ func TestGraphQLDataSource(t *testing.T) { t.Run("featuring consecutive inline fragments (shared selection in middle)", RunTest( federatedSchemaWithComplexNestedFragments, ` - query TestQuery { - user { - username - pets { - ... on Cat { - catField + query TestQuery { + user { + username + pets { + ... on Cat { + catField + details { + age + } + } + name + ... on Dog { + dogField + species + } details { - age + hasOwner } } - name - ... on Dog { - dogField - species - } - details { - hasOwner - } } } - } - `, + `, "TestQuery", &plan.SynchronousResponsePlan{ Response: &resolve.GraphQLResponse{ @@ -6526,11 +6525,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.BatchFetch{ Fetch: &resolve.SingleFetch{ BufferId: 1, - // Note: __typename is included in the Cat and Dog inline fragments - // because the field were originally themselves in inline fragments - // that were inlined. The additional __typename selections are - // harmless. - Input: `{"method":"POST","url":"http://pet.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {pets {__typename ... on Cat {catField details {age}} name ... on Dog {dogField species} details {hasOwner}}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, + Input: `{"method":"POST","url":"http://pet.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {pets {__typename ... on Cat {catField details {age}} name ... on Dog {dogField species} details {hasOwner}}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, Variables: resolve.NewVariables( &resolve.ObjectVariable{ Path: []string{"id"}, @@ -6571,7 +6566,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"catField"}, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("details"), @@ -6586,7 +6581,7 @@ func TestGraphQLDataSource(t *testing.T) { }, }, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("name"), @@ -6599,14 +6594,14 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"dogField"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { Name: []byte("species"), Value: &resolve.String{ Path: []string{"species"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { Name: []byte("details"), @@ -6734,28 +6729,28 @@ func TestGraphQLDataSource(t *testing.T) { t.Run("featuring consecutive inline fragments (shared selection at bottom)", RunTest( federatedSchemaWithComplexNestedFragments, ` - query TestQuery { - user { - username - pets { - ... on Cat { - catField + query TestQuery { + user { + username + pets { + ... on Cat { + catField + details { + age + } + } + ... on Dog { + dogField + species + } details { - age + hasOwner } + name } - ... on Dog { - dogField - species - } - details { - hasOwner - } - name } } - } - `, + `, "TestQuery", &plan.SynchronousResponsePlan{ Response: &resolve.GraphQLResponse{ @@ -6821,7 +6816,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"catField"}, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("details"), @@ -6836,21 +6831,21 @@ func TestGraphQLDataSource(t *testing.T) { }, }, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("dogField"), Value: &resolve.String{ Path: []string{"dogField"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { Name: []byte("species"), Value: &resolve.String{ Path: []string{"species"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { Name: []byte("details"), @@ -6984,41 +6979,41 @@ func TestGraphQLDataSource(t *testing.T) { t.Run("Federation with field query (defined in pet subgraph) featuring consecutive inline union fragments", RunTest( ` - type Query { - user: User - } - type User { + type Query { + user: User + } + type User { id: ID! - username: String! - pets: [CatOrDog!]! - } - type Cat { - name: String! - catField: String! - } - type Dog { - name: String! - dogField: String! - } - union CatOrDog = Cat | Dog - `, + username: String! + pets: [CatOrDog!]! + } + type Cat { + name: String! + catField: String! + } + type Dog { + name: String! + dogField: String! + } + union CatOrDog = Cat | Dog + `, ` - query TestQuery { - user { - username - pets { - ... on Cat { + query TestQuery { + user { + username + pets { + ... on Cat { name - catField - } - ... on Dog { + catField + } + ... on Dog { name - dogField - } - } - } - } - `, + dogField + } + } + } + } + `, "TestQuery", &plan.SynchronousResponsePlan{ Response: &resolve.GraphQLResponse{ @@ -7039,11 +7034,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.BatchFetch{ Fetch: &resolve.SingleFetch{ BufferId: 1, - // Note: __typename is included in the Cat and Dog inline fragments - // because the field were originally themselves in inline fragments - // that were inlined. The additional __typename selections are - // harmless. - Input: `{"method":"POST","url":"http://pet.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {pets {__typename ... on Cat {name catField} ... on Dog {name dogField}}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, + Input: `{"method":"POST","url":"http://pet.service","body":{"query":"query($representations: [_Any!]!){_entities(representations: $representations){__typename ... on User {pets {__typename ... on Cat {name catField} ... on Dog {name dogField}}}}}","variables":{"representations":[{"id":$$0$$,"__typename":"User"}]}}}`, Variables: resolve.NewVariables( &resolve.ObjectVariable{ Path: []string{"id"}, @@ -7084,28 +7075,28 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"name"}, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("catField"), Value: &resolve.String{ Path: []string{"catField"}, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { Name: []byte("name"), Value: &resolve.String{ Path: []string{"name"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { Name: []byte("dogField"), Value: &resolve.String{ Path: []string{"dogField"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, }, }, @@ -7168,20 +7159,20 @@ func TestGraphQLDataSource(t *testing.T) { Federation: FederationConfiguration{ Enabled: true, ServiceSDL: ` - union CatOrDog = Cat | Dog - type Cat { - name: String! - catField: String! - } - type Dog { - name: String! - dogField: String! - } + union CatOrDog = Cat | Dog + type Cat { + name: String! + catField: String! + } + type Dog { + name: String! + dogField: String! + } extend type User @key(fields: "id") { id: ID! @external pets: [CatOrDog!]! } - `, + `, }, }), Factory: federationFactory, @@ -7301,7 +7292,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"name"}, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { HasBuffer: true, @@ -7310,7 +7301,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"catField"}, }, - OnTypeName: []byte("Cat"), + OnTypeNames: [][]byte{[]byte("Cat")}, }, { HasBuffer: true, @@ -7319,7 +7310,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"name"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, { HasBuffer: true, @@ -7328,7 +7319,7 @@ func TestGraphQLDataSource(t *testing.T) { Value: &resolve.String{ Path: []string{"dogField"}, }, - OnTypeName: []byte("Dog"), + OnTypeNames: [][]byte{[]byte("Dog")}, }, }, }, @@ -7431,11 +7422,21 @@ func TestGraphQLDataSource(t *testing.T) { FieldName: "name", RequiresFields: []string{"id"}, }, + { + TypeName: "Cat", + FieldName: "catField", + RequiresFields: []string{"id"}, + }, { TypeName: "Dog", FieldName: "name", RequiresFields: []string{"id"}, }, + { + TypeName: "Dog", + FieldName: "dogField", + RequiresFields: []string{"id"}, + }, }, DisableResolveFieldPositions: true, })) @@ -7460,7 +7461,7 @@ func TestGraphQLDataSource(t *testing.T) { Fetch: &resolve.SingleFetch{ DataSource: &Source{}, BufferId: 0, - Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$2$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($droidId: ID!, $reviewId: ReviewID!){droid(id: $droidId){name aliased: name friends {__typename name} primaryFunction} review(id: $reviewId){stars}}","variables":{"reviewId":$$1$$,"droidId":$$0$$}}}`, + Input: `{"method":"POST","url":"https://swapi.com/graphql","header":{"Authorization":["$$2$$"],"Invalid-Template":["{{ request.headers.Authorization }}"]},"body":{"query":"query($droidId: ID!, $reviewId: ReviewID!){droid(id: $droidId){name aliased: name friends {name} primaryFunction} review(id: $reviewId){stars}}","variables":{"reviewId":$$1$$,"droidId":$$0$$}}}`, Variables: resolve.NewVariables( &resolve.ContextVariable{ Path: []string{"droidId"}, @@ -8579,7 +8580,7 @@ type Droid implements Character { friends: [Character] } -type Startship { +type Starship { name: String! length: Float! }` @@ -8655,7 +8656,7 @@ type Droid implements Character { friends: [Character] } -type Startship { +type Starship { name: String! length: Float! }` @@ -8735,7 +8736,7 @@ type Droid implements Character { friends: [Character] } -type Startship { +type Starship { name: String! length: Float! }` @@ -8812,7 +8813,7 @@ type Droid_api implements Character_api { friends: [Character_api] } -type Startship_api { +type Starship_api { name: String! length: Float! }` diff --git a/pkg/engine/plan/plan.go b/pkg/engine/plan/plan.go index 11ffb9eef2..d1f8b89333 100644 --- a/pkg/engine/plan/plan.go +++ b/pkg/engine/plan/plan.go @@ -156,7 +156,7 @@ type ArgumentConfiguration struct { type DataSourceConfiguration struct { // RootNodes - defines the nodes where the responsibility of the DataSource begins - // When you enter a node and it is not a child node + // When you enter a node, and it is not a child node // when you have entered into a field which representing data source - it means that we starting a new planning stage RootNodes []TypeField // ChildNodes - describes additional fields which will be requested along with fields which has a datasources @@ -224,6 +224,7 @@ func NewPlanner(ctx context.Context, config Configuration) *Planner { requiredFieldsWalker.RegisterEnterDocumentVisitor(requiredFieldsV) requiredFieldsWalker.RegisterEnterOperationVisitor(requiredFieldsV) requiredFieldsWalker.RegisterEnterFieldVisitor(requiredFieldsV) + requiredFieldsWalker.RegisterLeaveDocumentVisitor(requiredFieldsV) // configuration @@ -511,7 +512,6 @@ func (v *Visitor) LeaveSelectionSet(_ int) { } func (v *Visitor) EnterField(ref int) { - if v.skipField(ref) { return } @@ -529,7 +529,7 @@ func (v *Visitor) EnterField(ref int) { Path: []string{"__typename"}, IsTypeName: true, }, - OnTypeName: v.resolveOnTypeName(), + OnTypeNames: v.resolveOnTypeNames(), Position: v.resolveFieldPosition(ref), SkipDirectiveDefined: skip, SkipVariableName: skipVariableName, @@ -575,7 +575,7 @@ func (v *Visitor) EnterField(ref int) { Value: v.resolveFieldValue(ref, fieldDefinitionType, true, path), HasBuffer: hasBuffer, BufferID: bufferID, - OnTypeName: v.resolveOnTypeName(), + OnTypeNames: v.resolveOnTypeNames(), Position: v.resolveFieldPosition(ref), SkipDirectiveDefined: skip, SkipVariableName: skipVariableName, @@ -648,7 +648,7 @@ func (v *Visitor) resolveInclude(directiveRefs []int) (bool, string) { return false, "" } -func (v *Visitor) resolveOnTypeName() []byte { +func (v *Visitor) resolveOnTypeNames() [][]byte { if len(v.Walker.Ancestors) < 2 { return nil } @@ -657,7 +657,29 @@ func (v *Visitor) resolveOnTypeName() []byte { return nil } typeName := v.Operation.InlineFragmentTypeConditionName(inlineFragment.Ref) - return v.Config.Types.RenameTypeNameOnMatchBytes(typeName) + if typeName == nil { + typeName = v.Walker.EnclosingTypeDefinition.NameBytes(v.Definition) + } + node, exists := v.Definition.NodeByName(typeName) + // If not an interface, return the concrete type + if !exists || !node.Kind.IsAbstractType() { + return [][]byte{v.Config.Types.RenameTypeNameOnMatchBytes(typeName)} + } + if node.Kind == ast.NodeKindUnionTypeDefinition { + // This should never be true. If it is, it's an error + panic("resolveOnTypeNames called with a union type") + } + // We're dealing with an interface, so add all objects that implement this interface to the slice + onTypeNames := make([][]byte, 0, 2) + for objectTypeDefinitionRef := range v.Definition.ObjectTypeDefinitions { + if v.Definition.ObjectTypeDefinitionImplementsInterface(objectTypeDefinitionRef, typeName) { + onTypeNames = append(onTypeNames, v.Definition.ObjectTypeDefinitionNameBytes(objectTypeDefinitionRef)) + } + } + if len(onTypeNames) < 1 { + return nil + } + return onTypeNames } func (v *Visitor) LeaveField(ref int) { @@ -1534,81 +1556,132 @@ func (c *configurationVisitor) EnterField(ref int) { fieldName := c.operation.FieldNameUnsafeString(ref) fieldAliasOrName := c.operation.FieldAliasOrNameString(ref) typeName := c.walker.EnclosingTypeDefinition.NameString(c.definition) - parent := c.walker.Path.DotDelimitedString() - current := parent + "." + fieldAliasOrName + parentPath := c.walker.Path.DotDelimitedString() + currentPath := parentPath + "." + fieldAliasOrName root := c.walker.Ancestors[0] if root.Kind != ast.NodeKindOperationDefinition { return } - isSubscription := c.isSubscription(root.Ref, current) + isSubscription := c.isSubscription(root.Ref, currentPath) for i, plannerConfig := range c.planners { planningBehaviour := plannerConfig.planner.DataSourcePlanningBehavior() - if plannerConfig.hasParent(parent) && plannerConfig.hasRootNode(typeName, fieldName) && planningBehaviour.MergeAliasedRootNodes { - // same parent + root node = root sibling - c.planners[i].paths = append(c.planners[i].paths, pathConfiguration{path: current, shouldWalkFields: true}) - c.fieldBuffers[ref] = plannerConfig.bufferID + if fieldAliasOrName == "__typename" && planningBehaviour.IncludeTypeNameFields { + c.planners[i].paths = append(c.planners[i].paths, pathConfiguration{path: currentPath, shouldWalkFields: true}) return } - if plannerConfig.hasPath(parent) && plannerConfig.hasChildNode(typeName, fieldName) { - // has parent path + has child node = child - c.planners[i].paths = append(c.planners[i].paths, pathConfiguration{path: current, shouldWalkFields: true}) + if plannerConfig.hasParent(parentPath) && plannerConfig.hasRootNode(typeName, fieldName) && planningBehaviour.MergeAliasedRootNodes { + // same parent + root node = root sibling + c.planners[i].paths = append(c.planners[i].paths, pathConfiguration{path: currentPath, shouldWalkFields: true}) + c.fieldBuffers[ref] = plannerConfig.bufferID return } - if fieldAliasOrName == "__typename" && planningBehaviour.IncludeTypeNameFields { - c.planners[i].paths = append(c.planners[i].paths, pathConfiguration{path: current, shouldWalkFields: true}) - return + if plannerConfig.hasPath(parentPath) { + if plannerConfig.hasChildNode(typeName, fieldName) { + // has parent path + has child node = child + c.planners[i].paths = append(c.planners[i].paths, pathConfiguration{path: currentPath, shouldWalkFields: true}) + return + } + + if pathAdded := c.addPlannerPathForUnionChildOfObjectParent(ref, i, currentPath); pathAdded { + return + } + + if pathAdded := c.addPlannerPathForChildOfAbstractParent(i, typeName, fieldName, currentPath); pathAdded { + return + } } } for i, config := range c.config.DataSources { - if config.HasRootNode(typeName, fieldName) { - var ( - bufferID int - ) - if !isSubscription { - bufferID = c.nextBufferID() - c.fieldBuffers[ref] = bufferID - } - planner := c.config.DataSources[i].Factory.Planner(c.ctx) - isParentAbstract := c.isParentTypeNodeAbstractType() - paths := []pathConfiguration{ + if !config.HasRootNode(typeName, fieldName) { + continue + } + var ( + bufferID int + ) + if !isSubscription { + bufferID = c.nextBufferID() + c.fieldBuffers[ref] = bufferID + } + planner := c.config.DataSources[i].Factory.Planner(c.ctx) + isParentAbstract := c.isParentTypeNodeAbstractType() + paths := []pathConfiguration{ + { + path: currentPath, + shouldWalkFields: true, + }, + } + if isParentAbstract { + // if the parent is abstract, we add the parent path as well + // this will ensure that we're walking into and out of the root inline fragments + // otherwise, we'd only walk into the fields inside the inline fragments in the root, + // so we'd miss the selection sets and inline fragments in the root + paths = append([]pathConfiguration{ { - path: current, - shouldWalkFields: true, + path: parentPath, + shouldWalkFields: false, }, - } - if isParentAbstract { - // if the parent is abstract, we add the parent path as well - // this will ensure that we're walking into and out of the root inline fragments - // otherwise, we'd only walk into the fields inside the inline fragments in the root, - // so we'd miss the selection sets and inline fragments in the root - paths = append([]pathConfiguration{ - { - path: parent, - shouldWalkFields: false, - }, - }, paths...) - } - c.planners = append(c.planners, plannerConfiguration{ - bufferID: bufferID, - parentPath: parent, - planner: planner, - paths: paths, - dataSourceConfiguration: config, - }) - fieldDefinition, ok := c.walker.FieldDefinition(ref) - if !ok { - continue - } - c.fetches = append(c.fetches, objectFetchConfiguration{ - bufferID: bufferID, - planner: planner, - isSubscription: isSubscription, - fieldRef: ref, - fieldDefinitionRef: fieldDefinition, - }) - return + }, paths...) + } + c.planners = append(c.planners, plannerConfiguration{ + bufferID: bufferID, + parentPath: parentPath, + planner: planner, + paths: paths, + dataSourceConfiguration: config, + }) + fieldDefinition, ok := c.walker.FieldDefinition(ref) + if !ok { + continue + } + c.fetches = append(c.fetches, objectFetchConfiguration{ + bufferID: bufferID, + planner: planner, + isSubscription: isSubscription, + fieldRef: ref, + fieldDefinitionRef: fieldDefinition, + }) + return + } +} + +func (c *configurationVisitor) addPlannerPathForUnionChildOfObjectParent(fieldRef int, plannerIndex int, currentPath string) (pathAdded bool) { + if c.walker.EnclosingTypeDefinition.Kind != ast.NodeKindObjectTypeDefinition { + return false + } + fieldDefRef, exists := c.definition.NodeFieldDefinitionByName(c.walker.EnclosingTypeDefinition, c.operation.FieldNameBytes(fieldRef)) + if !exists { + return false + } + + fieldDefTypeRef := c.definition.FieldDefinitionType(fieldDefRef) + fieldDefTypeName := c.definition.TypeNameBytes(fieldDefTypeRef) + node, ok := c.definition.NodeByName(fieldDefTypeName) + if !ok { + return false + } + + if node.Kind == ast.NodeKindUnionTypeDefinition { + c.planners[plannerIndex].paths = append(c.planners[plannerIndex].paths, pathConfiguration{path: currentPath, shouldWalkFields: true}) + return true + } + return false +} + +func (c *configurationVisitor) addPlannerPathForChildOfAbstractParent( + plannerIndex int, typeName, fieldName, currentPath string, +) (pathAdded bool) { + if !c.isParentTypeNodeAbstractType() { + return false + } + // If the field is a root node in any of the data sources, the path shouldn't be handled here + for _, d := range c.config.DataSources { + if d.HasRootNode(typeName, fieldName) { + return false } } + // The path for this field should only be added if the parent path also exists on this planner + c.planners[plannerIndex].paths = append(c.planners[plannerIndex].paths, pathConfiguration{path: currentPath, shouldWalkFields: true}) + return true } func (c *configurationVisitor) isParentTypeNodeAbstractType() bool { @@ -1662,23 +1735,44 @@ func (c *configurationVisitor) isSubscription(root int, path string) bool { return strings.Count(path, ".") == 1 } +type skipFieldData struct { + selectionSetRef int + fieldConfig *FieldConfiguration + requiredField string + fieldPath string +} + type requiredFieldsVisitor struct { operation, definition *ast.Document walker *astvisitor.Walker config *Configuration operationName string skipFieldPaths []string + // selectedFieldPaths is a set of all explicitly selected field paths + selectedFieldPaths map[string]struct{} + // skipFieldDataPaths is to prevent appending duplicate skipFieldData to potentialSkipFieldDatas + skipFieldDataPaths map[string]struct{} + // potentialSkipFieldDatas is used in LeaveDocument to determine whether a required field should be skipped + // Must be a slice to preserve field order, which is why duplicates are handled with a set + potentialSkipFieldDatas []*skipFieldData } func (r *requiredFieldsVisitor) EnterDocument(_, _ *ast.Document) { r.skipFieldPaths = r.skipFieldPaths[:0] + r.selectedFieldPaths = make(map[string]struct{}) + r.potentialSkipFieldDatas = make([]*skipFieldData, 0) } func (r *requiredFieldsVisitor) EnterField(ref int) { typeName := r.walker.EnclosingTypeDefinition.NameString(r.definition) fieldName := r.operation.FieldNameUnsafeString(ref) fieldConfig := r.config.Fields.ForTypeField(typeName, fieldName) + path := r.walker.Path.DotDelimitedString() if fieldConfig == nil { + // Record all explicitly selected fields + // A field selected on an interface will have the same field path as a fragment on an object + // LeaveDocument uses this record to ensure only required fields that were not explicitly selected are skipped + r.selectedFieldPaths[fmt.Sprintf("%s.%s", path, fieldName)] = struct{}{} return } if len(fieldConfig.RequiresFields) == 0 { @@ -1688,12 +1782,24 @@ func (r *requiredFieldsVisitor) EnterField(ref int) { if selectionSet.Kind != ast.NodeKindSelectionSet { return } - for i := range fieldConfig.RequiresFields { - r.handleRequiredField(selectionSet.Ref, fieldConfig.RequiresFields[i]) + for _, requiredField := range fieldConfig.RequiresFields { + requiredFieldPath := fmt.Sprintf("%s.%s", path, requiredField) + // Prevent adding duplicates to the slice (order is necessary; hence, a separate map) + if _, ok := r.skipFieldDataPaths[requiredFieldPath]; ok { + continue + } + // For each required field, collect the data required to handle (in LeaveDocument) whether we should skip it + data := &skipFieldData{ + selectionSetRef: selectionSet.Ref, + fieldConfig: fieldConfig, + requiredField: requiredField, + fieldPath: requiredFieldPath, + } + r.potentialSkipFieldDatas = append(r.potentialSkipFieldDatas, data) } } -func (r *requiredFieldsVisitor) handleRequiredField(selectionSet int, requiredFieldName string) { +func (r *requiredFieldsVisitor) handleRequiredField(selectionSet int, requiredFieldName, fullFieldPath string) { for _, ref := range r.operation.SelectionSets[selectionSet].SelectionRefs { selection := r.operation.Selections[ref] if selection.Kind != ast.SelectionKindField { @@ -1705,10 +1811,10 @@ func (r *requiredFieldsVisitor) handleRequiredField(selectionSet int, requiredFi return } } - r.addRequiredField(requiredFieldName, selectionSet) + r.addRequiredField(requiredFieldName, selectionSet, fullFieldPath) } -func (r *requiredFieldsVisitor) addRequiredField(fieldName string, selectionSet int) { +func (r *requiredFieldsVisitor) addRequiredField(fieldName string, selectionSet int, fullFieldPath string) { field := ast.Field{ Name: r.operation.Input.AppendInputString(fieldName), } @@ -1718,8 +1824,7 @@ func (r *requiredFieldsVisitor) addRequiredField(fieldName string, selectionSet Ref: addedField.Ref, } r.operation.AddSelection(selectionSet, selection) - addedFieldPath := r.walker.Path.DotDelimitedString() + "." + fieldName - r.skipFieldPaths = append(r.skipFieldPaths, addedFieldPath) + r.skipFieldPaths = append(r.skipFieldPaths, fullFieldPath) } func (r *requiredFieldsVisitor) EnterOperationDefinition(ref int) { @@ -1729,3 +1834,13 @@ func (r *requiredFieldsVisitor) EnterOperationDefinition(ref int) { return } } + +func (r *requiredFieldsVisitor) LeaveDocument(_, _ *ast.Document) { + for _, data := range r.potentialSkipFieldDatas { + path := data.fieldPath + // If a field was not explicitly selected, skip it + if _, ok := r.selectedFieldPaths[path]; !ok { + r.handleRequiredField(data.selectionSetRef, data.requiredField, path) + } + } +} diff --git a/pkg/engine/plan/plan_test.go b/pkg/engine/plan/plan_test.go index 72a9f728a1..e793f5e073 100644 --- a/pkg/engine/plan/plan_test.go +++ b/pkg/engine/plan/plan_test.go @@ -208,6 +208,58 @@ func TestPlanner_Plan(t *testing.T) { DefaultFlushIntervalMillis: 0, })) + t.Run("Union response type with interface fragments", test(testDefinition, ` + query SearchResults { + searchResults { + ... on Character { + name + } + ... on Vehicle { + length + } + } + } + `, "SearchResults", &SynchronousResponsePlan{ + Response: &resolve.GraphQLResponse{ + Data: &resolve.Object{ + Nullable: false, + Fields: []*resolve.Field{ + { + Name: []byte("searchResults"), + Value: &resolve.Array{ + Path: []string{"searchResults"}, + Nullable: true, + ResolveAsynchronous: false, + Item: &resolve.Object{ + Nullable: true, + Fields: []*resolve.Field{ + { + Name: []byte("name"), + Value: &resolve.String{ + Path: []string{"name"}, + Nullable: false, + }, + OnTypeNames: [][]byte{[]byte("Human"), []byte("Droid")}, + }, + { + Name: []byte("length"), + Value: &resolve.Float{ + Path: []string{"length"}, + Nullable: false, + }, + OnTypeNames: [][]byte{[]byte("Starship")}, + }, + }, + }, + }, + }, + }, + }, + }, + }, Configuration{ + DisableResolveFieldPositions: true, + })) + t.Run("operation selection", func(t *testing.T) { t.Run("should successfully plan a single named query by providing an operation name", test(testDefinition, ` query MyHero { @@ -524,6 +576,7 @@ type Query { hero: Character droid(id: ID!): Droid search(name: String!): SearchResult + searchResults: [SearchResult] } type Mutation { @@ -570,7 +623,12 @@ type Droid implements Character { favoriteEpisode: Episode } -type Startship { +interface Vehicle { + length: Float! +} + +type Starship implements Vehicle { name: String! length: Float! -}` +} +` diff --git a/pkg/engine/resolve/resolve.go b/pkg/engine/resolve/resolve.go index a936be77f5..e95b99ff90 100644 --- a/pkg/engine/resolve/resolve.go +++ b/pkg/engine/resolve/resolve.go @@ -1101,7 +1101,6 @@ func (r *Resolver) resolveObject(ctx *Context, object *Object, data []byte, obje first := true skipCount := 0 for i := range object.Fields { - if object.Fields[i].SkipDirectiveDefined { skip, err := jsonparser.GetBoolean(ctx.Variables, object.Fields[i].SkipVariableName) if err == nil && skip { @@ -1130,9 +1129,16 @@ func (r *Resolver) resolveObject(ctx *Context, object *Object, data []byte, obje fieldData = data } - if object.Fields[i].OnTypeName != nil { + if object.Fields[i].OnTypeNames != nil { typeName, _, _, _ := jsonparser.Get(fieldData, "__typename") - if !bytes.Equal(typeName, object.Fields[i].OnTypeName) { + hasMatch := false + for _, onTypeName := range object.Fields[i].OnTypeNames { + if bytes.Equal(typeName, onTypeName) { + hasMatch = true + break + } + } + if !hasMatch { typeNameSkip = true // Restore the response elements that may have been reset above. ctx.responseElements = responseElements @@ -1374,7 +1380,7 @@ type Field struct { Stream *StreamField HasBuffer bool BufferID int - OnTypeName []byte + OnTypeNames [][]byte SkipDirectiveDefined bool SkipVariableName string IncludeDirectiveDefined bool diff --git a/pkg/engine/resolve/resolve_test.go b/pkg/engine/resolve/resolve_test.go index 7c2cd1786f..45def34a91 100644 --- a/pkg/engine/resolve/resolve_test.go +++ b/pkg/engine/resolve/resolve_test.go @@ -1209,10 +1209,10 @@ func TestResolver_ResolveNode(t *testing.T) { Item: &Object{ Fields: []*Field{ { - BufferID: 0, - HasBuffer: true, - OnTypeName: []byte("Dog"), - Name: []byte("name"), + BufferID: 0, + HasBuffer: true, + OnTypeNames: [][]byte{[]byte("Dog")}, + Name: []byte("name"), Value: &String{ Path: []string{"name"}, }, @@ -1240,8 +1240,8 @@ func TestResolver_ResolveNode(t *testing.T) { Nullable: false, Fields: []*Field{ { - OnTypeName: []byte("Cat"), - Name: []byte("name"), + OnTypeNames: [][]byte{[]byte("Cat")}, + Name: []byte("name"), Value: &String{ Path: []string{"name"}, }, @@ -1274,8 +1274,8 @@ func TestResolver_ResolveNode(t *testing.T) { Path: []string{"namespaceCreate"}, Fields: []*Field{ { - Name: []byte("namespace"), - OnTypeName: []byte("NamespaceCreated"), + Name: []byte("namespace"), + OnTypeNames: [][]byte{[]byte("NamespaceCreated")}, Value: &Object{ Path: []string{"namespace"}, Nullable: false, @@ -1298,16 +1298,16 @@ func TestResolver_ResolveNode(t *testing.T) { }, }, { - Name: []byte("code"), - OnTypeName: []byte("Error"), + Name: []byte("code"), + OnTypeNames: [][]byte{[]byte("Error")}, Value: &String{ Nullable: false, Path: []string{"code"}, }, }, { - Name: []byte("message"), - OnTypeName: []byte("Error"), + Name: []byte("message"), + OnTypeNames: [][]byte{[]byte("Error")}, Value: &String{ Nullable: false, Path: []string{"message"}, @@ -1339,10 +1339,10 @@ func TestResolver_ResolveNode(t *testing.T) { Item: &Object{ Fields: []*Field{ { - BufferID: 0, - HasBuffer: true, - OnTypeName: []byte("Dog"), - Name: []byte("name"), + BufferID: 0, + HasBuffer: true, + OnTypeNames: [][]byte{[]byte("Dog")}, + Name: []byte("name"), Value: &String{ Path: []string{"name"}, }, @@ -1387,10 +1387,10 @@ func TestResolver_ResolveNode(t *testing.T) { Nullable: true, Fields: []*Field{ { - BufferID: 0, - HasBuffer: false, - OnTypeName: []byte("Dog"), - Name: []byte("name"), + BufferID: 0, + HasBuffer: false, + OnTypeNames: [][]byte{[]byte("Dog")}, + Name: []byte("name"), Value: &String{ Path: []string{"name"}, }, @@ -1423,10 +1423,10 @@ func TestResolver_ResolveNode(t *testing.T) { Item: &Object{ Fields: []*Field{ { - BufferID: 0, - HasBuffer: true, - OnTypeName: []byte("Dog"), - Name: []byte("name"), + BufferID: 0, + HasBuffer: true, + OnTypeNames: [][]byte{[]byte("Dog")}, + Name: []byte("name"), Value: &String{ Path: []string{"name"}, }, diff --git a/pkg/graphql/execution_engine_v2_test.go b/pkg/graphql/execution_engine_v2_test.go index 9723247ff4..d0fa4072e7 100644 --- a/pkg/graphql/execution_engine_v2_test.go +++ b/pkg/graphql/execution_engine_v2_test.go @@ -412,7 +412,7 @@ func TestExecutionEngineV2_Execute(t *testing.T) { `, } }, - expectedResponse: `{"data":{"q":{"name":"Query","kind":"OBJECT","fields":[{"name":"droid"},{"name":"search"}]},"h":{"name":"Human","fields":[{"name":"name"},{"name":"friends"}]}}}`, + expectedResponse: `{"data":{"q":{"name":"Query","kind":"OBJECT","fields":[{"name":"droid"},{"name":"search"},{"name":"searchResults"}]},"h":{"name":"Human","fields":[{"name":"name"},{"name":"friends"}]}}}`, }, )) @@ -456,7 +456,7 @@ func TestExecutionEngineV2_Execute(t *testing.T) { }`, } }, - expectedResponse: `{"data":{"__type":{"name":"Query","kind":"OBJECT","fields":[{"name":"hero"},{"name":"droid"},{"name":"search"}]}}}`, + expectedResponse: `{"data":{"__type":{"name":"Query","kind":"OBJECT","fields":[{"name":"hero"},{"name":"droid"},{"name":"search"},{"name":"searchResults"}]}}}`, }, )) @@ -466,7 +466,7 @@ func TestExecutionEngineV2_Execute(t *testing.T) { operation: func(t *testing.T) Request { return requestForQuery(t, starwars.FileIntrospectionQuery) }, - expectedResponse: `{"data":{"__schema":{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"subscriptionType":{"name":"Subscription"},"types":[{"kind":"UNION","name":"SearchResult","description":"","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Human","ofType":null},{"kind":"OBJECT","name":"Droid","ofType":null},{"kind":"OBJECT","name":"Starship","ofType":null}]},{"kind":"OBJECT","name":"Query","description":"","fields":[{"name":"hero","description":"","args":[],"type":{"kind":"INTERFACE","name":"Character","ofType":null},"isDeprecated":true,"deprecationReason":"No longer supported"},{"name":"droid","description":"","args":[{"name":"id","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Droid","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"search","description":"","args":[{"name":"name","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"UNION","name":"SearchResult","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Mutation","description":"","fields":[{"name":"createReview","description":"","args":[{"name":"episode","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Episode","ofType":null}},"defaultValue":null},{"name":"review","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"INPUT_OBJECT","name":"ReviewInput","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Review","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Subscription","description":"","fields":[{"name":"remainingJedis","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"INPUT_OBJECT","name":"ReviewInput","description":"","fields":null,"inputFields":[{"name":"stars","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}},"defaultValue":null},{"name":"commentary","description":"","type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null}],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Review","description":"","fields":[{"name":"id","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stars","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"commentary","description":"","args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"ENUM","name":"Episode","description":"","fields":null,"inputFields":[],"interfaces":[],"enumValues":[{"name":"NEWHOPE","description":"","isDeprecated":false,"deprecationReason":null},{"name":"EMPIRE","description":"","isDeprecated":false,"deprecationReason":null},{"name":"JEDI","description":"","isDeprecated":true,"deprecationReason":"No longer supported"}],"possibleTypes":[]},{"kind":"INTERFACE","name":"Character","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"friends","description":"","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"INTERFACE","name":"Character","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Human","ofType":null},{"kind":"OBJECT","name":"Droid","ofType":null}]},{"kind":"OBJECT","name":"Human","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"height","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":true,"deprecationReason":"No longer supported"},{"name":"friends","description":"","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"INTERFACE","name":"Character","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[{"kind":"INTERFACE","name":"Character","ofType":null}],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Droid","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"primaryFunction","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"friends","description":"","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"INTERFACE","name":"Character","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[{"kind":"INTERFACE","name":"Character","ofType":null}],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Starship","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"length","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Float","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"Int","description":"The 'Int' scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"Float","description":"The 'Float' scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"String","description":"The 'String' scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"Boolean","description":"The 'Boolean' scalar type represents 'true' or 'false' .","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"ID","description":"The 'ID' scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as '4') or integer (such as 4) input value will be accepted as an ID.","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]}],"directives":[{"name":"include","description":"Directs the executor to include this field or fragment only when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Included when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"skip","description":"Directs the executor to skip this field or fragment when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Skipped when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"deprecated","description":"Marks an element of a GraphQL schema as no longer supported.","locations":["FIELD_DEFINITION","ENUM_VALUE"],"args":[{"name":"reason","description":"Explains why this element was deprecated, usually also including a suggestion\n for how to access supported similar data. Formatted in\n [Markdown](https://daringfireball.net/projects/markdown/).","type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":"\"No longer supported\""}]},{"name":"removeNullVariables","description":"The @removeNullVariables directive allows you to remove variables with null value from your GraphQL Query or Mutation Operations.\n\nA potential use-case could be that you have a graphql upstream which is not accepting null values for variables.\nBy enabling this directive all variables with null values will be removed from upstream query.\n\nquery ($say: String, $name: String) @removeNullVariables {\n\thello(say: $say, name: $name)\n}\n\nDirective will transform variables json and remove top level null values.\n{ \"say\": null, \"name\": \"world\" }\n\nSo upstream will receive the following variables:\n\n{ \"name\": \"world\" }","locations":["QUERY","MUTATION"],"args":[]}]}}}`, + expectedResponse: `{"data":{"__schema":{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"subscriptionType":{"name":"Subscription"},"types":[{"kind":"UNION","name":"SearchResult","description":"","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Human","ofType":null},{"kind":"OBJECT","name":"Droid","ofType":null},{"kind":"OBJECT","name":"Starship","ofType":null}]},{"kind":"OBJECT","name":"Query","description":"","fields":[{"name":"hero","description":"","args":[],"type":{"kind":"INTERFACE","name":"Character","ofType":null},"isDeprecated":true,"deprecationReason":"No longer supported"},{"name":"droid","description":"","args":[{"name":"id","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Droid","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"search","description":"","args":[{"name":"name","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"defaultValue":null}],"type":{"kind":"UNION","name":"SearchResult","ofType":null},"isDeprecated":false,"deprecationReason":null},{"name":"searchResults","description":"","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"UNION","name":"SearchResult","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Mutation","description":"","fields":[{"name":"createReview","description":"","args":[{"name":"episode","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"ENUM","name":"Episode","ofType":null}},"defaultValue":null},{"name":"review","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"INPUT_OBJECT","name":"ReviewInput","ofType":null}},"defaultValue":null}],"type":{"kind":"OBJECT","name":"Review","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Subscription","description":"","fields":[{"name":"remainingJedis","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"INPUT_OBJECT","name":"ReviewInput","description":"","fields":null,"inputFields":[{"name":"stars","description":"","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}},"defaultValue":null},{"name":"commentary","description":"","type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":null}],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Review","description":"","fields":[{"name":"id","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"ID","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"stars","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Int","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"commentary","description":"","args":[],"type":{"kind":"SCALAR","name":"String","ofType":null},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"ENUM","name":"Episode","description":"","fields":null,"inputFields":[],"interfaces":[],"enumValues":[{"name":"NEWHOPE","description":"","isDeprecated":false,"deprecationReason":null},{"name":"EMPIRE","description":"","isDeprecated":false,"deprecationReason":null},{"name":"JEDI","description":"","isDeprecated":true,"deprecationReason":"No longer supported"}],"possibleTypes":[]},{"kind":"INTERFACE","name":"Character","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"friends","description":"","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"INTERFACE","name":"Character","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Human","ofType":null},{"kind":"OBJECT","name":"Droid","ofType":null}]},{"kind":"OBJECT","name":"Human","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"height","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":true,"deprecationReason":"No longer supported"},{"name":"friends","description":"","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"INTERFACE","name":"Character","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[{"kind":"INTERFACE","name":"Character","ofType":null}],"enumValues":null,"possibleTypes":[]},{"kind":"OBJECT","name":"Droid","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"primaryFunction","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"friends","description":"","args":[],"type":{"kind":"LIST","name":null,"ofType":{"kind":"INTERFACE","name":"Character","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[{"kind":"INTERFACE","name":"Character","ofType":null}],"enumValues":null,"possibleTypes":[]},{"kind":"INTERFACE","name":"Vehicle","description":"","fields":[{"name":"length","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Float","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[{"kind":"OBJECT","name":"Starship","ofType":null}]},{"kind":"OBJECT","name":"Starship","description":"","fields":[{"name":"name","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"String","ofType":null}},"isDeprecated":false,"deprecationReason":null},{"name":"length","description":"","args":[],"type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Float","ofType":null}},"isDeprecated":false,"deprecationReason":null}],"inputFields":[],"interfaces":[{"kind":"INTERFACE","name":"Vehicle","ofType":null}],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"Int","description":"The 'Int' scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"Float","description":"The 'Float' scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"String","description":"The 'String' scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"Boolean","description":"The 'Boolean' scalar type represents 'true' or 'false' .","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]},{"kind":"SCALAR","name":"ID","description":"The 'ID' scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as '4') or integer (such as 4) input value will be accepted as an ID.","fields":null,"inputFields":[],"interfaces":[],"enumValues":null,"possibleTypes":[]}],"directives":[{"name":"include","description":"Directs the executor to include this field or fragment only when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Included when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"skip","description":"Directs the executor to skip this field or fragment when the argument is true.","locations":["FIELD","FRAGMENT_SPREAD","INLINE_FRAGMENT"],"args":[{"name":"if","description":"Skipped when true.","type":{"kind":"NON_NULL","name":null,"ofType":{"kind":"SCALAR","name":"Boolean","ofType":null}},"defaultValue":null}]},{"name":"deprecated","description":"Marks an element of a GraphQL schema as no longer supported.","locations":["FIELD_DEFINITION","ENUM_VALUE"],"args":[{"name":"reason","description":"Explains why this element was deprecated, usually also including a suggestion\n for how to access supported similar data. Formatted in\n [Markdown](https://daringfireball.net/projects/markdown/).","type":{"kind":"SCALAR","name":"String","ofType":null},"defaultValue":"\"No longer supported\""}]},{"name":"removeNullVariables","description":"The @removeNullVariables directive allows you to remove variables with null value from your GraphQL Query or Mutation Operations.\n\nA potential use-case could be that you have a graphql upstream which is not accepting null values for variables.\nBy enabling this directive all variables with null values will be removed from upstream query.\n\nquery ($say: String, $name: String) @removeNullVariables {\n\thello(say: $say, name: $name)\n}\n\nDirective will transform variables json and remove top level null values.\n{ \"say\": null, \"name\": \"world\" }\n\nSo upstream will receive the following variables:\n\n{ \"name\": \"world\" }","locations":["QUERY","MUTATION"],"args":[]}]}}}`, }, )) }) @@ -1434,7 +1434,7 @@ func TestExecutionEngineV2_Execute(t *testing.T) { HTTPClient: testNetHttpClient(t, roundTripperTestCase{ expectedHost: "example.com", expectedPath: "/", - expectedBody: `{"query":"{codeType {__typename code ... on Country {name}}}"}`, + expectedBody: `{"query":"{codeType {code __typename ... on Country {name}}}"}`, sendResponseBody: `{"data":{"codeType":{"__typename":"Country","code":"de","name":"Germany"}}}`, sendStatusCode: 200, }), @@ -1673,6 +1673,54 @@ func TestExecutionEngineV2_Execute(t *testing.T) { }, "fragment spread: fragment reviewFields must be spread on type Review and not type Droid", )) + + t.Run("execute the correct operation when sending multiple queries", runWithoutError( + ExecutionEngineV2TestCase{ + schema: starwarsSchema(t), + operation: func(t *testing.T) Request { + request := loadStarWarsQuery(starwars.FileInterfaceFragmentsOnUnion, nil)(t) + request.OperationName = "SearchResults" + return request + }, + dataSources: []plan.DataSourceConfiguration{ + { + RootNodes: []plan.TypeField{ + { + TypeName: "Query", + FieldNames: []string{"searchResults"}, + }, + }, + ChildNodes: []plan.TypeField{ + { + TypeName: "Character", + FieldNames: []string{"name"}, + }, + { + TypeName: "Vehicle", + FieldNames: []string{"length"}, + }, + }, + Factory: &graphql_datasource.Factory{ + HTTPClient: testNetHttpClient(t, roundTripperTestCase{ + expectedHost: "example.com", + expectedPath: "/", + expectedBody: "", + sendResponseBody: `{"data":{"searchResults":[{"name"":"Luke Skywalker"},{"length":13.37}]}}`, + sendStatusCode: 200, + }), + }, + Custom: graphql_datasource.ConfigJson(graphql_datasource.Configuration{ + Fetch: graphql_datasource.FetchConfiguration{ + URL: "https://example.com/", + Method: "GET", + }, + }), + }, + }, + fields: []plan.FieldConfiguration{}, + expectedResponse: `{"data":{"searchResults":null}}`, + }, + )) } func testNetHttpClient(t *testing.T, testCase roundTripperTestCase) *http.Client { @@ -1766,7 +1814,7 @@ func TestExecutionWithOptions(t *testing.T) { resultWriter := NewEngineResultWriter() err = engine.Execute(context.Background(), &operation, &resultWriter, WithBeforeFetchHook(before), WithAfterFetchHook(after)) - assert.Equal(t, `{"method":"GET","url":"https://example.com/","body":{"query":"{hero {__typename name}}"}}`, before.input) + assert.Equal(t, `{"method":"GET","url":"https://example.com/","body":{"query":"{hero {name}}"}}`, before.input) assert.Equal(t, `{"hero":{"name":"Luke Skywalker"}}`, after.data) assert.Equal(t, "", after.err) assert.NoError(t, err) diff --git a/pkg/starwars/starwars.go b/pkg/starwars/starwars.go index 94f263472b..04c8d8cbbd 100644 --- a/pkg/starwars/starwars.go +++ b/pkg/starwars/starwars.go @@ -33,6 +33,7 @@ const ( FileMultiQueriesWithArguments = "testdata/queries/multi_queries_with_arguments.query" FileInvalidQuery = "testdata/queries/invalid.query" FileInvalidFragmentsQuery = "testdata/queries/invalid_fragments.query" + FileInterfaceFragmentsOnUnion = "testdata/queries/interface_fragments_on_union.graphql" ) var ( diff --git a/pkg/starwars/testdata/queries/interface_fragments_on_union.graphql b/pkg/starwars/testdata/queries/interface_fragments_on_union.graphql new file mode 100644 index 0000000000..db3162de20 --- /dev/null +++ b/pkg/starwars/testdata/queries/interface_fragments_on_union.graphql @@ -0,0 +1,10 @@ +query SearchResults { + searchResults { + ...on Character { + name + } + ...on Vehicle { + length + } + } +} \ No newline at end of file diff --git a/pkg/starwars/testdata/star_wars.graphql b/pkg/starwars/testdata/star_wars.graphql index f2be59b536..8a63a06921 100644 --- a/pkg/starwars/testdata/star_wars.graphql +++ b/pkg/starwars/testdata/star_wars.graphql @@ -10,6 +10,7 @@ type Query { hero: Character @deprecated droid(id: ID!): Droid search(name: String!): SearchResult + searchResults: [SearchResult] } type Mutation { @@ -54,7 +55,11 @@ type Droid implements Character { friends: [Character] } -type Starship { +interface Vehicle { + length: Float! +} + +type Starship implements Vehicle { name: String! length: Float! } diff --git a/pkg/testing/federationtesting/accounts/graph/entity.resolvers.go b/pkg/testing/federationtesting/accounts/graph/entity.resolvers.go index 5d997960a5..29d10cd411 100644 --- a/pkg/testing/federationtesting/accounts/graph/entity.resolvers.go +++ b/pkg/testing/federationtesting/accounts/graph/entity.resolvers.go @@ -2,6 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.22 import ( "context" diff --git a/pkg/testing/federationtesting/accounts/graph/generated/generated.go b/pkg/testing/federationtesting/accounts/graph/generated/generated.go index 1b0c4929a7..649d696ae5 100644 --- a/pkg/testing/federationtesting/accounts/graph/generated/generated.go +++ b/pkg/testing/federationtesting/accounts/graph/generated/generated.go @@ -54,20 +54,23 @@ type ComplexityRoot struct { } Purchase struct { - Product func(childComplexity int) int - Wallet func(childComplexity int) int + Product func(childComplexity int) int + Quantity func(childComplexity int) int + Wallet func(childComplexity int) int } Query struct { Histories func(childComplexity int) int + Identifiable func(childComplexity int) int Me func(childComplexity int) int __resolve__service func(childComplexity int) int __resolve_entities func(childComplexity int, representations []map[string]interface{}) int } Sale struct { - Product func(childComplexity int) int - Rating func(childComplexity int) int + Location func(childComplexity int) int + Product func(childComplexity int) int + Rating func(childComplexity int) int } User struct { @@ -98,6 +101,7 @@ type EntityResolver interface { } type QueryResolver interface { Me(ctx context.Context) (*model.User, error) + Identifiable(ctx context.Context) (model.Identifiable, error) Histories(ctx context.Context) ([]model.History, error) } @@ -142,6 +146,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Purchase.Product(childComplexity), true + case "Purchase.quantity": + if e.complexity.Purchase.Quantity == nil { + break + } + + return e.complexity.Purchase.Quantity(childComplexity), true + case "Purchase.wallet": if e.complexity.Purchase.Wallet == nil { break @@ -156,6 +167,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Histories(childComplexity), true + case "Query.identifiable": + if e.complexity.Query.Identifiable == nil { + break + } + + return e.complexity.Query.Identifiable(childComplexity), true + case "Query.me": if e.complexity.Query.Me == nil { break @@ -182,6 +200,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.__resolve_entities(childComplexity, args["representations"].([]map[string]interface{})), true + case "Sale.location": + if e.complexity.Sale.Location == nil { + break + } + + return e.complexity.Sale.Location(childComplexity), true + case "Sale.product": if e.complexity.Sale.Product == nil { break @@ -320,10 +345,15 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er var sources = []*ast.Source{ {Name: "../schema.graphqls", Input: `extend type Query { me: User + identifiable: Identifiable histories: [History] } -type User @key(fields: "id") { +interface Identifiable { + id: ID! +} + +type User implements Identifiable @key(fields: "id") { id: ID! username: String! history: [History!]! @@ -335,14 +365,24 @@ extend type Product @key(fields: "upc") { union History = Purchase | Sale -type Purchase { +interface Info { + quantity: Int! +} + +type Purchase implements Info { product: Product! wallet: Wallet + quantity: Int! } -type Sale { +interface Store { + location: String! +} + +type Sale implements Store { product: Product! rating: Int! + location: String! } interface Wallet { @@ -677,6 +717,50 @@ func (ec *executionContext) fieldContext_Purchase_wallet(ctx context.Context, fi return fc, nil } +func (ec *executionContext) _Purchase_quantity(ctx context.Context, field graphql.CollectedField, obj *model.Purchase) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Purchase_quantity(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Quantity, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Purchase_quantity(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Purchase", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Query_me(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_me(ctx, field) if err != nil { @@ -726,6 +810,47 @@ func (ec *executionContext) fieldContext_Query_me(ctx context.Context, field gra return fc, nil } +func (ec *executionContext) _Query_identifiable(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_identifiable(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Identifiable(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(model.Identifiable) + fc.Result = res + return ec.marshalOIdentifiable2githubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋaccountsᚋgraphᚋmodelᚐIdentifiable(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_identifiable(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("FieldContext.Child cannot be called on type INTERFACE") + }, + } + return fc, nil +} + func (ec *executionContext) _Query_histories(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_histories(ctx, field) if err != nil { @@ -1091,6 +1216,50 @@ func (ec *executionContext) fieldContext_Sale_rating(ctx context.Context, field return fc, nil } +func (ec *executionContext) _Sale_location(ctx context.Context, field graphql.CollectedField, obj *model.Sale) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Sale_location(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Location, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Sale_location(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Sale", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { fc, err := ec.fieldContext_User_id(ctx, field) if err != nil { @@ -3328,6 +3497,54 @@ func (ec *executionContext) _History(ctx context.Context, sel ast.SelectionSet, } } +func (ec *executionContext) _Identifiable(ctx context.Context, sel ast.SelectionSet, obj model.Identifiable) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case model.User: + return ec._User(ctx, sel, &obj) + case *model.User: + if obj == nil { + return graphql.Null + } + return ec._User(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + +func (ec *executionContext) _Info(ctx context.Context, sel ast.SelectionSet, obj model.Info) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case model.Purchase: + return ec._Purchase(ctx, sel, &obj) + case *model.Purchase: + if obj == nil { + return graphql.Null + } + return ec._Purchase(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + +func (ec *executionContext) _Store(ctx context.Context, sel ast.SelectionSet, obj model.Store) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case model.Sale: + return ec._Sale(ctx, sel, &obj) + case *model.Sale: + if obj == nil { + return graphql.Null + } + return ec._Sale(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + func (ec *executionContext) _Wallet(ctx context.Context, sel ast.SelectionSet, obj model.Wallet) graphql.Marshaler { switch obj := (obj).(type) { case nil: @@ -3459,7 +3676,7 @@ func (ec *executionContext) _Product(ctx context.Context, sel ast.SelectionSet, return out } -var purchaseImplementors = []string{"Purchase", "History"} +var purchaseImplementors = []string{"Purchase", "History", "Info"} func (ec *executionContext) _Purchase(ctx context.Context, sel ast.SelectionSet, obj *model.Purchase) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, purchaseImplementors) @@ -3480,6 +3697,13 @@ func (ec *executionContext) _Purchase(ctx context.Context, sel ast.SelectionSet, out.Values[i] = ec._Purchase_wallet(ctx, field, obj) + case "quantity": + + out.Values[i] = ec._Purchase_quantity(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -3527,6 +3751,26 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) } + out.Concurrently(i, func() graphql.Marshaler { + return rrm(innerCtx) + }) + case "identifiable": + field := field + + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_identifiable(ctx, field) + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) + } + out.Concurrently(i, func() graphql.Marshaler { return rrm(innerCtx) }) @@ -3619,7 +3863,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return out } -var saleImplementors = []string{"Sale", "History"} +var saleImplementors = []string{"Sale", "History", "Store"} func (ec *executionContext) _Sale(ctx context.Context, sel ast.SelectionSet, obj *model.Sale) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, saleImplementors) @@ -3640,6 +3884,13 @@ func (ec *executionContext) _Sale(ctx context.Context, sel ast.SelectionSet, obj out.Values[i] = ec._Sale_rating(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "location": + + out.Values[i] = ec._Sale_location(ctx, field, obj) + if out.Values[i] == graphql.Null { invalids++ } @@ -3654,7 +3905,7 @@ func (ec *executionContext) _Sale(ctx context.Context, sel ast.SelectionSet, obj return out } -var userImplementors = []string{"User", "_Entity"} +var userImplementors = []string{"User", "Identifiable", "_Entity"} func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *model.User) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, userImplementors) @@ -4713,6 +4964,13 @@ func (ec *executionContext) marshalOHistory2ᚕgithubᚗcomᚋwundergraphᚋgrap return ret } +func (ec *executionContext) marshalOIdentifiable2githubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋaccountsᚋgraphᚋmodelᚐIdentifiable(ctx context.Context, sel ast.SelectionSet, v model.Identifiable) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Identifiable(ctx, sel, v) +} + func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/pkg/testing/federationtesting/accounts/graph/histories.go b/pkg/testing/federationtesting/accounts/graph/histories.go index f41bf2e0cf..de47d5e3ae 100644 --- a/pkg/testing/federationtesting/accounts/graph/histories.go +++ b/pkg/testing/federationtesting/accounts/graph/histories.go @@ -6,42 +6,51 @@ import ( var histories = []model.History{ &model.Purchase{ - Product: &model.Product{Upc: "top-1"}, - Wallet: walletOne, + Product: &model.Product{Upc: "top-1"}, + Wallet: walletOne, + Quantity: 1, }, &model.Sale{ - Product: &model.Product{Upc: "top-2"}, - Rating: 5, + Product: &model.Product{Upc: "top-2"}, + Rating: 5, + Location: "Germany", }, &model.Purchase{ - Product: &model.Product{Upc: "top-3"}, - Wallet: walletTwo, + Product: &model.Product{Upc: "top-3"}, + Wallet: walletTwo, + Quantity: 3, }, } var allHistories = []model.History{ &model.Purchase{ - Product: &model.Product{Upc: "top-1"}, - Wallet: walletOne, + Product: &model.Product{Upc: "top-1"}, + Wallet: walletOne, + Quantity: 1, }, &model.Sale{ - Product: &model.Product{Upc: "top-1"}, - Rating: 1, + Product: &model.Product{Upc: "top-1"}, + Rating: 1, + Location: "Germany", }, &model.Purchase{ - Product: &model.Product{Upc: "top-2"}, - Wallet: walletTwo, + Product: &model.Product{Upc: "top-2"}, + Wallet: walletTwo, + Quantity: 2, }, &model.Sale{ - Product: &model.Product{Upc: "top-2"}, - Rating: 2, + Product: &model.Product{Upc: "top-2"}, + Rating: 2, + Location: "UK", }, &model.Purchase{ - Product: &model.Product{Upc: "top-3"}, - Wallet: walletTwo, + Product: &model.Product{Upc: "top-3"}, + Wallet: walletTwo, + Quantity: 3, }, &model.Sale{ - Product: &model.Product{Upc: "top-3"}, - Rating: 3, + Product: &model.Product{Upc: "top-3"}, + Rating: 3, + Location: "Ukraine", }, } diff --git a/pkg/testing/federationtesting/accounts/graph/model/models_gen.go b/pkg/testing/federationtesting/accounts/graph/model/models_gen.go index c0128a8913..36bb4f9b45 100644 --- a/pkg/testing/federationtesting/accounts/graph/model/models_gen.go +++ b/pkg/testing/federationtesting/accounts/graph/model/models_gen.go @@ -6,8 +6,25 @@ type History interface { IsHistory() } +type Identifiable interface { + IsIdentifiable() + GetID() string +} + +type Info interface { + IsInfo() + GetQuantity() int +} + +type Store interface { + IsStore() + GetLocation() string +} + type Wallet interface { IsWallet() + GetCurrency() string + GetAmount() float64 } type Product struct { @@ -17,25 +34,36 @@ type Product struct { func (Product) IsEntity() {} type Purchase struct { - Product *Product `json:"product"` - Wallet Wallet `json:"wallet"` + Product *Product `json:"product"` + Wallet Wallet `json:"wallet"` + Quantity int `json:"quantity"` } func (Purchase) IsHistory() {} +func (Purchase) IsInfo() {} +func (this Purchase) GetQuantity() int { return this.Quantity } + type Sale struct { - Product *Product `json:"product"` - Rating int `json:"rating"` + Product *Product `json:"product"` + Rating int `json:"rating"` + Location string `json:"location"` } func (Sale) IsHistory() {} +func (Sale) IsStore() {} +func (this Sale) GetLocation() string { return this.Location } + type User struct { ID string `json:"id"` Username string `json:"username"` History []History `json:"history"` } +func (User) IsIdentifiable() {} +func (this User) GetID() string { return this.ID } + func (User) IsEntity() {} type WalletType1 struct { @@ -44,7 +72,9 @@ type WalletType1 struct { SpecialField1 string `json:"specialField1"` } -func (WalletType1) IsWallet() {} +func (WalletType1) IsWallet() {} +func (this WalletType1) GetCurrency() string { return this.Currency } +func (this WalletType1) GetAmount() float64 { return this.Amount } type WalletType2 struct { Currency string `json:"currency"` @@ -52,4 +82,6 @@ type WalletType2 struct { SpecialField2 string `json:"specialField2"` } -func (WalletType2) IsWallet() {} +func (WalletType2) IsWallet() {} +func (this WalletType2) GetCurrency() string { return this.Currency } +func (this WalletType2) GetAmount() float64 { return this.Amount } diff --git a/pkg/testing/federationtesting/accounts/graph/schema.graphqls b/pkg/testing/federationtesting/accounts/graph/schema.graphqls index 12a1dc13a4..3e7df02aa3 100644 --- a/pkg/testing/federationtesting/accounts/graph/schema.graphqls +++ b/pkg/testing/federationtesting/accounts/graph/schema.graphqls @@ -1,9 +1,14 @@ extend type Query { me: User + identifiable: Identifiable histories: [History] } -type User @key(fields: "id") { +interface Identifiable { + id: ID! +} + +type User implements Identifiable @key(fields: "id") { id: ID! username: String! history: [History!]! @@ -15,14 +20,24 @@ extend type Product @key(fields: "upc") { union History = Purchase | Sale -type Purchase { +interface Info { + quantity: Int! +} + +type Purchase implements Info { product: Product! wallet: Wallet + quantity: Int! +} + +interface Store { + location: String! } -type Sale { +type Sale implements Store { product: Product! rating: Int! + location: String! } interface Wallet { diff --git a/pkg/testing/federationtesting/accounts/graph/schema.resolvers.go b/pkg/testing/federationtesting/accounts/graph/schema.resolvers.go index 2f7cb1c964..e9c2aca3b9 100644 --- a/pkg/testing/federationtesting/accounts/graph/schema.resolvers.go +++ b/pkg/testing/federationtesting/accounts/graph/schema.resolvers.go @@ -2,6 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.22 import ( "context" @@ -19,6 +20,15 @@ func (r *queryResolver) Me(ctx context.Context) (*model.User, error) { }, nil } +// Identifiable is the resolver for the identifiable field. +func (r *queryResolver) Identifiable(ctx context.Context) (model.Identifiable, error) { + return &model.User{ + ID: "1234", + Username: "Me", + History: histories, + }, nil +} + // Histories is the resolver for the histories field. func (r *queryResolver) Histories(ctx context.Context) ([]model.History, error) { return allHistories, nil diff --git a/pkg/testing/federationtesting/federation_integration_test.go b/pkg/testing/federationtesting/federation_integration_test.go index ca162276a5..5ca445a77a 100644 --- a/pkg/testing/federationtesting/federation_integration_test.go +++ b/pkg/testing/federationtesting/federation_integration_test.go @@ -282,6 +282,141 @@ func TestFederationIntegrationTest(t *testing.T) { }` assert.Equal(t, compact(expected), string(resp)) }) + + t.Run("Object response type with interface and object fragment", func(t *testing.T) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + resp := gqlClient.Query(ctx, setup.gatewayServer.URL, path.Join("testdata", "queries/interface_fragment_on_object.graphql"), nil, t) + expected := ` +{ + "data": { + "me": { + "id": "1234", + "username": "Me" + } + } +}` + assert.Equal(t, compact(expected), string(resp)) + }) + + t.Run("Interface response type with object fragment", func(t *testing.T) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + resp := gqlClient.Query(ctx, setup.gatewayServer.URL, path.Join("testdata", "queries/object_fragment_on_interface.graphql"), nil, t) + expected := ` +{ + "data": { + "identifiable": { + "__typename": "User", + "id": "1234", + "username": "Me" + } + } +}` + assert.Equal(t, compact(expected), string(resp)) + }) + + t.Run("Union response type with interface fragments", func(t *testing.T) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + resp := gqlClient.Query(ctx, setup.gatewayServer.URL, path.Join("testdata", "queries/interface_fragments_on_union.graphql"), nil, t) + expected := ` +{ + "data": { + "histories": [ + { + "__typename": "Purchase", + "quantity": 1 + }, + { + "__typename": "Sale", + "location": "Germany" + }, + { + "__typename": "Purchase", + "quantity": 2 + }, + { + "__typename": "Sale", + "location": "UK" + }, + { + "__typename": "Purchase", + "quantity": 3 + }, + { + "__typename": "Sale", + "location": "Ukraine" + } + ] + } +}` + assert.Equal(t, compact(expected), string(resp)) + }) + + // This response data of this test returns location twice: from the interface selection and from the type fragment + // Duplicated properties (and therefore invalid JSON) are usually removed during normalization processes. + // It is not yet decided whether this should be addressed before these normalization processes. + t.Run("Complex nesting", func(t *testing.T) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + resp := gqlClient.Query(ctx, setup.gatewayServer.URL, path.Join("testdata", "queries/complex_nesting.graphql"), nil, t) + expected := ` +{ + "data": { + "me": { + "id": "1234", + "username": "Me", + "history": [ + { + "wallet": { + "currency": "USD" + } + }, + { + "location": "Germany", + "location": "Germany", + "product": { + "upc": "top-2", + "name": "Fedora" + } + }, + { + "wallet": { + "currency": "USD" + } + } + ], + "reviews": [ + { + "__typename": "Review", + "attachments": [ + { + "upc": "top-1", + "body": "How do I turn it on?" + } + ] + }, + { + "__typename": "Review", + "attachments": [ + { + "upc": "top-2", + "body": "The best hat I have ever bought in my life." + }, + { + "upc": "top-2", + "size": 13.37 + } + ] + } + ] + } + } +} +` + assert.Equal(t, compact(expected), string(resp)) + }) } func compact(input string) string { diff --git a/pkg/testing/federationtesting/products/graph/entity.resolvers.go b/pkg/testing/federationtesting/products/graph/entity.resolvers.go index 55ea81ad6a..f1d4399529 100644 --- a/pkg/testing/federationtesting/products/graph/entity.resolvers.go +++ b/pkg/testing/federationtesting/products/graph/entity.resolvers.go @@ -2,6 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.22 import ( "context" diff --git a/pkg/testing/federationtesting/products/graph/schema.resolvers.go b/pkg/testing/federationtesting/products/graph/schema.resolvers.go index 48272e9e3d..f912f4bc21 100644 --- a/pkg/testing/federationtesting/products/graph/schema.resolvers.go +++ b/pkg/testing/federationtesting/products/graph/schema.resolvers.go @@ -2,6 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.22 import ( "context" diff --git a/pkg/testing/federationtesting/reviews/graph/attachments.go b/pkg/testing/federationtesting/reviews/graph/attachments.go new file mode 100644 index 0000000000..5168636424 --- /dev/null +++ b/pkg/testing/federationtesting/reviews/graph/attachments.go @@ -0,0 +1,32 @@ +package graph + +import "github.com/wundergraph/graphql-go-tools/pkg/testing/federationtesting/reviews/graph/model" + +var attachments = []model.Attachment{ + model.Question{ + Upc: "top-1", + Body: "How do I turn it on?", + }, + model.Question{ + Upc: "top-3", + Body: "Any recommendations for other teacosies?", + }, + model.Rating{ + Upc: "top-2", + Body: "The best hat I have ever bought in my life.", + Score: 5, + }, + model.Rating{ + Upc: "top-3", + Body: "Terrible teacosy!!!", + Score: 0, + }, + model.Video{ + Upc: "top-2", + Size: 13.37, + }, + model.Video{ + Upc: "top-3", + Size: 4.20, + }, +} diff --git a/pkg/testing/federationtesting/reviews/graph/entity.resolvers.go b/pkg/testing/federationtesting/reviews/graph/entity.resolvers.go index 730443165e..3f06f1377e 100644 --- a/pkg/testing/federationtesting/reviews/graph/entity.resolvers.go +++ b/pkg/testing/federationtesting/reviews/graph/entity.resolvers.go @@ -2,6 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.22 import ( "context" diff --git a/pkg/testing/federationtesting/reviews/graph/generated/generated.go b/pkg/testing/federationtesting/reviews/graph/generated/generated.go index b44bdad19b..2983b9cafc 100644 --- a/pkg/testing/federationtesting/reviews/graph/generated/generated.go +++ b/pkg/testing/federationtesting/reviews/graph/generated/generated.go @@ -40,6 +40,7 @@ type ResolverRoot interface { Entity() EntityResolver Mutation() MutationResolver Product() ProductResolver + Review() ReviewResolver User() UserResolver } @@ -66,10 +67,22 @@ type ComplexityRoot struct { __resolve_entities func(childComplexity int, representations []map[string]interface{}) int } + Question struct { + Body func(childComplexity int) int + Upc func(childComplexity int) int + } + + Rating struct { + Body func(childComplexity int) int + Score func(childComplexity int) int + Upc func(childComplexity int) int + } + Review struct { - Author func(childComplexity int) int - Body func(childComplexity int) int - Product func(childComplexity int) int + Attachments func(childComplexity int) int + Author func(childComplexity int) int + Body func(childComplexity int) int + Product func(childComplexity int) int } User struct { @@ -78,6 +91,11 @@ type ComplexityRoot struct { Username func(childComplexity int) int } + Video struct { + Size func(childComplexity int) int + Upc func(childComplexity int) int + } + _Service struct { SDL func(childComplexity int) int } @@ -93,6 +111,9 @@ type MutationResolver interface { type ProductResolver interface { Reviews(ctx context.Context, obj *model.Product) ([]*model.Review, error) } +type ReviewResolver interface { + Attachments(ctx context.Context, obj *model.Review) ([]model.Attachment, error) +} type UserResolver interface { Username(ctx context.Context, obj *model.User) (string, error) Reviews(ctx context.Context, obj *model.User) ([]*model.Review, error) @@ -182,6 +203,48 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.__resolve_entities(childComplexity, args["representations"].([]map[string]interface{})), true + case "Question.body": + if e.complexity.Question.Body == nil { + break + } + + return e.complexity.Question.Body(childComplexity), true + + case "Question.upc": + if e.complexity.Question.Upc == nil { + break + } + + return e.complexity.Question.Upc(childComplexity), true + + case "Rating.body": + if e.complexity.Rating.Body == nil { + break + } + + return e.complexity.Rating.Body(childComplexity), true + + case "Rating.score": + if e.complexity.Rating.Score == nil { + break + } + + return e.complexity.Rating.Score(childComplexity), true + + case "Rating.upc": + if e.complexity.Rating.Upc == nil { + break + } + + return e.complexity.Rating.Upc(childComplexity), true + + case "Review.attachments": + if e.complexity.Review.Attachments == nil { + break + } + + return e.complexity.Review.Attachments(childComplexity), true + case "Review.author": if e.complexity.Review.Author == nil { break @@ -224,6 +287,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.Username(childComplexity), true + case "Video.size": + if e.complexity.Video.Size == nil { + break + } + + return e.complexity.Video.Size(childComplexity), true + + case "Video.upc": + if e.complexity.Video.Upc == nil { + break + } + + return e.complexity.Video.Upc(childComplexity), true + case "_Service.sdl": if e.complexity._Service.SDL == nil { break @@ -298,12 +375,36 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var sources = []*ast.Source{ - {Name: "../schema.graphqls", Input: `type Review { + {Name: "../schema.graphqls", Input: `interface Comment { + upc: String! + body: String! +} + +type Review { body: String! author: User! @provides(fields: "username") product: Product! + attachments: [Attachment] +} + +type Question implements Comment { + upc: String! + body: String! +} + +type Rating implements Comment { + upc: String! + body: String! + score: Int! +} + +type Video { + upc: String! + size: Float! } +union Attachment = Question | Rating | Video + extend type User @key(fields: "id") { id: ID! @external username: String! @external @@ -657,6 +758,8 @@ func (ec *executionContext) fieldContext_Mutation_addReview(ctx context.Context, return ec.fieldContext_Review_author(ctx, field) case "product": return ec.fieldContext_Review_product(ctx, field) + case "attachments": + return ec.fieldContext_Review_attachments(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Review", field.Name) }, @@ -761,6 +864,8 @@ func (ec *executionContext) fieldContext_Product_reviews(ctx context.Context, fi return ec.fieldContext_Review_author(ctx, field) case "product": return ec.fieldContext_Review_product(ctx, field) + case "attachments": + return ec.fieldContext_Review_attachments(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Review", field.Name) }, @@ -1000,6 +1105,226 @@ func (ec *executionContext) fieldContext_Query___schema(ctx context.Context, fie return fc, nil } +func (ec *executionContext) _Question_upc(ctx context.Context, field graphql.CollectedField, obj *model.Question) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Question_upc(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Upc, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Question_upc(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Question", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Question_body(ctx context.Context, field graphql.CollectedField, obj *model.Question) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Question_body(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Body, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Question_body(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Question", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Rating_upc(ctx context.Context, field graphql.CollectedField, obj *model.Rating) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Rating_upc(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Upc, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Rating_upc(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Rating", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Rating_body(ctx context.Context, field graphql.CollectedField, obj *model.Rating) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Rating_body(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Body, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Rating_body(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Rating", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Rating_score(ctx context.Context, field graphql.CollectedField, obj *model.Rating) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Rating_score(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Score, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int) + fc.Result = res + return ec.marshalNInt2int(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Rating_score(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Rating", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Review_body(ctx context.Context, field graphql.CollectedField, obj *model.Review) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Review_body(ctx, field) if err != nil { @@ -1146,6 +1471,47 @@ func (ec *executionContext) fieldContext_Review_product(ctx context.Context, fie return fc, nil } +func (ec *executionContext) _Review_attachments(ctx context.Context, field graphql.CollectedField, obj *model.Review) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Review_attachments(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Review().Attachments(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]model.Attachment) + fc.Result = res + return ec.marshalOAttachment2ᚕgithubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋreviewsᚋgraphᚋmodelᚐAttachment(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Review_attachments(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Review", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Attachment does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { fc, err := ec.fieldContext_User_id(ctx, field) if err != nil { @@ -1181,17 +1547,112 @@ func (ec *executionContext) fieldContext_User_id(ctx context.Context, field grap fc = &graphql.FieldContext{ Object: "User", Field: field, - IsMethod: false, - IsResolver: false, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _User_username(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_username(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.User().Username(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_username(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _User_reviews(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_reviews(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.User().Reviews(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*model.Review) + fc.Result = res + return ec.marshalOReview2ᚕᚖgithubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋreviewsᚋgraphᚋmodelᚐReview(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_reviews(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + Field: field, + IsMethod: true, + IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ID does not have child fields") + switch field.Name { + case "body": + return ec.fieldContext_Review_body(ctx, field) + case "author": + return ec.fieldContext_Review_author(ctx, field) + case "product": + return ec.fieldContext_Review_product(ctx, field) + case "attachments": + return ec.fieldContext_Review_attachments(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Review", field.Name) }, } return fc, nil } -func (ec *executionContext) _User_username(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_User_username(ctx, field) +func (ec *executionContext) _Video_upc(ctx context.Context, field graphql.CollectedField, obj *model.Video) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Video_upc(ctx, field) if err != nil { return graphql.Null } @@ -1204,7 +1665,7 @@ func (ec *executionContext) _User_username(ctx context.Context, field graphql.Co }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.User().Username(rctx, obj) + return obj.Upc, nil }) if err != nil { ec.Error(ctx, err) @@ -1221,12 +1682,12 @@ func (ec *executionContext) _User_username(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_User_username(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Video_upc(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "User", + Object: "Video", Field: field, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { return nil, errors.New("field of type String does not have child fields") }, @@ -1234,8 +1695,8 @@ func (ec *executionContext) fieldContext_User_username(ctx context.Context, fiel return fc, nil } -func (ec *executionContext) _User_reviews(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_User_reviews(ctx, field) +func (ec *executionContext) _Video_size(ctx context.Context, field graphql.CollectedField, obj *model.Video) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Video_size(ctx, field) if err != nil { return graphql.Null } @@ -1248,36 +1709,31 @@ func (ec *executionContext) _User_reviews(ctx context.Context, field graphql.Col }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.User().Reviews(rctx, obj) + return obj.Size, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.([]*model.Review) + res := resTmp.(float64) fc.Result = res - return ec.marshalOReview2ᚕᚖgithubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋreviewsᚋgraphᚋmodelᚐReview(ctx, field.Selections, res) + return ec.marshalNFloat2float64(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_User_reviews(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Video_size(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "User", + Object: "Video", Field: field, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "body": - return ec.fieldContext_Review_body(ctx, field) - case "author": - return ec.fieldContext_Review_author(ctx, field) - case "product": - return ec.fieldContext_Review_product(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type Review", field.Name) + return nil, errors.New("field of type Float does not have child fields") }, } return fc, nil @@ -3101,6 +3557,59 @@ func (ec *executionContext) fieldContext___Type_specifiedByURL(ctx context.Conte // region ************************** interface.gotpl *************************** +func (ec *executionContext) _Attachment(ctx context.Context, sel ast.SelectionSet, obj model.Attachment) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case model.Question: + return ec._Question(ctx, sel, &obj) + case *model.Question: + if obj == nil { + return graphql.Null + } + return ec._Question(ctx, sel, obj) + case model.Rating: + return ec._Rating(ctx, sel, &obj) + case *model.Rating: + if obj == nil { + return graphql.Null + } + return ec._Rating(ctx, sel, obj) + case model.Video: + return ec._Video(ctx, sel, &obj) + case *model.Video: + if obj == nil { + return graphql.Null + } + return ec._Video(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + +func (ec *executionContext) _Comment(ctx context.Context, sel ast.SelectionSet, obj model.Comment) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case model.Question: + return ec._Question(ctx, sel, &obj) + case *model.Question: + if obj == nil { + return graphql.Null + } + return ec._Question(ctx, sel, obj) + case model.Rating: + return ec._Rating(ctx, sel, &obj) + case *model.Rating: + if obj == nil { + return graphql.Null + } + return ec._Rating(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + func (ec *executionContext) __Entity(ctx context.Context, sel ast.SelectionSet, obj fedruntime.Entity) graphql.Marshaler { switch obj := (obj).(type) { case nil: @@ -3376,6 +3885,83 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return out } +var questionImplementors = []string{"Question", "Comment", "Attachment"} + +func (ec *executionContext) _Question(ctx context.Context, sel ast.SelectionSet, obj *model.Question) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, questionImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Question") + case "upc": + + out.Values[i] = ec._Question_upc(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "body": + + out.Values[i] = ec._Question_body(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var ratingImplementors = []string{"Rating", "Comment", "Attachment"} + +func (ec *executionContext) _Rating(ctx context.Context, sel ast.SelectionSet, obj *model.Rating) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, ratingImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Rating") + case "upc": + + out.Values[i] = ec._Rating_upc(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "body": + + out.Values[i] = ec._Rating_body(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "score": + + out.Values[i] = ec._Rating_score(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var reviewImplementors = []string{"Review"} func (ec *executionContext) _Review(ctx context.Context, sel ast.SelectionSet, obj *model.Review) graphql.Marshaler { @@ -3391,22 +3977,39 @@ func (ec *executionContext) _Review(ctx context.Context, sel ast.SelectionSet, o out.Values[i] = ec._Review_body(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "author": out.Values[i] = ec._Review_author(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "product": out.Values[i] = ec._Review_product(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) + } + case "attachments": + field := field + + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Review_attachments(ctx, field, obj) + return res } + + out.Concurrently(i, func() graphql.Marshaler { + return innerFunc(ctx) + + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -3483,6 +4086,41 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj return out } +var videoImplementors = []string{"Video", "Attachment"} + +func (ec *executionContext) _Video(ctx context.Context, sel ast.SelectionSet, obj *model.Video) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, videoImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Video") + case "upc": + + out.Values[i] = ec._Video_upc(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "size": + + out.Values[i] = ec._Video_size(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var _ServiceImplementors = []string{"_Service"} func (ec *executionContext) __Service(ctx context.Context, sel ast.SelectionSet, obj *fedruntime.Service) graphql.Marshaler { @@ -3841,6 +4479,21 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v interface{}) (float64, error) { + res, err := graphql.UnmarshalFloatContext(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNFloat2float64(ctx context.Context, sel ast.SelectionSet, v float64) graphql.Marshaler { + res := graphql.MarshalFloatContext(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return graphql.WrapContextMarshaler(ctx, res) +} + func (ec *executionContext) unmarshalNID2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalID(v) return res, graphql.ErrorOnPath(ctx, err) @@ -3856,6 +4509,21 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec return res } +func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) { + res, err := graphql.UnmarshalInt(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.SelectionSet, v int) graphql.Marshaler { + res := graphql.MarshalInt(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + func (ec *executionContext) marshalNProduct2githubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋreviewsᚋgraphᚋmodelᚐProduct(ctx context.Context, sel ast.SelectionSet, v model.Product) graphql.Marshaler { return ec._Product(ctx, sel, &v) } @@ -4276,6 +4944,54 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a return res } +func (ec *executionContext) marshalOAttachment2githubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋreviewsᚋgraphᚋmodelᚐAttachment(ctx context.Context, sel ast.SelectionSet, v model.Attachment) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Attachment(ctx, sel, v) +} + +func (ec *executionContext) marshalOAttachment2ᚕgithubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋreviewsᚋgraphᚋmodelᚐAttachment(ctx context.Context, sel ast.SelectionSet, v []model.Attachment) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalOAttachment2githubᚗcomᚋwundergraphᚋgraphqlᚑgoᚑtoolsᚋpkgᚋtestingᚋfederationtestingᚋreviewsᚋgraphᚋmodelᚐAttachment(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + return ret +} + func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v interface{}) (bool, error) { res, err := graphql.UnmarshalBoolean(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/pkg/testing/federationtesting/reviews/graph/model/models_gen.go b/pkg/testing/federationtesting/reviews/graph/model/models_gen.go index 8e0d251dce..9bdad47300 100644 --- a/pkg/testing/federationtesting/reviews/graph/model/models_gen.go +++ b/pkg/testing/federationtesting/reviews/graph/model/models_gen.go @@ -1,3 +1,43 @@ // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. package model + +type Attachment interface { + IsAttachment() +} + +type Comment interface { + IsComment() + GetUpc() string + GetBody() string +} + +type Question struct { + Upc string `json:"upc"` + Body string `json:"body"` +} + +func (Question) IsComment() {} +func (this Question) GetUpc() string { return this.Upc } +func (this Question) GetBody() string { return this.Body } + +func (Question) IsAttachment() {} + +type Rating struct { + Upc string `json:"upc"` + Body string `json:"body"` + Score int `json:"score"` +} + +func (Rating) IsComment() {} +func (this Rating) GetUpc() string { return this.Upc } +func (this Rating) GetBody() string { return this.Body } + +func (Rating) IsAttachment() {} + +type Video struct { + Upc string `json:"upc"` + Size float64 `json:"size"` +} + +func (Video) IsAttachment() {} diff --git a/pkg/testing/federationtesting/reviews/graph/schema.graphqls b/pkg/testing/federationtesting/reviews/graph/schema.graphqls index e77fb17753..1aacccf290 100644 --- a/pkg/testing/federationtesting/reviews/graph/schema.graphqls +++ b/pkg/testing/federationtesting/reviews/graph/schema.graphqls @@ -1,9 +1,33 @@ +interface Comment { + upc: String! + body: String! +} + type Review { body: String! author: User! @provides(fields: "username") product: Product! + attachments: [Attachment] +} + +type Question implements Comment { + upc: String! + body: String! } +type Rating implements Comment { + upc: String! + body: String! + score: Int! +} + +type Video { + upc: String! + size: Float! +} + +union Attachment = Question | Rating | Video + extend type User @key(fields: "id") { id: ID! @external username: String! @external diff --git a/pkg/testing/federationtesting/reviews/graph/schema.resolvers.go b/pkg/testing/federationtesting/reviews/graph/schema.resolvers.go index cc7a93d77b..206c168bae 100644 --- a/pkg/testing/federationtesting/reviews/graph/schema.resolvers.go +++ b/pkg/testing/federationtesting/reviews/graph/schema.resolvers.go @@ -2,6 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.22 import ( "context" @@ -37,6 +38,30 @@ func (r *productResolver) Reviews(ctx context.Context, obj *model.Product) ([]*m return res, nil } +// Attachments is the resolver for the attachments field. +func (r *reviewResolver) Attachments(ctx context.Context, obj *model.Review) ([]model.Attachment, error) { + var res []model.Attachment + + for _, attachment := range attachments { + switch v := attachment.(type) { + case model.Question: + if v.Upc == obj.Product.Upc { + res = append(res, attachment) + } + case model.Rating: + if v.Upc == obj.Product.Upc { + res = append(res, attachment) + } + case model.Video: + if v.Upc == obj.Product.Upc { + res = append(res, attachment) + } + } + } + + return res, nil +} + // Username is the resolver for the username field. func (r *userResolver) Username(ctx context.Context, obj *model.User) (string, error) { username := fmt.Sprintf("User %s", obj.ID) @@ -65,9 +90,13 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol // Product returns generated.ProductResolver implementation. func (r *Resolver) Product() generated.ProductResolver { return &productResolver{r} } +// Review returns generated.ReviewResolver implementation. +func (r *Resolver) Review() generated.ReviewResolver { return &reviewResolver{r} } + // User returns generated.UserResolver implementation. func (r *Resolver) User() generated.UserResolver { return &userResolver{r} } type mutationResolver struct{ *Resolver } type productResolver struct{ *Resolver } +type reviewResolver struct{ *Resolver } type userResolver struct{ *Resolver } diff --git a/pkg/testing/federationtesting/testdata/queries/complex_nesting.graphql b/pkg/testing/federationtesting/testdata/queries/complex_nesting.graphql new file mode 100644 index 0000000000..771f8ebba0 --- /dev/null +++ b/pkg/testing/federationtesting/testdata/queries/complex_nesting.graphql @@ -0,0 +1,41 @@ +query ComplexNesting { + me { + id + username + history { + ... on Store { + location + } + ... on Purchase { + wallet { + currency + } + } + ... on Sale { + location + product { + upc + name + } + } + } + reviews { + __typename + attachments { + ... on Comment { + upc + ... on Rating { + body + } + } + ... on Question { + body + } + ... on Video { + upc + size + } + } + } + } +} \ No newline at end of file diff --git a/pkg/testing/federationtesting/testdata/queries/interface_fragment_on_object.graphql b/pkg/testing/federationtesting/testdata/queries/interface_fragment_on_object.graphql new file mode 100644 index 0000000000..80aa738993 --- /dev/null +++ b/pkg/testing/federationtesting/testdata/queries/interface_fragment_on_object.graphql @@ -0,0 +1,10 @@ +query InterfaceFragment { + me { + ... on Identifiable { + id + } + ... on User { + username + } + } +} \ No newline at end of file diff --git a/pkg/testing/federationtesting/testdata/queries/interface_fragments_on_union.graphql b/pkg/testing/federationtesting/testdata/queries/interface_fragments_on_union.graphql new file mode 100644 index 0000000000..84eba5a472 --- /dev/null +++ b/pkg/testing/federationtesting/testdata/queries/interface_fragments_on_union.graphql @@ -0,0 +1,12 @@ +query InterfaceFragmentsOnUnion { + histories { + ... on Store { + __typename + location + } + ... on Info { + __typename + quantity + } + } +} \ No newline at end of file diff --git a/pkg/testing/federationtesting/testdata/queries/object_fragment_on_interface.graphql b/pkg/testing/federationtesting/testdata/queries/object_fragment_on_interface.graphql new file mode 100644 index 0000000000..dc08d01a99 --- /dev/null +++ b/pkg/testing/federationtesting/testdata/queries/object_fragment_on_interface.graphql @@ -0,0 +1,9 @@ +query InterfaceResponse { + identifiable { + __typename + id + ... on User { + username + } + } +} \ No newline at end of file