Skip to content

Conversation

@mpritham
Copy link
Contributor

Separating #2608 to make it easier to review. This PR should be updated to create separate test files instead of using the existing dialogue test case, but using the existing test case makes it easy to see what concretely changes when the new functionality isused.

@mpritham mpritham changed the base branch from develop to mpritham/exceptions-with-rich-params August 26, 2025 20:57
@mpritham mpritham force-pushed the mpritham/rich-exceptions-tests branch from eaab496 to 6e392f4 Compare August 26, 2025 21:00
@mpritham mpritham changed the title [dnm] Add tests for #2608 Add tests for #2608 Aug 26, 2025
@palantir palantir deleted a comment from changelog-app bot Aug 26, 2025
Comment on lines 70 to 73
public static final class DifferentPackageErrorException extends RemoteException {
private DifferentPackageErrorSerializableError error;
Copy link
Contributor

Choose a reason for hiding this comment

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

RemoteException is serializable, due to being a subclass of Throwable (and contains a serialVersionUid). I'm wondering whether this means we need to make all these exceptions and the AbstractSerializableError also serializable (which would raise the question of how to maintain compatibility upon changes)

I don't think we would be relying on this anywhere, but this might be worth to explicitly acknowledge this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, I'm not sure of the history here, but we use jackson to deserialize some subclass of AbstractSerializableError, and construct this class - it's never deserialized to directly off the wire. I'm not sure that RemoteException is either.

I can acknowledge this in a comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

All Throwables are Serializable, so it's really just a matter of whether we care. It sounds like RemoteException actually has a serialVersionUid. It sounds like it was added form the get-go though palantir/conjure-java-runtime-api#9 and I can't imagine this being used as serializable anywhere.

With that said, it might be worth just adding a default serialVersionUid on the subclasses nonetheless? 🤔 (adding it after the fact is a bit annoying after all)

Comment on lines +43 to +74
.exception(
ConjureErrors.CONFLICTING_CAUSE_SAFE_ARG.name(),
new TypeMarker<ConjureErrors.ConflictingCauseSafeArgSerializableError>() {},
new TypeMarker<ConjureErrors.ConflictingCauseSafeArgException>() {})
.exception(
ConjureErrors.CONFLICTING_CAUSE_UNSAFE_ARG.name(),
new TypeMarker<ConjureErrors.ConflictingCauseUnsafeArgSerializableError>() {},
new TypeMarker<ConjureErrors.ConflictingCauseUnsafeArgException>() {})
.exception(
ConjureErrors.ERROR_WITH_COMPLEX_ARGS.name(),
new TypeMarker<ConjureErrors.ErrorWithComplexArgsSerializableError>() {},
new TypeMarker<ConjureErrors.ErrorWithComplexArgsException>() {})
.exception(
ConjureErrors.INVALID_SERVICE_DEFINITION.name(),
new TypeMarker<ConjureErrors.InvalidServiceDefinitionSerializableError>() {},
new TypeMarker<ConjureErrors.InvalidServiceDefinitionException>() {})
.exception(
ConjureErrors.INVALID_TYPE_DEFINITION.name(),
new TypeMarker<ConjureErrors.InvalidTypeDefinitionSerializableError>() {},
new TypeMarker<ConjureErrors.InvalidTypeDefinitionException>() {})
.exception(
ConjureJavaErrors.JAVA_COMPILATION_FAILED.name(),
new TypeMarker<ConjureJavaErrors.JavaCompilationFailedSerializableError>() {},
new TypeMarker<ConjureJavaErrors.JavaCompilationFailedException>() {})
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this not contain the error from the different package too? It can be thrown by the service that defined it, right?

Comment on lines 39 to 75
private <T> ExceptionDeserializerArgs<T> createExceptionDeserializerArgs(TypeMarker<T> returnType) {
return ExceptionDeserializerArgs.<T>builder()
.returnType(returnType)
.exception(
ConjureErrors.CONFLICTING_CAUSE_SAFE_ARG.name(),
new TypeMarker<ConjureErrors.ConflictingCauseSafeArgSerializableError>() {},
new TypeMarker<ConjureErrors.ConflictingCauseSafeArgException>() {})
.exception(
ConjureErrors.CONFLICTING_CAUSE_UNSAFE_ARG.name(),
new TypeMarker<ConjureErrors.ConflictingCauseUnsafeArgSerializableError>() {},
new TypeMarker<ConjureErrors.ConflictingCauseUnsafeArgException>() {})
.exception(
ConjureErrors.ERROR_WITH_COMPLEX_ARGS.name(),
new TypeMarker<ConjureErrors.ErrorWithComplexArgsSerializableError>() {},
new TypeMarker<ConjureErrors.ErrorWithComplexArgsException>() {})
.exception(
ConjureErrors.INVALID_SERVICE_DEFINITION.name(),
new TypeMarker<ConjureErrors.InvalidServiceDefinitionSerializableError>() {},
new TypeMarker<ConjureErrors.InvalidServiceDefinitionException>() {})
.exception(
ConjureErrors.INVALID_TYPE_DEFINITION.name(),
new TypeMarker<ConjureErrors.InvalidTypeDefinitionSerializableError>() {},
new TypeMarker<ConjureErrors.InvalidTypeDefinitionException>() {})
.exception(
ConjureJavaErrors.JAVA_COMPILATION_FAILED.name(),
new TypeMarker<ConjureJavaErrors.JavaCompilationFailedSerializableError>() {},
new TypeMarker<ConjureJavaErrors.JavaCompilationFailedException>() {})
.build();
}
Copy link
Contributor

@aldexis aldexis Aug 27, 2025

Choose a reason for hiding this comment

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

It's bugging me a bit that we're defining the exact same method in each service, and then calling it to create new deserializer args for each endpoint (which will be the same if they share a return type).
Do you envision this being some kind of problem or maybe instead a benefit down the line? (e.g. whether on memory allocation, since we're copying the same map of possible hundreds of errors on each endpoint, which might be many as well, or our ability to change the code-gen later on)

Maybe it is better this way, because if we need to later have different exceptions deserializable for different services, we don't need to break the ABI by removing a method that creates the generic deserializer. With that said, I'm not sure when we'd want to not support all errors, but only a subset.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One solution to this could be to create a new file that just has this method which all generated services can use. From the comment above regarding adding all errors in the Conjure definitions, we'd have only one helper method definition in this file.

So concretely, I'm thinking of a file ServiceErrorUtils that is generated which has one method createExceptionDeserializerArgs.

If we end up changing how the deserializers are created, I think we shouldn't have an ABI break?

With that said, I'm not sure when we'd want to not support all errors, but only a subset.

I think this is related to the comment above: whether we filter errors on defined in the same package, or just include every error in the Conjure definition provided. I implemented the former, but I'm sort of learning toward the latter to make migration easier. Some folks end up creating errors in a separate errors package.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the changes I want to make here:

  • take out the filtering on package - consider every error defined for every service
  • add a separate utility file for this helper method

With the separate utility file, we will still end up creating loads of maps. One idea is to introduce a cache from returnType -> exceptionDeserializerArgs.

Copy link
Contributor

Choose a reason for hiding this comment

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

We chatted about this yesterday. There unfortunately isn't really a great place to put such a file, since it can't be in a predefined package, to avoid conflicts with other dialogue client libraries. I think it's fine to leave it as one method per client for now, and get rid of it if we realize this causes problems. This is also why we have various safe rollout mechanisms after all.

This (and the fact that we can't deserialize exceptions that aren't known at codegen time) makes me wonder however whether we could instead try to discover exceptions at runtime (e.g. through some autoservice or other mechanism, making it possible to build a centralized map of all exceptions, instead of having each client and deserializer to know about them).

getOptionalBinaryPresentChannel,
_request.build(),
_runtime.bodySerDe().optionalInputStreamDeserializer());
.call(getOptionalBinaryPresentChannel, _request.build(), getOptionalBinaryPresentDeserializer);
Copy link
Contributor

Choose a reason for hiding this comment

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

side-note: the name of this endpoint is so confusing, because I though getOptionalBinaryPresentChannel and getOptionalBinaryPresentDeserializer were methods (e.g. suppliers), when they were in fact fields

Comment on lines 42 to 93
return ExceptionDeserializerArgs.<T>builder()
.returnType(returnType)
.build();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is CookieServiceAsync considered as part of a different conjure project? If not, should it also support deserializing all errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is related to the comments above regarding the filtering around what errors are considered. In the implementation, I added filters so services would be able to handle all errors defined in the same package. After looking at some usages, it's probably safest to include all errors defined - some folks put errors in a separate package com.palantir.x.errors.

Even if they didn't, it's possible, like you said, for a service to throw errors defined in a different package.

// .contains("optional-value");
// });
// e should be an instance of ErrorWithComplexArgsException, which has rich parameters as well
assertThat(e.getCause()).isInstanceOfSatisfying(ErrorWithComplexArgsException.class, exception -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be

Suggested change
assertThat(e.getCause()).isInstanceOfSatisfying(ErrorWithComplexArgsException.class, exception -> {
assertThat(e).isInstanceOfSatisfying(ErrorWithComplexArgsException.class, exception -> {

rather than e.getCause()?

Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed about this yesterday. This is because of the pattern from https://github.com/palantir/dialogue/blob/c17a28c5673cd3988056a4181d38f5f1eae43f59/dialogue-serde/src/main/java/com/palantir/conjure/java/dialogue/serde/DefaultClients.java#L148.

We ought to either replicate this pattern with the new remote exceptions, or instead get rid of it entirely (and do what is done for other types of exceptions)

@mpritham mpritham force-pushed the mpritham/exceptions-with-rich-params branch 2 times, most recently from 05f862d to 76484ec Compare August 28, 2025 20:59
@mpritham mpritham force-pushed the mpritham/rich-exceptions-tests branch from 6e392f4 to ed1a75d Compare August 28, 2025 21:18
@mpritham mpritham force-pushed the mpritham/exceptions-with-rich-params branch from 76484ec to b13b56d Compare August 28, 2025 21:42
@mpritham mpritham force-pushed the mpritham/rich-exceptions-tests branch from ed1a75d to 1e4f594 Compare September 7, 2025 23:26
@mpritham mpritham force-pushed the mpritham/exceptions-with-rich-params branch from 58ed0e4 to 9394834 Compare September 10, 2025 21:43
@mpritham mpritham force-pushed the mpritham/rich-exceptions-tests branch from 1e4f594 to 948b05d Compare September 10, 2025 21:46
Copy link
Contributor

@aldexis aldexis left a comment

Choose a reason for hiding this comment

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

Didn't do a very deep review of the code changes (i.e. I didn't inspect each line, because they seem mostly duplicates of each other), so I could have missed one specific change that is problematic among all the others, but this seems fine to me 👍

@mpritham
Copy link
Contributor Author

I'm going to modify #2608 to create a new test case (instead of setting the flag to an existing test case like I did in this PR). No need to merge this PR into #2608.

@mpritham mpritham force-pushed the mpritham/exceptions-with-rich-params branch 2 times, most recently from 63549f0 to a2135bf Compare September 18, 2025 14:41
@stale
Copy link

stale bot commented Oct 18, 2025

This PR has been automatically marked as stale because it has not been touched in the last 14 days. If you'd like to keep it open, please leave a comment or add the 'long-lived' label, otherwise it'll be closed in 7 days.

@stale stale bot added the stale label Oct 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants