From fe5044dd221883109062092c548ed0a1bac6591c Mon Sep 17 00:00:00 2001 From: Pritham Marupaka Date: Mon, 14 Jul 2025 15:30:23 -0400 Subject: [PATCH 1/4] Add AbstractServiceError --- .../java/api/errors/AbstractServiceError.java | 30 ++++++++ .../api/errors/EndpointServiceException.java | 4 +- .../java/api/errors/ServiceException.java | 4 +- .../conjure/java/api/testing/Assertions.java | 5 ++ .../api/testing/ServiceExceptionAssert.java | 13 ++-- .../java/api/testing/AssertionsTest.java | 2 +- .../testing/ServiceExceptionAssertTest.java | 68 +++++++++++++++++++ 7 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceError.java diff --git a/errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceError.java b/errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceError.java new file mode 100644 index 000000000..f35a06873 --- /dev/null +++ b/errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceError.java @@ -0,0 +1,30 @@ +/* + * (c) Copyright 2025 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.api.errors; + +import com.palantir.logsafe.SafeLoggable; + +/** + * This class should not be used directly. The parent class for ServiceException and EndpointServiceException. + */ +public abstract class AbstractServiceError extends RuntimeException implements SafeLoggable { + public abstract ErrorType getErrorType(); + + protected AbstractServiceError(Throwable cause) { + super(cause); + } +} diff --git a/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java b/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java index d8008c160..108334b45 100644 --- a/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java +++ b/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java @@ -17,7 +17,6 @@ package com.palantir.conjure.java.api.errors; import com.palantir.logsafe.Arg; -import com.palantir.logsafe.SafeLoggable; import java.util.List; import javax.annotation.Nullable; @@ -25,7 +24,7 @@ * This is identical to ServiceException, but is used in Conjure-generated code to indicate that an exception was thrown * from a service endpoint. */ -public abstract class EndpointServiceException extends RuntimeException implements SafeLoggable { +public abstract class EndpointServiceException extends AbstractServiceError { private static final String EXCEPTION_NAME = "EndpointServiceException"; private final ErrorType errorType; private final List> args; // This is an unmodifiable list. @@ -52,6 +51,7 @@ public EndpointServiceException(ErrorType errorType, @Nullable Throwable cause, } /** The {@link ErrorType} that gave rise to this exception. */ + @Override public final ErrorType getErrorType() { return errorType; } diff --git a/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java b/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java index 6b2e985d7..e3479794a 100644 --- a/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java +++ b/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java @@ -17,12 +17,11 @@ package com.palantir.conjure.java.api.errors; import com.palantir.logsafe.Arg; -import com.palantir.logsafe.SafeLoggable; import java.util.List; import javax.annotation.Nullable; /** A {@link ServiceException} thrown in server-side code to indicate server-side {@link ErrorType error states}. */ -public final class ServiceException extends RuntimeException implements SafeLoggable { +public final class ServiceException extends AbstractServiceError { private static final String EXCEPTION_NAME = "ServiceException"; private final ErrorType errorType; private final List> args; // unmodifiable @@ -53,6 +52,7 @@ public ServiceException(ErrorType errorType, @Nullable Throwable cause, Arg.. } /** The {@link ErrorType} that gave rise to this exception. */ + @Override public ErrorType getErrorType() { return errorType; } diff --git a/test-utils/src/main/java/com/palantir/conjure/java/api/testing/Assertions.java b/test-utils/src/main/java/com/palantir/conjure/java/api/testing/Assertions.java index 420500425..4d940c919 100644 --- a/test-utils/src/main/java/com/palantir/conjure/java/api/testing/Assertions.java +++ b/test-utils/src/main/java/com/palantir/conjure/java/api/testing/Assertions.java @@ -16,6 +16,7 @@ package com.palantir.conjure.java.api.testing; +import com.palantir.conjure.java.api.errors.EndpointServiceException; import com.palantir.conjure.java.api.errors.QosException; import com.palantir.conjure.java.api.errors.RemoteException; import com.palantir.conjure.java.api.errors.ServiceException; @@ -32,6 +33,10 @@ public static ServiceExceptionAssert assertThat(ServiceException actual) { return new ServiceExceptionAssert(actual); } + public static ServiceExceptionAssert assertThat(EndpointServiceException actual) { + return new ServiceExceptionAssert(actual); + } + public static RemoteExceptionAssert assertThat(RemoteException actual) { return new RemoteExceptionAssert(actual); } diff --git a/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java b/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java index d2366f28d..5fa525300 100644 --- a/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java +++ b/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java @@ -16,8 +16,8 @@ package com.palantir.conjure.java.api.testing; +import com.palantir.conjure.java.api.errors.AbstractServiceError; import com.palantir.conjure.java.api.errors.ErrorType; -import com.palantir.conjure.java.api.errors.ServiceException; import com.palantir.logsafe.Arg; import java.util.Arrays; import java.util.HashMap; @@ -28,16 +28,17 @@ import org.assertj.core.api.InstanceOfAssertFactory; import org.assertj.core.util.Throwables; -public class ServiceExceptionAssert extends AbstractThrowableAssert { +public class ServiceExceptionAssert extends AbstractThrowableAssert { - private static final InstanceOfAssertFactory INSTANCE_OF_ASSERT_FACTORY = - new InstanceOfAssertFactory<>(ServiceException.class, ServiceExceptionAssert::new); + private static final InstanceOfAssertFactory + INSTANCE_OF_ASSERT_FACTORY = + new InstanceOfAssertFactory<>(AbstractServiceError.class, ServiceExceptionAssert::new); - ServiceExceptionAssert(ServiceException actual) { + ServiceExceptionAssert(AbstractServiceError actual) { super(actual, ServiceExceptionAssert.class); } - public static InstanceOfAssertFactory instanceOfAssertFactory() { + public static InstanceOfAssertFactory instanceOfAssertFactory() { return INSTANCE_OF_ASSERT_FACTORY; } diff --git a/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java b/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java index dc6bca0f1..8f7831514 100644 --- a/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java +++ b/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java @@ -45,7 +45,7 @@ public void testAssertThatServiceExceptionThrownBy_failsIfWrongExceptionThrown() throw new RuntimeException("My message"); })) .hasMessageContaining( - "com.palantir.conjure.java.api.errors.ServiceException", + "com.palantir.conjure.java.api.errors.AbstractServiceError", "java.lang.RuntimeException", "My message"); } diff --git a/test-utils/src/test/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssertTest.java b/test-utils/src/test/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssertTest.java index 964727deb..6483f5abd 100644 --- a/test-utils/src/test/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssertTest.java +++ b/test-utils/src/test/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssertTest.java @@ -18,14 +18,22 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.palantir.conjure.java.api.errors.EndpointServiceException; import com.palantir.conjure.java.api.errors.ErrorType; import com.palantir.conjure.java.api.errors.ServiceException; +import com.palantir.logsafe.Arg; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.UnsafeArg; import org.junit.jupiter.api.Test; public class ServiceExceptionAssertTest { + private static class TestEndpointServiceException extends EndpointServiceException { + TestEndpointServiceException(ErrorType errorType, Arg... safeArgs) { + super(errorType, safeArgs); + } + } + @Test public void testSanity() { ErrorType actualType = ErrorType.FAILED_PRECONDITION; @@ -35,47 +43,92 @@ public void testSanity() { .hasType(actualType) .hasArgs(SafeArg.of("a", "b"), UnsafeArg.of("c", "d")); + Assertions.assertThat( + new TestEndpointServiceException(actualType, SafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) + .hasCode(actualType.code()) + .hasType(actualType) + .hasArgs(SafeArg.of("a", "b"), UnsafeArg.of("c", "d")); + Assertions.assertThat(new ServiceException(actualType, SafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) .hasCode(actualType.code()) .hasType(actualType) .hasArgs(UnsafeArg.of("c", "d"), SafeArg.of("a", "b")); // Order doesn't matter + Assertions.assertThat( + new TestEndpointServiceException(actualType, SafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) + .hasCode(actualType.code()) + .hasType(actualType) + .hasArgs(UnsafeArg.of("c", "d"), SafeArg.of("a", "b")); // Order doesn't matter + Assertions.assertThat(new ServiceException(actualType)).hasNoArgs(); + Assertions.assertThat(new TestEndpointServiceException(actualType)).hasNoArgs(); assertThatThrownBy(() -> Assertions.assertThat(new ServiceException(actualType)).hasCode(ErrorType.Code.INTERNAL)) .isInstanceOf(AssertionError.class) .hasMessageContaining( "Expected ErrorType.Code to be %s, but found %s", ErrorType.Code.INTERNAL, actualType.code()); + assertThatThrownBy(() -> Assertions.assertThat(new TestEndpointServiceException(actualType)) + .hasCode(ErrorType.Code.INTERNAL)) + .isInstanceOf(AssertionError.class) + .hasMessageContaining( + "Expected ErrorType.Code to be %s, but found %s", ErrorType.Code.INTERNAL, actualType.code()); assertThatThrownBy(() -> Assertions.assertThat(new ServiceException(actualType)).hasType(ErrorType.INTERNAL)) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected ErrorType to be %s, but found %s", ErrorType.INTERNAL, actualType); + assertThatThrownBy(() -> Assertions.assertThat(new TestEndpointServiceException(actualType)) + .hasType(ErrorType.INTERNAL)) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected ErrorType to be %s, but found %s", ErrorType.INTERNAL, actualType); assertThatThrownBy(() -> Assertions.assertThat(new ServiceException(actualType, SafeArg.of("a", "b"))) .hasArgs(SafeArg.of("c", "d"))) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected safe args to be {c=d}, but found {a=b}"); + assertThatThrownBy( + () -> Assertions.assertThat(new TestEndpointServiceException(actualType, SafeArg.of("a", "b"))) + .hasArgs(SafeArg.of("c", "d"))) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected safe args to be {c=d}, but found {a=b}"); assertThatThrownBy(() -> Assertions.assertThat(new ServiceException(actualType, UnsafeArg.of("a", "b"))) .hasArgs(UnsafeArg.of("c", "d"))) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected unsafe args to be {c=d}, but found {a=b}"); + assertThatThrownBy(() -> Assertions.assertThat( + new TestEndpointServiceException(actualType, UnsafeArg.of("a", "b"))) + .hasArgs(UnsafeArg.of("c", "d"))) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected unsafe args to be {c=d}, but found {a=b}"); assertThatThrownBy(() -> Assertions.assertThat(new ServiceException(actualType, SafeArg.of("a", "b"))) .hasNoArgs()) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected no args, but found {a=b}"); + assertThatThrownBy( + () -> Assertions.assertThat(new TestEndpointServiceException(actualType, SafeArg.of("a", "b"))) + .hasNoArgs()) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected no args, but found {a=b}"); assertThatThrownBy(() -> Assertions.assertThat( new ServiceException(actualType, SafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) .hasNoArgs()) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected no args, but found {a=b, c=d}"); + assertThatThrownBy(() -> Assertions.assertThat(new TestEndpointServiceException( + actualType, SafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) + .hasNoArgs()) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected no args, but found {a=b, c=d}"); Assertions.assertThat(new ServiceException(actualType, UnsafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) .containsArgs(UnsafeArg.of("a", "b")); + Assertions.assertThat( + new TestEndpointServiceException(actualType, UnsafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) + .containsArgs(UnsafeArg.of("a", "b")); // Safety matters assertThatThrownBy(() -> Assertions.assertThat( @@ -83,15 +136,30 @@ public void testSanity() { .containsArgs(UnsafeArg.of("a", "b"))) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected unsafe args to contain {a=b}, but found {c=d}"); + assertThatThrownBy(() -> Assertions.assertThat(new TestEndpointServiceException( + actualType, SafeArg.of("a", "b"), UnsafeArg.of("c", "d"))) + .containsArgs(UnsafeArg.of("a", "b"))) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected unsafe args to contain {a=b}, but found {c=d}"); assertThatThrownBy(() -> Assertions.assertThat(new ServiceException(actualType, SafeArg.of("a", "b"))) .containsArgs(SafeArg.of("c", "d"))) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected safe args to contain {c=d}, but found {a=b}"); + assertThatThrownBy( + () -> Assertions.assertThat(new TestEndpointServiceException(actualType, SafeArg.of("a", "b"))) + .containsArgs(SafeArg.of("c", "d"))) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected safe args to contain {c=d}, but found {a=b}"); assertThatThrownBy(() -> Assertions.assertThat(new ServiceException(actualType, UnsafeArg.of("a", "b"))) .containsArgs(UnsafeArg.of("c", "d"))) .isInstanceOf(AssertionError.class) .hasMessageContaining("Expected unsafe args to contain {c=d}, but found {a=b}"); + assertThatThrownBy(() -> Assertions.assertThat( + new TestEndpointServiceException(actualType, UnsafeArg.of("a", "b"))) + .containsArgs(UnsafeArg.of("c", "d"))) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected unsafe args to contain {c=d}, but found {a=b}"); } } From 83da95f9e9c790a2bb291ffb53b5e810094d0dee Mon Sep 17 00:00:00 2001 From: svc-autorelease Date: Tue, 15 Jul 2025 15:25:04 +0000 Subject: [PATCH 2/4] Release 2.61.0-rc1 [skip ci] From 94a5ced7c7fc43345823df5a1672e4892b480697 Mon Sep 17 00:00:00 2001 From: Pritham Marupaka Date: Thu, 17 Jul 2025 10:27:57 -0400 Subject: [PATCH 3/4] review change --- ...rviceError.java => AbstractServiceException.java} | 4 ++-- .../java/api/errors/EndpointServiceException.java | 2 +- .../conjure/java/api/errors/ServiceException.java | 2 +- .../java/api/testing/ServiceExceptionAssert.java | 12 ++++++------ .../conjure/java/api/testing/AssertionsTest.java | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename errors/src/main/java/com/palantir/conjure/java/api/errors/{AbstractServiceError.java => AbstractServiceException.java} (85%) diff --git a/errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceError.java b/errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceException.java similarity index 85% rename from errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceError.java rename to errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceException.java index f35a06873..b92e5311c 100644 --- a/errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceError.java +++ b/errors/src/main/java/com/palantir/conjure/java/api/errors/AbstractServiceException.java @@ -21,10 +21,10 @@ /** * This class should not be used directly. The parent class for ServiceException and EndpointServiceException. */ -public abstract class AbstractServiceError extends RuntimeException implements SafeLoggable { +public abstract class AbstractServiceException extends RuntimeException implements SafeLoggable { public abstract ErrorType getErrorType(); - protected AbstractServiceError(Throwable cause) { + protected AbstractServiceException(Throwable cause) { super(cause); } } diff --git a/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java b/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java index 108334b45..037952c00 100644 --- a/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java +++ b/errors/src/main/java/com/palantir/conjure/java/api/errors/EndpointServiceException.java @@ -24,7 +24,7 @@ * This is identical to ServiceException, but is used in Conjure-generated code to indicate that an exception was thrown * from a service endpoint. */ -public abstract class EndpointServiceException extends AbstractServiceError { +public abstract class EndpointServiceException extends AbstractServiceException { private static final String EXCEPTION_NAME = "EndpointServiceException"; private final ErrorType errorType; private final List> args; // This is an unmodifiable list. diff --git a/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java b/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java index e3479794a..b420ae740 100644 --- a/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java +++ b/errors/src/main/java/com/palantir/conjure/java/api/errors/ServiceException.java @@ -21,7 +21,7 @@ import javax.annotation.Nullable; /** A {@link ServiceException} thrown in server-side code to indicate server-side {@link ErrorType error states}. */ -public final class ServiceException extends AbstractServiceError { +public final class ServiceException extends AbstractServiceException { private static final String EXCEPTION_NAME = "ServiceException"; private final ErrorType errorType; private final List> args; // unmodifiable diff --git a/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java b/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java index 5fa525300..29a80c8e3 100644 --- a/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java +++ b/test-utils/src/main/java/com/palantir/conjure/java/api/testing/ServiceExceptionAssert.java @@ -16,7 +16,7 @@ package com.palantir.conjure.java.api.testing; -import com.palantir.conjure.java.api.errors.AbstractServiceError; +import com.palantir.conjure.java.api.errors.AbstractServiceException; import com.palantir.conjure.java.api.errors.ErrorType; import com.palantir.logsafe.Arg; import java.util.Arrays; @@ -28,17 +28,17 @@ import org.assertj.core.api.InstanceOfAssertFactory; import org.assertj.core.util.Throwables; -public class ServiceExceptionAssert extends AbstractThrowableAssert { +public class ServiceExceptionAssert extends AbstractThrowableAssert { - private static final InstanceOfAssertFactory + private static final InstanceOfAssertFactory INSTANCE_OF_ASSERT_FACTORY = - new InstanceOfAssertFactory<>(AbstractServiceError.class, ServiceExceptionAssert::new); + new InstanceOfAssertFactory<>(AbstractServiceException.class, ServiceExceptionAssert::new); - ServiceExceptionAssert(AbstractServiceError actual) { + ServiceExceptionAssert(AbstractServiceException actual) { super(actual, ServiceExceptionAssert.class); } - public static InstanceOfAssertFactory instanceOfAssertFactory() { + public static InstanceOfAssertFactory instanceOfAssertFactory() { return INSTANCE_OF_ASSERT_FACTORY; } diff --git a/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java b/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java index 8f7831514..d7c82d178 100644 --- a/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java +++ b/test-utils/src/test/java/com/palantir/conjure/java/api/testing/AssertionsTest.java @@ -45,7 +45,7 @@ public void testAssertThatServiceExceptionThrownBy_failsIfWrongExceptionThrown() throw new RuntimeException("My message"); })) .hasMessageContaining( - "com.palantir.conjure.java.api.errors.AbstractServiceError", + "com.palantir.conjure.java.api.errors.AbstractServiceException", "java.lang.RuntimeException", "My message"); } From 0522eedffe74c3a7961f9664a10bd387c4e5baba Mon Sep 17 00:00:00 2001 From: svc-changelog Date: Thu, 17 Jul 2025 14:28:41 +0000 Subject: [PATCH 4/4] Add generated changelog entries --- changelog/@unreleased/pr-1383.v2.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog/@unreleased/pr-1383.v2.yml diff --git a/changelog/@unreleased/pr-1383.v2.yml b/changelog/@unreleased/pr-1383.v2.yml new file mode 100644 index 000000000..c589ce06a --- /dev/null +++ b/changelog/@unreleased/pr-1383.v2.yml @@ -0,0 +1,6 @@ +type: improvement +improvement: + description: Add `AbstractServiceException` and extend `ServiceExceptionAssert` + to support `EndpointServiceException` + links: + - https://github.com/palantir/conjure-java-runtime-api/pull/1383