-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add DelegatingDataFetcherExceptionHandler #2355
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
Add DelegatingDataFetcherExceptionHandler #2355
Conversation
Create DelegatingDataFetcherExceptionHandler which accepts a mapping of Predicate to DataFetcherExceptionHandler. The corresponding DataFetcherHandler of the first Predicate to match is used. If no match is found, then the default handler is used.
Allows easily creating a DelegatingDataFetcherExceptionHandler mapping from the type of Exception that was thrown.
| */ | ||
| public void setDefaultHandler(DataFetcherExceptionHandler defaultHandler) { | ||
| this.defaultHandler = assertNotNull(defaultHandler, () -> "defaultHandler can't be null"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dont make this mutable - use a builder pattern or have constructors for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed an update to use a Builder to ensure that DelegatingDataFetcherExceptionHandler is immutable.
| * | ||
| * @return the {@link DelegatingDataFetcherExceptionHandler} to use | ||
| */ | ||
| public static DelegatingDataFetcherExceptionHandler fromThrowableTypeMapping(LinkedHashMap<Class<? extends Throwable>, DataFetcherExceptionHandler> types) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A specific implementation in parameters is strange
LinkedHashMap - It should be just Map
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that it wants to be in order - but I think the caller should decide not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Order matters and LinkedHashMap is the only Map implementation that preserves order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed an update to use a Builder which encapsulates how the mapping and order are preserved. Internally a List<PredicateDelegateMapping> is now used such that PredicateDelegateMapping is a private class with the members Predicate<Throwable> and DataFetcherExceptionHandler.
| return new DelegatingDataFetcherExceptionHandler(handlers); | ||
| } | ||
|
|
||
| private static <M extends Map<K, V>, K, V> M assertNotEmpty(M map, String message) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go in the graphql java Assert class AND use a Supplier for the message - see that code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the code with a commit that moves this to the Assert class as described. However, with the introduction of the Builder which encapsulates the mapping in List<PredicateDelegateMapping> there is no need for it, so I removed it in an additional commit.
|
|
||
| then: 'fail assert on validation' | ||
| thrown AssertException | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find these types of test low value - asserts are important BUT not the key "regression" point in code base.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want me to remove all the tests or just this one or...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed all tests in DelegatingDataFetcherExceptionHandlerTest that are asserting AssertException was thrown
| def actualResult = handler.onException(handlerParameters) | ||
|
|
||
| then: 'only the matching delegate is invoked' | ||
| 0 * _ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure this is testing anything??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think then needs updated to be none of the delegates are invoked. This is verify that if none of the delegates match, then none of them are used (the default should be used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated then description to be accurate.
Previously then was incorrectly stated "only the matching delegate is invoked". However, the assertion correctly validated that "none of the delegates are invoked". The then description is now corrected.
Per the feedback in the code review these tests don't test `key "regression" point in code base`.
Per the review feedback it is preferred to keep this in Assert class rather than in a private method to DelegatingDataFetcherExceptionHandler.
Previously assertNotEmpty does not have a return type that preserves the type passed in. This commit returns the same type as what was asserted. This allows assertion of a List and ensuring the assigning type can still be declared as a List.
This addresses two concerns: The first is the use of LinkedHashMap for the delegates. LinkedHashMap was chosen intentionally because the order matters. However, it is strange to rely on an implementation. The Builder now encapsulates how the mapping is done (using a List and private class named PredicateDelegateMapping). The second concern it addresses is to ensure DelegatingDataFetcherExceptionHandler is immutable allowing defaultHandler to only be set in the Builder.
This is no longer required by any code in spring-graphql.
|
I believe I have addressed all of your comments. I did so in a commit per comment to make it easier to follow along with what I've done. I'm happy to squash and rebase before the merge or preserve the commits. |
|
I think having this packaged as a In addition, depending on how #2356 is implemented, it may change the way this here is done. |
|
@rstoyanchev I do agree that it will depend on the result of gh-2356, but I don't understand your other comments:
Which contract are you referring to?
Why is |
|
Take a scenario with two frameworks having independent This is why I think the ability to have multiple exception handlers should be built into the very contracts of GraphQL Java or otherwise left as it is. |
|
I don't think that the logic of determining when to invoke a For cases where the logic for both the decision and the handling logic should be in the same object the object can implement both I think it's worth pointing out that this is only introducing a contract for a single implementation that should work for a large majority of use cases. It does not prevent additional implementations from having their own semantics. |
|
Right, so exception handlers have to be built with |
|
We intend to merge a "async" exception handler in #2371 . This will affect this code. Please have a look at tell us of any concerns |
No description provided.