diff --git a/build.gradle b/build.gradle index a693f9c43..79db2d4b2 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,11 @@ allprojects { details.useTarget group: details.requested.group, name: details.requested.name, version: '2.15.3' details.because 'https://github.com/FasterXML/jackson-databind/pull/4230' } + if (details.requested.group.startsWith('org.slf4j')) { + details.useTarget group: details.requested.group, name: details.requested.name, version: '1.7.36' + details.because 'Dropwizard 2.x is used in tests. This version of Dropwizard only supports SLF4J < ' + + '2.x. Until we upgrade Dropwizard, we should continue using a version of SLF4J < 2.x' + } } } } diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/another/EndpointSpecificErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/another/EndpointSpecificErrors.java new file mode 100644 index 000000000..164854cfd --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/another/EndpointSpecificErrors.java @@ -0,0 +1,21 @@ +package com.palantir.another; + +import com.palantir.conjure.java.api.errors.ErrorType; +import com.palantir.conjure.java.api.errors.RemoteException; +import com.palantir.logsafe.Preconditions; +import javax.annotation.processing.Generated; + +@Generated("com.palantir.conjure.java.types.ErrorGenerator") +public final class EndpointSpecificErrors { + /** An error in a different package. */ + public static final ErrorType DIFFERENT_PACKAGE = + ErrorType.create(ErrorType.Code.INTERNAL, "EndpointSpecific:DifferentPackage"); + + private EndpointSpecificErrors() {} + + /** Returns true if the {@link RemoteException} is named EndpointSpecific:DifferentPackage */ + public static boolean isDifferentPackage(RemoteException remoteException) { + Preconditions.checkNotNull(remoteException, "remote exception must not be null"); + return DIFFERENT_PACKAGE.name().equals(remoteException.getError().errorName()); + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/another/EndpointSpecificServerErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/another/EndpointSpecificServerErrors.java new file mode 100644 index 000000000..720a12d5b --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/another/EndpointSpecificServerErrors.java @@ -0,0 +1,37 @@ +package com.palantir.another; + +import com.palantir.conjure.java.api.errors.CheckedServiceException; +import javax.annotation.Nullable; +import javax.annotation.processing.Generated; +import org.jetbrains.annotations.Contract; + +@Generated("com.palantir.conjure.java.types.CheckedErrorGenerator") +public final class EndpointSpecificServerErrors { + private EndpointSpecificServerErrors() {} + + public static DifferentPackage differentPackage() { + return new DifferentPackage(null); + } + + public static DifferentPackage differentPackage(@Nullable Throwable cause) { + return new DifferentPackage(cause); + } + + /** + * Throws a {@link DifferentPackage} when {@code shouldThrow} is true. + * + * @param shouldThrow Cause the method to throw when true + */ + @Contract("true -> fail") + public static void throwIfDifferentPackage(boolean shouldThrow) throws DifferentPackage { + if (shouldThrow) { + throw differentPackage(); + } + } + + public static final class DifferentPackage extends CheckedServiceException { + private DifferentPackage(@Nullable Throwable cause) { + super(EndpointSpecificErrors.DIFFERENT_PACKAGE, cause); + } + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificErrors.java new file mode 100644 index 000000000..68f823c09 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificErrors.java @@ -0,0 +1,21 @@ +package com.palantir.product; + +import com.palantir.conjure.java.api.errors.ErrorType; +import com.palantir.conjure.java.api.errors.RemoteException; +import com.palantir.logsafe.Preconditions; +import javax.annotation.processing.Generated; + +@Generated("com.palantir.conjure.java.types.ErrorGenerator") +public final class EndpointSpecificErrors { + /** Docs for an endpoint error. */ + public static final ErrorType ENDPOINT_ERROR = + ErrorType.create(ErrorType.Code.INVALID_ARGUMENT, "EndpointSpecific:EndpointError"); + + private EndpointSpecificErrors() {} + + /** Returns true if the {@link RemoteException} is named EndpointSpecific:EndpointError */ + public static boolean isEndpointError(RemoteException remoteException) { + Preconditions.checkNotNull(remoteException, "remote exception must not be null"); + return ENDPOINT_ERROR.name().equals(remoteException.getError().errorName()); + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificServerErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificServerErrors.java new file mode 100644 index 000000000..f4f7452f2 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificServerErrors.java @@ -0,0 +1,49 @@ +package com.palantir.product; + +import com.palantir.conjure.java.api.errors.CheckedServiceException; +import com.palantir.logsafe.Safe; +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.Unsafe; +import com.palantir.logsafe.UnsafeArg; +import javax.annotation.Nullable; +import javax.annotation.processing.Generated; +import org.jetbrains.annotations.Contract; + +@Generated("com.palantir.conjure.java.types.CheckedErrorGenerator") +public final class EndpointSpecificServerErrors { + private EndpointSpecificServerErrors() {} + + public static EndpointError endpointError(@Safe String typeName, @Unsafe Object typeDef) { + return new EndpointError(typeName, typeDef, null); + } + + public static EndpointError endpointError( + @Safe String typeName, @Unsafe Object typeDef, @Nullable Throwable cause) { + return new EndpointError(typeName, typeDef, cause); + } + + /** + * Throws a {@link EndpointError} when {@code shouldThrow} is true. + * + * @param shouldThrow Cause the method to throw when true + * @param typeName + * @param typeDef + */ + @Contract("true, _, _ -> fail") + public static void throwIfEndpointError(boolean shouldThrow, @Safe String typeName, @Unsafe Object typeDef) + throws EndpointError { + if (shouldThrow) { + throw endpointError(typeName, typeDef); + } + } + + public static final class EndpointError extends CheckedServiceException { + private EndpointError(@Safe String typeName, @Unsafe Object typeDef, @Nullable Throwable cause) { + super( + EndpointSpecificErrors.ENDPOINT_ERROR, + cause, + SafeArg.of("typeName", typeName), + UnsafeArg.of("typeDef", typeDef)); + } + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificTwoErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificTwoErrors.java new file mode 100644 index 000000000..58540074c --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificTwoErrors.java @@ -0,0 +1,21 @@ +package com.palantir.product; + +import com.palantir.conjure.java.api.errors.ErrorType; +import com.palantir.conjure.java.api.errors.RemoteException; +import com.palantir.logsafe.Preconditions; +import javax.annotation.processing.Generated; + +@Generated("com.palantir.conjure.java.types.ErrorGenerator") +public final class EndpointSpecificTwoErrors { + /** An error in a different namespace. */ + public static final ErrorType DIFFERENT_NAMESPACE = + ErrorType.create(ErrorType.Code.INTERNAL, "EndpointSpecificTwo:DifferentNamespace"); + + private EndpointSpecificTwoErrors() {} + + /** Returns true if the {@link RemoteException} is named EndpointSpecificTwo:DifferentNamespace */ + public static boolean isDifferentNamespace(RemoteException remoteException) { + Preconditions.checkNotNull(remoteException, "remote exception must not be null"); + return DIFFERENT_NAMESPACE.name().equals(remoteException.getError().errorName()); + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificTwoServerErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificTwoServerErrors.java new file mode 100644 index 000000000..7a8304b2d --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/EndpointSpecificTwoServerErrors.java @@ -0,0 +1,37 @@ +package com.palantir.product; + +import com.palantir.conjure.java.api.errors.CheckedServiceException; +import javax.annotation.Nullable; +import javax.annotation.processing.Generated; +import org.jetbrains.annotations.Contract; + +@Generated("com.palantir.conjure.java.types.CheckedErrorGenerator") +public final class EndpointSpecificTwoServerErrors { + private EndpointSpecificTwoServerErrors() {} + + public static DifferentNamespace differentNamespace() { + return new DifferentNamespace(null); + } + + public static DifferentNamespace differentNamespace(@Nullable Throwable cause) { + return new DifferentNamespace(cause); + } + + /** + * Throws a {@link DifferentNamespace} when {@code shouldThrow} is true. + * + * @param shouldThrow Cause the method to throw when true + */ + @Contract("true -> fail") + public static void throwIfDifferentNamespace(boolean shouldThrow) throws DifferentNamespace { + if (shouldThrow) { + throw differentNamespace(); + } + } + + public static final class DifferentNamespace extends CheckedServiceException { + private DifferentNamespace(@Nullable Throwable cause) { + super(EndpointSpecificTwoErrors.DIFFERENT_NAMESPACE, cause); + } + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/ErrorServiceEndpoints.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/ErrorServiceEndpoints.java new file mode 100644 index 000000000..226262fad --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/ErrorServiceEndpoints.java @@ -0,0 +1,179 @@ +package com.palantir.product; + +import com.google.common.collect.ImmutableList; +import com.palantir.conjure.java.undertow.lib.Endpoint; +import com.palantir.conjure.java.undertow.lib.Serializer; +import com.palantir.conjure.java.undertow.lib.TypeMarker; +import com.palantir.conjure.java.undertow.lib.UndertowRuntime; +import com.palantir.conjure.java.undertow.lib.UndertowService; +import com.palantir.tokens.auth.AuthHeader; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; +import java.io.IOException; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated("com.palantir.conjure.java.services.UndertowServiceHandlerGenerator") +public final class ErrorServiceEndpoints implements UndertowService { + private final UndertowErrorService delegate; + + private ErrorServiceEndpoints(UndertowErrorService delegate) { + this.delegate = delegate; + } + + public static UndertowService of(UndertowErrorService delegate) { + return new ErrorServiceEndpoints(delegate); + } + + @Override + public List endpoints(UndertowRuntime runtime) { + return ImmutableList.of( + new TestBasicErrorEndpoint(runtime, delegate), + new TestImportedErrorEndpoint(runtime, delegate), + new TestMultipleErrorsAndPackagesEndpoint(runtime, delegate)); + } + + private static final class TestBasicErrorEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowErrorService delegate; + + private final Serializer serializer; + + TestBasicErrorEndpoint(UndertowRuntime runtime, UndertowErrorService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}, this); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws IOException, TestServerErrors.InvalidArgument { + AuthHeader authHeader = runtime.auth().header(exchange); + String result = delegate.testBasicError(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/basic"; + } + + @Override + public String serviceName() { + return "ErrorService"; + } + + @Override + public String name() { + return "testBasicError"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestImportedErrorEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowErrorService delegate; + + private final Serializer serializer; + + TestImportedErrorEndpoint(UndertowRuntime runtime, UndertowErrorService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}, this); + } + + @Override + public void handleRequest(HttpServerExchange exchange) + throws IOException, EndpointSpecificServerErrors.EndpointError { + AuthHeader authHeader = runtime.auth().header(exchange); + String result = delegate.testImportedError(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/imported"; + } + + @Override + public String serviceName() { + return "ErrorService"; + } + + @Override + public String name() { + return "testImportedError"; + } + + @Override + public HttpHandler handler() { + return this; + } + } + + private static final class TestMultipleErrorsAndPackagesEndpoint implements HttpHandler, Endpoint { + private final UndertowRuntime runtime; + + private final UndertowErrorService delegate; + + private final Serializer serializer; + + TestMultipleErrorsAndPackagesEndpoint(UndertowRuntime runtime, UndertowErrorService delegate) { + this.runtime = runtime; + this.delegate = delegate; + this.serializer = runtime.bodySerDe().serializer(new TypeMarker() {}, this); + } + + @Override + public void handleRequest(HttpServerExchange exchange) + throws IOException, TestServerErrors.InvalidArgument, TestServerErrors.NotFound, + EndpointSpecificTwoServerErrors.DifferentNamespace, + com.palantir.another.EndpointSpecificServerErrors.DifferentPackage { + AuthHeader authHeader = runtime.auth().header(exchange); + String result = delegate.testMultipleErrorsAndPackages(authHeader); + serializer.serialize(result, exchange); + } + + @Override + public HttpString method() { + return Methods.GET; + } + + @Override + public String template() { + return "/base/multiple"; + } + + @Override + public String serviceName() { + return "ErrorService"; + } + + @Override + public String name() { + return "testMultipleErrorsAndPackages"; + } + + @Override + public HttpHandler handler() { + return this; + } + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/TestErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/TestErrors.java new file mode 100644 index 000000000..124b8a508 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/TestErrors.java @@ -0,0 +1,28 @@ +package com.palantir.product; + +import com.palantir.conjure.java.api.errors.ErrorType; +import com.palantir.conjure.java.api.errors.RemoteException; +import com.palantir.logsafe.Preconditions; +import javax.annotation.processing.Generated; + +@Generated("com.palantir.conjure.java.types.ErrorGenerator") +public final class TestErrors { + public static final ErrorType INVALID_ARGUMENT = + ErrorType.create(ErrorType.Code.INVALID_ARGUMENT, "Test:InvalidArgument"); + + public static final ErrorType NOT_FOUND = ErrorType.create(ErrorType.Code.NOT_FOUND, "Test:NotFound"); + + private TestErrors() {} + + /** Returns true if the {@link RemoteException} is named Test:InvalidArgument */ + public static boolean isInvalidArgument(RemoteException remoteException) { + Preconditions.checkNotNull(remoteException, "remote exception must not be null"); + return INVALID_ARGUMENT.name().equals(remoteException.getError().errorName()); + } + + /** Returns true if the {@link RemoteException} is named Test:NotFound */ + public static boolean isNotFound(RemoteException remoteException) { + Preconditions.checkNotNull(remoteException, "remote exception must not be null"); + return NOT_FOUND.name().equals(remoteException.getError().errorName()); + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/TestServerErrors.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/TestServerErrors.java new file mode 100644 index 000000000..7a0a93c4b --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/TestServerErrors.java @@ -0,0 +1,71 @@ +package com.palantir.product; + +import com.palantir.conjure.java.api.errors.CheckedServiceException; +import com.palantir.logsafe.Safe; +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.Unsafe; +import com.palantir.logsafe.UnsafeArg; +import javax.annotation.Nullable; +import javax.annotation.processing.Generated; +import org.jetbrains.annotations.Contract; + +@Generated("com.palantir.conjure.java.types.CheckedErrorGenerator") +public final class TestServerErrors { + private TestServerErrors() {} + + public static InvalidArgument invalidArgument(@Safe String field, @Unsafe String value) { + return new InvalidArgument(field, value, null); + } + + public static InvalidArgument invalidArgument(@Safe String field, @Unsafe String value, @Nullable Throwable cause) { + return new InvalidArgument(field, value, cause); + } + + public static NotFound notFound(@Safe String resource) { + return new NotFound(resource, null); + } + + public static NotFound notFound(@Safe String resource, @Nullable Throwable cause) { + return new NotFound(resource, cause); + } + + /** + * Throws a {@link InvalidArgument} when {@code shouldThrow} is true. + * + * @param shouldThrow Cause the method to throw when true + * @param field + * @param value + */ + @Contract("true, _, _ -> fail") + public static void throwIfInvalidArgument(boolean shouldThrow, @Safe String field, @Unsafe String value) + throws InvalidArgument { + if (shouldThrow) { + throw invalidArgument(field, value); + } + } + + /** + * Throws a {@link NotFound} when {@code shouldThrow} is true. + * + * @param shouldThrow Cause the method to throw when true + * @param resource + */ + @Contract("true, _ -> fail") + public static void throwIfNotFound(boolean shouldThrow, @Safe String resource) throws NotFound { + if (shouldThrow) { + throw notFound(resource); + } + } + + public static final class InvalidArgument extends CheckedServiceException { + private InvalidArgument(@Safe String field, @Unsafe String value, @Nullable Throwable cause) { + super(TestErrors.INVALID_ARGUMENT, cause, SafeArg.of("field", field), UnsafeArg.of("value", value)); + } + } + + public static final class NotFound extends CheckedServiceException { + private NotFound(@Safe String resource, @Nullable Throwable cause) { + super(TestErrors.NOT_FOUND, cause, SafeArg.of("resource", resource)); + } + } +} diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/product/UndertowErrorService.java b/conjure-java-core/src/integrationInput/java/com/palantir/product/UndertowErrorService.java new file mode 100644 index 000000000..62fc76a39 --- /dev/null +++ b/conjure-java-core/src/integrationInput/java/com/palantir/product/UndertowErrorService.java @@ -0,0 +1,31 @@ +package com.palantir.product; + +import com.palantir.tokens.auth.AuthHeader; +import javax.annotation.processing.Generated; + +@Generated("com.palantir.conjure.java.services.UndertowServiceInterfaceGenerator") +public interface UndertowErrorService { + /** + * @apiNote {@code GET /base/basic} + * @throws TestServerErrors.InvalidArgument + */ + String testBasicError(AuthHeader authHeader) throws TestServerErrors.InvalidArgument; + + /** + * @apiNote {@code GET /base/imported} + * @throws EndpointSpecificServerErrors.EndpointError + */ + String testImportedError(AuthHeader authHeader) throws EndpointSpecificServerErrors.EndpointError; + + /** + * @apiNote {@code GET /base/multiple} + * @throws TestServerErrors.InvalidArgument + * @throws TestServerErrors.NotFound Something was not found. + * @throws EndpointSpecificTwoServerErrors.DifferentNamespace + * @throws com.palantir.another.EndpointSpecificServerErrors.DifferentPackage + */ + String testMultipleErrorsAndPackages(AuthHeader authHeader) + throws TestServerErrors.InvalidArgument, TestServerErrors.NotFound, + EndpointSpecificTwoServerErrors.DifferentNamespace, + com.palantir.another.EndpointSpecificServerErrors.DifferentPackage; +} diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/JerseyServiceGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/JerseyServiceGenerator.java index 32ee51494..82bb3fc2f 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/JerseyServiceGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/JerseyServiceGenerator.java @@ -22,6 +22,9 @@ import com.palantir.conjure.java.ConjureTags; import com.palantir.conjure.java.Generator; import com.palantir.conjure.java.Options; +import com.palantir.conjure.java.services.ServiceGenerators.EndpointErrorsJavaDoc; +import com.palantir.conjure.java.services.ServiceGenerators.EndpointJavaDocGenerationOptions; +import com.palantir.conjure.java.services.ServiceGenerators.RequestLineJavaDoc; import com.palantir.conjure.java.types.ClassNameVisitor; import com.palantir.conjure.java.types.DefaultClassNameVisitor; import com.palantir.conjure.java.types.SafetyEvaluator; @@ -105,11 +108,19 @@ public Stream generate(ConjureDefinition conjureDefinition) { .map(serviceDef -> generateService(serviceDef, safetyEvaluator, returnTypeMapper, argumentTypeMapper)); } + private static boolean hasEndpointErrorsDefined(ServiceDefinition serviceDefinition) { + return serviceDefinition.getEndpoints().stream() + .anyMatch(endpoint -> !endpoint.getErrors().isEmpty()); + } + private JavaFile generateService( ServiceDefinition serviceDefinition, SafetyEvaluator safetyEvaluator, TypeMapper returnTypeMapper, TypeMapper argumentTypeMapper) { + if (hasEndpointErrorsDefined(serviceDefinition)) { + throw new IllegalArgumentException("Endpoint errors are not supported for Jersey servers."); + } com.palantir.conjure.spec.TypeName prefixedName = Packages.getPrefixedName(serviceDefinition.getServiceName(), options.packagePrefix()); TypeSpec.Builder serviceBuilder = TypeSpec.interfaceBuilder(prefixedName.getName()) @@ -194,7 +205,12 @@ private MethodSpec generateServiceMethod( methodBuilder.addAnnotations(ConjureAnnotations.getClientEndpointAnnotations(endpointDef)); - ServiceGenerators.getJavaDoc(endpointDef).ifPresent(content -> methodBuilder.addJavadoc("$L", content)); + ServiceGenerators.addJavaDocForEndpointDefinition( + methodBuilder, + options.packagePrefix(), + endpointDef, + // Endpoint errors are not supported for Jersey servers + new EndpointJavaDocGenerationOptions(RequestLineJavaDoc.EXCLUDE, EndpointErrorsJavaDoc.EXCLUDE)); return methodBuilder.build(); } diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/ServiceGenerators.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/ServiceGenerators.java index 95dd4a124..b09d1d51b 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/ServiceGenerators.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/ServiceGenerators.java @@ -17,45 +17,74 @@ package com.palantir.conjure.java.services; import com.google.common.base.Strings; +import com.palantir.conjure.java.util.ErrorGenerationUtils; import com.palantir.conjure.java.util.Javadoc; +import com.palantir.conjure.java.util.Packages; import com.palantir.conjure.spec.EndpointDefinition; +import com.palantir.javapoet.ClassName; +import com.palantir.javapoet.CodeBlock; +import com.palantir.javapoet.MethodSpec; import java.util.Optional; import java.util.stream.Collectors; public final class ServiceGenerators { - - public static Optional getJavaDoc(EndpointDefinition endpointDef) { - return getJavaDocInternal(endpointDef, false); + public enum RequestLineJavaDoc { + INCLUDE, + EXCLUDE } - public static String getJavaDocWithRequestLine(EndpointDefinition endpointDef) { - return getJavaDocInternal(endpointDef, true).get(); + public enum EndpointErrorsJavaDoc { + INCLUDE, + EXCLUDE } - private static Optional getJavaDocInternal(EndpointDefinition endpointDef, boolean includeRequestLine) { - Optional depr = endpointDef.getDeprecated().map(Javadoc::getDeprecatedJavadoc); - - Optional incDoc = Javadoc.getIncubatingJavadoc(endpointDef.getTags()); - - Optional docs = endpointDef.getDocs().map(Javadoc::render); + public record EndpointJavaDocGenerationOptions( + RequestLineJavaDoc requestLineJavaDoc, EndpointErrorsJavaDoc endpointErrorsJavaDoc) {} - Optional requestLine = Optional.empty(); + public static void addJavaDocForEndpointDefinition( + MethodSpec.Builder methodBuilder, + Optional maybePackagePrefix, + EndpointDefinition endpointDefinition, + EndpointJavaDocGenerationOptions options) { + addJavaDocForEndpointDefinitionInternal(methodBuilder, maybePackagePrefix, endpointDefinition, options); + } - if (includeRequestLine) { - requestLine = Optional.of(Javadoc.getRequestLine(endpointDef.getHttpMethod(), endpointDef.getHttpPath())); + private static void addJavaDocForEndpointDefinitionInternal( + MethodSpec.Builder methodBuilder, + Optional maybePackagePrefix, + EndpointDefinition endpointDefinition, + EndpointJavaDocGenerationOptions options) { + endpointDefinition.getDocs().map(Javadoc::render).ifPresent(doc -> methodBuilder.addJavadoc("$L", doc)); + if (options.requestLineJavaDoc() == RequestLineJavaDoc.INCLUDE) { + methodBuilder.addJavadoc( + "$L", Javadoc.getRequestLine(endpointDefinition.getHttpMethod(), endpointDefinition.getHttpPath())); } - - Optional params = Optional.ofNullable(Strings.emptyToNull(endpointDef.getArgs().stream() - .flatMap(argument -> Javadoc.getParameterJavadoc(argument, endpointDef).stream()) - .collect(Collectors.joining("\n")))); - - StringBuilder sb = new StringBuilder(); - docs.ifPresent(sb::append); - requestLine.ifPresent(sb::append); - params.ifPresent(sb::append); - depr.ifPresent(sb::append); - incDoc.ifPresent(sb::append); - return sb.length() > 0 ? Optional.of(sb.toString()) : Optional.empty(); + Optional.ofNullable(Strings.emptyToNull(endpointDefinition.getArgs().stream() + .flatMap(argument -> Javadoc.getParameterJavadoc(argument, endpointDefinition).stream()) + .collect(Collectors.joining("\n")))) + .ifPresent(params -> methodBuilder.addJavadoc("$L", params)); + if (options.endpointErrorsJavaDoc() == EndpointErrorsJavaDoc.INCLUDE) { + methodBuilder.addJavadoc(endpointDefinition.getErrors().stream() + .map(endpointError -> CodeBlock.of( + "@throws $T $L", + ClassName.get( + Packages.getPrefixedPackage( + endpointError.getError().getPackage(), maybePackagePrefix), + ErrorGenerationUtils.errorExceptionsClassName( + endpointError.getError().getNamespace()), + endpointError.getError().getName()), + endpointError + .getDocs() + .map(endpointErrorDocs -> " " + Javadoc.render(endpointErrorDocs)) + .orElse(""))) + .collect(CodeBlock.joining("\n"))); + } + endpointDefinition + .getDeprecated() + .map(Javadoc::getDeprecatedJavadoc) + .ifPresent(d -> methodBuilder.addJavadoc("$L", d)); + Javadoc.getIncubatingJavadoc(endpointDefinition.getTags()) + .ifPresent(ind -> methodBuilder.addJavadoc("$L", ind)); } private ServiceGenerators() {} diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java index 82749cf17..6685b5195 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceHandlerGenerator.java @@ -39,6 +39,7 @@ import com.palantir.conjure.java.undertow.lib.TypeMarker; import com.palantir.conjure.java.undertow.lib.UndertowRuntime; import com.palantir.conjure.java.undertow.lib.UndertowService; +import com.palantir.conjure.java.util.ErrorGenerationUtils; import com.palantir.conjure.java.util.JavaNameSanitizer; import com.palantir.conjure.java.util.Packages; import com.palantir.conjure.java.util.ParameterOrder; @@ -51,6 +52,7 @@ import com.palantir.conjure.spec.CookieAuthType; import com.palantir.conjure.spec.EndpointDefinition; import com.palantir.conjure.spec.EndpointName; +import com.palantir.conjure.spec.ErrorTypeName; import com.palantir.conjure.spec.ExternalReference; import com.palantir.conjure.spec.HeaderAuthType; import com.palantir.conjure.spec.ListType; @@ -231,6 +233,15 @@ private TypeSpec generateEndpointHandler( .addModifiers(Modifier.PUBLIC) .addParameter(HttpServerExchange.class, EXCHANGE_VAR_NAME) .addException(IOException.class) + .addExceptions(endpointDefinition.getErrors().stream() + .map(endpointError -> { + ErrorTypeName errorTypeName = endpointError.getError(); + return ClassName.get( + Packages.getPrefixedPackage(errorTypeName.getPackage(), options.packagePrefix()), + ErrorGenerationUtils.errorExceptionsClassName(errorTypeName.getNamespace()), + errorTypeName.getName()); + }) + .toList()) .addCode(endpointInvocation( endpointDefinition, typeDefinitions, typeMapper, returnTypeMapper, safetyEvaluator)); @@ -364,7 +375,6 @@ private TypeSpec generateEndpointHandler( .returns(ParameterizedTypeName.get(ClassName.get(Optional.class), ClassName.get(String.class))) .addStatement("return $1T.of($2S)", Optional.class, documentation) .build())); - return endpointBuilder.build(); } diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceInterfaceGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceInterfaceGenerator.java index e448f96d1..105c06d48 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceInterfaceGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/UndertowServiceInterfaceGenerator.java @@ -20,9 +20,13 @@ import com.palantir.conjure.java.ConjureAnnotations; import com.palantir.conjure.java.ConjureTags; import com.palantir.conjure.java.Options; +import com.palantir.conjure.java.services.ServiceGenerators.EndpointErrorsJavaDoc; +import com.palantir.conjure.java.services.ServiceGenerators.EndpointJavaDocGenerationOptions; +import com.palantir.conjure.java.services.ServiceGenerators.RequestLineJavaDoc; import com.palantir.conjure.java.types.SafetyEvaluator; import com.palantir.conjure.java.types.TypeMapper; import com.palantir.conjure.java.undertow.lib.RequestContext; +import com.palantir.conjure.java.util.ErrorGenerationUtils; import com.palantir.conjure.java.util.JavaNameSanitizer; import com.palantir.conjure.java.util.Javadoc; import com.palantir.conjure.java.util.Packages; @@ -31,6 +35,7 @@ import com.palantir.conjure.spec.AuthType; import com.palantir.conjure.spec.CookieAuthType; import com.palantir.conjure.spec.EndpointDefinition; +import com.palantir.conjure.spec.ErrorTypeName; import com.palantir.conjure.spec.HeaderAuthType; import com.palantir.conjure.spec.LogSafety; import com.palantir.conjure.spec.ServiceDefinition; @@ -96,7 +101,11 @@ private MethodSpec generateServiceInterfaceMethod( endpointDef.getDeprecated().ifPresent(deprecatedDocsValue -> methodBuilder.addAnnotation(Deprecated.class)); - methodBuilder.addJavadoc("$L", ServiceGenerators.getJavaDocWithRequestLine(endpointDef)); + ServiceGenerators.addJavaDocForEndpointDefinition( + methodBuilder, + options.packagePrefix(), + endpointDef, + new EndpointJavaDocGenerationOptions(RequestLineJavaDoc.INCLUDE, EndpointErrorsJavaDoc.INCLUDE)); if (UndertowTypeFunctions.isAsync(endpointDef, options)) { methodBuilder.returns(UndertowTypeFunctions.getAsyncReturnType(endpointDef, returnTypeMapper, options)); @@ -104,6 +113,16 @@ private MethodSpec generateServiceInterfaceMethod( endpointDef.getReturns().ifPresent(type -> methodBuilder.returns(returnTypeMapper.getClassName(type))); } + methodBuilder.addExceptions(endpointDef.getErrors().stream() + .map(endpointError -> { + ErrorTypeName errorTypeName = endpointError.getError(); + return ClassName.get( + Packages.getPrefixedPackage(errorTypeName.getPackage(), options.packagePrefix()), + ErrorGenerationUtils.errorExceptionsClassName(errorTypeName.getNamespace()), + errorTypeName.getName()); + }) + .collect(Collectors.toList())); + return methodBuilder.build(); } diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/dialogue/DialogueInterfaceGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/dialogue/DialogueInterfaceGenerator.java index 86a28a4e5..a52af6527 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/services/dialogue/DialogueInterfaceGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/services/dialogue/DialogueInterfaceGenerator.java @@ -23,6 +23,9 @@ import com.palantir.conjure.java.Options; import com.palantir.conjure.java.services.IsUndertowAsyncMarkerVisitor; import com.palantir.conjure.java.services.ServiceGenerators; +import com.palantir.conjure.java.services.ServiceGenerators.EndpointErrorsJavaDoc; +import com.palantir.conjure.java.services.ServiceGenerators.EndpointJavaDocGenerationOptions; +import com.palantir.conjure.java.services.ServiceGenerators.RequestLineJavaDoc; import com.palantir.conjure.java.util.Packages; import com.palantir.conjure.spec.EndpointDefinition; import com.palantir.conjure.spec.ServiceDefinition; @@ -175,7 +178,11 @@ private MethodSpec apiMethod(EndpointDefinition endpointDef, Function generate(ConjureDefinition definition) { + Map types = TypeFunctions.toTypesMap(definition); + SafetyEvaluator safetyEvaluator = new SafetyEvaluator(types); + TypeMapper typeMapper = new TypeMapper(types, options); + DeclaredEndpointErrors endpointErrors = DeclaredEndpointErrors.from(definition); + return ErrorGenerationUtils.getNamespacedErrorsFromDefinitions(definition.getErrors()).stream() + .flatMap(namespacedErrors -> { + List filteredErrorDefinitions = namespacedErrors.errors().stream() + .filter(endpointErrors::contains) + .toList(); + if (filteredErrorDefinitions.isEmpty()) { + return Stream.empty(); + } + return Stream.of(generateErrorExceptionsForNamespace( + typeMapper, + safetyEvaluator, + Packages.getPrefixedPackage(namespacedErrors.javaPackage(), options.packagePrefix()), + namespacedErrors.namespace(), + filteredErrorDefinitions)); + }); + } + + private JavaFile generateErrorExceptionsForNamespace( + TypeMapper typeMapper, + SafetyEvaluator safetyEvaluator, + String conjurePackage, + ErrorNamespace namespace, + List errorDefinitions) { + List constructors = errorDefinitions.stream() + .flatMap(entry -> { + MethodSpec withoutCause = generateExceptionFactory(typeMapper, entry, conjurePackage, false); + MethodSpec withCause = generateExceptionFactory(typeMapper, entry, conjurePackage, true); + return Stream.of(withoutCause, withCause); + }) + .toList(); + TypeSpec.Builder typeBuilder = TypeSpec.classBuilder( + ClassName.get(conjurePackage, ErrorGenerationUtils.errorExceptionsClassName(namespace))) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addMethod(ErrorGenerationUtils.privateConstructor()) + .addMethods(constructors) + .addMethods(generateConditionalExceptionFactories( + typeMapper, safetyEvaluator, errorDefinitions, conjurePackage, options)) + .addTypes(errorDefinitions.stream() + .map(def -> generateErrorException(typeMapper, conjurePackage, namespace, def)) + .toList()) + .addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(CheckedErrorGenerator.class)); + return JavaFile.builder(conjurePackage, typeBuilder.build()) + .skipJavaLangImports(true) + .indent(" ") + .build(); + } + + private static MethodSpec generateExceptionFactory( + TypeMapper typeMapper, ErrorDefinition errorDefinition, String conjurePackage, boolean withCause) { + String methodName = CaseFormat.UPPER_CAMEL.to( + CaseFormat.LOWER_CAMEL, errorDefinition.getErrorName().getName()); + + ClassName exceptionClass = ClassName.get( + conjurePackage, + ErrorGenerationUtils.errorExceptionsClassName(errorDefinition.getNamespace()), + errorDefinition.getErrorName().getName()); + + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(exceptionClass); + + methodBuilder.addCode("return new $T(", exceptionClass); + + List args = new ArrayList<>( + Stream.concat(errorDefinition.getSafeArgs().stream(), errorDefinition.getUnsafeArgs().stream()) + .map(arg -> CodeBlock.of("$N", arg.getFieldName().get())) + .toList()); + if (withCause) { + args.add(CodeBlock.of("$N", "cause")); + } else { + args.add(CodeBlock.of("$N", "null")); + } + methodBuilder.addCode("$L", args.stream().collect(CodeBlock.joining(","))); + + ErrorGenerationUtils.addAllParametersWithSafetyAnnotationsToMethodBuilder( + typeMapper, methodBuilder, errorDefinition); + if (withCause) { + ParameterSpec causeParameter = ParameterSpec.builder(Throwable.class, "cause") + .addAnnotation(Nullable.class) + .build(); + methodBuilder.addParameter(causeParameter); + } + + methodBuilder.addCode(");"); + + return methodBuilder.build(); + } + + private static List generateConditionalExceptionFactories( + TypeMapper typeMapper, + SafetyEvaluator safetyEvaluator, + List errorDefinitions, + String conjurePackage, + Options options) { + return errorDefinitions.stream() + .map(errorDefinition -> { + ClassName exceptionClassName = ClassName.get( + conjurePackage, + ErrorGenerationUtils.errorExceptionsClassName(errorDefinition.getNamespace()), + errorDefinition.getErrorName().getName()); + return ErrorGenerationUtils.conditionalStaticFactoryMethodBuilder( + typeMapper, + safetyEvaluator, + errorDefinition, + options, + exceptionClassName, + Optional.empty()) + .addException(exceptionClassName) + .build(); + }) + .collect(Collectors.toList()); + } + + private TypeSpec generateErrorException( + TypeMapper typeMapper, String conjurePackage, ErrorNamespace namespace, ErrorDefinition errorDefinition) { + return TypeSpec.classBuilder(errorDefinition.getErrorName().getName()) + .superclass(CheckedServiceException.class) + .addMethod(buildExceptionConstructor(typeMapper, conjurePackage, namespace, errorDefinition)) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .build(); + } + + private MethodSpec buildExceptionConstructor( + TypeMapper typeMapper, String conjurePackage, ErrorNamespace namespace, ErrorDefinition errorDefinition) { + MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .addCode( + "super($T.$L", + ErrorGenerator.errorTypesClassName(conjurePackage, namespace), + CaseFormat.UPPER_CAMEL.to( + CaseFormat.UPPER_UNDERSCORE, + errorDefinition.getErrorName().getName())); + methodBuilder.addCode(", cause"); + ErrorGenerationUtils.addAllLogSafeArgumentsToMethodBuilder(typeMapper, errorDefinition, methodBuilder); + methodBuilder.addParameter(ParameterSpec.builder(Throwable.class, "cause") + .addAnnotation(Nullable.class) + .build()); + methodBuilder.addCode(");"); + return methodBuilder.build(); + } +} diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ErrorGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ErrorGenerator.java index ee8f8efb3..bc0b826c1 100644 --- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ErrorGenerator.java +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ErrorGenerator.java @@ -18,43 +18,33 @@ import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Streams; import com.palantir.conjure.java.ConjureAnnotations; import com.palantir.conjure.java.Generator; import com.palantir.conjure.java.Options; import com.palantir.conjure.java.api.errors.ErrorType; import com.palantir.conjure.java.api.errors.RemoteException; import com.palantir.conjure.java.api.errors.ServiceException; -import com.palantir.conjure.java.util.Javadoc; +import com.palantir.conjure.java.util.ErrorGenerationUtils; +import com.palantir.conjure.java.util.ErrorGenerationUtils.DeclaredEndpointErrors; import com.palantir.conjure.java.util.Packages; import com.palantir.conjure.java.util.TypeFunctions; import com.palantir.conjure.spec.ConjureDefinition; import com.palantir.conjure.spec.ErrorDefinition; import com.palantir.conjure.spec.ErrorNamespace; -import com.palantir.conjure.spec.FieldDefinition; -import com.palantir.conjure.spec.LogSafety; import com.palantir.conjure.spec.TypeDefinition; -import com.palantir.javapoet.AnnotationSpec; import com.palantir.javapoet.ClassName; import com.palantir.javapoet.CodeBlock; import com.palantir.javapoet.FieldSpec; import com.palantir.javapoet.JavaFile; import com.palantir.javapoet.MethodSpec; -import com.palantir.javapoet.ParameterSpec; import com.palantir.javapoet.TypeName; import com.palantir.javapoet.TypeSpec; -import com.palantir.logsafe.SafeArg; -import com.palantir.logsafe.UnsafeArg; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.Nullable; import javax.lang.model.element.Modifier; -import org.apache.commons.lang3.StringUtils; public final class ErrorGenerator implements Generator { @@ -71,30 +61,15 @@ public Stream generate(ConjureDefinition definition) { Map types = TypeFunctions.toTypesMap(definition); TypeMapper typeMapper = new TypeMapper(types, options); SafetyEvaluator safetyEvaluator = new SafetyEvaluator(types); - return splitErrorDefsByNamespace(definition.getErrors()).entrySet().stream() - .flatMap(entry -> entry.getValue().entrySet().stream() - .map(innerEntry -> generateErrorTypesForNamespace( - typeMapper, - safetyEvaluator, - Packages.getPrefixedPackage(entry.getKey(), options.packagePrefix()), - innerEntry.getKey(), - innerEntry.getValue()))); - } - - private static Map>> splitErrorDefsByNamespace( - List errorTypeNameToDef) { - Map>> pkgToNamespacedErrorDefs = new HashMap<>(); - errorTypeNameToDef.forEach(errorDef -> { - String errorPkg = errorDef.getErrorName().getPackage(); - pkgToNamespacedErrorDefs.computeIfAbsent(errorPkg, key -> new HashMap<>()); - - Map> namespacedErrorDefs = pkgToNamespacedErrorDefs.get(errorPkg); - ErrorNamespace namespace = errorDef.getNamespace(); - // TODO(rfink): Use Multimap? - namespacedErrorDefs.computeIfAbsent(namespace, key -> new ArrayList<>()); - namespacedErrorDefs.get(namespace).add(errorDef); - }); - return pkgToNamespacedErrorDefs; + DeclaredEndpointErrors endpointErrors = DeclaredEndpointErrors.from(definition); + return ErrorGenerationUtils.getNamespacedErrorsFromDefinitions(definition.getErrors()).stream() + .flatMap(namespacedErrors -> Stream.of(generateErrorTypesForNamespace( + typeMapper, + safetyEvaluator, + endpointErrors, + Packages.getPrefixedPackage(namespacedErrors.javaPackage(), options.packagePrefix()), + namespacedErrors.namespace(), + namespacedErrors.errors()))); } private static ImmutableList generateErrorTypeFields( @@ -124,11 +99,15 @@ private static ImmutableList generateErrorTypeFields( private JavaFile generateErrorTypesForNamespace( TypeMapper typeMapper, SafetyEvaluator safetyEvaluator, + DeclaredEndpointErrors endpointErrors, String conjurePackage, ErrorNamespace namespace, List errorTypeDefinitions) { // Generate ServiceException factory methods List methodSpecs = errorTypeDefinitions.stream() + // Skip ServiceFactory method creation for errors defined in endpoints. Users should throw the checked + // service exception. + .filter(errorDefinition -> !endpointErrors.contains(errorDefinition)) .flatMap(entry -> { MethodSpec withoutCause = generateExceptionFactory(typeMapper, entry, false); MethodSpec withCause = generateExceptionFactory(typeMapper, entry, true); @@ -138,89 +117,15 @@ private JavaFile generateErrorTypesForNamespace( // Generate ServiceException factory check methods List checkMethodSpecs = errorTypeDefinitions.stream() - .map(entry -> { - String exceptionMethodName = CaseFormat.UPPER_CAMEL.to( - CaseFormat.LOWER_CAMEL, entry.getErrorName().getName()); - String methodName = "throwIf" + entry.getErrorName().getName(); - - String shouldThrowVar = "shouldThrow"; - - MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addJavadoc( - "Throws a {@link $T} of type $L when {@code $L} is true.\n", - ServiceException.class, - entry.getErrorName().getName(), - shouldThrowVar) - .addParameter(ParameterSpec.builder(TypeName.BOOLEAN, shouldThrowVar) - .addJavadoc("Cause the method to throw when true\n") - .build()) - .addParameters(Streams.concat( - entry.getSafeArgs().stream().map(field -> FieldDefinition.builder() - .from(field) - .safety(LogSafety.SAFE) - .build()), - entry.getUnsafeArgs().stream().map(field -> FieldDefinition.builder() - .from(field) - .safety(LogSafety.UNSAFE) - .build())) - .map(arg -> { - TypeName argumentTypeName = typeMapper.getClassName(arg.getType()); - Optional underlyingTypeSafety = - safetyEvaluator.getUsageTimeSafety(arg); - Optional typeSafety = safetyEvaluator.evaluate(arg.getType()); - if (!SafetyEvaluator.allows(underlyingTypeSafety, typeSafety)) { - throw new IllegalStateException(String.format( - "Cannot use %s type %s as a %s parameter in error %s -> %s", - typeSafety - .map(Object::toString) - .orElse("unknown"), - argumentTypeName, - underlyingTypeSafety - .map(Object::toString) - .orElse("unknown"), - entry.getErrorName().getName(), - arg.getFieldName())); - } - return ParameterSpec.builder( - argumentTypeName, - arg.getFieldName().get()) - .addAnnotations(ConjureAnnotations.safety(underlyingTypeSafety)) - .addJavadoc( - "$L", - StringUtils.appendIfMissing( - arg.getDocs() - .map(Javadoc::render) - .orElse(""), - "\n")) - .build(); - }) - .collect(ImmutableList.toImmutableList())); - if (options.jetbrainsContractAnnotations()) { - String contract = String.format( - "true%s -> fail", - ", _" - .repeat(entry.getSafeArgs().size() - + entry.getUnsafeArgs().size())); - methodBuilder.addAnnotation( - AnnotationSpec.builder(ClassName.get("org.jetbrains.annotations", "Contract")) - .addMember("value", "$S", contract) - .build()); - } - - return methodBuilder - .beginControlFlow("if ($N)", shouldThrowVar) - .addCode( - "throw $L;", - Expressions.localMethodCall( - exceptionMethodName, - Streams.concat(entry.getSafeArgs().stream(), entry.getUnsafeArgs().stream()) - .map(arg -> - arg.getFieldName().get()) - .collect(Collectors.toList()))) - .endControlFlow() - .build(); - }) + .filter(errorDefinition -> !endpointErrors.contains(errorDefinition)) + .map(entry -> ErrorGenerationUtils.conditionalStaticFactoryMethodBuilder( + typeMapper, + safetyEvaluator, + entry, + options, + ClassName.get(ServiceException.class), + Optional.of(entry.getErrorName().getName())) + .build()) .collect(Collectors.toList()); List isRemoteExceptionDefinitions = errorTypeDefinitions.stream() @@ -249,7 +154,7 @@ private JavaFile generateErrorTypesForNamespace( .collect(Collectors.toList()); TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(errorTypesClassName(conjurePackage, namespace)) - .addMethod(privateConstructor()) + .addMethod(ErrorGenerationUtils.privateConstructor()) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addFields(generateErrorTypeFields(namespace, errorTypeDefinitions)) .addMethods(methodSpecs) @@ -277,41 +182,18 @@ private static MethodSpec generateExceptionFactory( methodBuilder.addCode("return new $T($L", ServiceException.class, typeName); if (withCause) { - ParameterSpec causeParameter = ParameterSpec.builder(Throwable.class, "cause") - .addAnnotation(Nullable.class) - .build(); - methodBuilder.addParameter(causeParameter); - methodBuilder.addCode(", cause"); + methodBuilder.addCode(", "); + ErrorGenerationUtils.addNullableThrowableCauseParameterToMethodBuilder(methodBuilder); } - entry.getSafeArgs().forEach(arg -> processArg(typeMapper, methodBuilder, arg, true)); + ErrorGenerationUtils.addAllLogSafeArgumentsToMethodBuilder(typeMapper, entry, methodBuilder); - entry.getUnsafeArgs().forEach(arg -> processArg(typeMapper, methodBuilder, arg, false)); methodBuilder.addCode(");"); return methodBuilder.build(); } - private static void processArg( - TypeMapper typeMapper, MethodSpec.Builder methodBuilder, FieldDefinition argDefinition, boolean isSafe) { - Optional safety = Optional.of(isSafe ? LogSafety.SAFE : LogSafety.UNSAFE); - String argName = argDefinition.getFieldName().get(); - TypeName argType = ConjureAnnotations.withSafety(typeMapper.getClassName(argDefinition.getType()), safety); - ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(argType, argName); - argDefinition - .getDocs() - .ifPresent(docs -> - parameterBuilder.addJavadoc("$L", StringUtils.appendIfMissing(Javadoc.render(docs), "\n"))); - methodBuilder.addParameter(parameterBuilder.build()); - Class clazz = isSafe ? SafeArg.class : UnsafeArg.class; - methodBuilder.addCode(",\n $T.of($S, $L)", clazz, argName, argName); - } - - private static ClassName errorTypesClassName(String conjurePackage, ErrorNamespace namespace) { + static ClassName errorTypesClassName(String conjurePackage, ErrorNamespace namespace) { return ClassName.get(conjurePackage, namespace.get() + "Errors"); } - - private static MethodSpec privateConstructor() { - return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build(); - } } diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/util/ErrorGenerationUtils.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/util/ErrorGenerationUtils.java new file mode 100644 index 000000000..8afea775a --- /dev/null +++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/util/ErrorGenerationUtils.java @@ -0,0 +1,250 @@ +/* + * (c) Copyright 2024 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.util; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableList; +import com.palantir.conjure.java.ConjureAnnotations; +import com.palantir.conjure.java.Options; +import com.palantir.conjure.java.types.Expressions; +import com.palantir.conjure.java.types.SafetyEvaluator; +import com.palantir.conjure.java.types.TypeMapper; +import com.palantir.conjure.spec.ConjureDefinition; +import com.palantir.conjure.spec.EndpointError; +import com.palantir.conjure.spec.ErrorDefinition; +import com.palantir.conjure.spec.ErrorNamespace; +import com.palantir.conjure.spec.ErrorTypeName; +import com.palantir.conjure.spec.FieldDefinition; +import com.palantir.conjure.spec.LogSafety; +import com.palantir.javapoet.AnnotationSpec; +import com.palantir.javapoet.ClassName; +import com.palantir.javapoet.MethodSpec; +import com.palantir.javapoet.ParameterSpec; +import com.palantir.javapoet.TypeName; +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.UnsafeArg; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import javax.lang.model.element.Modifier; +import org.apache.commons.lang3.StringUtils; + +public final class ErrorGenerationUtils { + public static MethodSpec privateConstructor() { + return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build(); + } + + public static String errorExceptionsClassName(ErrorNamespace namespace) { + return namespace.get() + "ServerErrors"; + } + + public record DeclaredEndpointErrors(Set errors) { + public static DeclaredEndpointErrors from(ConjureDefinition definition) { + return new DeclaredEndpointErrors(definition.getServices().stream() + .flatMap(service -> service.getEndpoints().stream()) + .flatMap(endpoint -> endpoint.getErrors().stream()) + .map(EndpointError::getError) + .collect(Collectors.toSet())); + } + + public boolean contains(ErrorDefinition errorDefinition) { + com.palantir.conjure.spec.TypeName errorName = errorDefinition.getErrorName(); + ErrorTypeName errorTypeName = ErrorTypeName.builder() + .name(errorName.getName()) + .package_(errorName.getPackage()) + .namespace(errorDefinition.getNamespace()) + .build(); + return errors.contains(errorTypeName); + } + } + + public record NamespacedErrors(String javaPackage, ErrorNamespace namespace, List errors) {} + + public static List getNamespacedErrorsFromDefinitions(List errorTypeNameToDef) { + record ErrorGroup(String javaPackage, ErrorNamespace namespace) {} + Map> errorsByGroup = new HashMap<>(); + errorTypeNameToDef.forEach(errorDef -> { + ErrorGroup errorGroup = new ErrorGroup(errorDef.getErrorName().getPackage(), errorDef.getNamespace()); + errorsByGroup.computeIfAbsent(errorGroup, key -> new ArrayList<>()).add(errorDef); + }); + return errorsByGroup.entrySet().stream() + .map(entry -> { + ErrorGroup errorGroup = entry.getKey(); + return new NamespacedErrors(errorGroup.javaPackage(), errorGroup.namespace(), entry.getValue()); + }) + .collect(ImmutableList.toImmutableList()); + } + + public static void addNullableThrowableCauseParameterToMethodBuilder(MethodSpec.Builder methodBuilder) { + ParameterSpec causeParameter = ParameterSpec.builder(Throwable.class, "cause") + .addAnnotation(Nullable.class) + .build(); + methodBuilder.addParameter(causeParameter); + methodBuilder.addCode("cause"); + } + + public static void addAllLogSafeArgumentsToMethodBuilder( + TypeMapper typeMapper, ErrorDefinition errorDefinition, MethodSpec.Builder methodBuilder) { + errorDefinition + .getSafeArgs() + .forEach(arg -> + ErrorGenerationUtils.addLogSafeArgumentToMethodBuilder(typeMapper, methodBuilder, arg, true)); + errorDefinition + .getUnsafeArgs() + .forEach(arg -> + ErrorGenerationUtils.addLogSafeArgumentToMethodBuilder(typeMapper, methodBuilder, arg, false)); + } + + private static void addLogSafeArgumentToMethodBuilder( + TypeMapper typeMapper, MethodSpec.Builder methodBuilder, FieldDefinition argDefinition, boolean isSafe) { + String argName = argDefinition.getFieldName().get(); + methodBuilder.addParameter( + ErrorGenerationUtils.buildParameterWithSafetyAnnotation(typeMapper, argDefinition, isSafe)); + Class clazz = isSafe ? SafeArg.class : UnsafeArg.class; + methodBuilder.addCode(",\n $T.of($S, $L)", clazz, argName, argName); + } + + public static void addAllParametersWithSafetyAnnotationsToMethodBuilder( + TypeMapper typeMapper, MethodSpec.Builder methodBuilder, ErrorDefinition errorDefinition) { + errorDefinition + .getSafeArgs() + .forEach(arg -> methodBuilder.addParameter( + ErrorGenerationUtils.buildParameterWithSafetyAnnotation(typeMapper, arg, true))); + errorDefinition + .getUnsafeArgs() + .forEach(arg -> methodBuilder.addParameter( + ErrorGenerationUtils.buildParameterWithSafetyAnnotation(typeMapper, arg, false))); + } + + public static ParameterSpec buildParameterWithSafetyAnnotation( + TypeMapper typeMapper, FieldDefinition argDefinition, boolean isSafe) { + Optional safety = Optional.of(isSafe ? LogSafety.SAFE : LogSafety.UNSAFE); + String argName = argDefinition.getFieldName().get(); + TypeName argType = ConjureAnnotations.withSafety(typeMapper.getClassName(argDefinition.getType()), safety); + ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(argType, argName); + argDefinition + .getDocs() + .ifPresent(docs -> + parameterBuilder.addJavadoc("$L", StringUtils.appendIfMissing(Javadoc.render(docs), "\n"))); + return parameterBuilder.build(); + } + + // Conditional factory method + public static MethodSpec.Builder conditionalStaticFactoryMethodBuilder( + TypeMapper typeMapper, + SafetyEvaluator safetyEvaluator, + ErrorDefinition errorDefinition, + Options options, + ClassName exceptionClassThrown, + Optional errorType) { + String exceptionMethodName = CaseFormat.UPPER_CAMEL.to( + CaseFormat.LOWER_CAMEL, errorDefinition.getErrorName().getName()); + String methodName = "throwIf" + errorDefinition.getErrorName().getName(); + String shouldThrowVar = "shouldThrow"; + + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(ParameterSpec.builder(TypeName.BOOLEAN, shouldThrowVar) + .addJavadoc("Cause the method to throw when true\n") + .build()) + .addParameters(ErrorGenerationUtils.createParametersForConditionalStaticFactory( + typeMapper, safetyEvaluator, errorDefinition)); + + if (options.jetbrainsContractAnnotations()) { + methodBuilder.addAnnotation( + ErrorGenerationUtils.createContractAnnotationForConditionalFactory(errorDefinition)); + } + + methodBuilder + .beginControlFlow("if ($N)", shouldThrowVar) + .addCode( + "throw $L;", + Expressions.localMethodCall( + exceptionMethodName, + Stream.concat( + errorDefinition.getSafeArgs().stream(), + errorDefinition.getUnsafeArgs().stream()) + .map(arg -> arg.getFieldName().get()) + .collect(Collectors.toList()))); + if (errorType.isPresent()) { + methodBuilder.addJavadoc( + "Throws a {@link $T} of type $L when {@code $L} is true.\n", + exceptionClassThrown, + errorType.get(), + shouldThrowVar); + } else { + methodBuilder.addJavadoc( + "Throws a {@link $T} when {@code $L} is true.\n", exceptionClassThrown, shouldThrowVar); + } + return methodBuilder.endControlFlow(); + } + + private static List createParametersForConditionalStaticFactory( + TypeMapper typeMapper, SafetyEvaluator safetyEvaluator, ErrorDefinition errorDefinition) { + return Stream.concat( + errorDefinition.getSafeArgs().stream().map(field -> FieldDefinition.builder() + .from(field) + .safety(LogSafety.SAFE) + .build()), + errorDefinition.getUnsafeArgs().stream().map(field -> FieldDefinition.builder() + .from(field) + .safety(LogSafety.UNSAFE) + .build())) + .map(arg -> { + TypeName argumentTypeName = typeMapper.getClassName(arg.getType()); + Optional underlyingTypeSafety = safetyEvaluator.getUsageTimeSafety(arg); + Optional typeSafety = safetyEvaluator.evaluate(arg.getType()); + if (!SafetyEvaluator.allows(underlyingTypeSafety, typeSafety)) { + throw new IllegalStateException(String.format( + "Cannot use %s type %s as a %s parameter in error %s -> %s", + typeSafety.map(Object::toString).orElse("unknown"), + argumentTypeName, + underlyingTypeSafety.map(Object::toString).orElse("unknown"), + errorDefinition.getErrorName().getName(), + arg.getFieldName())); + } + return ParameterSpec.builder( + argumentTypeName, arg.getFieldName().get()) + .addAnnotations(ConjureAnnotations.safety(underlyingTypeSafety)) + .addJavadoc( + "$L", + StringUtils.appendIfMissing( + arg.getDocs().map(Javadoc::render).orElse(""), "\n")) + .build(); + }) + .collect(ImmutableList.toImmutableList()); + } + + private static AnnotationSpec createContractAnnotationForConditionalFactory(ErrorDefinition errorDefinition) { + String contract = String.format( + "true%s -> fail", + ", _" + .repeat(errorDefinition.getSafeArgs().size() + + errorDefinition.getUnsafeArgs().size())); + return AnnotationSpec.builder(ClassName.get("org.jetbrains.annotations", "Contract")) + .addMember("value", "$S", contract) + .build(); + } + + private ErrorGenerationUtils() {} +} diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java index d79009fd6..6c8eef89f 100644 --- a/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java +++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/UndertowServiceEteTest.java @@ -37,6 +37,8 @@ import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.conjure.java.serialization.ObjectMappers; import com.palantir.conjure.java.services.UndertowServiceGenerator; +import com.palantir.conjure.java.types.CheckedErrorGenerator; +import com.palantir.conjure.java.types.ErrorGenerator; import com.palantir.conjure.java.types.ObjectGenerator; import com.palantir.conjure.java.undertow.runtime.ConjureHandler; import com.palantir.conjure.spec.ConjureDefinition; @@ -68,6 +70,7 @@ import java.net.InetSocketAddress; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; @@ -86,6 +89,7 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +// MARK(pm). @Execution(ExecutionMode.CONCURRENT) public final class UndertowServiceEteTest extends TestBase { private static final ObjectMapper CLIENT_OBJECT_MAPPER = ObjectMappers.newClientObjectMapper(); @@ -558,7 +562,8 @@ public static void beforeClass() throws IOException { new File("src/test/resources/ete-service.yml"), new File("src/test/resources/ete-binary.yml"), new File("src/test/resources/alias-test-service.yml"), - new File("src/test/resources/external-long-test-service.yml"))); + new File("src/test/resources/external-long-test-service.yml"), + new File("src/test/resources/example-endpoint-errors.yml"))); Options options = Options.builder() .undertowServicePrefix(true) .nonNullCollections(true) @@ -567,9 +572,26 @@ public static void beforeClass() throws IOException { .build(); List files = new GenerationCoordinator( MoreExecutors.directExecutor(), - ImmutableSet.of(new UndertowServiceGenerator(options), new ObjectGenerator(options))) + ImmutableSet.of( + new UndertowServiceGenerator(options), + new ObjectGenerator(options), + new ErrorGenerator(options), + new CheckedErrorGenerator(options))) .emit(def, folder); - validateGeneratorOutput(files, Paths.get("src/integrationInput/java/com/palantir/product")); + validateGeneratedOutput(files, Paths.get("src/integrationInput/java")); + } + + private static void validateGeneratedOutput(List files, Path outputDir) throws IOException { + for (Path file : files) { + Path relativePath = folder.toPath().relativize(file); + Path output = outputDir.resolve(relativePath); + if (Boolean.valueOf(System.getProperty("recreate", "false"))) { + Files.createDirectories(relativePath.getParent()); + Files.deleteIfExists(output); + Files.copy(file, output); + } + assertThat(readFromFile(file)).isEqualTo(readFromFile(output)); + } } private static HttpURLConnection openConnectionToTestApi(String path) throws IOException { diff --git a/conjure-java-core/src/test/resources/errors-for-endpoints.yml b/conjure-java-core/src/test/resources/errors-for-endpoints.yml new file mode 100644 index 000000000..6788dc0f5 --- /dev/null +++ b/conjure-java-core/src/test/resources/errors-for-endpoints.yml @@ -0,0 +1,22 @@ +types: + definitions: + default-package: com.palantir.product + errors: + EndpointError: + namespace: EndpointSpecific + code: INVALID_ARGUMENT + docs: Docs for an endpoint error. + safe-args: + typeName: string + unsafe-args: + typeDef: any + DifferentPackage: + package: com.palantir.another + namespace: EndpointSpecific + docs: An error in a different package. + code: INTERNAL + DifferentNamespace: + package: com.palantir.product + namespace: EndpointSpecificTwo + docs: An error in a different namespace. + code: INTERNAL diff --git a/conjure-java-core/src/test/resources/example-endpoint-errors.yml b/conjure-java-core/src/test/resources/example-endpoint-errors.yml new file mode 100644 index 000000000..7f686c164 --- /dev/null +++ b/conjure-java-core/src/test/resources/example-endpoint-errors.yml @@ -0,0 +1,44 @@ +types: + conjure-imports: + importedErrors: errors-for-endpoints.yml + definitions: + default-package: com.palantir.product + errors: + InvalidArgument: + namespace: Test + code: INVALID_ARGUMENT + safe-args: + field: string + unsafe-args: + value: string + NotFound: + namespace: Test + code: NOT_FOUND + safe-args: + resource: string +services: + ErrorService: + name: Error Service + package: com.palantir.product + default-auth: header + base-path: /base + endpoints: + testBasicError: + http: GET /basic + returns: string + errors: + - InvalidArgument + testImportedError: + http: GET /imported + returns: string + errors: + - importedErrors.EndpointError + testMultipleErrorsAndPackages: + http: GET /multiple + returns: string + errors: + - InvalidArgument + - error: NotFound + docs: Something was not found. + - importedErrors.DifferentNamespace + - importedErrors.DifferentPackage diff --git a/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java b/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java index 53dc6512f..e76a2d38a 100644 --- a/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java +++ b/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java @@ -29,6 +29,7 @@ import com.palantir.conjure.java.services.JerseyServiceGenerator; import com.palantir.conjure.java.services.UndertowServiceGenerator; import com.palantir.conjure.java.services.dialogue.DialogueServiceGenerator; +import com.palantir.conjure.java.types.CheckedErrorGenerator; import com.palantir.conjure.java.types.ErrorGenerator; import com.palantir.conjure.java.types.ObjectGenerator; import com.palantir.conjure.spec.ConjureDefinition; @@ -254,7 +255,9 @@ public void run() { generatorBuilder.add(new JerseyServiceGenerator(config.options())); } if (config.generateUndertow()) { - generatorBuilder.add(new UndertowServiceGenerator(config.options())); + generatorBuilder.add( + new UndertowServiceGenerator(config.options()), + new CheckedErrorGenerator(config.options())); } if (config.generateDialogue()) { generatorBuilder.add(new DialogueServiceGenerator(config.options())); diff --git a/versions.lock b/versions.lock index 953766a6e..d2429bcbe 100644 --- a/versions.lock +++ b/versions.lock @@ -2,28 +2,28 @@ com.atlassian.commonmark:commonmark:0.12.1 (1 constraints: 36052a3b) com.fasterxml.jackson.core:jackson-annotations:2.18.1 (24 constraints: 62955f13) com.fasterxml.jackson.core:jackson-core:2.18.1 (22 constraints: 25c18302) -com.fasterxml.jackson.core:jackson-databind:2.18.1 (35 constraints: f99a16ff) +com.fasterxml.jackson.core:jackson-databind:2.18.1 (35 constraints: ff9ae617) com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.18.1 (4 constraints: e9653c50) com.fasterxml.jackson.dataformat:jackson-dataformat-smile:2.18.1 (1 constraints: 811ca2a4) -com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1 (3 constraints: 3b25da0f) +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.1 (3 constraints: 3e257510) com.fasterxml.jackson.datatype:jackson-datatype-guava:2.18.1 (3 constraints: f839a026) -com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.1 (4 constraints: 2c3f6790) +com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.1 (4 constraints: 2f3fda91) com.fasterxml.jackson.datatype:jackson-datatype-joda:2.18.1 (2 constraints: 322b47b0) com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.1 (2 constraints: 322b47b0) com.github.ben-manes.caffeine:caffeine:3.1.8 (10 constraints: 63b008f3) com.google.auto:auto-common:1.2.2 (2 constraints: 1d175731) com.google.code.findbugs:jsr305:3.0.2 (35 constraints: b94940f6) -com.google.errorprone:error_prone_annotations:2.7.1 (27 constraints: afc2edbf) +com.google.errorprone:error_prone_annotations:2.7.1 (27 constraints: b7c2d6e1) com.google.guava:failureaccess:1.0.2 (1 constraints: 150ae2b4) -com.google.guava:guava:33.3.1-jre (36 constraints: 659b358d) +com.google.guava:guava:33.3.1-jre (36 constraints: 2b9c8d13) com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava (1 constraints: bd17c918) com.google.j2objc:j2objc-annotations:3.0.0 (1 constraints: 150aeab4) com.palantir.common:streams:2.3.0 (1 constraints: 0705fe35) -com.palantir.conjure:conjure-api-objects:4.36.0 (3 constraints: 9f27d521) -com.palantir.conjure:conjure-generator-common:4.36.0 (2 constraints: 05146783) -com.palantir.conjure.java.api:errors:2.54.0 (6 constraints: df71bda8) -com.palantir.conjure.java.api:service-config:2.54.0 (6 constraints: 3366ded6) -com.palantir.conjure.java.api:ssl-config:2.54.0 (5 constraints: b24f2a2c) +com.palantir.conjure:conjure-api-objects:4.50.0-rc2 (3 constraints: 2f2b18f2) +com.palantir.conjure:conjure-generator-common:4.50.0-rc2 (2 constraints: 65162626) +com.palantir.conjure.java.api:errors:2.56.0-rc1 (6 constraints: 497456c9) +com.palantir.conjure.java.api:service-config:2.56.0-rc1 (6 constraints: 6867583d) +com.palantir.conjure.java.api:ssl-config:2.56.0-rc1 (5 constraints: 1c52cc6a) com.palantir.conjure.java.runtime:client-config:8.9.0 (6 constraints: 9e6a9c2e) com.palantir.conjure.java.runtime:conjure-java-jackson-optimizations:8.9.0 (1 constraints: 571c4b88) com.palantir.conjure.java.runtime:conjure-java-jackson-serialization:8.9.0 (4 constraints: 6b46b1dd) @@ -65,7 +65,7 @@ javax.annotation:javax.annotation-api:1.3.2 (7 constraints: 396987f4) javax.inject:javax.inject:1 (12 constraints: b6c96528) javax.ws.rs:javax.ws.rs-api:2.1.1 (14 constraints: b4f4b54a) joda-time:joda-time:2.12.7 (3 constraints: c329a724) -org.apache.commons:commons-lang3:3.14.0 (4 constraints: 483074c5) +org.apache.commons:commons-lang3:3.17.0 (4 constraints: 4d301dc7) org.apache.httpcomponents.client5:httpclient5:5.3.1 (1 constraints: cd13996e) org.apache.httpcomponents.core5:httpcore5:5.2.4 (3 constraints: 6639870e) org.apache.httpcomponents.core5:httpcore5-h2:5.2.4 (1 constraints: 3f130d3c) @@ -81,9 +81,9 @@ org.jboss.logging:jboss-logging:3.4.1.Final (4 constraints: a24557a8) org.jboss.threads:jboss-threads:3.5.0.Final (3 constraints: b52a2fe5) org.jboss.xnio:xnio-api:3.8.16.Final (2 constraints: d71a2474) org.jboss.xnio:xnio-nio:3.8.16.Final (1 constraints: f80dc53d) -org.jetbrains:annotations:24.1.0 (4 constraints: ff397696) +org.jetbrains:annotations:24.1.0 (4 constraints: 013a3c97) org.mpierce.metrics.reservoir:hdrhistogram-metrics-reservoir:1.1.3 (1 constraints: 0d10f991) -org.slf4j:slf4j-api:1.7.36 (26 constraints: 1471d65d) +org.slf4j:slf4j-api:1.7.36 (26 constraints: 07714e2c) org.slf4j:slf4j-simple:1.7.36 (1 constraints: 43054b3b) org.wildfly.client:wildfly-client-config:1.0.1.Final (1 constraints: 940c6308) org.wildfly.common:wildfly-common:1.7.0.Final (3 constraints: 9523cba3) @@ -107,8 +107,8 @@ com.google.truth:truth:1.1.3 (1 constraints: 18120efb) com.helger:profiler:1.1.1 (1 constraints: e21053b8) com.netflix.feign:feign-core:8.18.0 (3 constraints: de3f76e0) com.netflix.feign:feign-jackson:8.18.0 (1 constraints: c718909e) -com.palantir.conjure:conjure-core:4.36.0 (1 constraints: 3f05553b) -com.palantir.conjure.java.api:test-utils:2.54.0 (1 constraints: 3d054b3b) +com.palantir.conjure:conjure-core:4.50.0-rc2 (1 constraints: 6f063953) +com.palantir.conjure.java.api:test-utils:2.56.0-rc1 (1 constraints: 72064e53) com.palantir.conjure.java.runtime:conjure-java-annotations:8.9.0 (1 constraints: 9718cf85) com.palantir.conjure.java.runtime:conjure-java-jaxrs-client:8.9.0 (1 constraints: 11052836) com.palantir.conjure.java.runtime:conjure-java-jersey-server:8.9.0 (1 constraints: 11052836) diff --git a/versions.props b/versions.props index 2e5ac2470..474f202e2 100644 --- a/versions.props +++ b/versions.props @@ -6,10 +6,10 @@ com.google.code.findbugs:jsr305 = 3.0.2 com.google.guava:guava = 33.1.0-jre com.google.testing.compile:compile-testing = 0.21.0 com.palantir.common:streams = 2.3.0 -com.palantir.conjure.java.api:* = 2.54.0 +com.palantir.conjure.java.api:* = 2.56.0-rc1 com.palantir.conjure.java.runtime:* = 8.7.0 com.palantir.conjure.verification:* = 0.19.0 -com.palantir.conjure:* = 4.36.0 +com.palantir.conjure:* = 4.50.0-rc2 com.palantir.dialogue:* = 3.135.0 com.palantir.goethe:* = 0.14.0 com.palantir.human-readable-types:* = 1.7.0