diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt index 726ff108d..17c363b95 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt @@ -4,12 +4,13 @@ import graphql.ExecutionInput import graphql.GraphQL import graphql.GraphqlErrorHelper.toSpecification import graphql.language.AstPrinter +import graphql.language.OperationDefinition import graphql.nadel.NadelDefinitionRegistry +import graphql.nadel.NadelServiceExecutionResultImpl import graphql.nadel.Service import graphql.nadel.ServiceExecution import graphql.nadel.ServiceExecutionParameters import graphql.nadel.ServiceExecutionResult -import graphql.nadel.NadelServiceExecutionResultImpl import graphql.nadel.engine.util.makeFieldCoordinates import graphql.nadel.engine.util.toBuilder import graphql.nadel.engine.util.toBuilderWithoutTypes @@ -40,6 +41,9 @@ open class NadelDefaultIntrospectionRunner(schema: GraphQLSchema) : ServiceExecu .build() override fun execute(serviceExecutionParameters: ServiceExecutionParameters): CompletableFuture { + if (serviceExecutionParameters.operationDefinition.operation == OperationDefinition.Operation.SUBSCRIPTION) { + return CompletableFuture.completedFuture(NadelServiceExecutionResultImpl()) + } return graphQL .executeAsync( ExecutionInput.newExecutionInput() diff --git a/test/src/test/kotlin/graphql/nadel/tests/hooks/remove-fields.kt b/test/src/test/kotlin/graphql/nadel/tests/hooks/remove-fields.kt index bb8a2132b..1b319f4df 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/hooks/remove-fields.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/hooks/remove-fields.kt @@ -101,6 +101,18 @@ class `top-level-field-is-removed` : EngineTestHook { override fun makeExecutionHints(builder: NadelExecutionHints.Builder) = builder.shortCircuitEmptyQuery { true } } +@UseHook +class `top-level-field-is-removed-for-a-subscription` : EngineTestHook { + override val customTransforms = listOf(RemoveFieldTestTransform()) + override fun makeExecutionHints(builder: NadelExecutionHints.Builder) = builder.shortCircuitEmptyQuery { true } +} + +@UseHook +class `top-level-field-is-removed-for-a-subscription-with-namespaced-field` : EngineTestHook { + override val customTransforms = listOf(RemoveFieldTestTransform()) + override fun makeExecutionHints(builder: NadelExecutionHints.Builder) = builder.shortCircuitEmptyQuery { true } +} + @UseHook class `top-level-field-is-removed-hint-is-off` : EngineTestHook { override val customTransforms = listOf(RemoveFieldTestTransform()) diff --git a/test/src/test/kotlin/graphql/nadel/tests/transforms/RemoveFieldTestTransform.kt b/test/src/test/kotlin/graphql/nadel/tests/transforms/RemoveFieldTestTransform.kt index 36a3f32d8..e83ec3392 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/transforms/RemoveFieldTestTransform.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/transforms/RemoveFieldTestTransform.kt @@ -1,5 +1,6 @@ package graphql.nadel.tests.transforms +import graphql.ErrorType import graphql.GraphQLError import graphql.introspection.Introspection import graphql.nadel.Service @@ -15,11 +16,10 @@ import graphql.nadel.engine.transform.query.NadelQueryTransformer import graphql.nadel.engine.transform.result.NadelResultInstruction import graphql.nadel.engine.transform.result.NadelResultKey import graphql.nadel.engine.transform.result.json.JsonNodes +import graphql.nadel.engine.util.newGraphQLError import graphql.nadel.engine.util.queryPath import graphql.normalized.ExecutableNormalizedField import graphql.schema.GraphQLObjectType -import graphql.validation.ValidationError.newValidationError -import graphql.validation.ValidationErrorType class RemoveFieldTestTransform : NadelTransform { override suspend fun isApplicable( @@ -40,7 +40,10 @@ class RemoveFieldTestTransform : NadelTransform { ?: return null if (objectType.getField(overallField.name)?.getDirective("toBeDeleted") != null) { - return newValidationError().validationErrorType(ValidationErrorType.WrongType).build() + return newGraphQLError( + "field `${objectType.name}.${overallField.name}` has been removed by RemoveFieldTestTransform", + ErrorType.DataFetchingException, + ) } return null diff --git a/test/src/test/resources/fixtures/chained transforms/two-transforms-on-a-field.yml b/test/src/test/resources/fixtures/chained transforms/two-transforms-on-a-field.yml index 43c36354e..e8194329e 100644 --- a/test/src/test/resources/fixtures/chained transforms/two-transforms-on-a-field.yml +++ b/test/src/test/resources/fixtures/chained transforms/two-transforms-on-a-field.yml @@ -77,12 +77,11 @@ response: |- }, "errors": [ { - "message": "null", + "message": "field `Foo.epicEntity` has been removed by RemoveFieldTestTransform", "locations": [], "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "extensions": {} + ] } diff --git a/test/src/test/resources/fixtures/field removed/hidden-namespaced-hydration-top-level-field-is-removed.yml b/test/src/test/resources/fixtures/field removed/hidden-namespaced-hydration-top-level-field-is-removed.yml index ad1f0847a..398643e7a 100644 --- a/test/src/test/resources/fixtures/field removed/hidden-namespaced-hydration-top-level-field-is-removed.yml +++ b/test/src/test/resources/fixtures/field removed/hidden-namespaced-hydration-top-level-field-is-removed.yml @@ -91,19 +91,19 @@ serviceCalls: # language=JSON response: |- { + "data": { + "issueById": { + "id": "C1", + "comment": null + } + }, "errors": [ { + "message": "field `CommentApi.commentById` has been removed by RemoveFieldTestTransform", "locations": [], - "message": "An error has occurred", "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "data": { - "issueById": { - "id": "C1", - "comment": null - } - } + ] } diff --git a/test/src/test/resources/fixtures/field removed/hydration-top-level-field-is-removed.yml b/test/src/test/resources/fixtures/field removed/hydration-top-level-field-is-removed.yml index 3a9957ed6..cb19124ee 100644 --- a/test/src/test/resources/fixtures/field removed/hydration-top-level-field-is-removed.yml +++ b/test/src/test/resources/fixtures/field removed/hydration-top-level-field-is-removed.yml @@ -80,19 +80,19 @@ serviceCalls: # language=JSON response: |- { + "data": { + "issueById": { + "id": "C1", + "comment": null + } + }, "errors": [ { + "message": "field `Query.commentById` has been removed by RemoveFieldTestTransform", "locations": [], - "message": "An error has occurred", "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "data": { - "issueById": { - "id": "C1", - "comment": null - } - } + ] } diff --git a/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed-with-renames.yml b/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed-with-renames.yml index 534314429..46d1b8033 100644 --- a/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed-with-renames.yml +++ b/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed-with-renames.yml @@ -40,18 +40,18 @@ serviceCalls: [ ] # language=JSON response: |- { + "data": { + "commentApi": { + "commentById": null + } + }, "errors": [ { + "message": "field `CommentApi.commentById` has been removed by RemoveFieldTestTransform", "locations": [], - "message": "null", "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "data": { - "commentApi": { - "commentById": null - } - } + ] } diff --git a/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed.yml b/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed.yml index 40d5804cf..de75272fe 100644 --- a/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed.yml +++ b/test/src/test/resources/fixtures/field removed/namespaced-field-is-removed.yml @@ -40,18 +40,18 @@ serviceCalls: [ ] # language=JSON response: |- { + "data": { + "commentApi": { + "commentById": null + } + }, "errors": [ { + "message": "field `CommentApi.commentById` has been removed by RemoveFieldTestTransform", "locations": [], - "message": "null", "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "data": { - "commentApi": { - "commentById": null - } - } + ] } diff --git a/test/src/test/resources/fixtures/field removed/namespaced-hydration-top-level-field-is-removed.yml b/test/src/test/resources/fixtures/field removed/namespaced-hydration-top-level-field-is-removed.yml index d1470c0ae..8d52776fc 100644 --- a/test/src/test/resources/fixtures/field removed/namespaced-hydration-top-level-field-is-removed.yml +++ b/test/src/test/resources/fixtures/field removed/namespaced-hydration-top-level-field-is-removed.yml @@ -87,19 +87,19 @@ serviceCalls: # language=JSON response: |- { + "data": { + "issueById": { + "id": "C1", + "comment": null + } + }, "errors": [ { + "message": "field `CommentApi.commentById` has been removed by RemoveFieldTestTransform", "locations": [], - "message": "An error has occurred", "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "data": { - "issueById": { - "id": "C1", - "comment": null - } - } + ] } diff --git a/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-for-a-subscription-with-namespaced-field.yml b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-for-a-subscription-with-namespaced-field.yml new file mode 100644 index 000000000..98306cc36 --- /dev/null +++ b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-for-a-subscription-with-namespaced-field.yml @@ -0,0 +1,60 @@ +name: "top level field is removed for a subscription with namespaced field" +enabled: true +# language=GraphQL +overallSchema: + CommentService: | + directive @toBeDeleted on FIELD_DEFINITION + type Query { + commentById(id: ID): Comment @toBeDeleted + } + type Subscription { + commentsApi: CommentsApi @namespaced + } + type CommentsApi { + onCommentUpdated(id: ID): Comment @toBeDeleted + } + type Comment { + id: ID + } +# language=GraphQL +underlyingSchema: + CommentService: | + type Query { + commentById(id: ID): Comment + } + type Subscription { + commentsApi: CommentsApi + } + type CommentsApi { + onCommentUpdated(id: ID): Comment + } + type Comment { + id: ID + } +# language=GraphQL +query: | + subscription { + commentsApi { + onCommentUpdated(id: "C1") { + id + } + } + } +variables: { } +serviceCalls: [ ] +# language=JSON +response: |- + { + "data": { + "commentsApi": null + }, + "errors": [ + { + "message": "field `CommentsApi.onCommentUpdated` has been removed by RemoveFieldTestTransform", + "locations": [], + "extensions": { + "classification": "DataFetchingException" + } + } + ] + } diff --git a/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-for-a-subscription.yml b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-for-a-subscription.yml new file mode 100644 index 000000000..ebb1ee94f --- /dev/null +++ b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-for-a-subscription.yml @@ -0,0 +1,52 @@ +name: "top level field is removed for a subscription" +enabled: true +# language=GraphQL +overallSchema: + CommentService: | + directive @toBeDeleted on FIELD_DEFINITION + type Query { + commentById(id: ID): Comment + } + type Subscription { + onCommentUpdated(id: ID): Comment @toBeDeleted + } + type Comment { + id: ID + } +# language=GraphQL +underlyingSchema: + CommentService: | + type Query { + commentById(id: ID): Comment + } + type Subscription { + onCommentUpdated(id: ID): Comment + } + type Comment { + id: ID + } +# language=GraphQL +query: | + subscription { + onCommentUpdated(id: "C1") { + id + } + } +variables: { } +serviceCalls: [ ] +# language=JSON +response: |- + { + "data": { + "onCommentUpdated": null + }, + "errors": [ + { + "message": "field `Subscription.onCommentUpdated` has been removed by RemoveFieldTestTransform", + "locations": [], + "extensions": { + "classification": "DataFetchingException" + } + } + ] + } diff --git a/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-hint-is-off.yml b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-hint-is-off.yml index f61962199..3b58fdb9d 100644 --- a/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-hint-is-off.yml +++ b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed-hint-is-off.yml @@ -47,16 +47,16 @@ serviceCalls: # language=JSON response: |- { + "data": { + "commentById": null + }, "errors": [ { + "message": "field `Query.commentById` has been removed by RemoveFieldTestTransform", "locations": [], - "message": "null", "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "data": { - "commentById": null - } + ] } diff --git a/test/src/test/resources/fixtures/field removed/top-level-field-is-removed.yml b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed.yml index 70deed0ab..a9f5f1c7d 100644 --- a/test/src/test/resources/fixtures/field removed/top-level-field-is-removed.yml +++ b/test/src/test/resources/fixtures/field removed/top-level-field-is-removed.yml @@ -31,16 +31,16 @@ serviceCalls: [ ] # language=JSON response: |- { + "data": { + "commentById": null + }, "errors": [ { + "message": "field `Query.commentById` has been removed by RemoveFieldTestTransform", "locations": [], - "message": "null", "extensions": { - "classification": "ValidationError" + "classification": "DataFetchingException" } } - ], - "data": { - "commentById": null - } + ] } diff --git a/test/src/test/resources/fixtures/introspection/no-introspections-on-subscriptions.yml b/test/src/test/resources/fixtures/introspection/no-introspections-on-subscriptions.yml new file mode 100644 index 000000000..4cbd4046d --- /dev/null +++ b/test/src/test/resources/fixtures/introspection/no-introspections-on-subscriptions.yml @@ -0,0 +1,52 @@ +name: "no introspections on subscriptions" +enabled: true +# language=GraphQL +overallSchema: + MyService: | + type Query { + comment: Comment + } + type Subscription { + onComment: Comment @namespaced + } + type Comment { + id: ID + } +# language=GraphQL +underlyingSchema: + MyService: | + type Query { + comment: Comment + } + type Subscription { + onComment: Comment + } + type Comment { + id: ID + } +# language=GraphQL +query: | + subscription { + __typename + } +variables: { } +serviceCalls: [ ] +# language=JSON +response: |- + { + "data": null, + "errors": [ + { + "message": "Validation error (SubscriptionIntrospectionRootField) : Subscription operation 'null' root field '__typename' cannot be an introspection field", + "locations": [ + { + "line": 2, + "column": 3 + } + ], + "extensions": { + "classification": "ValidationError" + } + } + ] + }