@@ -223,6 +223,7 @@ type _executionTestOptions struct {
223223 propagateFetchReasons bool
224224 validateRequiredExternalFields bool
225225 computeStaticCost bool
226+ relaxFieldSelectionMergingNullability bool
226227}
227228
228229type executionTestOptions func (* _executionTestOptions )
@@ -253,6 +254,12 @@ func computeStaticCost() executionTestOptions {
253254 }
254255}
255256
257+ func relaxFieldSelectionMergingNullability () executionTestOptions {
258+ return func (options * _executionTestOptions ) {
259+ options .relaxFieldSelectionMergingNullability = true
260+ }
261+ }
262+
256263func TestExecutionEngine_Execute (t * testing.T ) {
257264 run := func (testCase ExecutionEngineTestCase , withError bool , expectedErrorMessage string , options ... executionTestOptions ) func (t * testing.T ) {
258265 t .Helper ()
@@ -289,6 +296,7 @@ func TestExecutionEngine_Execute(t *testing.T) {
289296 engineConf .plannerConfig .ValidateRequiredExternalFields = opts .validateRequiredExternalFields
290297 engineConf .plannerConfig .ComputeStaticCost = opts .computeStaticCost
291298 engineConf .plannerConfig .StaticCostDefaultListSize = 10
299+ engineConf .plannerConfig .RelaxSubgraphOperationFieldSelectionMergingNullability = opts .relaxFieldSelectionMergingNullability
292300 resolveOpts := resolve.ResolverOptions {
293301 MaxConcurrency : 1024 ,
294302 ResolvableOptions : opts .resolvableOptions ,
@@ -321,7 +329,7 @@ func TestExecutionEngine_Execute(t *testing.T) {
321329 if withError {
322330 require .Error (t , err )
323331 if expectedErrorMessage != "" {
324- assert .Contains (t , err .Error (), expectedErrorMessage )
332+ assert .Equal (t , expectedErrorMessage , err .Error ())
325333 }
326334 } else {
327335 require .NoError (t , err )
@@ -6812,6 +6820,134 @@ func TestExecutionEngine_Execute(t *testing.T) {
68126820 })
68136821
68146822 })
6823+
6824+ t .Run ("field merging with different nullability on non-overlapping union types" , func (t * testing.T ) {
6825+ unionSchema := `
6826+ union Entity = User | Organization
6827+ type Query { entity: Entity }
6828+ type User { id: ID!, email: String! }
6829+ type Organization { id: ID!, email: String }
6830+ `
6831+ schema , err := graphql .NewSchemaFromString (unionSchema )
6832+ require .NoError (t , err )
6833+
6834+ rootNodes := []plan.TypeField {
6835+ {TypeName : "Query" , FieldNames : []string {"entity" }},
6836+ {TypeName : "User" , FieldNames : []string {"id" , "email" }},
6837+ {TypeName : "Organization" , FieldNames : []string {"id" , "email" }},
6838+ }
6839+
6840+ customConfig := mustConfiguration (t , graphql_datasource.ConfigurationInput {
6841+ Fetch : & graphql_datasource.FetchConfiguration {
6842+ URL : "https://example.com/" ,
6843+ Method : "POST" ,
6844+ },
6845+ SchemaConfiguration : mustSchemaConfig (t , nil , unionSchema ),
6846+ })
6847+
6848+ fieldConfig := []plan.FieldConfiguration {
6849+ {
6850+ TypeName : "Query" ,
6851+ FieldName : "entity" ,
6852+ Path : []string {"entity" },
6853+ },
6854+ }
6855+
6856+ t .Run ("without relaxation flag, validation rejects differing nullability" , runWithAndCompareError (
6857+ ExecutionEngineTestCase {
6858+ schema : schema ,
6859+ operation : func (t * testing.T ) graphql.Request {
6860+ return graphql.Request {
6861+ OperationName : "O" ,
6862+ Query : `query O { entity { ... on User { email } ... on Organization { email } } }` ,
6863+ }
6864+ },
6865+ dataSources : []plan.DataSource {
6866+ mustGraphqlDataSourceConfiguration (t , "ds-id" ,
6867+ mustFactory (t ,
6868+ testNetHttpClient (t , roundTripperTestCase {
6869+ expectedHost : "example.com" ,
6870+ expectedPath : "/" ,
6871+ expectedBody : "" ,
6872+ sendResponseBody : `{"data":{"entity":{"__typename":"User","email":"user@test.com"}}}` ,
6873+ sendStatusCode : 200 ,
6874+ }),
6875+ ),
6876+ & plan.DataSourceMetadata {
6877+ RootNodes : rootNodes ,
6878+ },
6879+ customConfig ,
6880+ ),
6881+ },
6882+ fields : fieldConfig ,
6883+ },
6884+ `fields 'email' conflict because they return conflicting types 'String!' and 'String', locations: [], path: [query,entity,$1Organization]` ,
6885+ ))
6886+
6887+ t .Run ("non-null email from User type" , runWithoutError (
6888+ ExecutionEngineTestCase {
6889+ schema : schema ,
6890+ operation : func (t * testing.T ) graphql.Request {
6891+ return graphql.Request {
6892+ OperationName : "O" ,
6893+ Query : `query O { entity { ... on User { email } ... on Organization { email } } }` ,
6894+ }
6895+ },
6896+ dataSources : []plan.DataSource {
6897+ mustGraphqlDataSourceConfiguration (t , "ds-id" ,
6898+ mustFactory (t ,
6899+ testNetHttpClient (t , roundTripperTestCase {
6900+ expectedHost : "example.com" ,
6901+ expectedPath : "/" ,
6902+ expectedBody : "" ,
6903+ sendResponseBody : `{"data":{"entity":{"__typename":"User","email":"user@test.com"}}}` ,
6904+ sendStatusCode : 200 ,
6905+ }),
6906+ ),
6907+ & plan.DataSourceMetadata {
6908+ RootNodes : rootNodes ,
6909+ },
6910+ customConfig ,
6911+ ),
6912+ },
6913+ fields : fieldConfig ,
6914+ expectedResponse : `{"data":{"entity":{"email":"user@test.com"}}}` ,
6915+ },
6916+ relaxFieldSelectionMergingNullability (),
6917+ ))
6918+
6919+ t .Run ("null email from Organization type" , runWithoutError (
6920+ ExecutionEngineTestCase {
6921+ schema : schema ,
6922+ operation : func (t * testing.T ) graphql.Request {
6923+ return graphql.Request {
6924+ OperationName : "O" ,
6925+ Query : `query O { entity { ... on User { email } ... on Organization { email } } }` ,
6926+ }
6927+ },
6928+ dataSources : []plan.DataSource {
6929+ mustGraphqlDataSourceConfiguration (t , "ds-id" ,
6930+ mustFactory (t ,
6931+ testNetHttpClient (t , roundTripperTestCase {
6932+ expectedHost : "example.com" ,
6933+ expectedPath : "/" ,
6934+ expectedBody : "" ,
6935+ sendResponseBody : `{"data":{"entity":{"__typename":"Organization","email":null}}}` ,
6936+ sendStatusCode : 200 ,
6937+ }),
6938+ ),
6939+ & plan.DataSourceMetadata {
6940+ RootNodes : rootNodes ,
6941+ },
6942+ customConfig ,
6943+ ),
6944+ },
6945+ fields : fieldConfig ,
6946+ expectedResponse : `{"data":{"entity":{"email":null}}}` ,
6947+ },
6948+ relaxFieldSelectionMergingNullability (),
6949+ ))
6950+ })
68156951}
68166952
68176953func testNetHttpClient (t * testing.T , testCase roundTripperTestCase ) * http.Client {
0 commit comments