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 dd61fa5b82ab CAMEL-25222: Route dump - keep the YAML list dash with 
source locations, and dump the exception of throwException (#27198)
dd61fa5b82ab is described below

commit dd61fa5b82ab2c3aa6a09db761cf251ccdf03075
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 1 13:59:06 2026 +0200

    CAMEL-25222: Route dump - keep the YAML list dash with source locations, 
and dump the exception of throwException (#27198)
    
    * CAMEL-25222: Route dump - keep the list dash of a YAML item whose source 
location comes first, and dump the exception of throwException
    
    The route-dump console drops the sourceLineNumber and sourceLocation lines 
of a YAML dump. When they started a list
    item, such as a when of a choice, the dash went with them and the item 
became a map. The dash and the line number
    now go to the next line.
    
    An exception given to throwException as a class or an instance was dumped 
empty in YAML and XML; the dumpers now
    write its exceptionType and message.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-25222: complete the truncated comment in the route dumpers
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    ---------
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/impl/console/RouteDumpDevConsole.java    | 42 ++++++++++----
 .../impl/console/RouteDumpDevConsoleTest.java      | 44 ++++++++++++++
 .../util/DumpModelThrowExceptionInstanceTest.java  | 67 ++++++++++++++++++++++
 .../org/apache/camel/xml/LwModelToXMLDumper.java   | 20 +++++++
 .../org/apache/camel/yaml/LwModelToYAMLDumper.java | 18 ++++++
 5 files changed, 181 insertions(+), 10 deletions(-)

diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
index 30e6fc79a37e..7e4d707d2b4e 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java
@@ -231,8 +231,11 @@ public class RouteDumpDevConsole extends 
AbstractDevConsole {
         return code.isEmpty() ? null : code;
     }
 
-    private static List<CodeLine> javaOrYamlLoadSourceAsJson(Reader reader) {
+    static List<CodeLine> javaOrYamlLoadSourceAsJson(Reader reader) {
         List<CodeLine> code = new ArrayList<>();
+        // a dropped source location line that started a YAML list item: its 
dash and line number go to the next line
+        int dashIndent = -1;
+        int dashLine = -1;
         try {
             LineNumberReader lnr = new LineNumberReader(reader);
             String t;
@@ -240,21 +243,40 @@ public class RouteDumpDevConsole extends 
AbstractDevConsole {
                 t = lnr.readLine();
                 if (t != null) {
                     // extra source location from code line
-                    if (t.contains("sourceLocation: ")) {
-                        // skip this line
-                    } else if (t.contains("sourceLineNumber: ")) {
-                        String idx = StringHelper.after(t, "sourceLineNumber: 
").trim();
-                        if (!code.isEmpty()) {
-                            // assign line number to previous code line
-                            CodeLine prev = code.get(code.size() - 1);
+                    boolean location = t.contains("sourceLocation: ");
+                    boolean lineNumber = !location && 
t.contains("sourceLineNumber: ");
+                    if (location || lineNumber) {
+                        int idx = -1;
+                        if (lineNumber) {
                             try {
-                                code.set(code.size() - 1, new 
CodeLine(Integer.parseInt(idx), prev.code()));
+                                idx = Integer.parseInt(StringHelper.after(t, 
"sourceLineNumber: ").trim());
                             } catch (NumberFormatException e) {
                                 // ignore
                             }
                         }
+                        int indent = t.length() - t.stripLeading().length();
+                        if (t.stripLeading().startsWith("- ")) {
+                            // the first key of a list item (such as a when of 
a choice): the next line takes the dash
+                            dashIndent = indent;
+                            dashLine = idx;
+                        } else if (dashIndent >= 0) {
+                            if (idx != -1) {
+                                dashLine = idx;
+                            }
+                        } else if (idx != -1 && !code.isEmpty()) {
+                            // assign line number to previous code line
+                            CodeLine prev = code.get(code.size() - 1);
+                            code.set(code.size() - 1, new CodeLine(idx, 
prev.code()));
+                        }
                     } else {
-                        code.add(new CodeLine(-1, Jsoner.escape(t)));
+                        if (dashIndent >= 0 && t.length() > dashIndent + 2 && 
t.startsWith(" ".repeat(dashIndent + 2))) {
+                            t = " ".repeat(dashIndent) + "- " + 
t.substring(dashIndent + 2);
+                            code.add(new CodeLine(dashLine, Jsoner.escape(t)));
+                        } else {
+                            code.add(new CodeLine(-1, Jsoner.escape(t)));
+                        }
+                        dashIndent = -1;
+                        dashLine = -1;
                     }
                 }
             } while (t != null);
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteDumpDevConsoleTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteDumpDevConsoleTest.java
index 0b4013cdd491..06ff94760b68 100644
--- 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteDumpDevConsoleTest.java
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteDumpDevConsoleTest.java
@@ -16,12 +16,16 @@
  */
 package org.apache.camel.impl.console;
 
+import java.io.StringReader;
+import java.util.List;
+
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.console.DevConsole;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -63,4 +67,44 @@ public class RouteDumpDevConsoleTest extends 
ContextTestSupport {
         JsonArray routes = out.getCollection("routes");
         Assertions.assertNotNull(routes);
     }
+
+    @Test
+    public void testYamlSourceLocationOfAListItem() {
+        // the source location of a when comes first in its list item: 
dropping it must keep the dash
+        String yaml = """
+                - route:
+                    from:
+                      sourceLineNumber: 7
+                      sourceLocation: OrderRoute.java
+                      uri: timer:orders
+                      steps:
+                        - choice:
+                            sourceLineNumber: 13
+                            sourceLocation: OrderRoute.java
+                            when:
+                              - sourceLineNumber: 14
+                                sourceLocation: OrderRoute.java
+                                expression:
+                                  simple:
+                                    expression: "${body} == 9"
+                                steps:
+                                  - throwException:
+                                      sourceLineNumber: 15
+                                      sourceLocation: OrderRoute.java
+                """;
+        List<RouteDumpDevConsole.CodeLine> code = 
RouteDumpDevConsole.javaOrYamlLoadSourceAsJson(new StringReader(yaml));
+        List<String> lines = code.stream().map(c -> c.line() + " " + 
Jsoner.unescape(c.code())).toList();
+        Assertions.assertEquals(List.of(
+                "-1 - route:",
+                "7     from:",
+                "-1       uri: timer:orders",
+                "-1       steps:",
+                "13         - choice:",
+                "-1             when:",
+                "14               - expression:",
+                "-1                   simple:",
+                "-1                     expression: \"${body} == 9\"",
+                "-1                 steps:",
+                "15                   - throwException:"), lines);
+    }
 }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/util/DumpModelThrowExceptionInstanceTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/util/DumpModelThrowExceptionInstanceTest.java
new file mode 100644
index 000000000000..073f8d008c19
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/util/DumpModelThrowExceptionInstanceTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.util;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.PluginHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * An exception given to throwException as an instance is dumped as its type 
and message.
+ */
+public class DumpModelThrowExceptionInstanceTest extends 
DumpModelAsYamlTestSupport {
+
+    @Test
+    public void testDumpModelAsYaml() throws Exception {
+        String out = 
PluginHelper.getModelToYAMLDumper(context).dumpModelAsYaml(context, 
context.getRouteDefinition("myRoute"));
+        assertThat(out).contains("- throwException:", """
+                            message: Out of stock
+                            exceptionType: java.lang.IllegalStateException
+                """);
+        String typed = 
PluginHelper.getModelToYAMLDumper(context).dumpModelAsYaml(context, 
context.getRouteDefinition("typed"));
+        assertThat(typed).contains("- throwException:", """
+                            message: Sold out
+                            exceptionType: java.lang.IllegalArgumentException
+                """);
+    }
+
+    @Test
+    public void testDumpModelAsXml() throws Exception {
+        String out = 
PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, 
context.getRouteDefinition("myRoute"));
+        assertThat(out).contains(
+                "message=\"Out of stock\" 
exceptionType=\"java.lang.IllegalStateException\"/>");
+        String typed = 
PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, 
context.getRouteDefinition("typed"));
+        assertThat(typed).contains(
+                "message=\"Sold out\" 
exceptionType=\"java.lang.IllegalArgumentException\"/>");
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("myRoute")
+                        .throwException(new IllegalStateException("Out of 
stock"));
+
+                from("direct:typed").routeId("typed")
+                        .throwException(IllegalArgumentException.class, "Sold 
out");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
index 9ebc1f233868..915e4cceb4a1 100644
--- 
a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
+++ 
b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java
@@ -45,6 +45,7 @@ import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
 import org.apache.camel.model.SendDefinition;
 import org.apache.camel.model.SwitchDefinition;
+import org.apache.camel.model.ThrowExceptionDefinition;
 import org.apache.camel.model.ToDynamicDefinition;
 import org.apache.camel.model.dataformat.DataFormatsDefinition;
 import org.apache.camel.model.language.ExpressionDefinition;
@@ -138,6 +139,25 @@ public class LwModelToXMLDumper implements 
ModelToXMLDumper {
                 }
             }
 
+            @Override
+            protected void doWriteThrowExceptionDefinition(String name, 
ThrowExceptionDefinition def) throws IOException {
+                Exception e = def.getException();
+                String type = def.getExceptionClass() != null
+                        ? def.getExceptionClass().getName() : e != null ? 
e.getClass().getName() : null;
+                if (type == null || def.getExceptionType() != null || 
def.getRef() != null) {
+                    super.doWriteThrowExceptionDefinition(name, def);
+                    return;
+                }
+                // an exception given as a class or an instance, as in 
throwException(new Exception("...")), is
+                // written as its type and message, as the class or instance 
itself cannot be serialized
+                startElement(name);
+                doWriteProcessorDefinitionAttributes(def);
+                String message = def.getMessage() != null ? def.getMessage() : 
e != null ? e.getMessage() : null;
+                doWriteAttribute("message", message, null);
+                doWriteAttribute("exceptionType", type, null);
+                endElement(name);
+            }
+
             @Override
             protected void doWriteValue(String value) throws IOException {
                 if (value != null && !value.isEmpty()) {
diff --git 
a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
 
b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
index 318e20db2cf9..c5db9efafe79 100644
--- 
a/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
+++ 
b/core/camel-yaml-io/src/main/java/org/apache/camel/yaml/LwModelToYAMLDumper.java
@@ -46,6 +46,7 @@ import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
 import org.apache.camel.model.SendDefinition;
 import org.apache.camel.model.SwitchDefinition;
+import org.apache.camel.model.ThrowExceptionDefinition;
 import org.apache.camel.model.ToDynamicDefinition;
 import org.apache.camel.model.dataformat.DataFormatsDefinition;
 import org.apache.camel.model.language.ExpressionDefinition;
@@ -137,6 +138,23 @@ public class LwModelToYAMLDumper implements 
ModelToYAMLDumper {
                 super.doWriteAttribute(jo, key, value, defaultValue);
             }
 
+            @Override
+            protected JsonObject 
doWriteThrowExceptionDefinition(ThrowExceptionDefinition def) {
+                JsonObject jo = super.doWriteThrowExceptionDefinition(def);
+                Exception e = def.getException();
+                String type = def.getExceptionClass() != null
+                        ? def.getExceptionClass().getName() : e != null ? 
e.getClass().getName() : null;
+                if (type != null && def.getExceptionType() == null && 
def.getRef() == null) {
+                    // an exception given as a class or an instance, as in 
throwException(new Exception("...")), is
+                    // written as its type and message, as the class or 
instance itself cannot be serialized
+                    if (def.getMessage() == null && e != null) {
+                        doWriteAttribute(jo, "message", e.getMessage(), null);
+                    }
+                    doWriteAttribute(jo, "exceptionType", type, null);
+                }
+                return jo;
+            }
+
             @Override
             protected void doWriteValue(JsonObject jo, String value) {
                 if (resolvePlaceholders && value != null) {

Reply via email to