diff --git a/.palantir/revapi.yml b/.palantir/revapi.yml index 50d35298f..a7ba76966 100644 --- a/.palantir/revapi.yml +++ b/.palantir/revapi.yml @@ -91,3 +91,216 @@ acceptedBreaks: old: "method void com.palantir.conjure.java.client.config.ClientConfiguration::checkTimeoutPrecision(java.time.Duration,\ \ java.lang.String)" justification: "Removed internal method" + com.palantir.conjure.java.runtime:conjure-java-jaxrs-client: + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateEncoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.EmptyContainerDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.EndpointNameHeaderEnrichmentContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaEmptyOptionalExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaNullOptionalExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.HeaderAccessUtils" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateEncoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8EmptyOptionalDoubleExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8EmptyOptionalExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8EmptyOptionalIntExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8EmptyOptionalLongExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8NullOptionalDoubleExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8NullOptionalExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8NullOptionalIntExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8NullOptionalLongExpander" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.MethodHeaderEnrichmentContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.NeverReturnNullDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.PathTemplateHeaderEnrichmentContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.QosErrorDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.SlashEncodingContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateEncoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureCborDelegateDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureCborDelegateEncoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureEmptyContainerDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureGuavaOptionalAwareDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureInputStreamDelegateDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureInputStreamDelegateEncoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureJava8OptionalAwareDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureNeverReturnNullDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureTextDelegateDecoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "class feign.ConjureTextDelegateEncoder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "enum com.palantir.conjure.java.client.jaxrs.feignimpl.NeverRetryingBackoffStrategy" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "enum com.palantir.conjure.java.client.jaxrs.feignimpl.PathTemplateHeaderRewriter" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.removed" + old: "interface com.palantir.conjure.java.client.jaxrs.feignimpl.BackoffStrategy" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.visibilityReduced" + old: "class com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + new: "class com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.visibilityReduced" + old: "class com.palantir.conjure.java.client.jaxrs.FeignJaxRsClientBuilder" + new: "class com.palantir.conjure.java.client.jaxrs.FeignJaxRsClientBuilder" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.visibilityReduced" + old: "class com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility" + new: "class com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility" + justification: "Removing Feign dependency and making internals private" + - code: "java.class.visibilityReduced" + old: "enum com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations" + new: "enum com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.removed" + old: "method feign.MethodMetadata feign.Contract.BaseContract::parseAndValidatateMetadata(java.lang.reflect.Method)\ + \ @ com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.removed" + old: "method java.util.Collection feign.Contract.BaseContract::addTemplatedParam(java.util.Collection,\ + \ java.lang.String) @ com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.removed" + old: "method java.util.List feign.Contract.BaseContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.removed" + old: "method void feign.Contract.BaseContract::nameParam(feign.MethodMetadata,\ + \ java.lang.String, int) @ com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method boolean com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::processAnnotationsOnParameter(feign.MethodMetadata,\ + \ java.lang.annotation.Annotation[], int)" + new: "method boolean com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::processAnnotationsOnParameter(com.palantir.conjure.java.client.jaxrs.MethodMetadata,\ + \ java.lang.annotation.Annotation[], int)" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method boolean com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations::matches(java.lang.Class)" + new: "method boolean com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations::matches(java.lang.Class)" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method boolean com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations::matches(java.util.Set>)" + new: "method boolean com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations::matches(java.util.Set>)" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsClientBuilder::getCborObjectMapper()" + new: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsClientBuilder::getCborObjectMapper()" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsClientBuilder::getObjectMapper()" + new: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsClientBuilder::getObjectMapper()" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method java.lang.annotation.Annotation com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations::getAnnotation(java.lang.reflect.AnnotatedElement)" + new: "method java.lang.annotation.Annotation com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations::getAnnotation(java.lang.reflect.AnnotatedElement)" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method void com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::()" + new: "method void com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::()" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method void com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::processAnnotationOnClass(feign.MethodMetadata,\ + \ java.lang.Class)" + new: "method void com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::processAnnotationOnClass(com.palantir.conjure.java.client.jaxrs.MethodMetadata,\ + \ java.lang.Class)" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method void com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::processAnnotationOnMethod(feign.MethodMetadata,\ + \ java.lang.annotation.Annotation, java.lang.reflect.Method)" + new: "method void com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::processAnnotationOnMethod(com.palantir.conjure.java.client.jaxrs.MethodMetadata,\ + \ java.lang.annotation.Annotation, java.lang.reflect.Method)" + justification: "Removing Feign dependency and making internals private" + com.palantir.conjure.java.runtime:conjure-scala-jaxrs-client: + - code: "java.class.visibilityReduced" + old: "class com.palantir.conjure.java.client.jaxrs.FeignJaxRsScalaClientBuilder" + new: "class com.palantir.conjure.java.client.jaxrs.FeignJaxRsScalaClientBuilder" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsScalaClientBuilder::getCborObjectMapper()" + new: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsScalaClientBuilder::getCborObjectMapper()" + justification: "Removing Feign dependency and making internals private" + - code: "java.method.visibilityReduced" + old: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsScalaClientBuilder::getObjectMapper()" + new: "method com.fasterxml.jackson.databind.ObjectMapper com.palantir.conjure.java.client.jaxrs.FeignJaxRsScalaClientBuilder::getObjectMapper()" + justification: "Removing Feign dependency and making internals private" diff --git a/conjure-java-jaxrs-client/build.gradle b/conjure-java-jaxrs-client/build.gradle index 257c6a170..93e7417e5 100644 --- a/conjure-java-jaxrs-client/build.gradle +++ b/conjure-java-jaxrs-client/build.gradle @@ -7,11 +7,6 @@ dependencies { api project(":client-config") api project(":conjure-java-legacy-clients") api "com.google.code.findbugs:jsr305" - // TODO(dsanduleac): Should be implementation, but can't because we expose feign.TextDelegateEncoder - api "com.netflix.feign:feign-core", { - // prefer jakarta.ws.rs:jakarta.ws.rs-api - exclude group: 'javax.ws.rs', module: 'javax.ws.rs-api' - } api "com.palantir.dialogue:dialogue-target" implementation project(":conjure-java-annotations") @@ -35,11 +30,9 @@ dependencies { implementation project(":conjure-java-jackson-serialization") implementation "com.google.guava:guava" implementation "com.github.ben-manes.caffeine:caffeine" - implementation "com.netflix.feign:feign-jackson" testImplementation project(":conjure-java-jersey-jakarta-server") testImplementation project(':keystores') testImplementation project(':undertow-jakarta-testing') - testImplementation "com.netflix.feign:feign-jackson" testImplementation "com.squareup.okhttp3:mockwebserver" testImplementation "org.junit.jupiter:junit-jupiter" testImplementation 'org.junit.jupiter:junit-jupiter-api' @@ -48,11 +41,3 @@ dependencies { testImplementation "org.mockito:mockito-junit-jupiter" testImplementation "com.palantir.safe-logging:preconditions-assertj" } - -// feign.DefaultMethodHandler sets Lookup.IMPL_LOOKUP accessible resulting in -// InaccessibleObjectException: Unable to make field static final java.lang.invoke.MethodHandles$Lookup -// java.lang.invoke.MethodHandles$Lookup.IMPL_LOOKUP accessible: module java.base does not -// "opens java.lang.invoke" to unnamed module -moduleJvmArgs { - opens 'java.base/java.lang.invoke' -} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/AbstractDelegatingContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/AbstractDelegatingContract.java similarity index 74% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/AbstractDelegatingContract.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/AbstractDelegatingContract.java index 4dcabca29..756257c93 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/AbstractDelegatingContract.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/AbstractDelegatingContract.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,11 +14,8 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; -import feign.Contract; -import feign.Feign; -import feign.MethodMetadata; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.List; @@ -26,7 +23,7 @@ /** * Base class that provides the structure for a delegating {@link Contract}. Delegates the initial - * {@link #parseAndValidatateMetadata(Class)} call to the wrapped Contract and then calls {@link #processMetadata(Class, + * {@link #parseAndValidateMetadata(Class)} call to the wrapped Contract and then calls {@link #processMetadata(Class, * Method, MethodMetadata)} on all of the methods that have metadata from the initial call. */ abstract class AbstractDelegatingContract implements Contract { @@ -38,8 +35,8 @@ abstract class AbstractDelegatingContract implements Contract { } @Override - public final List parseAndValidatateMetadata(Class targetType) { - List mdList = delegate.parseAndValidatateMetadata(targetType); + public final List parseAndValidateMetadata(Class targetType) { + List mdList = delegate.parseAndValidateMetadata(targetType); Map methodMetadataByConfigKey = new LinkedHashMap(); for (MethodMetadata md : mdList) { @@ -60,5 +57,5 @@ public final List parseAndValidatateMetadata(Class targetType return mdList; } - protected abstract void processMetadata(Class targetType, Method method, MethodMetadata metadata); + abstract void processMetadata(Class targetType, Method method, MethodMetadata metadata); } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/AbstractFeignJaxRsClientBuilder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/AbstractFeignJaxRsClientBuilder.java index f757faa83..577b1f9b8 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/AbstractFeignJaxRsClientBuilder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/AbstractFeignJaxRsClientBuilder.java @@ -21,21 +21,6 @@ import com.palantir.conjure.java.annotations.JaxRsServer; import com.palantir.conjure.java.api.config.service.UserAgent; import com.palantir.conjure.java.client.config.ClientConfiguration; -import com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateDecoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateEncoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.EmptyContainerDecoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.EndpointNameHeaderEnrichmentContract; -import com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareContract; -import com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareDecoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateDecoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateEncoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareContract; -import com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareDecoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.MethodHeaderEnrichmentContract; -import com.palantir.conjure.java.client.jaxrs.feignimpl.NeverReturnNullDecoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.SlashEncodingContract; -import com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateDecoder; -import com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateEncoder; import com.palantir.conjure.java.dialogue.serde.DefaultConjureRuntime; import com.palantir.conjure.java.okhttp.HostEventsSink; import com.palantir.dialogue.Channel; @@ -46,17 +31,6 @@ import com.palantir.logsafe.Safe; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; -import feign.Contract; -import feign.Feign; -import feign.Logger; -import feign.Request; -import feign.RequestTemplate; -import feign.Retryer; -import feign.Target; -import feign.codec.Decoder; -import feign.codec.Encoder; -import feign.jackson.JacksonDecoder; -import java.util.Objects; /** Not meant to be implemented outside of this library. */ abstract class AbstractFeignJaxRsClientBuilder { @@ -72,9 +46,9 @@ abstract class AbstractFeignJaxRsClientBuilder { this.config = config; } - protected abstract ObjectMapper getObjectMapper(); + abstract ObjectMapper getObjectMapper(); - protected abstract ObjectMapper getCborObjectMapper(); + abstract ObjectMapper getCborObjectMapper(); /** Set the host metrics registry to use when constructing the OkHttp client. */ final AbstractFeignJaxRsClientBuilder hostEventsSink(HostEventsSink newHostEventsSink) { @@ -116,8 +90,6 @@ static T create( .decoder(createDecoder(clientNameForLogging, jsonMapper, cborMapper)) .errorDecoder(new DialogueFeignClient.RemoteExceptionDecoder(runtime)) .client(new DialogueFeignClient(serviceClass, channel, runtime, FeignDialogueTarget.BASE_URL)) - .logLevel(Logger.Level.NONE) // we use Dialogue for logging. (note that NONE is the default) - .retryer(new Retryer.Default(0, 0, 1)) // use dialogue retry mechanism only .target(new FeignDialogueTarget<>(serviceClass, channel)); } @@ -126,57 +98,25 @@ static T create( * target. However, there's a great deal of other configuration, and we handle failover/retries in Dialogue * which makes every client appear to use the same URL. */ - private static final class FeignDialogueTarget implements Target { + private record FeignDialogueTarget(Class serviceClass, Channel channel) implements Target { private static final String BASE_URL = "dialogue://feign"; - private final Class serviceClass; - private final Target delegate; - // For equality checks - private final Channel channel; - - FeignDialogueTarget(Class serviceClass, Channel channel) { - this.serviceClass = serviceClass; - this.channel = channel; - this.delegate = new HardCodedTarget<>(serviceClass, BASE_URL); - } - @Override public Class type() { return serviceClass; } - @Override - public String name() { - return delegate.name(); - } - @Override public String url() { - return delegate.url(); + return BASE_URL; } @Override public Request apply(RequestTemplate input) { - return delegate.apply(input); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; + if (input.url().indexOf("http") != 0) { + input.insert(0, url()); } - FeignDialogueTarget that = (FeignDialogueTarget) other; - return serviceClass.equals(that.serviceClass) - && delegate.equals(that.delegate) - && channel.equals(that.channel); - } - - @Override - public int hashCode() { - return Objects.hash(serviceClass, delegate, channel); + return input.request(); } } @@ -205,7 +145,7 @@ private static Decoder createDecoder( private static Encoder createEncoder( @Safe String clientNameForLogging, ObjectMapper jsonMapper, ObjectMapper cborMapper) { - Encoder encoder = new ConjureFeignJacksonEncoder(jsonMapper); + Encoder encoder = new JacksonEncoder(jsonMapper); encoder = new CborDelegateEncoder(cborMapper, encoder); encoder = new TextDelegateEncoder(encoder); encoder = new InputStreamDelegateEncoder(clientNameForLogging, encoder); diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CborDelegateDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CborDelegateDecoder.java similarity index 88% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CborDelegateDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CborDelegateDecoder.java index 830ef1d76..894d5dcde 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CborDelegateDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CborDelegateDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,15 +14,12 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.net.HttpHeaders; -import feign.FeignException; -import feign.Response; -import feign.codec.Decoder; import java.io.IOException; import java.io.PushbackInputStream; import java.lang.reflect.Type; @@ -37,18 +34,18 @@ *

Ideally we'll codegen a client which handles the content-type switching where necessary (multiple possible * response Content-Types from the server) and does not do the checking where this is known at compile time. */ -public final class CborDelegateDecoder implements Decoder { +final class CborDelegateDecoder implements Decoder { private final ObjectMapper cborMapper; private final Decoder delegate; - public CborDelegateDecoder(ObjectMapper cborMapper, Decoder delegate) { + CborDelegateDecoder(ObjectMapper cborMapper, Decoder delegate) { this.cborMapper = cborMapper; this.delegate = delegate; } @Override - public Object decode(Response response, Type type) throws IOException, FeignException { + public Object decode(Response response, Type type) throws IOException { Collection contentTypes = HeaderAccessUtils.caseInsensitiveGet(response.headers(), HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CborDelegateEncoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CborDelegateEncoder.java similarity index 82% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CborDelegateEncoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CborDelegateEncoder.java index 2f62e6c11..7fbc6c1e3 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CborDelegateEncoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CborDelegateEncoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,19 +14,15 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.codec.Encoder; import java.io.UncheckedIOException; import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; import java.util.Collection; /** @@ -38,20 +34,20 @@ *

In the future we will likely codegen the client and thus remove the need for scanning the headers on every * request. */ -public final class CborDelegateEncoder implements Encoder { +final class CborDelegateEncoder implements Encoder { public static final String MIME_TYPE = "application/cbor"; private final ObjectMapper cborMapper; private final Encoder delegate; - public CborDelegateEncoder(ObjectMapper cborMapper, Encoder delegate) { + CborDelegateEncoder(ObjectMapper cborMapper, Encoder delegate) { this.cborMapper = cborMapper; this.delegate = delegate; } @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + public void encode(Object object, Type bodyType, RequestTemplate template) { Collection contentTypes = HeaderAccessUtils.caseInsensitiveGet(template.headers(), HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { @@ -65,7 +61,7 @@ public void encode(Object object, Type bodyType, RequestTemplate template) throw try { JavaType javaType = cborMapper.getTypeFactory().constructType(bodyType); - template.body(cborMapper.writerFor(javaType).writeValueAsBytes(object), StandardCharsets.UTF_8); + template.body(cborMapper.writerFor(javaType).writeValueAsBytes(object)); } catch (JsonProcessingException e) { throw new UncheckedIOException(e); } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Client.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Client.java new file mode 100644 index 000000000..5f78c1997 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Client.java @@ -0,0 +1,33 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.io.IOException; + +/** + * Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe. + */ +interface Client { + + /** + * Executes a request against its {@link Request#url() url} and returns a response. + * + * @param request safe to replay. + * @return connected response, {@link Response.Body} is absent or unread + * @throws IOException on a network error connecting to {@link Request#url()}. + */ + Response execute(Request request) throws IOException; +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CompatibleJaxRsContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CompatibleJaxRsContract.java index 42361492a..9fa0936d5 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CompatibleJaxRsContract.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/CompatibleJaxRsContract.java @@ -21,8 +21,6 @@ import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalStateException; -import feign.Contract; -import feign.MethodMetadata; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collection; @@ -34,10 +32,10 @@ * JAXRSContract.java which is licensed under Apache 2. * We have modified the implementation to handle both jaxrs and jakarta annotations, easing migrations. */ -public final class CompatibleJaxRsContract extends Contract.BaseContract { +final class CompatibleJaxRsContract extends Contract.BaseContract { @Override - protected void processAnnotationOnClass(MethodMetadata data, Class clz) { + void processAnnotationOnClass(MethodMetadata data, Class clz) { Annotation path = Annotations.PATH.getAnnotation(clz); if (path != null) { String pathValue = Strings.emptyToNull(getAnnotationValue(path)); @@ -63,7 +61,7 @@ protected void processAnnotationOnClass(MethodMetadata data, Class clz) { } @Override - protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { + void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { Class annotationType = methodAnnotation.annotationType(); Annotation http = Annotations.HTTP_METHOD.getAnnotation(annotationType); if (http != null) { @@ -112,7 +110,7 @@ private void handleConsumesAnnotation(MethodMetadata data, Annotation consumes, } @Override - protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { + boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { boolean isHttpParam = false; for (Annotation parameterAnnotation : annotations) { Class annotationType = diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Contract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Contract.java new file mode 100644 index 000000000..8f6c8bc78 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Contract.java @@ -0,0 +1,176 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Defines what annotations and values are valid on interfaces. + */ +@SuppressWarnings("MissingSummary") +interface Contract { + + /** + * Called to parse the methods in the class that are linked to HTTP requests. + * + * @param targetType {@link Target#type() type} of the Feign interface. + */ + List parseAndValidateMetadata(Class targetType); + + abstract class BaseContract implements Contract { + + @Override + public List parseAndValidateMetadata(Class targetType) { + Util.checkState( + targetType.getTypeParameters().length == 0, + "Parameterized types unsupported: %s", + targetType.getSimpleName()); + Util.checkState( + targetType.getInterfaces().length <= 1, + "Only single inheritance supported: %s", + targetType.getSimpleName()); + if (targetType.getInterfaces().length == 1) { + Util.checkState( + targetType.getInterfaces()[0].getInterfaces().length == 0, + "Only single-level inheritance supported: %s", + targetType.getSimpleName()); + } + Map result = new LinkedHashMap(); + for (Method method : targetType.getMethods()) { + if (method.getDeclaringClass() == Object.class + || (method.getModifiers() & Modifier.STATIC) != 0 + || method.isDefault()) { + continue; + } + MethodMetadata metadata = parseAndValidateMetadata(targetType, method); + Util.checkState( + !result.containsKey(metadata.configKey()), "Overrides unsupported: %s", metadata.configKey()); + result.put(metadata.configKey(), metadata); + } + return new ArrayList(result.values()); + } + + /** + * Called indirectly by {@link #parseAndValidateMetadata(Class)}. + */ + MethodMetadata parseAndValidateMetadata(Class targetType, Method method) { + MethodMetadata data = new MethodMetadata(); + data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType())); + data.configKey(Feign.configKey(targetType, method)); + + if (targetType.getInterfaces().length == 1) { + processAnnotationOnClass(data, targetType.getInterfaces()[0]); + } + processAnnotationOnClass(data, targetType); + + for (Annotation methodAnnotation : method.getAnnotations()) { + processAnnotationOnMethod(data, methodAnnotation, method); + } + Util.checkState( + data.template().method() != null, + "Method %s not annotated with HTTP method type (ex. GET, POST)", + method.getName()); + Class[] parameterTypes = method.getParameterTypes(); + + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + int count = parameterAnnotations.length; + for (int i = 0; i < count; i++) { + boolean isHttpAnnotation = false; + if (parameterAnnotations[i] != null) { + isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i); + } + if (parameterTypes[i] == URI.class) { + data.urlIndex(i); + } else if (!isHttpAnnotation) { + Util.checkState( + data.formParams().isEmpty(), "Body parameters cannot be used with form parameters."); + Util.checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method); + data.bodyIndex(i); + data.bodyType(Types.resolve(targetType, targetType, method.getGenericParameterTypes()[i])); + } + } + + if (data.headerMapIndex() != null) { + Util.checkState( + Map.class.isAssignableFrom(parameterTypes[data.headerMapIndex()]), + "HeaderMap parameter must be a Map: %s", + parameterTypes[data.headerMapIndex()]); + } + + if (data.queryMapIndex() != null) { + Util.checkState( + Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()]), + "QueryMap parameter must be a Map: %s", + parameterTypes[data.queryMapIndex()]); + } + + return data; + } + + /** + * Called by parseAndValidateMetadata twice, first on the declaring class, then on the + * target type (unless they are the same). + * + * @param data metadata collected so far relating to the current java method. + * @param clz the class to process + */ + abstract void processAnnotationOnClass(MethodMetadata data, Class clz); + + /** + * @param data metadata collected so far relating to the current java method. + * @param annotation annotations present on the current method annotation. + * @param method method currently being processed. + */ + abstract void processAnnotationOnMethod(MethodMetadata data, Annotation annotation, Method method); + + /** + * @param data metadata collected so far relating to the current java method. + * @param annotations annotations present on the current parameter annotation. + * @param paramIndex if you find a name in {@code annotations}, call {@link + * #nameParam(MethodMetadata, String, int)} with this as the last parameter. + * @return true if you called {@link #nameParam(MethodMetadata, String, int)} after finding an + * http-relevant annotation. + */ + abstract boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex); + + @SuppressWarnings("ParameterAssignment") + Collection addTemplatedParam(Collection possiblyNull, String name) { + if (possiblyNull == null) { + possiblyNull = new ArrayList(); + } + possiblyNull.add(String.format("{%s}", name)); + return possiblyNull; + } + + /** + * links a parameter name to its index in the method signature. + */ + void nameParam(MethodMetadata data, String name, int index) { + Collection names = + data.indexToName().containsKey(index) ? data.indexToName().get(index) : new ArrayList(); + names.add(name); + data.indexToName().put(index, names); + } + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Decoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Decoder.java new file mode 100644 index 000000000..d64bea901 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Decoder.java @@ -0,0 +1,63 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.io.IOException; +import java.lang.reflect.Type; + +/** + * Decodes an HTTP response into a single object of the given {@code type}. Invoked when {@link + * Response#status()} is in the 2xx range and the return type is neither {@code void} nor {@code + * Response}.

Example Implementation:

+ *

+ * public class GsonDecoder implements Decoder {
+ *   private final Gson gson = new Gson();
+ *
+ *   @Override
+ *   public Object decode(Response response, Type type) throws IOException {
+ *     try {
+ *       return gson.fromJson(response.body().asReader(), type);
+ *     } catch (JsonIOException e) {
+ *       if (e.getCause() != null &&
+ *           e.getCause() instanceof IOException) {
+ *         throw IOException.class.cast(e.getCause());
+ *       }
+ *       throw e;
+ *     }
+ *   }
+ * }
+ * 
+ *

Implementation Note

The {@code type} parameter will correspond to the {@link + * java.lang.reflect.Method#getGenericReturnType() generic return type} of an {@link + * Target#type() interface} processed by {@link Feign#newInstance(Target)}. When + * writing your implementation of Decoder, ensure you also test parameterized types such as {@code + * List}. + */ +interface Decoder { + + /** + * Decodes an http response into an object corresponding to its {@link + * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap + * exceptions, please do so via {@link DecodeException}. + * + * @param response the response to decode + * @param type {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of + * the method corresponding to this {@code response}. + * @return instance of {@code type} + * @throws IOException will be propagated safely to the caller. + */ + Object decode(Response response, Type type) throws IOException; +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java index de98b3118..b453c3880 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/DialogueFeignClient.java @@ -26,8 +26,6 @@ import com.google.common.primitives.Ints; import com.google.common.util.concurrent.UncheckedExecutionException; import com.palantir.conjure.java.api.errors.UnknownRemoteException; -import com.palantir.conjure.java.client.jaxrs.feignimpl.EndpointNameHeaderEnrichmentContract; -import com.palantir.conjure.java.client.jaxrs.feignimpl.MethodHeaderEnrichmentContract; import com.palantir.dialogue.Channel; import com.palantir.dialogue.ConjureRuntime; import com.palantir.dialogue.Deserializer; @@ -43,7 +41,6 @@ import com.palantir.logsafe.UnsafeArg; import com.palantir.logsafe.exceptions.SafeIllegalStateException; import com.palantir.logsafe.exceptions.SafeUncheckedIoException; -import feign.Request; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -64,10 +61,10 @@ import org.immutables.value.Value; /** - * {@link DialogueFeignClient} is an adapter from {@link feign.Client} to {@link Channel Dialogue Channel} + * {@link DialogueFeignClient} is an adapter from {@link Client} to {@link Channel Dialogue Channel} * taking advantage of the superior observability and stability provided by Dialogue. */ -final class DialogueFeignClient implements feign.Client { +final class DialogueFeignClient implements Client { private static final String REQUEST_URL_PATH_PARAM = "request-url"; private static final Splitter PATH_SPLITTER = Splitter.on('/'); @@ -93,7 +90,7 @@ final class DialogueFeignClient implements feign.Client { } @Override - public feign.Response execute(Request request, Request.Options _options) throws IOException { + public com.palantir.conjure.java.client.jaxrs.Response execute(Request request) throws IOException { com.palantir.dialogue.Request.Builder builder = com.palantir.dialogue.Request.builder(); builder.putPathParams(REQUEST_URL_PATH_PARAM, request.url()); @@ -209,7 +206,7 @@ public void close() { } } - private static final class DialogueResponseBody implements feign.Response.Body { + private static final class DialogueResponseBody implements com.palantir.conjure.java.client.jaxrs.Response.Body { private final Response response; @@ -275,12 +272,12 @@ public String toString() { } } - enum FeignResponseDeserializer implements Deserializer { + enum FeignResponseDeserializer implements Deserializer { INSTANCE; @Override - public feign.Response deserialize(Response response) { - return feign.Response.create( + public com.palantir.conjure.java.client.jaxrs.Response deserialize(Response response) { + return com.palantir.conjure.java.client.jaxrs.Response.create( response.code(), null, Multimaps.asMap((Multimap) response.headers()), @@ -297,17 +294,17 @@ public Optional accepts() { /** Converts back from a feign response into a dialogue response for exception mapping. */ private static final class FeignDialogueResponse implements Response { - private final feign.Response delegate; + private final com.palantir.conjure.java.client.jaxrs.Response delegate; private final ResponseAttachments attachments; - FeignDialogueResponse(feign.Response delegate) { + FeignDialogueResponse(com.palantir.conjure.java.client.jaxrs.Response delegate) { this.delegate = delegate; this.attachments = ResponseAttachments.create(); } @Override public InputStream body() { - feign.Response.Body body = delegate.body(); + com.palantir.conjure.java.client.jaxrs.Response.Body body = delegate.body(); if (body != null) { try { return body.asInputStream(); @@ -355,7 +352,7 @@ public String toString() { } /** Implements exception handling equivalent dialogue decoders. */ - static final class RemoteExceptionDecoder implements feign.codec.ErrorDecoder { + static final class RemoteExceptionDecoder implements ErrorDecoder { private final ConjureRuntime runtime; @@ -364,7 +361,7 @@ static final class RemoteExceptionDecoder implements feign.codec.ErrorDecoder { } @Override - public Exception decode(String _methodKey, feign.Response response) { + public Exception decode(String _methodKey, com.palantir.conjure.java.client.jaxrs.Response response) { try { // The dialogue empty body deserializer properly handles exception mapping runtime.bodySerDe().emptyBodyDeserializer().deserialize(new FeignDialogueResponse(response)); @@ -468,7 +465,7 @@ private static FeignEndpointKey of(Request request) { getFirstHeader(request, MethodHeaderEnrichmentContract.METHOD_HEADER) .orElse(""), getFirstHeader(request, EndpointNameHeaderEnrichmentContract.ENDPOINT_NAME_HEADER) - .orElse("feign")); + .orElse("com/palantir/conjure/java/client/jaxrs")); } } } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EmptyContainerDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/EmptyContainerDecoder.java similarity index 95% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EmptyContainerDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/EmptyContainerDecoder.java index a1c2dc169..772f6aff9 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EmptyContainerDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/EmptyContainerDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.databind.ObjectMapper; @@ -25,8 +25,6 @@ import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.logger.SafeLogger; import com.palantir.logsafe.logger.SafeLoggerFactory; -import feign.Response; -import feign.codec.Decoder; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -48,12 +46,12 @@ * *

Empty instances are cached and re-used to avoid reflection and exceptions on a hot codepath. */ -public final class EmptyContainerDecoder implements Decoder { +final class EmptyContainerDecoder implements Decoder { private final LoadingCache blankInstanceCache; private final Decoder delegate; - public EmptyContainerDecoder(ObjectMapper mapper, Decoder delegate) { + EmptyContainerDecoder(ObjectMapper mapper, Decoder delegate) { this.delegate = delegate; this.blankInstanceCache = Caffeine.newBuilder() .maximumSize(1000) diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Encoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Encoder.java new file mode 100644 index 000000000..42c53f4f1 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Encoder.java @@ -0,0 +1,70 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.lang.reflect.Type; + +/** + * Encodes an object into an HTTP request body. Like {@code javax.websocket.Encoder}. {@code + * Encoder} is used when a method parameter has no {@code @Param} annotation. For example:
+ *

+ *

+ * @POST
+ * @Path("/")
+ * void create(User user);
+ * 
+ * Example implementation:

+ *

+ * public class GsonEncoder implements Encoder {
+ *   private final Gson gson;
+ *
+ *   public GsonEncoder(Gson gson) {
+ *     this.gson = gson;
+ *   }
+ *
+ *   @Override
+ *   public void encode(Object object, Type bodyType, RequestTemplate template) {
+ *     template.body(gson.toJson(object, bodyType));
+ *   }
+ * }
+ * 
+ * + *

Form encoding

If any parameters are found in {@link + * MethodMetadata#formParams()}, they will be collected and passed to the Encoder as a map. + * + *

Ex. The following is a form. Notice the parameters aren't consumed in the request line. A map + * including "username" and "password" keys will passed to the encoder, and the body type will be + * {@link #MAP_STRING_WILDCARD}. + *

+ * @RequestLine("POST /")
+ * Session login(@Param("username") String username, @Param("password") String
+ * password);
+ * 
+ */ +interface Encoder { + /** Type literal for {@code Map}, indicating the object to encode is a form. */ + Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD; + + /** + * Converts objects to an appropriate representation in the template. + * + * @param object what to encode as the request body. + * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD} + * indicates form encoding. + * @param template the request template to populate. + */ + void encode(Object object, Type bodyType, RequestTemplate template); +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EndpointNameHeaderEnrichmentContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/EndpointNameHeaderEnrichmentContract.java similarity index 78% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EndpointNameHeaderEnrichmentContract.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/EndpointNameHeaderEnrichmentContract.java index 208969303..f6845d962 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EndpointNameHeaderEnrichmentContract.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/EndpointNameHeaderEnrichmentContract.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,14 +14,12 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.palantir.dialogue.HttpMethod; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; import com.palantir.logsafe.exceptions.SafeNullPointerException; -import feign.Contract; -import feign.MethodMetadata; import java.lang.reflect.Method; import java.util.Locale; @@ -30,16 +28,16 @@ * * This should be considered internal API and should not be depended upon. */ -public final class EndpointNameHeaderEnrichmentContract extends AbstractDelegatingContract { +final class EndpointNameHeaderEnrichmentContract extends AbstractDelegatingContract { - public static final String ENDPOINT_NAME_HEADER = "dialogue-endpoint-name"; + static final String ENDPOINT_NAME_HEADER = "dialogue-endpoint-name"; - public EndpointNameHeaderEnrichmentContract(Contract delegate) { + EndpointNameHeaderEnrichmentContract(Contract delegate) { super(delegate); } @Override - protected void processMetadata(Class targetType, Method method, MethodMetadata metadata) { + void processMetadata(Class targetType, Method method, MethodMetadata metadata) { String httpMethod = metadata.template().method(); if (httpMethod == null) { throw new SafeNullPointerException( diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ErrorDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ErrorDecoder.java new file mode 100644 index 000000000..825f309f8 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ErrorDecoder.java @@ -0,0 +1,59 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +/** + * Allows you to massage an exception into a application-specific one. Converting out to a throttle + * exception are examples of this in use. + * + *

Ex: + *

+ * class IllegalArgumentExceptionOn404Decoder extends ErrorDecoder {
+ *
+ *   @Override
+ *   public Exception decode(String methodKey, Response response) {
+ *    if (response.status() == 400)
+ *        throw new IllegalArgumentException("bad zone name");
+ *    return new ErrorDecoder.Default().decode(methodKey, request, response);
+ *   }
+ *
+ * }
+ * 
+ * + *

Error handling + * + *

Responses where {@link Response#status()} is not in the 2xx + * range are classified as errors, addressed by the {@link ErrorDecoder}. That said, certain RPC + * apis return errors defined in the {@link Response#body()} even on a 200 status. For example, in + * the DynECT api, a job still running condition is returned with a 200 status, encoded in json. + * When scenarios like this occur, you should raise an application-specific exception. + */ +interface ErrorDecoder { + + /** + * Implement this method in order to decode an HTTP {@link Response} when {@link + * Response#status()} is not in the 2xx range. Please raise application-specific exceptions where + * possible. + * + * @param methodKey {@link Feign#configKey} of the java method that invoked the request. + * ex. {@code IAM#getUser()} + * @param response HTTP response where {@link Response#status() status} is greater than or equal + * to {@code 300}. + * @return Exception IOException, if there was a network error reading the response or an + * application-specific exception decoded by the implementation. + */ + Exception decode(String methodKey, Response response); +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverRetryingBackoffStrategy.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Expander.java similarity index 58% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverRetryingBackoffStrategy.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Expander.java index b06a84a52..477111b17 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverRetryingBackoffStrategy.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Expander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,14 +14,12 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; -/** A {@link BackoffStrategy} that attempts the operation exactly once, i.e., returns false always. */ -public enum NeverRetryingBackoffStrategy implements BackoffStrategy { - INSTANCE; +interface Expander { - @Override - public boolean backoff(int _numFailedAttempts) { - return false; - } + /** + * Expands the value into a string. Does not accept or return null. + */ + String expand(Object value); } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Feign.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Feign.java new file mode 100644 index 000000000..2d44dc05a --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Feign.java @@ -0,0 +1,111 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import com.palantir.conjure.java.client.jaxrs.ReflectiveFeign.ParseHandlersByName; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * Feign's purpose is to ease development against http apis that feign restfulness.
In + * implementation, Feign is a {@link Feign#newInstance factory} for generating {@link Target + * targeted} http apis. + */ +abstract class Feign { + + static Builder builder() { + return new Builder(); + } + + /** + *
Configuration keys are formatted as unresolved see tags.
For example.

  • {@code Route53}: would match a class such as {@code + * denominator.route53.Route53}
  • {@code Route53#list()}: would match a method such as {@code + * denominator.route53.Route53#list()}
  • {@code Route53#listAt(Marker)}: would match a method + * such as {@code denominator.route53.Route53#listAt(denominator.route53.Marker)}
  • {@code + * Route53#listByNameAndType(String, String)}: would match a method such as {@code + * denominator.route53.Route53#listAt(String, String)}

Note that there is no whitespace + * expected in a key! + * + * @param targetType {@link Target#type() type} of the Feign interface. + * @param method invoked method, present on {@code type} or its super. + */ + @SuppressWarnings("ModifiedControlVariable") + static String configKey(Class targetType, Method method) { + StringBuilder builder = new StringBuilder(); + builder.append(targetType.getSimpleName()); + builder.append('#').append(method.getName()).append('('); + for (Type param : method.getGenericParameterTypes()) { + param = Types.resolve(targetType, targetType, param); + builder.append(Types.getRawType(param).getSimpleName()).append(','); + } + if (method.getParameterTypes().length > 0) { + builder.deleteCharAt(builder.length() - 1); + } + return builder.append(')').toString(); + } + + /** + * Returns a new instance of an HTTP API, defined by annotations in the {@link Feign Contract}, + * for the specified {@code target}. You should cache this result. + */ + abstract T newInstance(Target target); + + @SuppressWarnings("HiddenField") + static final class Builder { + + private Contract contract; + private Client client; + private Encoder encoder; + private Decoder decoder; + private ErrorDecoder errorDecoder; + + private Builder() {} + + Builder contract(Contract contract) { + this.contract = contract; + return this; + } + + Builder client(Client client) { + this.client = client; + return this; + } + + Builder encoder(Encoder encoder) { + this.encoder = encoder; + return this; + } + + Builder decoder(Decoder decoder) { + this.decoder = decoder; + return this; + } + + Builder errorDecoder(ErrorDecoder errorDecoder) { + this.errorDecoder = errorDecoder; + return this; + } + + T target(Target target) { + SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = + new SynchronousMethodHandler.Factory(client); + ParseHandlersByName handlersByName = + new ParseHandlersByName(contract, encoder, decoder, errorDecoder, synchronousMethodHandlerFactory); + return new ReflectiveFeign(handlersByName).newInstance(target); + } + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsClientBuilder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsClientBuilder.java index 8d4e3c5f4..bf7e80e79 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsClientBuilder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsClientBuilder.java @@ -22,7 +22,7 @@ import com.palantir.conjure.java.client.config.ClientConfiguration; import com.palantir.conjure.java.serialization.ObjectMappers; -public final class FeignJaxRsClientBuilder extends AbstractFeignJaxRsClientBuilder { +final class FeignJaxRsClientBuilder extends AbstractFeignJaxRsClientBuilder { static final JsonMapper JSON_MAPPER = ObjectMappers.newClientJsonMapper(); static final CBORMapper CBOR_MAPPER = ObjectMappers.newClientCborMapper(); @@ -32,12 +32,12 @@ public final class FeignJaxRsClientBuilder extends AbstractFeignJaxRsClientBuild } @Override - protected ObjectMapper getObjectMapper() { + ObjectMapper getObjectMapper() { return JSON_MAPPER; } @Override - protected ObjectMapper getCborObjectMapper() { + ObjectMapper getCborObjectMapper() { return CBOR_MAPPER; } } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaEmptyOptionalExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaEmptyOptionalExpander.java similarity index 85% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaEmptyOptionalExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaEmptyOptionalExpander.java index a562bef98..896e7b355 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaEmptyOptionalExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaEmptyOptionalExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.base.Preconditions; -import feign.Param.Expander; import java.util.Objects; /** * Expands Optional by using the empty string for {@link com.google.common.base.Optional#absent()} and the * {@link Object#toString()} of the value otherwise. */ -public final class GuavaEmptyOptionalExpander implements Expander { +final class GuavaEmptyOptionalExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaNullOptionalExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaNullOptionalExpander.java similarity index 84% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaNullOptionalExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaNullOptionalExpander.java index 7d84b08b9..a330d5e11 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaNullOptionalExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaNullOptionalExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.base.Preconditions; -import feign.Param.Expander; import java.util.Objects; /** * Expands Optional by using null for {@link com.google.common.base.Optional#absent()} and the {@link Object#toString()} * of the value otherwise. */ -public final class GuavaNullOptionalExpander implements Expander { +final class GuavaNullOptionalExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareContract.java similarity index 89% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareContract.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareContract.java index 8bb69a344..646f9eda4 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareContract.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareContract.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,11 +14,9 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations; -import feign.Contract; -import feign.MethodMetadata; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; @@ -35,9 +33,9 @@ *

{@link jakarta.ws.rs.PathParam}s require a value, and so we explicitly disallow use with * {@link com.google.common.base.Optional}. */ -public final class GuavaOptionalAwareContract extends AbstractDelegatingContract { +final class GuavaOptionalAwareContract extends AbstractDelegatingContract { - public GuavaOptionalAwareContract(Contract delegate) { + GuavaOptionalAwareContract(Contract delegate) { super(delegate); } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareDecoder.java similarity index 84% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareDecoder.java index a0036021e..d45ec3dff 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,13 +14,10 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkNotNull; -import feign.FeignException; -import feign.Response; -import feign.codec.Decoder; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -29,16 +26,16 @@ * Decorates a Feign {@link Decoder} such that it returns {@link com.google.common.base.Optional#absent} when observing * an HTTP 204 error code for a method with {@link Type} {@link com.google.common.base.Optional}. */ -public final class GuavaOptionalAwareDecoder implements Decoder { +final class GuavaOptionalAwareDecoder implements Decoder { private final Decoder delegate; - public GuavaOptionalAwareDecoder(Decoder delegate) { + GuavaOptionalAwareDecoder(Decoder delegate) { this.delegate = delegate; } @Override - public Object decode(Response response, Type type) throws IOException, FeignException { + public Object decode(Response response, Type type) throws IOException { if (RawTypes.get(type).equals(com.google.common.base.Optional.class)) { if (response.status() == 204) { return com.google.common.base.Optional.absent(); diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/HeaderAccessUtils.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/HeaderAccessUtils.java similarity index 75% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/HeaderAccessUtils.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/HeaderAccessUtils.java index c043ded03..1fa371697 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/HeaderAccessUtils.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/HeaderAccessUtils.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import java.util.ArrayList; import java.util.Collection; @@ -24,14 +24,14 @@ /** * Used to access headers in a case-insensitive manner. This is necessary for compatibility with OkHttp 3.3.0+ as it * lower-cases header names whereas we use constants from {@link com.google.common.net.HttpHeaders} where header names - * are in Train-Case. This can be removed once {@link feign.Request} and {@link feign.Response} expose the headers as a + * are in Train-Case. This can be removed once {@link Request} and {@link Response} expose the headers as a * map which is case-insensitive with respect to the key. com.netflix.feign:feign-core:8.18.0 will have it for the - * {@link feign.Response} headers due to https://github.com/Netflix/feign/pull/418. + * {@link Response} headers due to https://github.com/Netflix/feign/pull/418. */ -public final class HeaderAccessUtils { +final class HeaderAccessUtils { private HeaderAccessUtils() {} - public static boolean caseInsensitiveContains(Map> headers, String headerName) { + static boolean caseInsensitiveContains(Map> headers, String headerName) { for (String key : headers.keySet()) { if (headerName.equalsIgnoreCase(key)) { return true; @@ -44,7 +44,7 @@ public static boolean caseInsensitiveContains(Map> he * Compares the keys of the map to the headerName in a case-insensitive manner and returns null if it was never * found. */ - public static Collection caseInsensitiveGet(Map> headers, String headerName) { + static Collection caseInsensitiveGet(Map> headers, String headerName) { List result = new ArrayList<>(); boolean neverFound = true; for (Map.Entry> entry : headers.entrySet()) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateDecoder.java similarity index 77% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateDecoder.java index f139dc50a..8b47cdc43 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,32 +14,28 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.codahale.metrics.Meter; -import com.palantir.conjure.java.client.jaxrs.feignimpl.FeignClientMetrics.DangerousBuffering_Direction; +import com.palantir.conjure.java.client.jaxrs.FeignClientMetrics.DangerousBuffering_Direction; import com.palantir.logsafe.Safe; import com.palantir.tritium.metrics.registry.SharedTaggedMetricRegistries; -import feign.FeignException; -import feign.Response; -import feign.Util; -import feign.codec.Decoder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; /** If the return type is InputStream, return it, otherwise delegate to provided decoder. */ -public final class InputStreamDelegateDecoder implements Decoder { +final class InputStreamDelegateDecoder implements Decoder { private final Decoder delegate; private final Meter dangerousBufferingMeter; - public InputStreamDelegateDecoder(Decoder delegate) { + InputStreamDelegateDecoder(Decoder delegate) { this("unknown", delegate); } @SuppressWarnings("deprecation") // No access to a TaggedMetricRegistry without breaking API - public InputStreamDelegateDecoder(@Safe String clientNameForLogging, Decoder delegate) { + InputStreamDelegateDecoder(@Safe String clientNameForLogging, Decoder delegate) { this.delegate = delegate; this.dangerousBufferingMeter = FeignClientMetrics.of(SharedTaggedMetricRegistries.getSingleton()) .dangerousBuffering() @@ -49,7 +45,7 @@ public InputStreamDelegateDecoder(@Safe String clientNameForLogging, Decoder del } @Override - public Object decode(Response response, Type type) throws IOException, FeignException { + public Object decode(Response response, Type type) throws IOException { if (type.equals(InputStream.class)) { byte[] body = response.body() != null ? Util.toByteArray(response.body().asInputStream()) : new byte[0]; diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateEncoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateEncoder.java similarity index 73% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateEncoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateEncoder.java index e75c16022..9858799c5 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateEncoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateEncoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,33 +14,28 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.codahale.metrics.Meter; -import com.palantir.conjure.java.client.jaxrs.feignimpl.FeignClientMetrics.DangerousBuffering_Direction; +import com.palantir.conjure.java.client.jaxrs.FeignClientMetrics.DangerousBuffering_Direction; import com.palantir.logsafe.Safe; import com.palantir.tritium.metrics.registry.SharedTaggedMetricRegistries; -import feign.RequestTemplate; -import feign.Util; -import feign.codec.EncodeException; -import feign.codec.Encoder; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; /** If the body type is an InputStream, write it into the body, otherwise pass to delegate. */ -public final class InputStreamDelegateEncoder implements Encoder { +final class InputStreamDelegateEncoder implements Encoder { private final Encoder delegate; private final Meter dangerousBufferingMeter; - public InputStreamDelegateEncoder(Encoder delegate) { + InputStreamDelegateEncoder(Encoder delegate) { this("unknown", delegate); } @SuppressWarnings("deprecation") // No access to a TaggedMetricRegistry without breaking API - public InputStreamDelegateEncoder(@Safe String clientNameForLogging, Encoder delegate) { + InputStreamDelegateEncoder(@Safe String clientNameForLogging, Encoder delegate) { this.delegate = delegate; this.dangerousBufferingMeter = FeignClientMetrics.of(SharedTaggedMetricRegistries.getSingleton()) .dangerousBuffering() @@ -50,12 +45,12 @@ public InputStreamDelegateEncoder(@Safe String clientNameForLogging, Encoder del } @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + public void encode(Object object, Type bodyType, RequestTemplate template) { if (bodyType.equals(InputStream.class)) { try { byte[] bytes = Util.toByteArray((InputStream) object); dangerousBufferingMeter.mark(Math.max(1, bytes.length)); - template.body(bytes, StandardCharsets.UTF_8); + template.body(bytes); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JacksonDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JacksonDecoder.java new file mode 100644 index 000000000..58b4e9b93 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JacksonDecoder.java @@ -0,0 +1,53 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Type; + +final class JacksonDecoder implements Decoder { + + private final ObjectMapper mapper; + + JacksonDecoder(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public Object decode(Response response, Type type) throws IOException { + if (response.status() == 404) { + return Util.emptyValueOf(type); + } + if (response.body() == null) { + return null; + } + Reader reader = response.body().asReader(); + if (!reader.markSupported()) { + reader = new BufferedReader(reader, 1); + } + + // Read the first byte to see if we have any data + reader.mark(1); + if (reader.read() == -1) { + return null; // Eagerly returning null avoids "No content to map due to end-of-input" + } + reader.reset(); + return mapper.readValue(reader, mapper.constructType(type)); + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ConjureFeignJacksonEncoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JacksonEncoder.java similarity index 66% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ConjureFeignJacksonEncoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JacksonEncoder.java index 600a45f21..f1de49ad4 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ConjureFeignJacksonEncoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JacksonEncoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -19,21 +19,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.codec.Encoder; +import java.io.UncheckedIOException; import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -/** - * Similar to {@link feign.jackson.JacksonEncoder}, but optimized to avoid intermediate String representation of request - * body. See upstream PR https://github.com/OpenFeign/feign/pull/989 . - */ -final class ConjureFeignJacksonEncoder implements Encoder { +final class JacksonEncoder implements Encoder { private final ObjectMapper mapper; - ConjureFeignJacksonEncoder(ObjectMapper mapper) { + JacksonEncoder(ObjectMapper mapper) { this.mapper = mapper; } @@ -41,9 +34,9 @@ final class ConjureFeignJacksonEncoder implements Encoder { public void encode(Object object, Type bodyType, RequestTemplate template) { try { JavaType javaType = mapper.getTypeFactory().constructType(bodyType); - template.body(mapper.writerFor(javaType).writeValueAsBytes(object), StandardCharsets.UTF_8); + template.body(mapper.writerFor(javaType).writeValueAsBytes(object)); } catch (JsonProcessingException e) { - throw new EncodeException(e.getMessage(), e); + throw new UncheckedIOException(e); } } } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalDoubleExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalDoubleExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalDoubleExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalDoubleExpander.java index 3d1b9a459..b9cb91778 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalDoubleExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalDoubleExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkArgument; -import feign.Param.Expander; import java.util.OptionalDouble; /** * Expands OptionalDouble by using the empty string for {@link OptionalDouble#empty()} and the {@link Double#toString()} * of the value otherwise. */ -public final class Java8EmptyOptionalDoubleExpander implements Expander { +final class Java8EmptyOptionalDoubleExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalExpander.java index d90624438..f34098157 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.base.Preconditions; -import feign.Param.Expander; import java.util.Objects; import java.util.Optional; @@ -25,7 +24,7 @@ * Expands Optional by using the empty string for {@link Optional#empty()} and the {@link Object#toString()} of the * value otherwise. */ -public final class Java8EmptyOptionalExpander implements Expander { +final class Java8EmptyOptionalExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalIntExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalIntExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalIntExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalIntExpander.java index 63e5043c8..ea49a7795 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalIntExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalIntExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkArgument; -import feign.Param.Expander; import java.util.OptionalInt; /** * Expands OptionalInt by using the empty string for {@link OptionalInt#empty()} and the {@link Integer#toString()} of * the value otherwise. */ -public final class Java8EmptyOptionalIntExpander implements Expander { +final class Java8EmptyOptionalIntExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalLongExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalLongExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalLongExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalLongExpander.java index 1ca30f647..fb854d718 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8EmptyOptionalLongExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8EmptyOptionalLongExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkArgument; -import feign.Param.Expander; import java.util.OptionalLong; /** * Expands OptionalLong by using the empty string for {@link OptionalLong#empty()} and the {@link Long#toString()} of * the value otherwise. */ -public final class Java8EmptyOptionalLongExpander implements Expander { +final class Java8EmptyOptionalLongExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalDoubleExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalDoubleExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalDoubleExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalDoubleExpander.java index 880736632..aa7f8fbfd 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalDoubleExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalDoubleExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkArgument; -import feign.Param.Expander; import java.util.OptionalDouble; /** * Expands OptionalDouble by using null for {@link OptionalDouble#empty()} and the {@link Double#toString()} of the * value otherwise. */ -public final class Java8NullOptionalDoubleExpander implements Expander { +final class Java8NullOptionalDoubleExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalExpander.java index 56bde28bd..aa257d3fa 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.base.Preconditions; -import feign.Param.Expander; import java.util.Objects; import java.util.Optional; /** * Expands Optional by using null for {@link Optional#empty()} and the {@link Object#toString()} of the value otherwise. */ -public final class Java8NullOptionalExpander implements Expander { +final class Java8NullOptionalExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalIntExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalIntExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalIntExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalIntExpander.java index 51e20a8b8..cab4aa0e9 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalIntExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalIntExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkArgument; -import feign.Param.Expander; import java.util.OptionalInt; /** * Expands OptionalInt by using null for {@link OptionalInt#empty()} and the {@link Integer#toString()} of the value * otherwise. */ -public final class Java8NullOptionalIntExpander implements Expander { +final class Java8NullOptionalIntExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalLongExpander.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalLongExpander.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalLongExpander.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalLongExpander.java index fb1d3cd76..c3e56646e 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8NullOptionalLongExpander.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8NullOptionalLongExpander.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkArgument; -import feign.Param.Expander; import java.util.OptionalLong; /** * Expands OptionalLong by using null for {@link OptionalLong#empty()} and the {@link Long#toString()} of the value * otherwise. */ -public final class Java8NullOptionalLongExpander implements Expander { +final class Java8NullOptionalLongExpander implements Expander { @Override public String expand(Object value) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareContract.java similarity index 91% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareContract.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareContract.java index 4444a36b4..60c4ae796 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareContract.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareContract.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,13 +14,10 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.collect.ImmutableList; import com.palantir.conjure.java.client.jaxrs.JaxRsJakartaCompatibility.Annotations; -import feign.Contract; -import feign.MethodMetadata; -import feign.Param.Expander; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; @@ -41,7 +38,7 @@ * *

{@link jakarta.ws.rs.PathParam}s require a value, and so we explicitly disallow use with {@link Optional}. */ -public final class Java8OptionalAwareContract extends AbstractDelegatingContract { +final class Java8OptionalAwareContract extends AbstractDelegatingContract { private static final List expanders = ImmutableList.of( new ExpanderDef(Optional.class, Java8EmptyOptionalExpander.class, Java8NullOptionalExpander.class), @@ -53,12 +50,12 @@ public final class Java8OptionalAwareContract extends AbstractDelegatingContract new ExpanderDef( OptionalLong.class, Java8EmptyOptionalLongExpander.class, Java8NullOptionalLongExpander.class)); - public Java8OptionalAwareContract(Contract delegate) { + Java8OptionalAwareContract(Contract delegate) { super(delegate); } @Override - protected void processMetadata(Class targetType, Method method, MethodMetadata metadata) { + void processMetadata(Class targetType, Method method, MethodMetadata metadata) { Class[] parameterTypes = method.getParameterTypes(); Annotation[][] annotations = method.getParameterAnnotations(); for (int i = 0; i < parameterTypes.length; i++) { diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareDecoder.java similarity index 83% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareDecoder.java index 105f1061b..8f391f718 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,13 +14,10 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static com.google.common.base.Preconditions.checkNotNull; -import feign.FeignException; -import feign.Response; -import feign.codec.Decoder; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -30,16 +27,16 @@ * Decorates a Feign {@link Decoder} such that it returns {@link Optional#empty} when observing an HTTP 204 error code * for a method with {@link Type} {@link Optional}. */ -public final class Java8OptionalAwareDecoder implements Decoder { +final class Java8OptionalAwareDecoder implements Decoder { private final Decoder delegate; - public Java8OptionalAwareDecoder(Decoder delegate) { + Java8OptionalAwareDecoder(Decoder delegate) { this.delegate = delegate; } @Override - public Object decode(Response response, Type type) throws IOException, FeignException { + public Object decode(Response response, Type type) throws IOException { if (RawTypes.get(type).equals(Optional.class)) { if (response.status() == 204) { return Optional.empty(); diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JaxRsJakartaCompatibility.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JaxRsJakartaCompatibility.java index e14df14a3..ec3b2ec42 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JaxRsJakartaCompatibility.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/JaxRsJakartaCompatibility.java @@ -21,7 +21,7 @@ import java.util.Set; import javax.annotation.Nullable; -public final class JaxRsJakartaCompatibility { +final class JaxRsJakartaCompatibility { private JaxRsJakartaCompatibility() {} @Nullable @@ -72,7 +72,7 @@ private JaxRsJakartaCompatibility() {} @Nullable private static final Class JAKARTA_QUERY_PARAM = resolve("jakarta.ws.rs.QueryParam"); - public enum Annotations { + enum Annotations { CONSUMES(JAVAX_CONSUMES, JAKARTA_CONSUMES), PRODUCES(JAVAX_PRODUCES, JAKARTA_PRODUCES), PATH_PARAM(JAVAX_PATH_PARAM, JAKARTA_PATH_PARAM), @@ -94,16 +94,16 @@ public enum Annotations { this.jakarta = jakarta; } - public boolean matches(Class annotation) { + boolean matches(Class annotation) { return annotation == jakarta || annotation == javax; } - public boolean matches(Set> annotations) { + boolean matches(Set> annotations) { return annotations.contains(jakarta) || annotations.contains(javax); } @Nullable - public Annotation getAnnotation(AnnotatedElement element) { + Annotation getAnnotation(AnnotatedElement element) { if (jakarta != null) { Annotation annotation = element.getAnnotation(jakarta); if (annotation != null) { @@ -120,7 +120,6 @@ public Annotation getAnnotation(AnnotatedElement element) { } @Nullable - @SuppressWarnings("unchecked") private static Class resolve(String fqcn) { try { return (Class) Class.forName(fqcn); diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/PathTemplateHeaderRewriter.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodHandler.java similarity index 58% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/PathTemplateHeaderRewriter.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodHandler.java index 16b398537..1c0d205d7 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/PathTemplateHeaderRewriter.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodHandler.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,22 +14,17 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; -import feign.RequestInterceptor; -import feign.RequestTemplate; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; /** - * No longer used. - * - * @deprecated no longer used + * Like {@link InvocationHandler#invoke(Object, Method, Object[])}, except for a + * single method. */ -@Deprecated -public enum PathTemplateHeaderRewriter implements RequestInterceptor { - INSTANCE; +public interface MethodHandler { - @Override - public void apply(RequestTemplate _template) { - // nop - } + @SuppressWarnings("IllegalThrows") + Object invoke(Object[] argv) throws Throwable; } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/MethodHeaderEnrichmentContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodHeaderEnrichmentContract.java similarity index 69% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/MethodHeaderEnrichmentContract.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodHeaderEnrichmentContract.java index 2068934b5..cfbf47b6d 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/MethodHeaderEnrichmentContract.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodHeaderEnrichmentContract.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; -import feign.Contract; -import feign.MethodMetadata; import java.lang.reflect.Method; /** @@ -25,16 +23,16 @@ * * This should be considered internal API and should not be depended upon. */ -public final class MethodHeaderEnrichmentContract extends AbstractDelegatingContract { +final class MethodHeaderEnrichmentContract extends AbstractDelegatingContract { public static final String METHOD_HEADER = "dialogue-method"; - public MethodHeaderEnrichmentContract(Contract delegate) { + MethodHeaderEnrichmentContract(Contract delegate) { super(delegate); } @Override - protected void processMetadata(Class _targetType, Method method, MethodMetadata metadata) { + void processMetadata(Class _targetType, Method method, MethodMetadata metadata) { metadata.template().header(METHOD_HEADER, method.toString()); } } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodMetadata.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodMetadata.java new file mode 100644 index 000000000..86d049819 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/MethodMetadata.java @@ -0,0 +1,158 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"HiddenField", "MissingSummary"}) +final class MethodMetadata { + + private String configKey; + private transient Type returnType; + private Integer urlIndex; + private Integer bodyIndex; + private Integer headerMapIndex; + private Integer queryMapIndex; + private boolean queryMapEncoded; + private transient Type bodyType; + private RequestTemplate template = new RequestTemplate(); + private List formParams = new ArrayList(); + private Map> indexToName = new LinkedHashMap>(); + private Map> indexToExpanderClass = + new LinkedHashMap>(); + private transient Map indexToExpander; + + MethodMetadata() {} + + /** + * @see Feign#configKey(Class, java.lang.reflect.Method) + */ + @SuppressWarnings("MissingSummary") + String configKey() { + return configKey; + } + + MethodMetadata configKey(String configKey) { + this.configKey = configKey; + return this; + } + + Type returnType() { + return returnType; + } + + MethodMetadata returnType(Type returnType) { + this.returnType = returnType; + return this; + } + + Integer urlIndex() { + return urlIndex; + } + + MethodMetadata urlIndex(Integer urlIndex) { + this.urlIndex = urlIndex; + return this; + } + + Integer bodyIndex() { + return bodyIndex; + } + + MethodMetadata bodyIndex(Integer bodyIndex) { + this.bodyIndex = bodyIndex; + return this; + } + + Integer headerMapIndex() { + return headerMapIndex; + } + + MethodMetadata headerMapIndex(Integer headerMapIndex) { + this.headerMapIndex = headerMapIndex; + return this; + } + + Integer queryMapIndex() { + return queryMapIndex; + } + + MethodMetadata queryMapIndex(Integer queryMapIndex) { + this.queryMapIndex = queryMapIndex; + return this; + } + + boolean queryMapEncoded() { + return queryMapEncoded; + } + + MethodMetadata queryMapEncoded(boolean queryMapEncoded) { + this.queryMapEncoded = queryMapEncoded; + return this; + } + + /** + * Type corresponding to {@link #bodyIndex()}. + */ + Type bodyType() { + return bodyType; + } + + MethodMetadata bodyType(Type bodyType) { + this.bodyType = bodyType; + return this; + } + + RequestTemplate template() { + return template; + } + + List formParams() { + return formParams; + } + + Map> indexToName() { + return indexToName; + } + + /** + * If {@link #indexToExpander} is null, classes here will be instantiated by newInstance. + */ + Map> indexToExpanderClass() { + return indexToExpanderClass; + } + + /** + * After {@link #indexToExpanderClass} is populated, this is set by contracts that support + * runtime injection. + */ + MethodMetadata indexToExpander(Map indexToExpander) { + this.indexToExpander = indexToExpander; + return this; + } + + /** + * When not null, this value will be used instead of {@link #indexToExpander()}. + */ + Map indexToExpander() { + return indexToExpander; + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverReturnNullDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/NeverReturnNullDecoder.java similarity index 70% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverReturnNullDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/NeverReturnNullDecoder.java index 8876178ba..f9f118bf6 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverReturnNullDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/NeverReturnNullDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,25 +14,22 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.palantir.logsafe.Preconditions; import com.palantir.logsafe.SafeArg; -import feign.FeignException; -import feign.Response; -import feign.codec.Decoder; import java.io.IOException; import java.lang.reflect.Type; -public final class NeverReturnNullDecoder implements Decoder { +final class NeverReturnNullDecoder implements Decoder { private final Decoder delegate; - public NeverReturnNullDecoder(Decoder delegate) { + NeverReturnNullDecoder(Decoder delegate) { this.delegate = delegate; } @Override - public Object decode(Response response, Type type) throws FeignException, IOException { + public Object decode(Response response, Type type) throws IOException { Object object = delegate.decode(response, type); Preconditions.checkNotNull(object, "Unexpected null body", SafeArg.of("status", response.status())); diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/QosErrorDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/QosErrorDecoder.java similarity index 82% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/QosErrorDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/QosErrorDecoder.java index 3f0cb3b7c..d2b0d680b 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/QosErrorDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/QosErrorDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,20 +14,18 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.palantir.conjure.java.QosExceptionResponseMapper; -import feign.Response; -import feign.codec.ErrorDecoder; import java.util.Collection; import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; -public final class QosErrorDecoder implements ErrorDecoder { +final class QosErrorDecoder implements ErrorDecoder { private final ErrorDecoder delegate; - public QosErrorDecoder(ErrorDecoder delegate) { + QosErrorDecoder(ErrorDecoder delegate) { this.delegate = delegate; } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/RawTypes.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/RawTypes.java similarity index 88% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/RawTypes.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/RawTypes.java index 266aa4535..c4edc7e30 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/RawTypes.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/RawTypes.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.reflect.TypeToken; import java.lang.reflect.Type; diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ReflectiveFeign.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ReflectiveFeign.java new file mode 100644 index 000000000..8ec1a655d --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/ReflectiveFeign.java @@ -0,0 +1,331 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +final class ReflectiveFeign extends Feign { + + private final ParseHandlersByName targetToHandlersByName; + + ReflectiveFeign(ParseHandlersByName targetToHandlersByName) { + this.targetToHandlersByName = targetToHandlersByName; + } + + /** + * creates an api binding to the {@code target}. As this invokes reflection, care should be taken + * to cache the result. + */ + @Override + @SuppressWarnings("ProxyNonConstantType") + T newInstance(Target target) { + Map nameToHandler = targetToHandlersByName.apply(target); + Map methodToHandler = new HashMap<>(); + + for (Method method : target.type().getMethods()) { + if (method.getDeclaringClass() == Object.class || method.isDefault()) { + continue; + } + methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); + } + InvocationHandler handler = new FeignInvocationHandler(target, methodToHandler); + T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[] {target.type()}, handler); + + return proxy; + } + + static class FeignInvocationHandler implements InvocationHandler { + + private final Target target; + private final Map dispatch; + + FeignInvocationHandler(Target target, Map dispatch) { + this.target = Util.checkNotNull(target, "target"); + this.dispatch = Util.checkNotNull(dispatch, "dispatch for %s", target); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.isDefault()) { + return InvocationHandler.invokeDefault(proxy, method, args); + } + + if ("equals".equals(method.getName())) { + try { + Object otherHandler = + args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; + return equals(otherHandler); + } catch (IllegalArgumentException e) { + return false; + } + } else if ("hashCode".equals(method.getName())) { + return hashCode(); + } else if ("toString".equals(method.getName())) { + return toString(); + } + return dispatch.get(method).invoke(args); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof FeignInvocationHandler other) { + return target.equals(other.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public String toString() { + return target.toString(); + } + } + + static final class ParseHandlersByName { + + private final Contract contract; + private final Encoder encoder; + private final Decoder decoder; + private final ErrorDecoder errorDecoder; + private final SynchronousMethodHandler.Factory factory; + + ParseHandlersByName( + Contract contract, + Encoder encoder, + Decoder decoder, + ErrorDecoder errorDecoder, + SynchronousMethodHandler.Factory factory) { + this.contract = Util.checkNotNull(contract, "contract"); + this.factory = Util.checkNotNull(factory, "factory"); + this.errorDecoder = Util.checkNotNull(errorDecoder, "errorDecoder"); + this.encoder = Util.checkNotNull(encoder, "encoder"); + this.decoder = Util.checkNotNull(decoder, "decoder"); + } + + Map apply(Target key) { + List metadata = contract.parseAndValidateMetadata(key.type()); + Map result = new LinkedHashMap(); + for (MethodMetadata md : metadata) { + BuildTemplateByResolvingArgs buildTemplate; + if (!md.formParams().isEmpty()) { + buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder); + } else if (md.bodyIndex() != null) { + buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder); + } else { + buildTemplate = new BuildTemplateByResolvingArgs(md); + } + result.put(md.configKey(), factory.create(key, md, buildTemplate, decoder, errorDecoder)); + } + return result; + } + } + + private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory { + + @SuppressWarnings("VisibilityModifier") + final MethodMetadata metadata; + + private final Map indexToExpander = new LinkedHashMap(); + + private BuildTemplateByResolvingArgs(MethodMetadata metadata) { + this.metadata = metadata; + if (metadata.indexToExpander() != null) { + indexToExpander.putAll(metadata.indexToExpander()); + return; + } + if (metadata.indexToExpanderClass().isEmpty()) { + return; + } + for (Entry> indexToExpanderClass : + metadata.indexToExpanderClass().entrySet()) { + try { + indexToExpander.put( + indexToExpanderClass.getKey(), + indexToExpanderClass + .getValue() + .getDeclaredConstructor() + .newInstance()); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException(e); + } + } + } + + @Override + public RequestTemplate create(Object[] argv) { + RequestTemplate mutable = new RequestTemplate(metadata.template()); + if (metadata.urlIndex() != null) { + int urlIndex = metadata.urlIndex(); + Util.checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex); + mutable.insert(0, String.valueOf(argv[urlIndex])); + } + Map varBuilder = new LinkedHashMap(); + for (Entry> entry : + metadata.indexToName().entrySet()) { + int index = entry.getKey(); + Object value = argv[entry.getKey()]; + if (value != null) { // Null values are skipped. + if (indexToExpander.containsKey(index)) { + value = expandElements(indexToExpander.get(index), value); + } + for (String name : entry.getValue()) { + varBuilder.put(name, value); + } + } + } + + RequestTemplate template = resolve(argv, mutable, varBuilder); + if (metadata.queryMapIndex() != null) { + // add query map parameters after initial resolve so that they take + // precedence over any predefined values + template = addQueryMapQueryParameters(argv, template); + } + + if (metadata.headerMapIndex() != null) { + template = addHeaderMapHeaders(argv, template); + } + + return template; + } + + private Object expandElements(Expander expander, Object value) { + if (value instanceof Iterable iterable) { + return expandIterable(expander, iterable); + } + return expander.expand(value); + } + + private List expandIterable(Expander expander, Iterable value) { + List values = new ArrayList(); + for (Object element : value) { + if (element != null) { + values.add(expander.expand(element)); + } + } + return values; + } + + private RequestTemplate addHeaderMapHeaders(Object[] argv, RequestTemplate mutable) { + Map headerMap = (Map) argv[metadata.headerMapIndex()]; + for (Entry currEntry : headerMap.entrySet()) { + Util.checkState( + currEntry.getKey().getClass() == String.class, + "HeaderMap key must be a String: %s", + currEntry.getKey()); + + Collection values = new ArrayList(); + + Object currValue = currEntry.getValue(); + if (currValue instanceof Iterable iterable) { + Iterator iter = iterable.iterator(); + while (iter.hasNext()) { + Object nextObject = iter.next(); + values.add(nextObject == null ? null : nextObject.toString()); + } + } else { + values.add(currValue == null ? null : currValue.toString()); + } + + mutable.header((String) currEntry.getKey(), values); + } + return mutable; + } + + private RequestTemplate addQueryMapQueryParameters(Object[] argv, RequestTemplate mutable) { + Map queryMap = (Map) argv[metadata.queryMapIndex()]; + for (Entry currEntry : queryMap.entrySet()) { + Util.checkState( + currEntry.getKey().getClass() == String.class, + "QueryMap key must be a String: %s", + currEntry.getKey()); + + Collection values = new ArrayList(); + + Object currValue = currEntry.getValue(); + if (currValue instanceof Iterable iterable) { + Iterator iter = iterable.iterator(); + while (iter.hasNext()) { + Object nextObject = iter.next(); + values.add(nextObject == null ? null : nextObject.toString()); + } + } else { + values.add(currValue == null ? null : currValue.toString()); + } + + mutable.query(metadata.queryMapEncoded(), (String) currEntry.getKey(), values); + } + return mutable; + } + + RequestTemplate resolve(Object[] _argv, RequestTemplate mutable, Map variables) { + return mutable.resolve(variables); + } + } + + private static final class BuildFormEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs { + + private final Encoder encoder; + + private BuildFormEncodedTemplateFromArgs(MethodMetadata metadata, Encoder encoder) { + super(metadata); + this.encoder = encoder; + } + + @Override + RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map variables) { + Map formVariables = new LinkedHashMap(); + for (Entry entry : variables.entrySet()) { + if (metadata.formParams().contains(entry.getKey())) { + formVariables.put(entry.getKey(), entry.getValue()); + } + } + encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable); + return super.resolve(argv, mutable, variables); + } + } + + private static final class BuildEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs { + + private final Encoder encoder; + + private BuildEncodedTemplateFromArgs(MethodMetadata metadata, Encoder encoder) { + super(metadata); + this.encoder = encoder; + } + + @Override + RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map variables) { + Object body = argv[metadata.bodyIndex()]; + Util.checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex()); + encoder.encode(body, metadata.bodyType(), mutable); + return super.resolve(argv, mutable, variables); + } + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Request.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Request.java new file mode 100644 index 000000000..3a2dbefe3 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Request.java @@ -0,0 +1,84 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Map; + +/** + * An immutable request to an http server. + */ +final class Request { + + /** + * No parameters can be null except {@code body} and {@code charset}. All parameters must be + * effectively immutable, via safe copies, not mutating or otherwise. + */ + static Request create(String method, String url, Map> headers, byte[] body) { + return new Request(method, url, headers, body); + } + + private final String method; + private final String url; + private final Map> headers; + private final byte[] body; + + Request(String method, String url, Map> headers, byte[] body) { + this.method = Util.checkNotNull(method, "method of %s", url); + this.url = Util.checkNotNull(url, "url"); + this.headers = Util.checkNotNull(headers, "headers of %s %s", method, url); + this.body = body; // nullable + } + + /** Method to invoke on the server. */ + String method() { + return method; + } + + /** Fully resolved URL including query. */ + String url() { + return url; + } + + /** Ordered list of headers that will be sent to the server. */ + Map> headers() { + return headers; + } + + /** + * If present, this is the replayable body to send to the server. In some cases, this may be + * interpretable as text. + */ + byte[] body() { + return body; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(method).append(' ').append(url).append(" HTTP/1.1\n"); + for (String field : headers.keySet()) { + for (String value : Util.valuesOrEmpty(headers, field)) { + builder.append(field).append(": ").append(value).append('\n'); + } + } + if (body != null) { + builder.append('\n').append(new String(body, StandardCharsets.UTF_8)); + } + return builder.toString(); + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/RequestTemplate.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/RequestTemplate.java new file mode 100644 index 000000000..7020e4388 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/RequestTemplate.java @@ -0,0 +1,579 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Builds a request to an http target. Not thread safe.


relationship to JAXRS + * 2.0

A combination of {@code javax.ws.rs.client.WebTarget} and {@code + * javax.ws.rs.client.Invocation.Builder}, ensuring you can modify any part of the request. However, + * this object is mutable, so needs to be guarded with the copy constructor. + */ +@SuppressWarnings({"HiddenField", "MissingSummary"}) +final class RequestTemplate { + + private final Map> queries = new LinkedHashMap>(); + private final Map> headers = new LinkedHashMap>(); + private String method; + /** final to encourage mutable use vs replacing the object. */ + private StringBuilder url = new StringBuilder(); + + private byte[] body; + private boolean decodeSlash = true; + + RequestTemplate() {} + + /** Copy constructor. Use this when making templates. */ + RequestTemplate(RequestTemplate toCopy) { + Util.checkNotNull(toCopy, "toCopy"); + this.method = toCopy.method; + this.url.append(toCopy.url); + this.queries.putAll(toCopy.queries); + this.headers.putAll(toCopy.headers); + this.body = toCopy.body; + this.decodeSlash = toCopy.decodeSlash; + } + + private static String urlDecode(String arg) { + return URLDecoder.decode(arg, StandardCharsets.UTF_8); + } + + private static String urlEncode(Object arg) { + return URLEncoder.encode(String.valueOf(arg), StandardCharsets.UTF_8); + } + + /** + * Expands a {@code template}, such as {@code username}, using the {@code variables} supplied. Any + * unresolved parameters will remain.
Note that if you'd like curly braces literally in the + * {@code template}, urlencode them first. + * + * @param template URI template that can be in level 1 RFC6570 + * form. + * @param variables to the URI template + * @return expanded template, leaving any unresolved parameters literal + */ + @SuppressWarnings("LocalVariableName") + static String expand(String template, Map variables) { + // skip expansion if there's no valid variables set. ex. {a} is the + // first valid + if (Util.checkNotNull(template, "template").length() < 3) { + return template.toString(); + } + Util.checkNotNull(variables, "variables for %s", template); + + boolean inVar = false; + StringBuilder var = new StringBuilder(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < template.length(); i++) { + char c = template.charAt(i); + switch (c) { + case '{' -> { + if (inVar) { + // '{{' is an escape: write the brace and don't interpret as a variable + builder.append("{"); + inVar = false; + break; + } + inVar = true; + } + case '}' -> { + if (!inVar) { // then write the brace literally + builder.append('}'); + break; + } + inVar = false; + String key = var.toString(); + Object value = variables.get(var.toString()); + if (value != null) { + builder.append(value); + } else { + builder.append('{').append(key).append('}'); + } + var = new StringBuilder(); + } + default -> { + if (inVar) { + var.append(c); + } else { + builder.append(c); + } + } + } + } + return builder.toString(); + } + + @SuppressWarnings("LocalVariableName") + private static Map> parseAndDecodeQueries(String queryLine) { + Map> map = new LinkedHashMap>(); + if (Util.emptyToNull(queryLine) == null) { + return map; + } + if (queryLine.indexOf('&') == -1) { + putKV(queryLine, map); + } else { + char[] chars = queryLine.toCharArray(); + int start = 0; + int i = 0; + for (; i < chars.length; i++) { + if (chars[i] == '&') { + putKV(queryLine.substring(start, i), map); + start = i + 1; + } + } + putKV(queryLine.substring(start, i), map); + } + return map; + } + + private static void putKV(String stringToParse, Map> map) { + String key; + String value; + // note that '=' can be a valid part of the value + int firstEq = stringToParse.indexOf('='); + if (firstEq == -1) { + key = urlDecode(stringToParse); + value = null; + } else { + key = urlDecode(stringToParse.substring(0, firstEq)); + value = urlDecode(stringToParse.substring(firstEq + 1)); + } + Collection values = map.containsKey(key) ? map.get(key) : new ArrayList(); + values.add(value); + map.put(key, values); + } + + /** + * Resolves any template parameters in the requests path, query, or headers against the supplied + * unencoded arguments.


relationship to JAXRS 2.0

This call is + * similar to {@code javax.ws.rs.client.WebTarget.resolveTemplates(templateValues, true)} , except + * that the template values apply to any part of the request, not just the URL + */ + RequestTemplate resolve(Map unencoded) { + replaceQueryValues(unencoded); + Map encoded = new LinkedHashMap(); + for (Entry entry : unencoded.entrySet()) { + encoded.put(entry.getKey(), urlEncode(String.valueOf(entry.getValue()))); + } + String resolvedUrl = expand(url.toString(), encoded).replace("+", "%20"); + if (decodeSlash) { + resolvedUrl = resolvedUrl.replace("%2F", "/"); + } + url = new StringBuilder(resolvedUrl); + + Map> resolvedHeaders = new LinkedHashMap>(); + for (String field : headers.keySet()) { + Collection resolvedValues = new ArrayList(); + for (String value : Util.valuesOrEmpty(headers, field)) { + String resolved = expand(value, unencoded); + resolvedValues.add(resolved); + } + resolvedHeaders.put(field, resolvedValues); + } + headers.clear(); + headers.putAll(resolvedHeaders); + return this; + } + + /** roughly analogous to {@code javax.ws.rs.client.Target.request()}. */ + Request request() { + Map> safeCopy = new LinkedHashMap>(); + safeCopy.putAll(headers); + return Request.create(method, url + queryLine(), Collections.unmodifiableMap(safeCopy), body); + } + + /** @see Request#method() */ + RequestTemplate method(String method) { + this.method = Util.checkNotNull(method, "method"); + Util.checkArgument(method.matches("^[A-Z]+$"), "Invalid HTTP Method: %s", method); + return this; + } + + /** @see Request#method() */ + String method() { + return method; + } + + RequestTemplate decodeSlash(boolean decodeSlash) { + this.decodeSlash = decodeSlash; + return this; + } + + boolean decodeSlash() { + return decodeSlash; + } + + /** @see #url() */ + RequestTemplate append(String value) { + url.append(value); + url = pullAnyQueriesOutOfUrl(url); + return this; + } + + /** @see #url() */ + @SuppressWarnings("ParameterAssignment") + RequestTemplate insert(int pos, String value) { + if (value.startsWith("http")) { + if (value.endsWith("/")) { + value = value.substring(0, value.length() - 1); + } + if (url.length() > 0 && url.charAt(0) != '/') { + url.insert(0, '/'); + } + } + url.insert(pos, pullAnyQueriesOutOfUrl(new StringBuilder(value))); + return this; + } + + String url() { + return url.toString(); + } + + /** + * Replaces queries with the specified {@code name} with the {@code values} supplied. + *
Values can be passed in decoded or in url-encoded form depending on the value of the + * {@code encoded} parameter. + *
When the {@code values} is {@code null}, all queries with the {@code configKey} are + * removed.


relationship to JAXRS 2.0

Like {@code WebTarget.query}, + * except the values can be templatized.
ex.
+ *

+     * template.query("Signature", "{signature}");
+     * 
+ *
Note: behavior of RequestTemplate is not consistent if a query parameter with + * unsafe characters is passed as both encoded and unencoded, although no validation is performed. + *
ex.
+ *
+     * template.query(true, "param[]", "value");
+     * template.query(false, "param[]", "value");
+     * 
+ * + * @param encoded whether name and values are already url-encoded + * @param name the name of the query + * @param values can be a single null to imply removing all values. Else no values are expected + * to be null. + * @see #queries() + */ + RequestTemplate query(boolean encoded, String name, String... values) { + return doQuery(encoded, name, values); + } + + /** @see #query(boolean, String, String...) */ + RequestTemplate query(boolean encoded, String name, Iterable values) { + return doQuery(encoded, name, values); + } + + /** + * Shortcut for {@code query(false, String, String...)} + * @see #query(boolean, String, String...) + */ + RequestTemplate query(String name, String... values) { + return doQuery(false, name, values); + } + + /** + * Shortcut for {@code query(false, String, Iterable)} + * @see #query(boolean, String, String...) + */ + RequestTemplate query(String name, Iterable values) { + return doQuery(false, name, values); + } + + private RequestTemplate doQuery(boolean encoded, String name, String... values) { + Util.checkNotNull(name, "name"); + String paramName = encoded ? name : encodeIfNotVariable(name); + queries.remove(paramName); + if (values != null && values.length > 0 && values[0] != null) { + List paramValues = new ArrayList(); + for (String value : values) { + paramValues.add(encoded ? value : encodeIfNotVariable(value)); + } + this.queries.put(paramName, paramValues); + } + return this; + } + + private RequestTemplate doQuery(boolean encoded, String name, Iterable values) { + if (values != null) { + return doQuery(encoded, name, Util.toArray(values, String.class)); + } + return doQuery(encoded, name, (String[]) null); + } + + private static String encodeIfNotVariable(String in) { + if (in == null || in.indexOf('{') == 0) { + return in; + } + return urlEncode(in); + } + + /** + * Replaces all existing queries with the newly supplied url decoded queries.
+ *

relationship to JAXRS 2.0

Like {@code WebTarget.queries}, except the + * values can be templatized.
ex.
+ *
+     * template.queries(ImmutableMultimap.of("Signature", "{signature}"));
+     * 
+ * + * @param queries if null, remove all queries. else value to replace all queries with. + * @see #queries() + */ + RequestTemplate queries(Map> queries) { + if (queries == null || queries.isEmpty()) { + this.queries.clear(); + } else { + for (Entry> entry : queries.entrySet()) { + query(entry.getKey(), Util.toArray(entry.getValue(), String.class)); + } + } + return this; + } + + /** + * Returns an immutable copy of the url decoded queries. + * + * @see Request#url() + */ + Map> queries() { + Map> decoded = new LinkedHashMap>(); + for (String field : queries.keySet()) { + Collection decodedValues = new ArrayList(); + for (String value : Util.valuesOrEmpty(queries, field)) { + if (value != null) { + decodedValues.add(urlDecode(value)); + } else { + decodedValues.add(null); + } + } + decoded.put(urlDecode(field), decodedValues); + } + return Collections.unmodifiableMap(decoded); + } + + /** + * Replaces headers with the specified {@code configKey} with the {@code values} supplied.
+ * When the {@code values} is {@code null}, all headers with the {@code configKey} are removed. + *


relationship to JAXRS 2.0

Like {@code WebTarget.queries} and + * {@code javax.ws.rs.client.Invocation.Builder.header}, except the values can be templatized. + *
ex.
+ *
+     * template.query("X-Application-Version", "{version}");
+     * 
+ * + * @param name the name of the header + * @param values can be a single null to imply removing all values. Else no values are expected to + * be null. + * @see #headers() + */ + RequestTemplate header(String name, String... values) { + Util.checkNotNull(name, "header name"); + if (values == null || (values.length == 1 && values[0] == null)) { + headers.remove(name); + } else { + List headers = new ArrayList(); + headers.addAll(Arrays.asList(values)); + this.headers.put(name, headers); + } + return this; + } + + /** @see #header(String, String...) */ + RequestTemplate header(String name, Iterable values) { + if (values != null) { + return header(name, Util.toArray(values, String.class)); + } + return header(name, (String[]) null); + } + + /** + * Replaces all existing headers with the newly supplied headers.


relationship to + * JAXRS 2.0

Like {@code Invocation.Builder.headers(MultivaluedMap)}, except the + * values can be templatized.
ex.
+ *
+     * template.headers(mapOf("X-Application-Version", asList("{version}")));
+     * 
+ * + * @param headers if null, remove all headers. else value to replace all headers with. + * @see #headers() + */ + RequestTemplate headers(Map> headers) { + if (headers == null || headers.isEmpty()) { + this.headers.clear(); + } else { + this.headers.putAll(headers); + } + return this; + } + + /** + * Returns an immutable copy of the current headers. + * + * @see Request#headers() + */ + Map> headers() { + return Collections.unmodifiableMap(headers); + } + + /** + * replaces the {@link Util#CONTENT_LENGTH} header.
Usually populated by an {@link + * Encoder}. + * + * @see Request#body() + */ + RequestTemplate body(byte[] bodyData) { + this.body = bodyData; + int bodyLength = bodyData != null ? bodyData.length : 0; + header(Util.CONTENT_LENGTH, String.valueOf(bodyLength)); + return this; + } + + /** + * @see Request#body() + */ + byte[] body() { + return body; + } + + /** + * if there are any query params in the URL, this will extract them out. + */ + private StringBuilder pullAnyQueriesOutOfUrl(StringBuilder url) { + // parse out queries + int queryIndex = url.indexOf("?"); + if (queryIndex != -1) { + String queryLine = url.substring(queryIndex + 1); + Map> firstQueries = parseAndDecodeQueries(queryLine); + if (!queries.isEmpty()) { + firstQueries.putAll(queries); + queries.clear(); + } + // Since we decode all queries, we want to use the + // query()-method to re-add them to ensure that all + // logic (such as url-encoding) are executed, giving + // a valid queryLine() + for (String key : firstQueries.keySet()) { + Collection values = firstQueries.get(key); + if (allValuesAreNull(values)) { + // Queries where all values are null will + // be ignored by the query(key, value)-method + // So we manually avoid this case here, to ensure that + // we still fulfill the contract (ex. parameters without values) + queries.put(urlEncode(key), values); + } else { + query(key, values); + } + } + return new StringBuilder(url.substring(0, queryIndex)); + } + return url; + } + + private boolean allValuesAreNull(Collection values) { + if (values == null || values.isEmpty()) { + return true; + } + for (String val : values) { + if (val != null) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return request().toString(); + } + + /** + * Replaces query values which are templated with corresponding values from the {@code unencoded} + * map. Any unresolved queries are removed. + */ + void replaceQueryValues(Map unencoded) { + Iterator>> iterator = + queries.entrySet().iterator(); + while (iterator.hasNext()) { + Entry> entry = iterator.next(); + if (entry.getValue() == null) { + continue; + } + Collection values = new ArrayList(); + for (String value : entry.getValue()) { + if (value.indexOf('{') == 0 && value.indexOf('}') == value.length() - 1) { + Object variableValue = unencoded.get(value.substring(1, value.length() - 1)); + // only add non-null expressions + if (variableValue == null) { + continue; + } + if (variableValue instanceof Iterable) { + for (Object val : Iterable.class.cast(variableValue)) { + values.add(urlEncode(String.valueOf(val))); + } + } else { + values.add(urlEncode(String.valueOf(variableValue))); + } + } else { + values.add(value); + } + } + if (values.isEmpty()) { + iterator.remove(); + } else { + entry.setValue(values); + } + } + } + + String queryLine() { + if (queries.isEmpty()) { + return ""; + } + StringBuilder queryBuilder = new StringBuilder(); + for (String field : queries.keySet()) { + for (String value : Util.valuesOrEmpty(queries, field)) { + queryBuilder.append('&'); + queryBuilder.append(field); + if (value != null) { + queryBuilder.append('='); + if (!value.isEmpty()) { + queryBuilder.append(value); + } + } + } + } + queryBuilder.deleteCharAt(0); + return queryBuilder.insert(0, '?').toString(); + } + + interface Factory { + + /** + * create a request template using args passed to a method invocation. + */ + RequestTemplate create(Object[] argv); + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Response.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Response.java new file mode 100644 index 000000000..e08bd3a10 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Response.java @@ -0,0 +1,261 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +/** + * An immutable response to an http invocation which only returns string content. + */ +final class Response implements Closeable { + + private final int status; + private final String reason; + private final Map> headers; + private final Body body; + + private Response(int status, String reason, Map> headers, Body body) { + Util.checkState(status >= 200, "Invalid status code: %s", status); + this.status = status; + this.reason = reason; // nullable + this.headers = Collections.unmodifiableMap(caseInsensitiveCopyOf(headers)); + this.body = body; // nullable + } + + static Response create( + int status, + String reason, + Map> headers, + InputStream inputStream, + Integer length) { + return new Response(status, reason, headers, InputStreamBody.orNull(inputStream, length)); + } + + static Response create(int status, String reason, Map> headers, byte[] data) { + return new Response(status, reason, headers, ByteArrayBody.orNull(data)); + } + + static Response create( + int status, String reason, Map> headers, String text, Charset charset) { + return new Response(status, reason, headers, ByteArrayBody.orNull(text, charset)); + } + + static Response create(int status, String reason, Map> headers, Body body) { + return new Response(status, reason, headers, body); + } + + /** + * status code. ex {@code 200} + * + * See rfc2616 + */ + int status() { + return status; + } + + /** + * Nullable and not set when using http/2 + * + * See https://github.com/http2/http2-spec/issues/202 + */ + String reason() { + return reason; + } + + /** + * Returns a case-insensitive mapping of header names to their values. + */ + Map> headers() { + return headers; + } + + /** + * if present, the response had a body + */ + Body body() { + return body; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("HTTP/1.1 ").append(status); + if (reason != null) { + builder.append(' ').append(reason); + } + builder.append('\n'); + for (String field : headers.keySet()) { + for (String value : Util.valuesOrEmpty(headers, field)) { + builder.append(field).append(": ").append(value).append('\n'); + } + } + if (body != null) { + builder.append('\n').append(body); + } + return builder.toString(); + } + + @Override + public void close() { + Util.ensureClosed(body); + } + + interface Body extends Closeable { + + /** + * length in bytes, if known. Null if unknown or greater than {@link Integer#MAX_VALUE}. + * + *


Note
This is an integer as + * most implementations cannot do bodies greater than 2GB. + */ + Integer length(); + + /** + * True if {@link #asInputStream()} and {@link #asReader()} can be called more than once. + */ + boolean isRepeatable(); + + /** + * It is the responsibility of the caller to close the stream. + */ + InputStream asInputStream() throws IOException; + + /** + * It is the responsibility of the caller to close the stream. + */ + Reader asReader() throws IOException; + } + + private static final class InputStreamBody implements Body { + + private final InputStream inputStream; + private final Integer length; + + private InputStreamBody(InputStream inputStream, Integer length) { + this.inputStream = inputStream; + this.length = length; + } + + private static Body orNull(InputStream inputStream, Integer length) { + if (inputStream == null) { + return null; + } + return new InputStreamBody(inputStream, length); + } + + @Override + public Integer length() { + return length; + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public InputStream asInputStream() throws IOException { + return inputStream; + } + + @Override + public Reader asReader() throws IOException { + return new InputStreamReader(inputStream, StandardCharsets.UTF_8); + } + + @Override + public void close() throws IOException { + inputStream.close(); + } + } + + private static final class ByteArrayBody implements Body { + + private final byte[] data; + + ByteArrayBody(byte[] data) { + this.data = data; + } + + private static Body orNull(byte[] data) { + if (data == null) { + return null; + } + return new ByteArrayBody(data); + } + + private static Body orNull(String text, Charset charset) { + if (text == null) { + return null; + } + Util.checkNotNull(charset, "charset"); + return new ByteArrayBody(text.getBytes(charset)); + } + + @Override + public Integer length() { + return data.length; + } + + @Override + public boolean isRepeatable() { + return true; + } + + @Override + public InputStream asInputStream() throws IOException { + return new ByteArrayInputStream(data); + } + + @Override + public Reader asReader() throws IOException { + return new InputStreamReader(asInputStream(), StandardCharsets.UTF_8); + } + + @Override + public void close() throws IOException {} + + @Override + public String toString() { + return Util.decodeOrDefault(data, StandardCharsets.UTF_8, "Binary data"); + } + } + + private static Map> caseInsensitiveCopyOf(Map> headers) { + Map> result = new TreeMap>(String.CASE_INSENSITIVE_ORDER); + + for (Map.Entry> entry : headers.entrySet()) { + String headerName = entry.getKey(); + if (!result.containsKey(headerName)) { + result.put(headerName.toLowerCase(Locale.ROOT), new ArrayList<>()); + } + result.get(headerName).addAll(entry.getValue()); + } + return result; + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/SlashEncodingContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/SlashEncodingContract.java similarity index 66% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/SlashEncodingContract.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/SlashEncodingContract.java index 94c0451c1..d3e433e90 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/SlashEncodingContract.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/SlashEncodingContract.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,21 +14,19 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; -import feign.Contract; -import feign.MethodMetadata; import java.lang.reflect.Method; /** Decorates a {@link Contract} and forces slashes to be encoded when they are part of a URL. */ -public final class SlashEncodingContract extends AbstractDelegatingContract { +final class SlashEncodingContract extends AbstractDelegatingContract { - public SlashEncodingContract(Contract delegate) { + SlashEncodingContract(Contract delegate) { super(delegate); } @Override - protected void processMetadata(Class _targetType, Method _method, MethodMetadata metadata) { + void processMetadata(Class _targetType, Method _method, MethodMetadata metadata) { metadata.template().decodeSlash(false); } } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/SynchronousMethodHandler.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/SynchronousMethodHandler.java new file mode 100644 index 000000000..15d010282 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/SynchronousMethodHandler.java @@ -0,0 +1,102 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +final class SynchronousMethodHandler implements MethodHandler { + + private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L; + + private final MethodMetadata metadata; + private final Target target; + private final Client client; + private final RequestTemplate.Factory buildTemplateFromArgs; + private final Decoder decoder; + private final ErrorDecoder errorDecoder; + + private SynchronousMethodHandler( + Target target, + Client client, + MethodMetadata metadata, + RequestTemplate.Factory buildTemplateFromArgs, + Decoder decoder, + ErrorDecoder errorDecoder) { + this.target = Util.checkNotNull(target, "target"); + this.client = Util.checkNotNull(client, "client for %s", target); + this.metadata = Util.checkNotNull(metadata, "metadata for %s", target); + this.buildTemplateFromArgs = Util.checkNotNull(buildTemplateFromArgs, "metadata for %s", target); + this.errorDecoder = Util.checkNotNull(errorDecoder, "errorDecoder for %s", target); + this.decoder = Util.checkNotNull(decoder, "decoder for %s", target); + } + + @Override + public Object invoke(Object[] argv) throws Throwable { + RequestTemplate template = buildTemplateFromArgs.create(argv); + return executeAndDecode(template); + } + + Object executeAndDecode(RequestTemplate template) throws Exception { + Request request = target.apply(new RequestTemplate(template)); + + Response response = client.execute(request); + + boolean shouldClose = true; + try { + if (Response.class == metadata.returnType()) { + if (response.body() == null) { + return response; + } + if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { + shouldClose = false; + return response; + } + // Ensure the response body is disconnected + byte[] bodyData = Util.toByteArray(response.body().asInputStream()); + return Response.create(response.status(), response.reason(), response.headers(), bodyData); + } + if (response.status() >= 200 && response.status() < 300) { + if (void.class == metadata.returnType()) { + return null; + } else { + return decoder.decode(response, metadata.returnType()); + } + } else { + throw errorDecoder.decode(metadata.configKey(), response); + } + } finally { + if (shouldClose) { + Util.ensureClosed(response.body()); + } + } + } + + static class Factory { + + private final Client client; + + Factory(Client client) { + this.client = Util.checkNotNull(client, "client"); + } + + public MethodHandler create( + Target target, + MethodMetadata md, + RequestTemplate.Factory buildTemplateFromArgs, + Decoder decoder, + ErrorDecoder errorDecoder) { + return new SynchronousMethodHandler(target, client, md, buildTemplateFromArgs, decoder, errorDecoder); + } + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Target.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Target.java new file mode 100644 index 000000000..a2472f56c --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Target.java @@ -0,0 +1,48 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +/** + *

relationship to JAXRS 2.0

Similar to {@code + * javax.ws.rs.client.WebTarget}, as it produces requests. However, {@link RequestTemplate} is a + * closer match to {@code WebTarget}. + * + * @param type of the interface this target applies to. + */ +public interface Target { + + /** The type of the interface this target applies to. ex. {@code Route53}. */ + Class type(); + + /** base HTTP URL of the target. For example, {@code https://api/v2}. */ + String url(); + + /** + * Targets a template to this target, adding the {@link #url() base url} and any target-specific + * headers or query parameters.

For example:
+ *
+     * public Request apply(RequestTemplate input) {
+     *     input.insert(0, url());
+     *     input.replaceHeader("X-Auth", currentToken);
+     *     return input.asRequest();
+     * }
+     * 
+ *


relationship to JAXRS 2.0

This call is similar to {@code + * javax.ws.rs.client.WebTarget.request()}, except that we expect transient, but necessary + * decoration to be applied on invocation. + */ + Request apply(RequestTemplate input); +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateDecoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/TextDelegateDecoder.java similarity index 67% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateDecoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/TextDelegateDecoder.java index c688a12b1..272931cac 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateDecoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/TextDelegateDecoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,34 +14,31 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.net.HttpHeaders; -import feign.FeignException; -import feign.Response; -import feign.codec.Decoder; -import feign.codec.StringDecoder; +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.exceptions.SafeRuntimeException; import java.io.IOException; import java.lang.reflect.Type; import java.util.Collection; /** - * Delegates to a {@link StringDecoder} if the response has a Content-Type of text/plain, or falls back to the given + * Decodes the response as a string if the response has a Content-Type of text/plain, or falls back to the given * delegate otherwise. */ -public final class TextDelegateDecoder implements Decoder { - private static final Decoder stringDecoder = new StringDecoder(); +final class TextDelegateDecoder implements Decoder { private final Decoder delegate; - public TextDelegateDecoder(Decoder delegate) { + TextDelegateDecoder(Decoder delegate) { this.delegate = delegate; } @Override - public Object decode(Response response, Type type) throws IOException, FeignException { + public Object decode(Response response, Type type) throws IOException { Collection contentTypes = HeaderAccessUtils.caseInsensitiveGet(response.headers(), HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { @@ -50,11 +47,14 @@ public Object decode(Response response, Type type) throws IOException, FeignExce // In the case of multiple content types, or an unknown content type, we'll use the delegate instead. if (contentTypes.size() == 1 && Iterables.getOnlyElement(contentTypes, "").startsWith("text/plain")) { - Object decoded = stringDecoder.decode(response, type); - if (decoded == null) { + Response.Body body = response.body(); + if (body == null) { return ""; } - return decoded; + if (String.class.equals(type)) { + return Util.toString(body.asReader()); + } + throw new SafeRuntimeException("Type is not supported by this encoder", SafeArg.of("type", type)); } return delegate.decode(response, type); diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateEncoder.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/TextDelegateEncoder.java similarity index 61% rename from conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateEncoder.java rename to conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/TextDelegateEncoder.java index 8a60f8709..adeda7695 100644 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateEncoder.java +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/TextDelegateEncoder.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,32 +14,31 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.net.HttpHeaders; -import feign.RequestTemplate; -import feign.codec.EncodeException; -import feign.codec.Encoder; +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.exceptions.SafeRuntimeException; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.util.Collection; /** - * Delegates to a {@link feign.codec.Encoder.Default} if the response has a Content-Type of text/plain, or falls back to - * the given delegate otherwise. + * Encodes the value as a string if the request has a Content-Type of text/plain, or falls back to the given delegate + * otherwise. */ -public final class TextDelegateEncoder implements Encoder { - private static final Encoder defaultEncoder = new Encoder.Default(); +final class TextDelegateEncoder implements Encoder { private final Encoder delegate; - public TextDelegateEncoder(Encoder delegate) { + TextDelegateEncoder(Encoder delegate) { this.delegate = delegate; } @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + public void encode(Object object, Type bodyType, RequestTemplate template) { Collection contentTypes = HeaderAccessUtils.caseInsensitiveGet(template.headers(), HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { @@ -49,7 +48,14 @@ public void encode(Object object, Type bodyType, RequestTemplate template) throw // In the case of multiple content types, or an unknown content type, we'll use the delegate instead. if (contentTypes.size() == 1 && Iterables.getOnlyElement(contentTypes, "").equals("text/plain")) { - defaultEncoder.encode(object, bodyType, template); + if (bodyType == String.class) { + template.body(object.toString().getBytes(StandardCharsets.UTF_8)); + } else if (bodyType == byte[].class) { + template.body((byte[]) object); + } else if (object != null) { + throw new SafeRuntimeException( + "Type is not supported by this encoder", SafeArg.of("type", object.getClass())); + } } else { delegate.encode(object, bodyType, template); } diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Types.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Types.java new file mode 100644 index 000000000..266a25a0f --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Types.java @@ -0,0 +1,456 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Arrays; +import java.util.NoSuchElementException; + +/** + * Static methods for working with types. + * + * @author Bob Lee + * @author Jesse Wilson + */ +@SuppressWarnings({"CyclomaticComplexity", "ParameterAssignment", "ParameterName"}) +final class Types { + + private static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; + + private Types() { + // No instances. + } + + static Class getRawType(Type type) { + if (type instanceof Class) { + // Type is a normal class. + return (Class) type; + + } else if (type instanceof ParameterizedType parameterizedType) { + // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but + // suspects some pathological case related to nested classes exists. + Type rawType = parameterizedType.getRawType(); + if (!(rawType instanceof Class)) { + throw new IllegalArgumentException(); + } + return (Class) rawType; + + } else if (type instanceof GenericArrayType genericArrayType) { + Type componentType = genericArrayType.getGenericComponentType(); + return Array.newInstance(getRawType(componentType), 0).getClass(); + + } else if (type instanceof TypeVariable) { + // We could use the variable's bounds, but that won't work if there are multiple. Having a raw + // type that's more general than necessary is okay. + return Object.class; + + } else if (type instanceof WildcardType wildcardType) { + return getRawType(wildcardType.getUpperBounds()[0]); + + } else { + String className = type == null ? "null" : type.getClass().getName(); + throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + + "GenericArrayType, but <" + type + "> is of type " + + className); + } + } + + /** + * Returns true if {@code a} and {@code b} are equal. + */ + static boolean equals(Type a, Type b) { + if (a == b) { + return true; // Also handles (a == null && b == null). + + } else if (a instanceof Class) { + return a.equals(b); // Class already specifies equals(). + + } else if (a instanceof ParameterizedType pa) { + if (!(b instanceof ParameterizedType pb)) { + return false; + } + return equal(pa.getOwnerType(), pb.getOwnerType()) + && pa.getRawType().equals(pb.getRawType()) + && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); + + } else if (a instanceof GenericArrayType ga) { + if (!(b instanceof GenericArrayType gb)) { + return false; + } + return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); + + } else if (a instanceof WildcardType wa) { + if (!(b instanceof WildcardType wb)) { + return false; + } + return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) + && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); + + } else if (a instanceof TypeVariable va) { + if (!(b instanceof TypeVariable vb)) { + return false; + } + return va.getGenericDeclaration() == vb.getGenericDeclaration() + && va.getName().equals(vb.getName()); + + } else { + return false; // This isn't a type we support! + } + } + + /** + * Returns the generic supertype for {@code supertype}. For example, given a class {@code + * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the + * result when the supertype is {@code Collection.class} is {@code Collection}. + */ + static Type getGenericSupertype(Type context, Class rawType, Class toResolve) { + if (toResolve == rawType) { + return context; + } + + // We skip searching through interfaces if unknown is an interface. + if (toResolve.isInterface()) { + Class[] interfaces = rawType.getInterfaces(); + for (int i = 0, length = interfaces.length; i < length; i++) { + if (interfaces[i] == toResolve) { + return rawType.getGenericInterfaces()[i]; + } else if (toResolve.isAssignableFrom(interfaces[i])) { + return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); + } + } + } + + // Check our supertypes. + if (!rawType.isInterface()) { + while (rawType != Object.class) { + Class rawSupertype = rawType.getSuperclass(); + if (rawSupertype == toResolve) { + return rawType.getGenericSuperclass(); + } else if (toResolve.isAssignableFrom(rawSupertype)) { + return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); + } + rawType = rawSupertype; + } + } + + // We can't resolve this further. + return toResolve; + } + + private static int indexOf(Object[] array, Object toFind) { + for (int i = 0; i < array.length; i++) { + if (toFind.equals(array[i])) { + return i; + } + } + throw new NoSuchElementException(); + } + + private static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + private static int hashCodeOrZero(Object o) { + return o != null ? o.hashCode() : 0; + } + + static String typeToString(Type type) { + return type instanceof Class ? ((Class) type).getName() : type.toString(); + } + + /** + * Returns the generic form of {@code supertype}. For example, if this is {@code + * ArrayList}, this returns {@code Iterable} given the input {@code + * Iterable.class}. + * + * @param supertype a superclass of, or interface implemented by, this. + */ + static Type getSupertype(Type context, Class contextRawType, Class supertype) { + if (!supertype.isAssignableFrom(contextRawType)) { + throw new IllegalArgumentException(); + } + return resolve(context, contextRawType, getGenericSupertype(context, contextRawType, supertype)); + } + + static Type resolve(Type context, Class contextRawType, Type toResolve) { + // This implementation is made a little more complicated in an attempt to avoid object-creation. + while (true) { + if (toResolve instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) toResolve; + toResolve = resolveTypeVariable(context, contextRawType, typeVariable); + if (toResolve == typeVariable) { + return toResolve; + } + + } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { + Class original = (Class) toResolve; + Type componentType = original.getComponentType(); + Type newComponentType = resolve(context, contextRawType, componentType); + return componentType == newComponentType ? original : new GenericArrayTypeImpl(newComponentType); + + } else if (toResolve instanceof GenericArrayType) { + GenericArrayType original = (GenericArrayType) toResolve; + Type componentType = original.getGenericComponentType(); + Type newComponentType = resolve(context, contextRawType, componentType); + return componentType == newComponentType ? original : new GenericArrayTypeImpl(newComponentType); + + } else if (toResolve instanceof ParameterizedType) { + ParameterizedType original = (ParameterizedType) toResolve; + Type ownerType = original.getOwnerType(); + Type newOwnerType = resolve(context, contextRawType, ownerType); + boolean changed = newOwnerType != ownerType; + + Type[] args = original.getActualTypeArguments(); + for (int t = 0, length = args.length; t < length; t++) { + Type resolvedTypeArgument = resolve(context, contextRawType, args[t]); + if (resolvedTypeArgument != args[t]) { + if (!changed) { + args = args.clone(); + changed = true; + } + args[t] = resolvedTypeArgument; + } + } + + return changed ? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args) : original; + + } else if (toResolve instanceof WildcardType) { + WildcardType original = (WildcardType) toResolve; + Type[] originalLowerBound = original.getLowerBounds(); + Type[] originalUpperBound = original.getUpperBounds(); + + if (originalLowerBound.length == 1) { + Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]); + if (lowerBound != originalLowerBound[0]) { + return new WildcardTypeImpl(new Type[] {Object.class}, new Type[] {lowerBound}); + } + } else if (originalUpperBound.length == 1) { + Type upperBound = resolve(context, contextRawType, originalUpperBound[0]); + if (upperBound != originalUpperBound[0]) { + return new WildcardTypeImpl(new Type[] {upperBound}, EMPTY_TYPE_ARRAY); + } + } + return original; + + } else { + return toResolve; + } + } + } + + private static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) { + Class declaredByRaw = declaringClassOf(unknown); + + // We can't reduce this further. + if (declaredByRaw == null) { + return unknown; + } + + Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); + if (declaredBy instanceof ParameterizedType parameterizedType) { + int index = indexOf(declaredByRaw.getTypeParameters(), unknown); + return parameterizedType.getActualTypeArguments()[index]; + } + + return unknown; + } + + /** + * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by + * a class. + */ + private static Class declaringClassOf(TypeVariable typeVariable) { + GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); + return genericDeclaration instanceof Class ? (Class) genericDeclaration : null; + } + + private static void checkNotPrimitive(Type type) { + if (type instanceof Class classType && classType.isPrimitive()) { + throw new IllegalArgumentException(); + } + } + + static final class ParameterizedTypeImpl implements ParameterizedType { + + private final Type ownerType; + private final Type rawType; + private final Type[] typeArguments; + + ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { + // Require an owner type if the raw type needs it. + if (rawType instanceof Class classType + && (ownerType == null) != (classType.getEnclosingClass() == null)) { + throw new IllegalArgumentException(); + } + + this.ownerType = ownerType; + this.rawType = rawType; + this.typeArguments = typeArguments.clone(); + + for (Type typeArgument : this.typeArguments) { + if (typeArgument == null) { + throw new NullPointerException(); + } + checkNotPrimitive(typeArgument); + } + } + + @Override + public Type[] getActualTypeArguments() { + return typeArguments.clone(); + } + + @Override + public Type getRawType() { + return rawType; + } + + @Override + public Type getOwnerType() { + return ownerType; + } + + @Override + public boolean equals(Object other) { + return other instanceof ParameterizedType parameterizedType && Types.equals(this, parameterizedType); + } + + @Override + public int hashCode() { + return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1)); + result.append(typeToString(rawType)); + if (typeArguments.length == 0) { + return result.toString(); + } + result.append("<").append(typeToString(typeArguments[0])); + for (int i = 1; i < typeArguments.length; i++) { + result.append(", ").append(typeToString(typeArguments[i])); + } + return result.append(">").toString(); + } + } + + private static final class GenericArrayTypeImpl implements GenericArrayType { + + private final Type componentType; + + GenericArrayTypeImpl(Type componentType) { + this.componentType = componentType; + } + + @Override + public Type getGenericComponentType() { + return componentType; + } + + @Override + public boolean equals(Object o) { + return o instanceof GenericArrayType genericArrayType && Types.equals(this, genericArrayType); + } + + @Override + public int hashCode() { + return componentType.hashCode(); + } + + @Override + public String toString() { + return typeToString(componentType) + "[]"; + } + } + + /** + * The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only + * support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper + * bound must be Object.class. + */ + static final class WildcardTypeImpl implements WildcardType { + + private final Type upperBound; + private final Type lowerBound; + + WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { + if (lowerBounds.length > 1) { + throw new IllegalArgumentException(); + } + if (upperBounds.length != 1) { + throw new IllegalArgumentException(); + } + + if (lowerBounds.length == 1) { + if (lowerBounds[0] == null) { + throw new NullPointerException(); + } + checkNotPrimitive(lowerBounds[0]); + if (upperBounds[0] != Object.class) { + throw new IllegalArgumentException(); + } + this.lowerBound = lowerBounds[0]; + this.upperBound = Object.class; + } else { + if (upperBounds[0] == null) { + throw new NullPointerException(); + } + checkNotPrimitive(upperBounds[0]); + this.lowerBound = null; + this.upperBound = upperBounds[0]; + } + } + + @Override + public Type[] getUpperBounds() { + return new Type[] {upperBound}; + } + + @Override + public Type[] getLowerBounds() { + return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY; + } + + @Override + public boolean equals(Object other) { + return other instanceof WildcardType wildcardType && Types.equals(this, wildcardType); + } + + @Override + public int hashCode() { + // This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()). + return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode()); + } + + @Override + public String toString() { + if (lowerBound != null) { + return "? super " + typeToString(lowerBound); + } + if (upperBound == Object.class) { + return "?"; + } + return "? extends " + typeToString(upperBound); + } + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Util.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Util.java new file mode 100644 index 000000000..8d4fd0ca9 --- /dev/null +++ b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/Util.java @@ -0,0 +1,257 @@ +/* + * (c) Copyright 2026 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.client.jaxrs; + +import com.google.errorprone.annotations.FormatMethod; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Utilities, typically copied in from guava, so as to avoid dependency conflicts. + */ +final class Util { + + /** + * The HTTP Content-Length header field name. + */ + static final String CONTENT_LENGTH = "Content-Length"; + /** + * UTF-8: eight-bit UCS Transformation Format. + */ + static final Charset UTF_8 = Charset.forName("UTF-8"); + + private static final int BUF_SIZE = 0x800; // 2K chars (4K bytes) + + /** + * Type literal for {@code Map}. + */ + static final Type MAP_STRING_WILDCARD = new Types.ParameterizedTypeImpl( + null, Map.class, String.class, new Types.WildcardTypeImpl(new Type[] {Object.class}, new Type[0])); + + private Util() { // no instances + } + + /** + * Copy of {@code com.google.common.base.Preconditions#checkArgument}. + */ + @FormatMethod + static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Copy of {@code com.google.common.base.Preconditions#checkNotNull}. + */ + @FormatMethod + static T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) { + if (reference == null) { + // If either of these parameters is null, the right thing happens anyway + throw new NullPointerException(String.format(errorMessageTemplate, errorMessageArgs)); + } + return reference; + } + + /** + * Copy of {@code com.google.common.base.Preconditions#checkState}. + */ + @FormatMethod + static void checkState(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException(String.format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Adapted from {@code com.google.common.base.Strings#emptyToNull}. + */ + static String emptyToNull(String string) { + return string == null || string.isEmpty() ? null : string; + } + + /** + * Adapted from {@code com.google.common.base.Strings#emptyToNull}. + */ + static T[] toArray(Iterable iterable, Class type) { + Collection collection; + if (iterable instanceof Collection) { + collection = (Collection) iterable; + } else { + collection = new ArrayList(); + for (T element : iterable) { + collection.add(element); + } + } + T[] array = (T[]) Array.newInstance(type, collection.size()); + return collection.toArray(array); + } + + /** + * Returns an unmodifiable collection which may be empty, but is never null. + */ + static Collection valuesOrEmpty(Map> map, String key) { + return map.containsKey(key) && map.get(key) != null ? map.get(key) : Collections.emptyList(); + } + + static void ensureClosed(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException ignored) { // NOPMD + } + } + } + + /** + * This returns well known empty values for well-known java types. This returns null for types not + * in the following list. + * + *
    + *
  • {@code [Bb]oolean}
  • + *
  • {@code byte[]}
  • + *
  • {@code Collection}
  • + *
  • {@code Iterator}
  • + *
  • {@code List}
  • + *
  • {@code Map}
  • + *
  • {@code Set}
  • + *
+ * + *

When {@link Feign.Builder#decode404() decoding HTTP 404 status}, you'll need to teach + * decoders a default empty value for a type. This method cheaply supports typical types by only + * looking at the raw type (vs type hierarchy). Decorate for sophistication. + */ + static Object emptyValueOf(Type type) { + return EMPTIES.get(Types.getRawType(type)); + } + + private static final Map, Object> EMPTIES; + + static { + Map, Object> empties = new LinkedHashMap, Object>(); + empties.put(boolean.class, false); + empties.put(Boolean.class, false); + empties.put(byte[].class, new byte[0]); + empties.put(Collection.class, Collections.emptyList()); + empties.put( + Iterator.class, + new Iterator() { // Collections.emptyIterator is a 1.7 api + @Override + public boolean hasNext() { + return false; + } + + @Override + public Object next() { + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new IllegalStateException(); + } + }); + empties.put(List.class, Collections.emptyList()); + empties.put(Map.class, Collections.emptyMap()); + empties.put(Set.class, Collections.emptySet()); + EMPTIES = Collections.unmodifiableMap(empties); + } + + /** + * Adapted from {@code com.google.common.io.CharStreams.toString()}. + */ + static String toString(Reader reader) throws IOException { + if (reader == null) { + return null; + } + try { + StringBuilder to = new StringBuilder(); + CharBuffer buf = CharBuffer.allocate(BUF_SIZE); + while (reader.read(buf) != -1) { + buf.flip(); + to.append(buf); + buf.clear(); + } + return to.toString(); + } finally { + ensureClosed(reader); + } + } + + /** + * Adapted from {@code com.google.common.io.ByteStreams.toByteArray()}. + */ + static byte[] toByteArray(InputStream in) throws IOException { + checkNotNull(in, "in"); + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + copy(in, out); + return out.toByteArray(); + } finally { + ensureClosed(in); + } + } + + /** + * Adapted from {@code com.google.common.io.ByteStreams.copy()}. + */ + private static long copy(InputStream from, OutputStream to) throws IOException { + checkNotNull(from, "from"); + checkNotNull(to, "to"); + byte[] buf = new byte[BUF_SIZE]; + long total = 0; + while (true) { + int read = from.read(buf); + if (read == -1) { + break; + } + to.write(buf, 0, read); + total += read; + } + return total; + } + + static String decodeOrDefault(byte[] data, Charset charset, String defaultValue) { + if (data == null) { + return defaultValue; + } + checkNotNull(charset, "charset"); + try { + return charset.newDecoder().decode(ByteBuffer.wrap(data)).toString(); + } catch (CharacterCodingException ex) { + return defaultValue; + } + } +} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/BackoffStrategy.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/BackoffStrategy.java deleted file mode 100644 index 2775c8531..000000000 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/BackoffStrategy.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * (c) Copyright 2017 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.client.jaxrs.feignimpl; - -/** Defines a strategy for waiting in between successive retries of an operation that is subject to failure. */ -public interface BackoffStrategy { - /** - * Blocks for an implementation-defined period of time and returns true iff the backed-off operation should be - * attempted again. - */ - boolean backoff(int numFailedAttempts); -} diff --git a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/PathTemplateHeaderEnrichmentContract.java b/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/PathTemplateHeaderEnrichmentContract.java deleted file mode 100644 index c6acafa94..000000000 --- a/conjure-java-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/feignimpl/PathTemplateHeaderEnrichmentContract.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * (c) Copyright 2017 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.client.jaxrs.feignimpl; - -import feign.Contract; -import feign.MethodMetadata; -import java.lang.reflect.Method; - -public final class PathTemplateHeaderEnrichmentContract extends AbstractDelegatingContract { - private static final String PATH_TEMPLATE_HEADER = "hr-path-template"; - /** - * No longer used. - * - * @deprecated no longer used - */ - @Deprecated - public static final char OPEN_BRACE_REPLACEMENT = '\0'; - /** - * No longer used. - * - * @deprecated no longer used - */ - @Deprecated - public static final char CLOSE_BRACE_REPLACEMENT = '\1'; - - public PathTemplateHeaderEnrichmentContract(Contract delegate) { - super(delegate); - } - - @Override - protected void processMetadata(Class _targetType, Method _method, MethodMetadata metadata) { - metadata.template() - .header( - PATH_TEMPLATE_HEADER, - metadata.template().method() - + " " - + metadata.template() - .url() - // escape from feign string interpolation - // See RequestTemplate.expand - .replace("{", "{{")); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureCborDelegateDecoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureCborDelegateDecoder.java deleted file mode 100644 index 1533d7066..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureCborDelegateDecoder.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateDecoder; -import feign.codec.Decoder; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * Use {@link CborDelegateDecoder}. - * - * @deprecated Use {@link CborDelegateDecoder}. - */ -@Deprecated -public final class ConjureCborDelegateDecoder implements Decoder { - - private final CborDelegateDecoder delegate; - - public ConjureCborDelegateDecoder(ObjectMapper cborMapper, Decoder delegate) { - this.delegate = new CborDelegateDecoder(cborMapper, delegate); - } - - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - return delegate.decode(response, type); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureCborDelegateEncoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureCborDelegateEncoder.java deleted file mode 100644 index 7192e4ffc..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureCborDelegateEncoder.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.palantir.conjure.java.client.jaxrs.feignimpl.CborDelegateEncoder; -import feign.codec.EncodeException; -import feign.codec.Encoder; -import java.lang.reflect.Type; - -/** - * Use {@link CborDelegateEncoder}. - * - * @deprecated Use {@link CborDelegateEncoder}. - */ -@Deprecated -public final class ConjureCborDelegateEncoder implements Encoder { - - @SuppressWarnings("unused") // public API - public static final String MIME_TYPE = CborDelegateEncoder.MIME_TYPE; - - private final Encoder delegate; - - public ConjureCborDelegateEncoder(ObjectMapper cborMapper, Encoder delegate) { - this.delegate = new CborDelegateEncoder(cborMapper, delegate); - } - - @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - delegate.encode(object, bodyType, template); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureEmptyContainerDecoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureEmptyContainerDecoder.java deleted file mode 100644 index c3d4e88cd..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureEmptyContainerDecoder.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * (c) Copyright 2018 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 feign; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.palantir.conjure.java.client.jaxrs.feignimpl.EmptyContainerDecoder; -import feign.codec.Decoder; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * Use {@link EmptyContainerDecoder}. - * - * @deprecated Use {@link EmptyContainerDecoder}. - */ -@Deprecated -public final class ConjureEmptyContainerDecoder implements Decoder { - - private final Decoder delegate; - - public ConjureEmptyContainerDecoder(ObjectMapper mapper, Decoder delegate) { - this.delegate = new EmptyContainerDecoder(mapper, delegate); - } - - @Override - public Object decode(Response response, Type type) throws IOException { - return delegate.decode(response, type); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureGuavaOptionalAwareDecoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureGuavaOptionalAwareDecoder.java deleted file mode 100644 index 81d0ad267..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureGuavaOptionalAwareDecoder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareDecoder; -import feign.codec.Decoder; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * Use {@link GuavaOptionalAwareDecoder}. - * - * @deprecated Use {@link GuavaOptionalAwareDecoder}. - */ -@Deprecated -public final class ConjureGuavaOptionalAwareDecoder implements Decoder { - - private final Decoder delegate; - - public ConjureGuavaOptionalAwareDecoder(Decoder delegate) { - this.delegate = new GuavaOptionalAwareDecoder(delegate); - } - - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - return delegate.decode(response, type); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureInputStreamDelegateDecoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureInputStreamDelegateDecoder.java deleted file mode 100644 index 1cc65ce4a..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureInputStreamDelegateDecoder.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateDecoder; -import feign.codec.Decoder; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * Use {@link InputStreamDelegateDecoder}. - * - * @deprecated Use {@link InputStreamDelegateDecoder}. - */ -@Deprecated -public final class ConjureInputStreamDelegateDecoder implements Decoder { - private final Decoder delegate; - - public ConjureInputStreamDelegateDecoder(Decoder delegate) { - this.delegate = new InputStreamDelegateDecoder("unknown", delegate); - } - - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - return delegate.decode(response, type); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureInputStreamDelegateEncoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureInputStreamDelegateEncoder.java deleted file mode 100644 index 326209c5b..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureInputStreamDelegateEncoder.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.palantir.conjure.java.client.jaxrs.feignimpl.InputStreamDelegateEncoder; -import feign.codec.EncodeException; -import feign.codec.Encoder; -import java.lang.reflect.Type; - -/** - * Use {@link InputStreamDelegateEncoder}. - * - * @deprecated Use {@link InputStreamDelegateEncoder}. - */ -@Deprecated -public final class ConjureInputStreamDelegateEncoder implements Encoder { - private final Encoder delegate; - - public ConjureInputStreamDelegateEncoder(Encoder delegate) { - this.delegate = new InputStreamDelegateEncoder("unknown", delegate); - } - - @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - delegate.encode(object, bodyType, template); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureJava8OptionalAwareDecoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureJava8OptionalAwareDecoder.java deleted file mode 100644 index 4084addb6..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureJava8OptionalAwareDecoder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareDecoder; -import feign.codec.Decoder; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * Use {@link Java8OptionalAwareDecoder}. - * - * @deprecated Use {@link Java8OptionalAwareDecoder}. - */ -@Deprecated -public final class ConjureJava8OptionalAwareDecoder implements Decoder { - - private final Decoder delegate; - - public ConjureJava8OptionalAwareDecoder(Decoder delegate) { - this.delegate = new Java8OptionalAwareDecoder(delegate); - } - - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - return delegate.decode(response, type); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureNeverReturnNullDecoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureNeverReturnNullDecoder.java deleted file mode 100644 index 90e61fe0b..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureNeverReturnNullDecoder.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * (c) Copyright 2018 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 feign; - -import com.palantir.conjure.java.client.jaxrs.feignimpl.NeverReturnNullDecoder; -import feign.codec.Decoder; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * Use {@link NeverReturnNullDecoder}. - * - * @deprecated Use {@link NeverReturnNullDecoder}. - */ -@Deprecated -public final class ConjureNeverReturnNullDecoder implements Decoder { - private final Decoder delegate; - - public ConjureNeverReturnNullDecoder(Decoder delegate) { - this.delegate = new NeverReturnNullDecoder(delegate); - } - - @Override - public Object decode(Response response, Type type) throws FeignException, IOException { - return delegate.decode(response, type); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureTextDelegateDecoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureTextDelegateDecoder.java deleted file mode 100644 index 91150ee99..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureTextDelegateDecoder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateDecoder; -import feign.codec.Decoder; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * Use {@link TextDelegateDecoder}. - * - * @deprecated Use {@link TextDelegateDecoder}. - */ -@Deprecated -public final class ConjureTextDelegateDecoder implements Decoder { - - private final Decoder delegate; - - public ConjureTextDelegateDecoder(Decoder delegate) { - this.delegate = new TextDelegateDecoder(delegate); - } - - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - return delegate.decode(response, type); - } -} diff --git a/conjure-java-jaxrs-client/src/main/java/feign/ConjureTextDelegateEncoder.java b/conjure-java-jaxrs-client/src/main/java/feign/ConjureTextDelegateEncoder.java deleted file mode 100644 index f9071af9f..000000000 --- a/conjure-java-jaxrs-client/src/main/java/feign/ConjureTextDelegateEncoder.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * (c) Copyright 2017 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 feign; - -import com.palantir.conjure.java.client.jaxrs.feignimpl.TextDelegateEncoder; -import feign.codec.EncodeException; -import feign.codec.Encoder; -import java.lang.reflect.Type; - -/** - * Use {@link TextDelegateEncoder}. - * - * @deprecated Use {@link TextDelegateEncoder}. - */ -@Deprecated -public final class ConjureTextDelegateEncoder implements Encoder { - - private final Encoder delegate; - - public ConjureTextDelegateEncoder(Encoder delegate) { - this.delegate = new TextDelegateEncoder(delegate); - } - - @Override - public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - delegate.encode(object, bodyType, template); - } -} diff --git a/conjure-java-jaxrs-client/src/main/metrics/dangerous-buffering.yml b/conjure-java-jaxrs-client/src/main/metrics/dangerous-buffering.yml index e4a0b5ba7..cfcc43941 100644 --- a/conjure-java-jaxrs-client/src/main/metrics/dangerous-buffering.yml +++ b/conjure-java-jaxrs-client/src/main/metrics/dangerous-buffering.yml @@ -1,5 +1,5 @@ options: - javaPackage: com.palantir.conjure.java.client.jaxrs.feignimpl + javaPackage: com.palantir.conjure.java.client.jaxrs javaVisibility: packagePrivate namespaces: feign.client: diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CollectionDefaultDecodingTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/CollectionDefaultDecodingTest.java similarity index 88% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CollectionDefaultDecodingTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/CollectionDefaultDecodingTest.java index b0dec49da..0565a7ab6 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/CollectionDefaultDecodingTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/CollectionDefaultDecodingTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,12 +14,10 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; import org.junit.jupiter.api.BeforeEach; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EmptyContainerDecoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/EmptyContainerDecoderTest.java similarity index 98% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EmptyContainerDecoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/EmptyContainerDecoderTest.java index a9a31bf8a..88696b163 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/EmptyContainerDecoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/EmptyContainerDecoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -31,8 +31,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; import com.palantir.conjure.java.serialization.ObjectMappers; -import feign.Response; -import feign.codec.Decoder; import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareDecoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareDecoderTest.java similarity index 96% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareDecoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareDecoderTest.java index b378904b6..ee73e8ec0 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalAwareDecoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalAwareDecoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,15 +14,13 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.google.common.collect.ImmutableMap; import com.palantir.conjure.java.api.errors.RemoteException; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; import java.nio.file.Paths; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalComplexType.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalComplexType.java similarity index 95% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalComplexType.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalComplexType.java index 65650e7ee..632d60ffa 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaOptionalComplexType.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaOptionalComplexType.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaTestServer.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaTestServer.java similarity index 98% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaTestServer.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaTestServer.java index f71b58489..dfa5327ca 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/GuavaTestServer.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/GuavaTestServer.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.collect.ImmutableMap; import com.palantir.conjure.java.server.jersey.ConjureJerseyFeature; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/HeaderAccessUtilsTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/HeaderAccessUtilsTest.java similarity index 94% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/HeaderAccessUtilsTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/HeaderAccessUtilsTest.java index 7916e7eaf..b353512f5 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/HeaderAccessUtilsTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/HeaderAccessUtilsTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateDecoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateDecoderTest.java similarity index 92% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateDecoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateDecoderTest.java index 317b41649..6dd7281bd 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateDecoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateDecoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,19 +14,15 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; -import feign.Response; -import feign.codec.Decoder; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateEncoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateEncoderTest.java similarity index 90% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateEncoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateEncoderTest.java index 00fdc1bbe..8707c1fa7 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/InputStreamDelegateEncoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/InputStreamDelegateEncoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,17 +14,13 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; -import feign.RequestTemplate; -import feign.codec.Encoder; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8ComplexType.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8ComplexType.java similarity index 95% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8ComplexType.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8ComplexType.java index 9a19dec6e..d71a26d8f 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8ComplexType.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8ComplexType.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareDecoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareDecoderTest.java similarity index 93% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareDecoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareDecoderTest.java index c6ee2cbe4..6d394de50 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8OptionalAwareDecoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8OptionalAwareDecoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,16 +14,14 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.google.common.collect.ImmutableMap; import com.palantir.conjure.java.api.errors.RemoteException; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; -import com.palantir.conjure.java.client.jaxrs.feignimpl.Java8TestServer.TestService; +import com.palantir.conjure.java.client.jaxrs.Java8TestServer.TestService; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; import java.nio.file.Paths; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8TestServer.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8TestServer.java similarity index 98% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8TestServer.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8TestServer.java index fa1639117..709b5e1bb 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/Java8TestServer.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/Java8TestServer.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverReturnNullDecoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/NeverReturnNullDecoderTest.java similarity index 91% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverReturnNullDecoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/NeverReturnNullDecoderTest.java index 68081a751..2a6572e26 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/NeverReturnNullDecoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/NeverReturnNullDecoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,14 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.ImmutableList; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.serialization.ObjectMappers; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.testing.Assertions; -import feign.Response; -import feign.codec.Decoder; -import feign.jackson.JacksonDecoder; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.HashMap; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/QosErrorDecoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/QosErrorDecoderTest.java similarity index 95% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/QosErrorDecoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/QosErrorDecoderTest.java index f84363f7c..3b56ad29c 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/QosErrorDecoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/QosErrorDecoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; @@ -28,8 +28,6 @@ import com.palantir.conjure.java.api.errors.QosReason.DueTo; import com.palantir.conjure.java.api.errors.QosReason.RetryHint; import com.palantir.conjure.java.api.errors.QosReasons; -import feign.Response; -import feign.codec.ErrorDecoder; import jakarta.ws.rs.core.HttpHeaders; import java.time.Duration; import java.util.Collection; @@ -41,7 +39,7 @@ public final class QosErrorDecoderTest { private static final String methodKey = "method"; static QosErrorDecoder decoder() { - return new QosErrorDecoder(new ErrorDecoder.Default()); + return new QosErrorDecoder(null); } @Test diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/SlashEncodingContractTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/SlashEncodingContractTest.java similarity index 92% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/SlashEncodingContractTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/SlashEncodingContractTest.java index cba7e736d..3f9004ec7 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/SlashEncodingContractTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/SlashEncodingContractTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -22,10 +22,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.palantir.conjure.java.client.jaxrs.ExtensionsWrapper; import com.palantir.conjure.java.client.jaxrs.ExtensionsWrapper.BeforeAndAfter; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; import jakarta.ws.rs.GET; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateDecoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextDelegateDecoderTest.java similarity index 91% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateDecoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextDelegateDecoderTest.java index 2fe8b0b9a..9912cd68a 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateDecoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextDelegateDecoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -26,14 +26,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; -import feign.FeignException; -import feign.Response; -import feign.codec.DecodeException; -import feign.codec.Decoder; import jakarta.ws.rs.core.MediaType; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -78,7 +72,8 @@ public void testUsesStringDecoderWithTextPlain() throws Exception { @Test public void testCannotReturnStringWithMediaTypeJson() { assertThatThrownBy(() -> service.getJsonString("foo")) - .isInstanceOf(FeignException.class) + .isInstanceOf(RuntimeException.class) + .cause() .hasMessageStartingWith("Unrecognized token 'foo': was expecting " + "(JSON String, Number, Array, Object or token 'null', 'true' or 'false')"); } @@ -146,8 +141,6 @@ public void testUsesDelegateWithNonTextContentType() throws Exception { @Test public void testStandardClientsUseTextDelegateEncoder() { assertThat(service.getString("string")).isEqualTo("string"); - assertThatExceptionOfType(DecodeException.class) - .isThrownBy(() -> service.getString(null)) - .withCauseInstanceOf(NullPointerException.class); + assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> service.getString(null)); } } diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateEncoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextDelegateEncoderTest.java similarity index 93% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateEncoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextDelegateEncoderTest.java index f99b5911a..17576353a 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextDelegateEncoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextDelegateEncoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,15 +14,13 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.google.common.net.HttpHeaders; -import feign.RequestTemplate; -import feign.codec.Encoder; import jakarta.ws.rs.core.MediaType; import java.util.Arrays; import java.util.Collection; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextEncoderTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextEncoderTest.java similarity index 88% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextEncoderTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextEncoderTest.java index aad1f466c..c5d773f09 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/TextEncoderTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/TextEncoderTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; -import com.palantir.conjure.java.client.jaxrs.ExtensionsWrapper; import com.palantir.conjure.java.client.jaxrs.ExtensionsWrapper.BeforeAndAfter; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/UserAgentTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/UserAgentTest.java similarity index 90% rename from conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/UserAgentTest.java rename to conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/UserAgentTest.java index c44dba7df..672380626 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/UserAgentTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/UserAgentTest.java @@ -1,5 +1,5 @@ /* - * (c) Copyright 2017 Palantir Technologies Inc. All rights reserved. + * (c) Copyright 2026 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. @@ -14,18 +14,14 @@ * limitations under the License. */ -package com.palantir.conjure.java.client.jaxrs.feignimpl; +package com.palantir.conjure.java.client.jaxrs; import static org.assertj.core.api.Assertions.assertThat; import com.google.common.base.MoreObjects; import com.palantir.conjure.java.api.config.service.UserAgent; import com.palantir.conjure.java.api.config.service.UserAgents; -import com.palantir.conjure.java.client.jaxrs.ExtensionsWrapper; import com.palantir.conjure.java.client.jaxrs.ExtensionsWrapper.BeforeAndAfter; -import com.palantir.conjure.java.client.jaxrs.JaxRsClient; -import com.palantir.conjure.java.client.jaxrs.TestBase; -import com.palantir.conjure.java.client.jaxrs.TestService; import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.dialogue.Channel; import okhttp3.mockwebserver.MockResponse; diff --git a/conjure-scala-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsScalaClientBuilder.java b/conjure-scala-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsScalaClientBuilder.java index 5c8664649..830b33d86 100644 --- a/conjure-scala-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsScalaClientBuilder.java +++ b/conjure-scala-jaxrs-client/src/main/java/com/palantir/conjure/java/client/jaxrs/FeignJaxRsScalaClientBuilder.java @@ -22,7 +22,7 @@ import com.palantir.conjure.java.client.config.ClientConfiguration; import com.palantir.conjure.java.serialization.ScalaObjectMappers; -public final class FeignJaxRsScalaClientBuilder extends AbstractFeignJaxRsClientBuilder { +final class FeignJaxRsScalaClientBuilder extends AbstractFeignJaxRsClientBuilder { private static final JsonMapper JSON_MAPPER = ScalaObjectMappers.newClientJsonMapper(); private static final CBORMapper CBOR_MAPPER = ScalaObjectMappers.newClientCborMapper(); @@ -32,12 +32,12 @@ public final class FeignJaxRsScalaClientBuilder extends AbstractFeignJaxRsClient } @Override - protected ObjectMapper getObjectMapper() { + ObjectMapper getObjectMapper() { return JSON_MAPPER; } @Override - protected ObjectMapper getCborObjectMapper() { + ObjectMapper getCborObjectMapper() { return CBOR_MAPPER; } } diff --git a/versions.lock b/versions.lock index d4f93b8b6..3635219ae 100644 --- a/versions.lock +++ b/versions.lock @@ -6,7 +6,7 @@ com.fasterxml.jackson.core:jackson-annotations:2.21 (12 constraints: 03de1b79) com.fasterxml.jackson.core:jackson-core:2.21.1 (14 constraints: a723ccfe) -com.fasterxml.jackson.core:jackson-databind:2.21.1 (18 constraints: 98637c49) +com.fasterxml.jackson.core:jackson-databind:2.21.1 (17 constraints: fe5524e3) com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.21.1 (2 constraints: 29206505) @@ -44,10 +44,6 @@ com.google.j2objc:j2objc-annotations:3.1 (1 constraints: b809f1a0) com.netflix.concurrency-limits:concurrency-limits-core:0.2.2 (1 constraints: 0605f335) -com.netflix.feign:feign-core:8.18.0 (2 constraints: 1213df52) - -com.netflix.feign:feign-jackson:8.18.0 (1 constraints: 43056b3b) - com.palantir.conjure.java:conjure-lib:8.75.0 (3 constraints: 7625f153) com.palantir.conjure.java.api:errors:2.70.0 (5 constraints: e144f1a9) @@ -206,8 +202,6 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 (3 constraints: eb27efa7) org.jspecify:jspecify:1.0.0 (20 constraints: 9d42438d) -org.jvnet:animal-sniffer-annotation:1.0 (1 constraints: f20b95eb) - org.mpierce.metrics.reservoir:hdrhistogram-metrics-reservoir:1.1.3 (1 constraints: 0d10f991) org.scala-lang:scala-library:2.12.21 (1 constraints: 3716742c) diff --git a/versions.props b/versions.props index c63d9f43b..39052153a 100644 --- a/versions.props +++ b/versions.props @@ -4,7 +4,6 @@ com.github.ben-manes.caffeine:caffeine = 3.2.3 com.google.code.findbugs:jsr305 = 3.0.2 com.google.guava:guava = 33.5.0-jre com.netflix.concurrency-limits:* = 0.2.2 -com.netflix.feign:feign-*= 8.18.0 com.palantir.conjure.java:* = 8.75.0 com.palantir.conjure.java:conjure-lib = 8.75.0 com.palantir.conjure.java.api:* = 2.70.0