Skip to content

Support for resolving exceptions from a subscription #398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

Munoon
Copy link
Contributor

@Munoon Munoon commented May 29, 2022

Closes gh-397

@pivotal-cla
Copy link

@Munoon Please sign the Contributor License Agreement!

Click here to manually synchronize the status of this Pull Request.

See the FAQ for frequently asked questions.

@pivotal-cla
Copy link

@Munoon Thank you for signing the Contributor License Agreement!

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 29, 2022
Copy link
Contributor

@rstoyanchev rstoyanchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the proposed changes.

My first thought was, there is probably no need for a dedicated contract. We could just expose an additional method on WebSocketGraphQlInterceptor.

However, a dedicated contract could work better, if it was supported for all transports. As it is right now, it is coupled to each transport handler which is what subscribes to the Publisher.

One idea would be to intercept and transform the ExecutionResult in DefaultExecutionGraphQlService, so that if the data is a Publisher, it is composed with Flux.onErrorResume to invoke the SubscriptionExceptionResolver and return something like a SubscriptionHandlingException with the List<GraphQLError>. Each transport handler can then handle this exception and turn it into the appropriate "error" message for the given transport.

Would you like to try and rework this? If not I can also take it from here.

@rstoyanchev rstoyanchev self-assigned this Jun 29, 2022
@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jun 29, 2022
@rstoyanchev rstoyanchev added this to the 1.0.1 milestone Jun 29, 2022
@rstoyanchev rstoyanchev changed the title Support subscription exception mapping Support for resolving exceptions from a subscription Jun 29, 2022
@Munoon
Copy link
Contributor Author

Munoon commented Jun 29, 2022

I will gladly make changes. 🙂

However, I'm not sure if I understand you fully correct. As I understand, I should remove calls to SubscriptionExceptionResolver in GraphQlWebSocketHandler's and move these calls to DefaultExecutionGraphQlService, right?

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jun 30, 2022

Yes your understanding is correct, but on further thought, DefaultExecutionGraphQlService is maybe not the right place. I think it would make sense to configure SubscriptionExceptionResolver via GraphQlSource.Builder just like DataFetcherExceptionResolver, but that means there is no easy way to pass it into DefaultExecutionGraphQlService, not without changing the GraphQlSource contract and it wouldn't make sense to change it for this.

That means a SubscriptionExceptionResolver chain has to be plugged in one way or another while building the GraphQLSchema and graphql.GraphQL. I think we can use ContextDataFetcherDecorator. It already decorates data fetchers, and is aware of subscriptions. All we need to so is pass SubscriptionExceptionResolver components to it and have them invoked via onErrorResume.

@Munoon
Copy link
Contributor Author

Munoon commented Jun 30, 2022

Fine, @rstoyanchev, everything is done! Please, review.

@Munoon Munoon requested a review from rstoyanchev July 5, 2022 18:45
Copy link
Contributor

@rstoyanchev rstoyanchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much! Overall headed in the right direction. Some comments below for another iteration.

private Publisher<?> interceptSubscriptionPublisherWithExceptionHandler(Publisher<?> publisher) {
Function<? super Throwable, Mono<DataFetcherResult<?>>> onErrorResumeFunction = e ->
subscriptionExceptionResolver.resolveException(e)
.map(errors -> DataFetcherResult.newResult().errors(errors).build());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how this even works, since it would emit a DataFetcherResult while the transport expects the Publisher to emit ExecutionResult instances.

I think this should return a Mono.error with a new exception (e.g. SubscriptionStreamException) that wraps and exposes the List<GraphQLError> through a getter. This would force the transport to terminate in the very least, but should actually handle this exception to extract the errors and create an error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just added a handler of SubscriptionStreamException in GraphQlWebSocketHandler (both webflux and webmvc). Should I add it somewhere else?

@Munoon Munoon requested a review from rstoyanchev July 6, 2022 18:46
Copy link
Contributor

@rstoyanchev rstoyanchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good!

rstoyanchev pushed a commit that referenced this pull request Jul 19, 2022
rstoyanchev added a commit that referenced this pull request Jul 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow resolving exceptions from a subscription
4 participants