From 63ba1e91c8b0b144f7ba04f62216e042cf648d23 Mon Sep 17 00:00:00 2001 From: Artem Labazin Date: Tue, 25 Jun 2024 23:39:44 +0200 Subject: [PATCH] 63 delegation leads to unexpected result (#122) * fix test * fix map argument handling --------- Co-authored-by: Artem Labazin --- feign-form/pom.xml | 1 + .../src/main/java/feign/form/FormEncoder.java | 4 +- .../src/test/java/feign/form/Server.java | 3 +- .../java/feign/form/issues/Issue63Test.java | 76 +++++++++++++ .../java/feign/form/utils/UndertowServer.java | 101 ++++++++++++++++++ pom.xml | 69 ++++++++++++ 6 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 feign-form/src/test/java/feign/form/issues/Issue63Test.java create mode 100644 feign-form/src/test/java/feign/form/utils/UndertowServer.java diff --git a/feign-form/pom.xml b/feign-form/pom.xml index e42c5c2..643eed6 100644 --- a/feign-form/pom.xml +++ b/feign-form/pom.xml @@ -41,6 +41,7 @@ limitations under the License. feign.form + feign.form.multipart diff --git a/feign-form/src/main/java/feign/form/FormEncoder.java b/feign-form/src/main/java/feign/form/FormEncoder.java index c6bb1c2..8d6ab02 100644 --- a/feign-form/src/main/java/feign/form/FormEncoder.java +++ b/feign-form/src/main/java/feign/form/FormEncoder.java @@ -88,13 +88,13 @@ public FormEncoder (Encoder delegate) { public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException { String contentTypeValue = getContentTypeValue(template.headers()); val contentType = ContentType.of(contentTypeValue); - if (!processors.containsKey(contentType)) { + if (processors.containsKey(contentType) == false) { delegate.encode(object, bodyType, template); return; } Map data; - if (MAP_STRING_WILDCARD.equals(bodyType)) { + if (object instanceof Map) { data = (Map) object; } else if (isUserPojo(bodyType)) { data = toMap(object); diff --git a/feign-form/src/test/java/feign/form/Server.java b/feign-form/src/test/java/feign/form/Server.java index f206560..4d81f10 100644 --- a/feign-form/src/test/java/feign/form/Server.java +++ b/feign-form/src/test/java/feign/form/Server.java @@ -35,7 +35,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -123,7 +122,7 @@ public ResponseEntity json (@RequestBody Dto dto) { return ResponseEntity.status(status).body("ok"); } - @GetMapping("/query_map") + @PostMapping("/query_map") public ResponseEntity queryMap ( @RequestParam("filter") List filters ) { diff --git a/feign-form/src/test/java/feign/form/issues/Issue63Test.java b/feign-form/src/test/java/feign/form/issues/Issue63Test.java new file mode 100644 index 0000000..c0d084f --- /dev/null +++ b/feign-form/src/test/java/feign/form/issues/Issue63Test.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 the original author or authors. + * + * 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.form.issues; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; + +import io.undertow.server.HttpServerExchange; +import lombok.val; +import org.junit.jupiter.api.Test; + +import feign.Feign; +import feign.Headers; +import feign.RequestLine; +import feign.form.FormEncoder; +import feign.form.utils.UndertowServer; +import feign.jackson.JacksonEncoder; + +// https://github.com/OpenFeign/feign-form/issues/63 +class Issue63Test { + + @Test + void test () { + try (val server = UndertowServer.builder().callback(this::handleRequest).start()) { + val client = Feign.builder() + .encoder(new FormEncoder(new JacksonEncoder())) + .target(Client.class, server.getConnectUrl()); + + val data = new HashMap(); + data.put("from", "+987654321"); + data.put("to", "+123456789"); + data.put("body", "hello world"); + + assertThat(client.map(data)) + .isEqualTo("ok"); + } + } + + private void handleRequest (HttpServerExchange exchange, byte[] message) { + // assert request + assertThat(exchange.getRequestHeaders().getFirst(io.undertow.util.Headers.CONTENT_TYPE)) + .isEqualTo("application/x-www-form-urlencoded; charset=UTF-8"); + assertThat(message) + .asString() + .isEqualTo("from=%2B987654321&to=%2B123456789&body=hello+world"); + + // build response + exchange.getResponseHeaders() + .put(io.undertow.util.Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender() + .send("ok"); + } + + interface Client { + + @RequestLine("POST") + @Headers("Content-Type: application/x-www-form-urlencoded; charset=utf-8") + String map (Map data); + } +} diff --git a/feign-form/src/test/java/feign/form/utils/UndertowServer.java b/feign-form/src/test/java/feign/form/utils/UndertowServer.java new file mode 100644 index 0000000..2348a8d --- /dev/null +++ b/feign-form/src/test/java/feign/form/utils/UndertowServer.java @@ -0,0 +1,101 @@ +/* + * Copyright 2024 the original author or authors. + * + * 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.form.utils; + +import java.net.InetSocketAddress; + +import io.appulse.utils.SocketUtils; +import io.undertow.Undertow; +import io.undertow.io.Receiver.FullBytesCallback; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.BlockingHandler; +import io.undertow.util.Headers; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.val; + +public final class UndertowServer implements AutoCloseable { + + private final Undertow undertow; + + @Builder(buildMethodName = "start") + private UndertowServer (FullBytesCallback callback) { + val port = SocketUtils.findFreePort() + .orElseThrow(() -> new IllegalStateException("no available port to start server")); + + undertow = Undertow.builder() + .addHttpListener(port, "localhost") + .setHandler( + new BlockingHandler( + new ReadAllBytesHandler(callback) + ) + ) + .build(); + + undertow.start(); + } + + /** + * Returns server connect URL. + * + * @return listining server's url. + */ + public String getConnectUrl () { + val listenerInfo = undertow.getListenerInfo() + .iterator() + .next(); + + val address = (InetSocketAddress) listenerInfo.getAddress(); + + return String.format( + "%s://%s:%d", + listenerInfo.getProtcol(), + address.getHostString(), + address.getPort() + ); + } + + @Override + public void close () { + undertow.stop(); + } + + @RequiredArgsConstructor + private static final class ReadAllBytesHandler implements HttpHandler { + + private final FullBytesCallback callback; + + @Override + public void handleRequest (HttpServerExchange exchange) throws Exception { + exchange.getRequestReceiver() + .receiveFullBytes(this::handleBytes); + } + + private void handleBytes (HttpServerExchange exchange, byte[] message) { + try { + callback.handle(exchange, message); + } catch (Throwable ex) { + exchange.setStatusCode(500); + exchange.getResponseHeaders() + .put(Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender() + .send(ex.getMessage()); + } + } + } +} diff --git a/pom.xml b/pom.xml index 1cc6b69..8ac4085 100644 --- a/pom.xml +++ b/pom.xml @@ -162,6 +162,19 @@ limitations under the License. test + + io.undertow + undertow-core + 2.3.14.Final + test + + + io.appulse + utils-java + 1.18.0 + test + + org.junit.jupiter junit-jupiter-engine @@ -402,6 +415,62 @@ limitations under the License. + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + ${skipUnitTests} + false + + **/*Test.java + **/*Tests.java + **/Test*.java + + + **/it/** + **/*IntegrationTest.java + **/*IntegrationTests.java + **/*IT.java + **/IT*.java + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.1.2 + + ${skipIntegrationTests} + false + + **/*IntegrationTest.java + **/*IntegrationTests.java + **/*IT.java + **/IT*.java + **/it/**/*Test.java + **/it/**/*Tests.java + **/it/**/Test*.java + + + ${project.groupId}:${project.artifactId} + + + ${project.build.outputDirectory} + + + + + integration-test + + integration-test + verify + + + + + maven-compiler-plugin 3.13.0