This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-25222 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 873d723fd8116b399dc19b76455a46491033d0ae Author: Claus Ibsen <[email protected]> AuthorDate: Thu Oct 1 12:28:33 2026 +0200 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/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..347f983d8e0f 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 + 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..657928a75aee 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 + 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) {
