This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 066ccc624823 CAMEL-25751: a Map or List body says to marshal it first, 
in the data format of the reply, the request or the file (#28048)
066ccc624823 is described below

commit 066ccc624823ec883d103684753292f39ae651c8
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 18 16:00:59 2026 +0200

    CAMEL-25751: a Map or List body says to marshal it first, in the data 
format of the reply, the request or the file (#28048)
    
    The platform-http reply, the file producer and the http producer cannot
    write a Map or a List (a body unmarshalled from JSON, XML or CSV, or built
    as a map). The reply and the file write only named the conversion ("No
    type converter available to convert from type: java.util.LinkedHashMap to
    the required type: java.io.InputStream"); now all three say what to do, in
    the data format the target asks for: the Content-Type of the reply or the
    request, the file name for a file write, and the usual ones (marshal: json,
    marshal: jacksonXml or marshal: csv) when nothing names one. The REST
    binding is offered only for json and xml, the formats it writes. The http
    producer's sentence from CAMEL-25309 follows the same rule, and "Cannot
    store file" now carries the cause's message when the body cannot be read
    as a stream.
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/component/file/FileOperations.java       |  68 +++++++++++-
 .../component/file/FileProducerMapBodyTest.java    | 100 ++++++++++++++++++
 .../apache/camel/component/http/HttpProducer.java  |  39 ++++++-
 .../component/http/HttpProducerMapBodyTest.java    |  21 +++-
 .../http/vertx/VertxPlatformHttpSupport.java       |  54 +++++++++-
 .../vertx/VertxPlatformHttpMapResponseTest.java    | 117 +++++++++++++++++++++
 6 files changed, 386 insertions(+), 13 deletions(-)

diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/FileOperations.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/FileOperations.java
index 24261c8c5d18..c30bac83e3eb 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/FileOperations.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/FileOperations.java
@@ -38,16 +38,22 @@ import java.nio.file.attribute.PosixFilePermissions;
 import java.security.DigestInputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
 import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Message;
 import org.apache.camel.WrappedFile;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.MimeTypeHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.slf4j.Logger;
@@ -351,7 +357,7 @@ public class FileOperations implements 
GenericFileOperations<File> {
                 // prefer FileInputStream over general stream
                 InputStream is = 
exchange.getMessage().getBody(FileInputStream.class);
                 if (is == null) {
-                    is = 
exchange.getMessage().getMandatoryBody(InputStream.class);
+                    is = mandatoryBodyStream(exchange, exchange.getMessage(), 
file);
                 }
                 dis = new DigestInputStream(is, digest);
             }
@@ -378,11 +384,65 @@ public class FileOperations implements 
GenericFileOperations<File> {
             setPermissions(file);
 
             return true;
-        } catch (InvalidPayloadException | NoSuchAlgorithmException | 
IOException e) {
+        } catch (InvalidPayloadException e) {
+            // say why: the body cannot be written as it is
+            throw new GenericFileOperationFailedException("Cannot store file: 
" + file + ": " + e.getMessage(), e);
+        } catch (NoSuchAlgorithmException | IOException e) {
             throw new GenericFileOperationFailedException("Cannot store file: 
" + file, e);
         }
     }
 
+    /**
+     * The body as a stream to write. A Map or a List (a body unmarshalled 
from JSON, XML or CSV, or built as a map)
+     * cannot become one, and the converter's message only names the 
conversion: say what to do, in the data format the
+     * file name names, or name the usual ones when it names none 
(CAMEL-25751).
+     */
+    static InputStream mandatoryBodyStream(Exchange exchange, Message message, 
File file) throws InvalidPayloadException {
+        try {
+            return message.getMandatoryBody(InputStream.class);
+        } catch (InvalidPayloadException e) {
+            Object body = message.getBody();
+            if (body instanceof Map || body instanceof Collection) {
+                String format = 
dataFormatOf(MimeTypeHelper.probeMimeType(file.getName()));
+                InvalidPayloadException answer = new InvalidPayloadException(
+                        exchange, InputStream.class, message,
+                        "a " + (body instanceof Map ? "Map" : body instanceof 
List ? "List" : "Collection")
+                                                              + " is not file 
content: "
+                                                              + (format != null
+                                                                      ? 
"marshal it first (marshal: " + format
+                                                                        + ", 
for the file name " + file.getName() + ")"
+                                                                      : 
"marshal it first with the data format of the file (such as"
+                                                                        + " 
marshal: json, marshal: jacksonXml or marshal: csv)")
+                                                              + " or set the 
body to the text");
+                answer.initCause(e.getCause() != null ? e.getCause() : e);
+                throw answer;
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * The data format that writes a content type, as the YAML DSL names it: 
json, jacksonXml, csv or yaml; or null
+     * (CAMEL-25751; CAMEL-25749 moves this onto one helper).
+     */
+    static String dataFormatOf(String contentType) {
+        if (contentType == null) {
+            return null;
+        }
+        int semicolon = contentType.indexOf(';');
+        String type = (semicolon > 0 ? contentType.substring(0, semicolon) : 
contentType).trim().toLowerCase(Locale.ROOT);
+        if (type.endsWith("/json") || type.endsWith("+json")) {
+            return "json";
+        } else if (type.endsWith("/xml") || type.endsWith("+xml")) {
+            return "jacksonXml";
+        } else if (type.endsWith("/csv") || 
type.endsWith("/tab-separated-values")) {
+            return "csv";
+        } else if (type.endsWith("/yaml") || type.endsWith("/x-yaml")) {
+            return "yaml";
+        }
+        return null;
+    }
+
     @Override
     public boolean storeFileDirectly(String name, String payload) throws 
GenericFileOperationFailedException {
         File file = new File(name);
@@ -399,7 +459,7 @@ public class FileOperations implements 
GenericFileOperations<File> {
     private void handleStreamAsFileSource(Exchange exchange, File file, 
InputStream is)
             throws InvalidPayloadException, IOException {
         if (is == null) {
-            is = exchange.getIn().getMandatoryBody(InputStream.class);
+            is = mandatoryBodyStream(exchange, exchange.getIn(), file);
         }
         writeFileByStream(is, file);
     }
@@ -420,7 +480,7 @@ public class FileOperations implements 
GenericFileOperations<File> {
             // okay no direct reader conversion, so use an input stream
             // (which a lot can be converted as)
             if (is == null) {
-                is = exchange.getIn().getMandatoryBody(InputStream.class);
+                is = mandatoryBodyStream(exchange, exchange.getIn(), file);
             }
             in = new InputStreamReader(is);
         }
diff --git 
a/components/camel-file/src/test/java/org/apache/camel/component/file/FileProducerMapBodyTest.java
 
b/components/camel-file/src/test/java/org/apache/camel/component/file/FileProducerMapBodyTest.java
new file mode 100644
index 000000000000..b90f5ce7c0c0
--- /dev/null
+++ 
b/components/camel-file/src/test/java/org/apache/camel/component/file/FileProducerMapBodyTest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.component.file;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * CAMEL-25751: a Map or a List written to a file - a body unmarshalled from 
JSON, XML or CSV - says to marshal it
+ * first, in the data format the file name names.
+ */
+public class FileProducerMapBodyTest {
+
+    @TempDir
+    Path dir;
+
+    @Test
+    public void testMapBody() throws Exception {
+        Map<String, Object> order = new LinkedHashMap<>();
+        order.put("orderId", "1001");
+        String message = failure(order, null, "order.json");
+        assertTrue(message.startsWith("Cannot store file: "), message);
+        assertTrue(message.contains("but has type: java.util.LinkedHashMap"), 
message);
+        assertTrue(message.contains("a Map is not file content: marshal it 
first (marshal: json, for the file name"
+                                    + " order.json) or set the body to the 
text"),
+                message);
+        assertFalse(Files.exists(dir.resolve("order.json")));
+    }
+
+    @Test
+    public void testListBodyToACsvFileWithCharset() throws Exception {
+        // the file name says the data format; a charset writes through a 
reader, made from the same stream
+        String message = failure(List.of(List.of("A1", "2")), "UTF-8", 
"lines.csv");
+        assertTrue(message.contains("a List is not file content: marshal it 
first (marshal: csv, for the file name"
+                                    + " lines.csv)"),
+                message);
+    }
+
+    @Test
+    public void testFileNameThatNamesNoDataFormat() throws Exception {
+        String message = failure(Map.of("sku", "A1"), null, "order.dat");
+        assertTrue(message.contains("a Map is not file content: marshal it 
first with the data format of the file (such as"
+                                    + " marshal: json, marshal: jacksonXml or 
marshal: csv)"),
+                message);
+    }
+
+    @Test
+    public void testOtherBodyKeepsTheMessage() throws Exception {
+        String message = failure(new Object(), null, "order.json");
+        assertTrue(message.contains("No body available of type: 
java.io.InputStream"), message);
+        assertFalse(message.contains("is not file content"), message);
+    }
+
+    private String failure(Object body, String charset, String fileName) 
throws Exception {
+        try (CamelContext context = new DefaultCamelContext()) {
+            FileEndpoint endpoint = new FileEndpoint("file:" + dir, new 
FileComponent(context));
+            endpoint.setFile(dir.toFile());
+            if (charset != null) {
+                endpoint.setCharset(charset);
+            }
+            Exchange exchange = new DefaultExchange(context);
+            exchange.getIn().setBody(body);
+            FileOperations operations = new FileOperations(endpoint);
+            GenericFileOperationFailedException e = 
assertThrows(GenericFileOperationFailedException.class,
+                    () -> 
operations.storeFile(dir.resolve(fileName).toString(), exchange, -1));
+            assertInstanceOf(InvalidPayloadException.class, e.getCause());
+            return e.getMessage();
+        }
+    }
+}
diff --git 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
index a15243f1d4eb..789eb887943e 100644
--- 
a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
+++ 
b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java
@@ -33,6 +33,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.stream.Stream;
@@ -742,8 +743,9 @@ public class HttpProducer extends DefaultProducer 
implements LineNumberAware {
     }
 
     /**
-     * The body as a stream for the request entity. A Map or a List (a body 
unmarshalled from JSON, or built as a map)
-     * cannot become one, and the converter's message only names the 
conversion: say what to do (CAMEL-25309).
+     * The body as a stream for the request entity. A Map or a List (a body 
unmarshalled from JSON, XML or CSV, or built
+     * as a map) cannot become one, and the converter's message only names the 
conversion: say what to do, in the data
+     * format the Content-Type names, or name the usual ones when it names 
none (CAMEL-25309, CAMEL-25751).
      */
     private static InputStream mandatoryBodyStream(Exchange exchange, Message 
in) throws InvalidPayloadException {
         try {
@@ -751,11 +753,18 @@ public class HttpProducer extends DefaultProducer 
implements LineNumberAware {
         } catch (InvalidPayloadException e) {
             Object body = in.getBody();
             if (body instanceof Map || body instanceof Collection) {
+                String contentType = ExchangeHelper.getContentType(exchange);
+                String format = dataFormatOf(contentType);
                 InvalidPayloadException answer = new InvalidPayloadException(
                         exchange, InputStream.class, in,
                         "a " + (body instanceof Map ? "Map" : body instanceof 
List ? "List" : "Collection")
-                                                         + " is not an HTTP 
request body: marshal it to JSON first "
-                                                         + "(marshal: json) or 
set the body to the JSON text");
+                                                         + " is not an HTTP 
request body: "
+                                                         + (format != null
+                                                                 ? "marshal it 
first (marshal: " + format
+                                                                   + ", for 
the Content-Type " + contentType + ")"
+                                                                 : "marshal it 
first with the data format of the request (such as"
+                                                                   + " 
marshal: json, marshal: jacksonXml or marshal: csv)")
+                                                         + " or set the body 
to the text");
                 answer.initCause(e.getCause() != null ? e.getCause() : e);
                 throw answer;
             }
@@ -763,6 +772,28 @@ public class HttpProducer extends DefaultProducer 
implements LineNumberAware {
         }
     }
 
+    /**
+     * The data format that writes a content type, as the YAML DSL names it: 
json, jacksonXml, csv or yaml; or null
+     * (CAMEL-25751; CAMEL-25749 moves this onto one helper).
+     */
+    static String dataFormatOf(String contentType) {
+        if (contentType == null) {
+            return null;
+        }
+        int semicolon = contentType.indexOf(';');
+        String type = (semicolon > 0 ? contentType.substring(0, semicolon) : 
contentType).trim().toLowerCase(Locale.ROOT);
+        if (type.endsWith("/json") || type.endsWith("+json")) {
+            return "json";
+        } else if (type.endsWith("/xml") || type.endsWith("+xml")) {
+            return "jacksonXml";
+        } else if (type.endsWith("/csv") || 
type.endsWith("/tab-separated-values")) {
+            return "csv";
+        } else if (type.endsWith("/yaml") || type.endsWith("/x-yaml")) {
+            return "yaml";
+        }
+        return null;
+    }
+
     /**
      * Creates a holder object for the data to send to the remote server.
      *
diff --git 
a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java
 
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java
index 6b75ae3810f2..7c087800b3f2 100644
--- 
a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java
+++ 
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java
@@ -31,7 +31,8 @@ import static 
org.apache.camel.component.http.HttpMethods.POST;
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * A Map or List body cannot be an HTTP request body; the error says to 
marshal it to JSON first (CAMEL-25309).
+ * A Map or List body cannot be an HTTP request body; the error says to 
marshal it first, in the data format the
+ * Content-Type names (CAMEL-25309, CAMEL-25751).
  */
 public class HttpProducerMapBodyTest extends BaseHttpTest {
 
@@ -57,18 +58,30 @@ public class HttpProducerMapBodyTest extends BaseHttpTest {
     }
 
     @Test
-    public void aMapBodySaysToMarshalItToJson() {
+    public void aMapBodySaysToMarshalIt() {
         Exchange exchange = template.request(endpointUrl + 
"/post?httpMethod=POST",
                 // the body after unmarshal: json with Jackson
                 e -> e.getIn().setBody(new LinkedHashMap<>(Map.of("orderId", 
"ORD-1001", "qty", 2))));
 
         
assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class)
                 
.hasMessageContaining("java.util.LinkedHashMap").hasMessageContaining("a Map is 
not an HTTP request body")
-                .hasMessageContaining("marshal it to JSON first (marshal: 
json)");
+                .hasMessageContaining("marshal it first with the data format 
of the request (such as marshal: json,"
+                                      + " marshal: jacksonXml or marshal: csv) 
or set the body to the text");
     }
 
     @Test
-    public void aListBodySaysToMarshalItToJson() {
+    public void theContentTypeNamesTheDataFormat() {
+        Exchange exchange = template.request(endpointUrl + 
"/post?httpMethod=POST", e -> {
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, "application/xml");
+            e.getIn().setBody(new LinkedHashMap<>(Map.of("orderId", 
"ORD-1001")));
+        });
+
+        
assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class)
+                .hasMessageContaining("marshal it first (marshal: jacksonXml, 
for the Content-Type application/xml)");
+    }
+
+    @Test
+    public void aListBodySaysToMarshalIt() {
         Exchange exchange = template.request(endpointUrl + 
"/post?httpMethod=POST",
                 e -> e.getIn().setBody(List.of(Map.of("sku", "CAMEL-MUG"))));
 
diff --git 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpSupport.java
 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpSupport.java
index bd789b64ae9c..df96cbaff3b1 100644
--- 
a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpSupport.java
+++ 
b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpSupport.java
@@ -21,8 +21,10 @@ import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.IllegalCharsetNameException;
 import java.nio.charset.StandardCharsets;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import io.netty.handler.codec.http.HttpHeaderValues;
@@ -42,6 +44,7 @@ import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.spi.HeaderFilterStrategy;
 import org.apache.camel.support.ExceptionHelper;
+import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.MessageHelper;
 import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.support.http.HttpUtil;
@@ -250,7 +253,56 @@ public final class VertxPlatformHttpSupport {
             writeResponseAs(promise, ctx, bb);
             return;
         }
-        throw new NoTypeConversionAvailableException(body, InputStream.class);
+        throw noTypeConversion(body, 
ExchangeHelper.getContentType(camelExchange));
+    }
+
+    /**
+     * A Map or a List (a body unmarshalled from JSON, XML or CSV, or built as 
a map) cannot become the response, and
+     * the converter's message only names the conversion: say what to do, in 
the data format the Content-Type names, or
+     * name the usual ones when it names none (CAMEL-25751).
+     */
+    static NoTypeConversionAvailableException noTypeConversion(Object body, 
String contentType) {
+        if (body instanceof Map || body instanceof Collection) {
+            String format = dataFormatOf(contentType);
+            String marshal = format != null
+                    ? "marshal it first (marshal: " + format + ", for the 
Content-Type " + contentType + ")"
+                    : "marshal it first with the data format of the reply 
(such as marshal: json, marshal: jacksonXml or"
+                      + " marshal: csv)";
+            // the REST binding writes json and xml
+            String binding = format == null ? ", or let the REST binding write 
it (bindingMode: json or xml)"
+                    : "json".equals(format) ? ", or let the REST binding write 
it (bindingMode: json)"
+                    : "jacksonXml".equals(format) ? ", or let the REST binding 
write it (bindingMode: xml)" : "";
+            return new NoTypeConversionAvailableException(
+                    NoTypeConversionAvailableException.createMessage(body, 
InputStream.class) + ": a "
+                                                          + (body instanceof 
Map ? "Map" : body instanceof List ? "List"
+                                                                  : 
"Collection")
+                                                          + " is not an HTTP 
response body: " + marshal
+                                                          + " as the last step 
of the route" + binding,
+                    body, InputStream.class);
+        }
+        return new NoTypeConversionAvailableException(body, InputStream.class);
+    }
+
+    /**
+     * The data format that writes a content type, as the YAML DSL names it: 
json, jacksonXml, csv or yaml; or null
+     * (CAMEL-25751; CAMEL-25749 moves this onto one helper).
+     */
+    static String dataFormatOf(String contentType) {
+        if (contentType == null) {
+            return null;
+        }
+        int semicolon = contentType.indexOf(';');
+        String type = (semicolon > 0 ? contentType.substring(0, semicolon) : 
contentType).trim().toLowerCase(Locale.ROOT);
+        if (type.endsWith("/json") || type.endsWith("+json")) {
+            return "json";
+        } else if (type.endsWith("/xml") || type.endsWith("+xml")) {
+            return "jacksonXml";
+        } else if (type.endsWith("/csv") || 
type.endsWith("/tab-separated-values")) {
+            return "csv";
+        } else if (type.endsWith("/yaml") || type.endsWith("/x-yaml")) {
+            return "yaml";
+        }
+        return null;
     }
 
     private static void writeResponseAs(Promise<Void> promise, RoutingContext 
ctx, ByteBuffer bb) {
diff --git 
a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpMapResponseTest.java
 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpMapResponseTest.java
new file mode 100644
index 000000000000..3d0de403f1da
--- /dev/null
+++ 
b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpMapResponseTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.component.platform.http.vertx;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.NoTypeConversionAvailableException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.ExceptionHandler;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * CAMEL-25751: a Map or a List as the reply - a body unmarshalled from JSON, 
XML or CSV - says to marshal it first, in
+ * the data format the Content-Type names.
+ */
+public class VertxPlatformHttpMapResponseTest {
+
+    @Test
+    public void testMapOrListReply() throws Exception {
+        final CamelContext context = 
VertxPlatformHttpEngineTest.createCamelContext();
+        final RecordingExceptionHandler handler = new 
RecordingExceptionHandler();
+        context.getRegistry().bind("recording", handler);
+
+        try {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    from("platform-http:/stock?exceptionHandler=#recording")
+                            .process(exchange -> {
+                                Map<String, Object> stock = new 
LinkedHashMap<>();
+                                stock.put("sku", "A1");
+                                exchange.getMessage().setBody(stock);
+                            });
+                    from("platform-http:/lines?exceptionHandler=#recording")
+                            .setHeader(Exchange.CONTENT_TYPE, 
constant("text/csv"))
+                            .process(exchange -> 
exchange.getMessage().setBody(List.of(List.of("A1", "2"))));
+                    from("platform-http:/order?exceptionHandler=#recording")
+                            .setHeader(Exchange.CONTENT_TYPE, 
constant("application/xml"))
+                            .process(exchange -> 
exchange.getMessage().setBody(Map.of("orderId", "1001")));
+                }
+            });
+
+            VertxPlatformHttpEngineTest.startCamelContext(context);
+
+            given().when().get("/stock").then().statusCode(500);
+            given().when().get("/lines").then().statusCode(500);
+            given().when().get("/order").then().statusCode(500);
+
+            assertEquals(3, handler.handled.size(), () -> "Handled: " + 
handler.handled);
+            // no Content-Type: the usual data formats
+            String map = 
assertInstanceOf(NoTypeConversionAvailableException.class, 
handler.handled.get(0)).getMessage();
+            assertTrue(map.startsWith("No type converter available to convert 
from type: java.util.LinkedHashMap"
+                                      + " to the required type: 
java.io.InputStream"),
+                    map);
+            assertTrue(map.contains("a Map is not an HTTP response body: 
marshal it first with the data format of the reply"
+                                    + " (such as marshal: json, marshal: 
jacksonXml or marshal: csv) as the last step of the"
+                                    + " route, or let the REST binding write 
it (bindingMode: json or xml)"),
+                    map);
+            // the Content-Type names the data format; the REST binding does 
not write csv
+            String csv = handler.handled.get(1).getMessage();
+            assertTrue(csv.contains("a List is not an HTTP response body: 
marshal it first (marshal: csv, for the Content-Type"
+                                    + " text/csv) as the last step of the 
route"),
+                    csv);
+            assertFalse(csv.contains("REST binding"), csv);
+            String xml = handler.handled.get(2).getMessage();
+            assertTrue(xml.contains("marshal it first (marshal: jacksonXml, 
for the Content-Type application/xml) as the last"
+                                    + " step of the route, or let the REST 
binding write it (bindingMode: xml)"),
+                    xml);
+        } finally {
+            context.stop();
+        }
+    }
+
+    private static final class RecordingExceptionHandler implements 
ExceptionHandler {
+        private final List<Throwable> handled = new CopyOnWriteArrayList<>();
+
+        @Override
+        public void handleException(Throwable exception) {
+            handled.add(exception);
+        }
+
+        @Override
+        public void handleException(String message, Throwable exception) {
+            handled.add(exception);
+        }
+
+        @Override
+        public void handleException(String message, Exchange exchange, 
Throwable exception) {
+            handled.add(exception);
+        }
+    }
+}

Reply via email to