diff --git a/.palantir/revapi.yml b/.palantir/revapi.yml index 792ab9e8f..17ad7f6ad 100644 --- a/.palantir/revapi.yml +++ b/.palantir/revapi.yml @@ -85,3 +85,52 @@ acceptedBreaks: new: "method T com.palantir.conjure.java.clients.ConjureClients.WithClientOptions::withConjureErrorParameterFormat(com.palantir.conjure.java.api.errors.ConjureErrorParameterFormat)" justification: "Adding method to allow clients to request error parameter serialization\ \ format" + "8.30.0": + com.palantir.conjure.java.runtime:conjure-java-jaxrs-client: + - code: "java.method.removed" + old: "method feign.MethodMetadata feign.Contract.BaseContract::parseAndValidatateMetadata(java.lang.reflect.Method)\ + \ @ com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.removed" + old: "method java.util.List com.palantir.conjure.java.client.jaxrs.feignimpl.AbstractDelegatingContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.feignimpl.EndpointNameHeaderEnrichmentContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.removed" + old: "method java.util.List com.palantir.conjure.java.client.jaxrs.feignimpl.AbstractDelegatingContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.feignimpl.GuavaOptionalAwareContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.removed" + old: "method java.util.List com.palantir.conjure.java.client.jaxrs.feignimpl.AbstractDelegatingContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.feignimpl.Java8OptionalAwareContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.removed" + old: "method java.util.List com.palantir.conjure.java.client.jaxrs.feignimpl.AbstractDelegatingContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.feignimpl.MethodHeaderEnrichmentContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.removed" + old: "method java.util.List com.palantir.conjure.java.client.jaxrs.feignimpl.AbstractDelegatingContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.feignimpl.PathTemplateHeaderEnrichmentContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.removed" + old: "method java.util.List com.palantir.conjure.java.client.jaxrs.feignimpl.AbstractDelegatingContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.feignimpl.SlashEncodingContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.removed" + old: "method java.util.List feign.Contract.BaseContract::parseAndValidatateMetadata(java.lang.Class)\ + \ @ com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." + - code: "java.method.visibilityReduced" + old: "method java.util.Collection feign.Contract.BaseContract::addTemplatedParam(java.util.Collection,\ + \ java.lang.String) @ com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract" + new: "method java.util.Collection com.palantir.conjure.java.client.jaxrs.CompatibleJaxRsContract::addTemplatedParam(java.util.Collection,\ + \ java.lang.String)" + justification: "Breaks are downstream of feign bump. We are bumping the major\ + \ version." diff --git a/conjure-java-jaxrs-client/build.gradle b/conjure-java-jaxrs-client/build.gradle index 257c6a170..dfaaf1576 100644 --- a/conjure-java-jaxrs-client/build.gradle +++ b/conjure-java-jaxrs-client/build.gradle @@ -8,7 +8,7 @@ dependencies { 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", { + api "io.github.openfeign:feign-core", { // prefer jakarta.ws.rs:jakarta.ws.rs-api exclude group: 'javax.ws.rs', module: 'javax.ws.rs-api' } @@ -35,11 +35,11 @@ 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" + implementation "io.github.openfeign:feign-jackson" testImplementation project(":conjure-java-jersey-jakarta-server") testImplementation project(':keystores') testImplementation project(':undertow-jakarta-testing') - testImplementation "com.netflix.feign:feign-jackson" + testImplementation "io.github.openfeign:feign-jackson" testImplementation "com.squareup.okhttp3:mockwebserver" testImplementation "org.junit.jupiter:junit-jupiter" testImplementation 'org.junit.jupiter:junit-jupiter-api' 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..a379c077c 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 @@ -127,7 +127,10 @@ static T create( * which makes every client appear to use the same URL. */ private static final class FeignDialogueTarget implements Target { - private static final String BASE_URL = "dialogue://feign"; + // Openfeign 13 seems to have a weird thing where it assumes any URL that doesn't start with "http" isn't + // absolute and thus causes apply to fail below when it calls RequestTemplate#target. Hopefully nobody + // is relying on this URL? + private static final String BASE_URL = "httpdialogue://feign"; private final Class serviceClass; private final Target delegate; 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..2dc972bfc 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 @@ -23,8 +23,10 @@ import com.palantir.logsafe.exceptions.SafeIllegalStateException; import feign.Contract; import feign.MethodMetadata; +import feign.Request.HttpMethod; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collection; import javax.annotation.Nullable; @@ -50,7 +52,7 @@ protected void processAnnotationOnClass(MethodMetadata data, Class clz) { // Strip off any trailing slashes, since the template has already had slashes appropriately added pathValue = pathValue.substring(0, pathValue.length() - 1); } - data.template().insert(0, pathValue); + data.template().uri(pathValue); } Annotation consumes = Annotations.CONSUMES.getAnnotation(clz); if (consumes != null) { @@ -74,7 +76,9 @@ protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodA SafeArg.of("method", method.getName()), SafeArg.of("existingMethod", data.template().method()), SafeArg.of("newMethod", httpValue)); - data.template().method(Preconditions.checkNotNull(httpValue, "Unexpected null HttpMethod value")); + data.template() + .method(Preconditions.checkNotNull( + HttpMethod.valueOf(httpValue), "Unexpected null HttpMethod value")); } else if (Annotations.PATH.matches(annotationType)) { String pathValue = Strings.emptyToNull(getAnnotationValue(methodAnnotation)); Preconditions.checkState( @@ -85,7 +89,7 @@ protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodA // jax-rs allows whitespace around the param name, as well as an optional regex. The contract should // strip these out appropriately. pathValue = pathValue.replaceAll("\\{\\s*(.+?)\\s*(:.+?)?\\}", "\\{$1\\}"); - data.template().append(pathValue); + data.template().uri(pathValue, true); } else if (Annotations.PRODUCES.matches(annotationType)) { handleProducesAnnotation(data, methodAnnotation, "method " + method.getName()); } else if (Annotations.CONSUMES.matches(annotationType)) { @@ -180,4 +184,13 @@ private static Object getAnnotationValueInternal(Annotation annotation) { "Failed to read annotation value", e, SafeArg.of("annotationType", annotation.annotationType())); } } + + private Collection addTemplatedParam(Collection possiblyNull, String name) { + Collection toUse = possiblyNull; + if (toUse == null) { + toUse = new ArrayList<>(); + } + toUse.add(String.format("{%s}", name)); + return toUse; + } } 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..6a4a10efb 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 @@ -53,6 +53,7 @@ import java.io.SequenceInputStream; import java.io.StringReader; import java.net.URLDecoder; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; @@ -110,7 +111,8 @@ public feign.Response execute(Request request, Request.Options _options) throws endpointChannels.computeIfAbsent(FeignEndpointKey.of(request), this::toEndpointChannel); try { - return runtime.clients().callBlocking(endpointChannel, builder.build(), FeignResponseDeserializer.INSTANCE); + return runtime.clients() + .callBlocking(endpointChannel, builder.build(), new FeignResponseDeserializer(request)); } catch (UncheckedExecutionException e) { // Rethrow IOException to match standard feign behavior Throwable cause = e.getCause(); @@ -236,6 +238,11 @@ public InputStream asInputStream() { @Override public Reader asReader() { + return asReader(StandardCharsets.UTF_8); + } + + @Override + public Reader asReader(Charset charset) { InputStream inputStream = asInputStream(); Integer maybeLength = length(); if (maybeLength != null) { @@ -251,7 +258,7 @@ public Reader asReader() { if (read <= length) { // fully read input inputStream.close(); - return new StringReader(new String(bytes, 0, read, StandardCharsets.UTF_8)); + return new StringReader(new String(bytes, 0, read, charset)); } // input was larger than provided content length, fallback to stream path inputStream = new SequenceInputStream(new ByteArrayInputStream(bytes), inputStream); @@ -261,7 +268,7 @@ public Reader asReader() { } } } - return new InputStreamReader(inputStream, StandardCharsets.UTF_8); + return new InputStreamReader(inputStream, charset); } @Override @@ -275,16 +282,21 @@ public String toString() { } } - enum FeignResponseDeserializer implements Deserializer { - INSTANCE; + private static final class FeignResponseDeserializer implements Deserializer { + private final Request request; + + FeignResponseDeserializer(Request request) { + this.request = request; + } @Override public feign.Response deserialize(Response response) { - return feign.Response.create( - response.code(), - null, - Multimaps.asMap((Multimap) response.headers()), - new DialogueResponseBody(response)); + return feign.Response.builder() + .status(response.code()) + .headers(Multimaps.asMap((Multimap) response.headers())) + .body(new DialogueResponseBody(response)) + .request(request) + .build(); } @Override @@ -464,7 +476,7 @@ interface FeignEndpointKey { private static FeignEndpointKey of(Request request) { return ImmutableFeignEndpointKey.of( - HttpMethod.valueOf(request.method().toUpperCase(Locale.ENGLISH)), + HttpMethod.valueOf(request.httpMethod().name().toUpperCase(Locale.ENGLISH)), getFirstHeader(request, MethodHeaderEnrichmentContract.METHOD_HEADER) .orElse(""), getFirstHeader(request, EndpointNameHeaderEnrichmentContract.ENDPOINT_NAME_HEADER) 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/feignimpl/AbstractDelegatingContract.java index 4dcabca29..6d56c5f21 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/feignimpl/AbstractDelegatingContract.java @@ -26,7 +26,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 +38,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) { 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/feignimpl/CborDelegateDecoder.java index 830ef1d76..fc8ce0793 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/feignimpl/CborDelegateDecoder.java @@ -49,8 +49,7 @@ public CborDelegateDecoder(ObjectMapper cborMapper, Decoder delegate) { @Override public Object decode(Response response, Type type) throws IOException, FeignException { - Collection contentTypes = - HeaderAccessUtils.caseInsensitiveGet(response.headers(), HttpHeaders.CONTENT_TYPE); + Collection contentTypes = response.headers().get(HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { contentTypes = ImmutableSet.of(); } 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/feignimpl/CborDelegateEncoder.java index 2f62e6c11..1173d9ab0 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/feignimpl/CborDelegateEncoder.java @@ -52,8 +52,7 @@ public CborDelegateEncoder(ObjectMapper cborMapper, Encoder delegate) { @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - Collection contentTypes = - HeaderAccessUtils.caseInsensitiveGet(template.headers(), HttpHeaders.CONTENT_TYPE); + Collection contentTypes = template.headers().get(HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { contentTypes = ImmutableSet.of(); } 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/feignimpl/HeaderAccessUtils.java index c043ded03..539e1af6b 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/feignimpl/HeaderAccessUtils.java @@ -28,6 +28,7 @@ * 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. */ +// XXX: Delete public final class HeaderAccessUtils { private HeaderAccessUtils() {} 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/feignimpl/TextDelegateDecoder.java index c688a12b1..a598c44f3 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/feignimpl/TextDelegateDecoder.java @@ -42,8 +42,7 @@ public TextDelegateDecoder(Decoder delegate) { @Override public Object decode(Response response, Type type) throws IOException, FeignException { - Collection contentTypes = - HeaderAccessUtils.caseInsensitiveGet(response.headers(), HttpHeaders.CONTENT_TYPE); + Collection contentTypes = response.headers().get(HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { contentTypes = ImmutableSet.of(); } 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/feignimpl/TextDelegateEncoder.java index 8a60f8709..90e164241 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/feignimpl/TextDelegateEncoder.java @@ -40,8 +40,7 @@ public TextDelegateEncoder(Encoder delegate) { @Override public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { - Collection contentTypes = - HeaderAccessUtils.caseInsensitiveGet(template.headers(), HttpHeaders.CONTENT_TYPE); + Collection contentTypes = template.headers().get(HttpHeaders.CONTENT_TYPE); if (contentTypes == null) { contentTypes = ImmutableSet.of(); } diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxRsClientDialogueEndpointTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxRsClientDialogueEndpointTest.java index 1395d4463..8ea3140a1 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxRsClientDialogueEndpointTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxRsClientDialogueEndpointTest.java @@ -144,7 +144,6 @@ public void testUnsupportedHttpMethod_trace() { Channel channel = mock(Channel.class); assertThatThrownBy(() -> JaxRsClient.create(TraceService.class, channel, runtime)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Unsupported HTTP method") .hasMessageContaining("TRACE"); } @@ -153,7 +152,6 @@ public void testUnsupportedHttpMethod_arbitrary() { Channel channel = mock(Channel.class); assertThatThrownBy(() -> JaxRsClient.create(ArbitraryMethodService.class, channel, runtime)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Unsupported HTTP method") .hasMessageContaining("ARBITRARY"); } diff --git a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxrsClientValidationTest.java b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxrsClientValidationTest.java index 8a6c31b94..ec0dcbac6 100644 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxrsClientValidationTest.java +++ b/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/JaxrsClientValidationTest.java @@ -31,7 +31,7 @@ public void testNonJaxrs() { Channel channel = mock(Channel.class); assertThatThrownBy(() -> JaxRsClient.create(NotJaxRs.class, channel, runtime)) .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Method getValue not annotated with HTTP method type"); + .hasMessageContaining("not annotated with HTTP method type"); } @SuppressWarnings("unused") 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/feignimpl/EmptyContainerDecoderTest.java index a9a31bf8a..ec9f0b487 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/feignimpl/EmptyContainerDecoderTest.java @@ -31,12 +31,12 @@ import com.google.common.collect.ImmutableSet; import com.google.common.net.HttpHeaders; import com.palantir.conjure.java.serialization.ObjectMappers; +import feign.Request; import feign.Response; import feign.codec.Decoder; import jakarta.ws.rs.core.MediaType; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -51,19 +51,27 @@ public class EmptyContainerDecoderTest { private static final JsonMapper mapper = ObjectMappers.newClientJsonMapper(); - private static final Response HTTP_204 = Response.create(204, "No Content", Collections.emptyMap(), new byte[] {}); + private static final Request REQUEST = + Request.create(Request.HttpMethod.GET, "url", Map.of(), new byte[] {}, StandardCharsets.UTF_8); + private static final Response HTTP_204 = Response.builder() + .status(204) + .reason("No content") + .request(REQUEST) + .body(new byte[] {}) + .build(); private final Decoder delegate = mock(Decoder.class); private final EmptyContainerDecoder emptyContainerDecoder = new EmptyContainerDecoder(mapper, delegate); @Test public void http_200_uses_delegate_decoder() throws IOException { when(delegate.decode(any(), eq(String.class))).thenReturn("text response"); - Response http200 = Response.create( - 200, - "OK", - ImmutableMap.of(HttpHeaders.CONTENT_TYPE, ImmutableSet.of(MediaType.TEXT_PLAIN)), - "text response", - StandardCharsets.UTF_8); + Response http200 = Response.builder() + .status(200) + .reason("OK") + .headers(ImmutableMap.of(HttpHeaders.CONTENT_TYPE, ImmutableSet.of(MediaType.TEXT_PLAIN))) + .request(REQUEST) + .body("text response", StandardCharsets.UTF_8) + .build(); emptyContainerDecoder.decode(http200, String.class); verify(delegate, times(1)).decode(any(), any()); 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/feignimpl/HeaderAccessUtilsTest.java deleted file mode 100644 index 7916e7eaf..000000000 --- a/conjure-java-jaxrs-client/src/test/java/com/palantir/conjure/java/client/jaxrs/feignimpl/HeaderAccessUtilsTest.java +++ /dev/null @@ -1,58 +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 static org.assertj.core.api.Assertions.assertThat; - -import com.google.common.collect.ImmutableMap; -import java.util.Arrays; -import java.util.Collection; -import org.junit.jupiter.api.Test; - -public final class HeaderAccessUtilsTest { - private static final ImmutableMap> TEST_HEADERS_MAP = ImmutableMap.of( - "header", - Arrays.asList("value1"), - "Header", - Arrays.asList("value2", "value3"), - "HEADER", - Arrays.asList("value4", "value5")); - - @Test - public void caseInsensitiveContainsShouldReturnTrueIgnoringCase() { - assertThat(HeaderAccessUtils.caseInsensitiveContains(TEST_HEADERS_MAP, "hEaDeR")) - .isTrue(); - } - - @Test - public void caseInsensitiveContainsShouldReturnFalseForNonExistentKey() { - assertThat(HeaderAccessUtils.caseInsensitiveContains(TEST_HEADERS_MAP, "invalid")) - .isFalse(); - } - - @Test - public void caseInsensitiveGetReturnsNullForNotExistingHeader() { - assertThat(HeaderAccessUtils.caseInsensitiveGet(TEST_HEADERS_MAP, "invalid")) - .isNull(); - } - - @Test - public void caseInsensitiveGetReturnsAllExistingHeaders() { - assertThat(HeaderAccessUtils.caseInsensitiveGet(TEST_HEADERS_MAP, "HeADER")) - .contains("value1", "value2", "value3", "value4", "value5"); - } -} 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/feignimpl/InputStreamDelegateDecoderTest.java index 317b41649..0d47cc943 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/feignimpl/InputStreamDelegateDecoderTest.java @@ -20,16 +20,17 @@ 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.Request; import feign.Response; import feign.codec.Decoder; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -40,6 +41,9 @@ public final class InputStreamDelegateDecoderTest extends TestBase { @RegisterExtension public static final UndertowServerExtension undertow = GuavaTestServer.createUndertow(); + private static final Request REQUEST = + Request.create(Request.HttpMethod.GET, "url", Map.of(), new byte[] {}, StandardCharsets.UTF_8); + private GuavaTestServer.TestService service; private Decoder delegate; private Decoder inputStreamDelegateDecoder; @@ -58,7 +62,12 @@ public void before() { public void testDecodesAsInputStream() throws Exception { String data = "data"; - Response response = Response.create(200, "OK", ImmutableMap.of(), data, StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .request(REQUEST) + .body(data, StandardCharsets.UTF_8) + .build(); InputStream decoded = (InputStream) inputStreamDelegateDecoder.decode(response, InputStream.class); @@ -70,7 +79,12 @@ public void testUsesDelegateWhenReturnTypeNotInputStream() throws Exception { String returned = "string"; when(delegate.decode(any(), any())).thenReturn(returned); - Response response = Response.create(200, "OK", ImmutableMap.of(), returned, StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .request(REQUEST) + .body(returned, StandardCharsets.UTF_8) + .build(); String decodedObject = (String) inputStreamDelegateDecoder.decode(response, String.class); assertThat(decodedObject).isEqualTo(returned); } @@ -78,7 +92,12 @@ public void testUsesDelegateWhenReturnTypeNotInputStream() throws Exception { @Test public void testSupportsNullBody() throws Exception { String data = ""; - Response response = Response.create(200, "OK", ImmutableMap.of(), (Response.Body) null); + Response response = Response.builder() + .status(200) + .reason("OK") + .request(REQUEST) + .body((Response.Body) null) + .build(); InputStream decoded = (InputStream) inputStreamDelegateDecoder.decode(response, InputStream.class); 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/feignimpl/NeverReturnNullDecoderTest.java index 68081a751..e77ad4b12 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/feignimpl/NeverReturnNullDecoderTest.java @@ -23,6 +23,7 @@ import com.palantir.conjure.java.serialization.ObjectMappers; import com.palantir.logsafe.SafeArg; import com.palantir.logsafe.testing.Assertions; +import feign.Request; import feign.Response; import feign.codec.Decoder; import feign.jackson.JacksonDecoder; @@ -38,10 +39,18 @@ public final class NeverReturnNullDecoderTest extends TestBase { private final Map> headers = new HashMap<>(); private final Decoder textDelegateDecoder = new NeverReturnNullDecoder(new JacksonDecoder(ObjectMappers.newClientJsonMapper())); + private static final Request REQUEST = + Request.create(Request.HttpMethod.GET, "url", Map.of(), new byte[] {}, StandardCharsets.UTF_8); @Test public void throws_nullpointerexception_when_body_is_null() { - Response response = Response.create(200, "OK", headers, null, StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body(null, StandardCharsets.UTF_8) + .build(); Assertions.assertThatLoggableExceptionThrownBy(() -> textDelegateDecoder.decode(response, List.class)) .isInstanceOf(NullPointerException.class) @@ -51,7 +60,13 @@ public void throws_nullpointerexception_when_body_is_null() { @Test public void throws_nullpointerexception_when_body_is_string_null() { - Response response = Response.create(200, "OK", headers, "null", StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body("null", StandardCharsets.UTF_8) + .build(); Assertions.assertThatLoggableExceptionThrownBy(() -> textDelegateDecoder.decode(response, List.class)) .isInstanceOf(NullPointerException.class) @@ -61,7 +76,13 @@ public void throws_nullpointerexception_when_body_is_string_null() { @Test public void throws_nullpointerexception_when_body_is_empty_string() { - Response response = Response.create(200, "OK", headers, "", StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body("", StandardCharsets.UTF_8) + .build(); Assertions.assertThatLoggableExceptionThrownBy(() -> textDelegateDecoder.decode(response, List.class)) .isInstanceOf(NullPointerException.class) @@ -71,7 +92,13 @@ public void throws_nullpointerexception_when_body_is_empty_string() { @Test public void works_fine_when_body_is_not_null() throws Exception { - Response response = Response.create(200, "OK", headers, "[1, 2, 3]", StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body("[1, 2, 3]", StandardCharsets.UTF_8) + .build(); Object decodedObject = textDelegateDecoder.decode(response, List.class); assertThat(decodedObject).isEqualTo(ImmutableList.of(1, 2, 3)); } 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/feignimpl/QosErrorDecoderTest.java index d2575210d..719f3c5bd 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/feignimpl/QosErrorDecoderTest.java @@ -28,9 +28,11 @@ 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.Request; import feign.Response; import feign.codec.ErrorDecoder; import jakarta.ws.rs.core.HttpHeaders; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collection; import java.util.Map; @@ -39,6 +41,8 @@ public final class QosErrorDecoderTest { private static final String methodKey = "method"; + private static final Request REQUEST = + Request.create(Request.HttpMethod.GET, "url", Map.of(), new byte[] {}, StandardCharsets.UTF_8); static QosErrorDecoder decoder() { return new QosErrorDecoder(new ErrorDecoder.Default()); @@ -48,7 +52,13 @@ static QosErrorDecoder decoder() { public void http_429_throw_qos_throttle() { QosReason expected = QosReason.builder().reason("client-qos-response").build(); Map> headers = headersFor(expected); - Response response = Response.create(429, "too many requests", headers, new byte[0]); + Response response = Response.builder() + .status(429) + .reason("too many requests") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); assertThat(decoder().decode(methodKey, response)) .isInstanceOfSatisfying(QosException.Throttle.class, throttle -> { assertThat(throttle.getRetryAfter()).isEmpty(); @@ -64,7 +74,13 @@ public void http_429_throw_qos_throttle_with_metadata() { .retryHint(RetryHint.DO_NOT_RETRY) .build(); Map> headers = headersFor(expected); - Response response = Response.create(429, "too many requests", headers, new byte[0]); + Response response = Response.builder() + .status(429) + .reason("too many requests") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); assertThat(decoder().decode(methodKey, response)) .isInstanceOfSatisfying(QosException.Throttle.class, throttle -> { assertThat(throttle.getRetryAfter()).isEmpty(); @@ -75,7 +91,13 @@ public void http_429_throw_qos_throttle_with_metadata() { @Test public void http_429_throw_qos_throttle_with_retry_after() { Map> headers = ImmutableMap.of(HttpHeaders.RETRY_AFTER, ImmutableList.of("5")); - Response response = Response.create(429, "too many requests", headers, new byte[0]); + Response response = Response.builder() + .status(429) + .reason("too many requests") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); assertThat(decoder().decode(methodKey, response)) .isInstanceOfSatisfying(QosException.Throttle.class, e -> assertThat(e.getRetryAfter()) .contains(Duration.ofSeconds(5))); @@ -85,7 +107,13 @@ public void http_429_throw_qos_throttle_with_retry_after() { public void http_503_throw_qos_unavailable() { QosReason expected = QosReason.builder().reason("client-qos-response").build(); Map> headers = headersFor(expected); - Response response = Response.create(503, "unavailable", headers, new byte[0]); + Response response = Response.builder() + .status(503) + .reason("unavailable") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); assertThat(decoder().decode(methodKey, response)) .isInstanceOfSatisfying( QosException.Unavailable.class, @@ -100,7 +128,13 @@ public void http_503_throw_qos_unavailable_with_metadata() { .retryHint(RetryHint.DO_NOT_RETRY) .build(); Map> headers = headersFor(expected); - Response response = Response.create(503, "unavailable", headers, new byte[0]); + Response response = Response.builder() + .status(503) + .reason("unavailable") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); assertThat(decoder().decode(methodKey, response)) .isInstanceOfSatisfying( QosException.Unavailable.class, 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/feignimpl/TextDelegateDecoderTest.java index 2fe8b0b9a..52246bc42 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/feignimpl/TextDelegateDecoderTest.java @@ -31,6 +31,7 @@ import com.palantir.conjure.java.okhttp.HostMetricsRegistry; import com.palantir.undertest.UndertowServerExtension; import feign.FeignException; +import feign.Request; import feign.Response; import feign.codec.DecodeException; import feign.codec.Decoder; @@ -49,6 +50,9 @@ public final class TextDelegateDecoderTest extends TestBase { @RegisterExtension public static final UndertowServerExtension undertow = GuavaTestServer.createUndertow(); + private static final Request REQUEST = + Request.create(Request.HttpMethod.GET, "url", Map.of(), new byte[] {}, StandardCharsets.UTF_8); + private GuavaTestServer.TestService service; private Map> headers; private Decoder delegate; @@ -68,7 +72,13 @@ public void before() { @Test public void testUsesStringDecoderWithTextPlain() throws Exception { headers.put(HttpHeaders.CONTENT_TYPE, ImmutableSet.of(MediaType.TEXT_PLAIN)); - Response response = Response.create(200, "OK", headers, "text response", StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body("text response", StandardCharsets.UTF_8) + .build(); Object decodedObject = textDelegateDecoder.decode(response, String.class); assertThat(decodedObject).isEqualTo("text response"); @@ -86,7 +96,13 @@ public void testCannotReturnStringWithMediaTypeJson() { @Test public void testUsesStringDecoderWithTextPlainAndCharset() throws Exception { headers.put(HttpHeaders.CONTENT_TYPE, ImmutableSet.of(MediaType.TEXT_PLAIN + "; charset=utf-8")); - Response response = Response.create(200, "OK", headers, "text response", StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body("text response", StandardCharsets.UTF_8) + .build(); Object decodedObject = textDelegateDecoder.decode(response, String.class); @@ -97,7 +113,13 @@ public void testUsesStringDecoderWithTextPlainAndCharset() throws Exception { @Test public void testUsesStringDecoderWithTextPlainWithWeirdHeaderCapitalization() throws Exception { headers.put("content-TYPE", ImmutableSet.of(MediaType.TEXT_PLAIN)); - Response response = Response.create(200, "OK", headers, "text response", StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body("text response", StandardCharsets.UTF_8) + .build(); Object decodedObject = textDelegateDecoder.decode(response, String.class); assertThat(decodedObject).isEqualTo("text response"); @@ -107,7 +129,13 @@ public void testUsesStringDecoderWithTextPlainWithWeirdHeaderCapitalization() th @Test public void testReturnsEmptyStringForNullResponseBodyWithTextPlain() throws Exception { headers.put(HttpHeaders.CONTENT_TYPE, ImmutableSet.of(MediaType.TEXT_PLAIN)); - Response response = Response.create(200, "OK", headers, null, StandardCharsets.UTF_8); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body(null, StandardCharsets.UTF_8) + .build(); Object decodedObject = textDelegateDecoder.decode(response, String.class); assertThat(decodedObject).isEqualTo(""); @@ -117,7 +145,13 @@ public void testReturnsEmptyStringForNullResponseBodyWithTextPlain() throws Exce @Test public void testUsesDelegateWithNoHeader() throws Exception { when(delegate.decode(any(), any())).thenReturn(DELEGATE_RESPONSE); - Response response = Response.create(200, "OK", headers, new byte[0]); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); Object decodedObject = textDelegateDecoder.decode(response, String.class); assertThat(decodedObject).isEqualTo(DELEGATE_RESPONSE); @@ -127,7 +161,13 @@ public void testUsesDelegateWithNoHeader() throws Exception { public void testUsesDelegateWithComplexHeader() throws Exception { headers.put(HttpHeaders.CONTENT_TYPE, ImmutableSet.of(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON)); when(delegate.decode(any(), any())).thenReturn(DELEGATE_RESPONSE); - Response response = Response.create(200, "OK", headers, new byte[0]); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); Object decodedObject = textDelegateDecoder.decode(response, String.class); assertThat(decodedObject).isEqualTo(DELEGATE_RESPONSE); @@ -137,7 +177,13 @@ public void testUsesDelegateWithComplexHeader() throws Exception { public void testUsesDelegateWithNonTextContentType() throws Exception { headers.put(HttpHeaders.CONTENT_TYPE, ImmutableSet.of(MediaType.APPLICATION_JSON)); when(delegate.decode(any(), any())).thenReturn(DELEGATE_RESPONSE); - Response response = Response.create(200, "OK", headers, new byte[0]); + Response response = Response.builder() + .status(200) + .reason("OK") + .headers(headers) + .request(REQUEST) + .body(new byte[0]) + .build(); Object decodedObject = textDelegateDecoder.decode(response, String.class); assertThat(decodedObject).isEqualTo(DELEGATE_RESPONSE); diff --git a/conjure-java-jersey-jakarta-server/build.gradle b/conjure-java-jersey-jakarta-server/build.gradle index f7811c0d1..e048babe8 100644 --- a/conjure-java-jersey-jakarta-server/build.gradle +++ b/conjure-java-jersey-jakarta-server/build.gradle @@ -17,7 +17,7 @@ dependencies { } implementation 'com.google.code.findbugs:jsr305' implementation 'com.google.guava:guava' - implementation "com.netflix.feign:feign-core" + implementation "io.github.openfeign:feign-core" implementation "com.palantir.safe-logging:safe-logging" implementation 'com.palantir.tokens:auth-tokens' implementation "com.palantir.tracing:tracing-jersey-jakarta" diff --git a/conjure-java-jersey-server/build.gradle b/conjure-java-jersey-server/build.gradle index 51fdf56ca..6a4d6d972 100644 --- a/conjure-java-jersey-server/build.gradle +++ b/conjure-java-jersey-server/build.gradle @@ -29,7 +29,7 @@ dependencies { } implementation 'com.google.code.findbugs:jsr305' implementation 'com.google.guava:guava' - implementation "com.netflix.feign:feign-core" + implementation "io.github.openfeign:feign-core" implementation "com.palantir.safe-logging:safe-logging" implementation "com.palantir.safe-logging:logger" implementation 'com.palantir.tokens:auth-tokens' diff --git a/versions.lock b/versions.lock index fe2d92144..d3aa99a20 100644 --- a/versions.lock +++ b/versions.lock @@ -6,7 +6,7 @@ com.fasterxml.jackson.core:jackson-annotations:2.20 (12 constraints: f8dd6a6b) com.fasterxml.jackson.core:jackson-core:2.20.1 (14 constraints: 9923f1e8) -com.fasterxml.jackson.core:jackson-databind:2.20.1 (18 constraints: 8863b529) +com.fasterxml.jackson.core:jackson-databind:2.20.1 (18 constraints: 8e64bf71) com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.20.1 (2 constraints: 27201405) @@ -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.67.0 (3 constraints: 7b25b854) com.palantir.conjure.java.api:errors:2.68.0 (5 constraints: 0045dcb5) @@ -118,6 +114,10 @@ com.thoughtworks.paranamer:paranamer:2.8.3 (1 constraints: dc154900) io.dropwizard.metrics:metrics-core:4.2.37 (6 constraints: 1f586933) +io.github.openfeign:feign-core:13.6 (2 constraints: 1c13f23c) + +io.github.openfeign:feign-jackson:13.6 (1 constraints: de040031) + io.smallrye.common:smallrye-common-annotation:2.14.0 (1 constraints: f20d0944) io.smallrye.common:smallrye-common-constraint:2.14.0 (4 constraints: e841a41c) @@ -206,8 +206,6 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 (3 constraints: eb27efa7) org.jspecify:jspecify:1.0.0 (19 constraints: ce345bc3) -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.20 (1 constraints: 3616732c) diff --git a/versions.props b/versions.props index 5862eedcf..4be10934e 100644 --- a/versions.props +++ b/versions.props @@ -4,7 +4,7 @@ com.github.ben-manes.caffeine:caffeine = 3.2.2 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 +io.github.openfeign:*= 13.6 com.palantir.conjure.java:* = 8.67.0 com.palantir.conjure.java:conjure-lib = 8.67.0 com.palantir.conjure.java.api:* = 2.68.0