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 80248a3a9819 CAMEL-25389: camel-jbang - route-diagram, route-structure
and route-topology print the parser notes on stderr (#27913)
80248a3a9819 is described below
commit 80248a3a9819e8f44b69e4b9c68fae55bf84ebde
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 15 23:26:42 2026 +0200
CAMEL-25389: camel-jbang - route-diagram, route-structure and
route-topology print the parser notes on stderr (#27913)
The parts of a Java route the Java DSL parser could not read are a warning
on
stderr, also with --json (which still lists them in "unread"), and a source
file of these commands that does not exist exits with 3.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 3 +++
.../core/commands/action/ActionBaseCommand.java | 26 +++++++++-------------
.../commands/action/CamelRouteStructureAction.java | 9 ++------
.../commands/action/CamelRouteTopologyAction.java | 5 -----
.../CamelRouteDiagramActionDispatchTest.java | 20 ++++++++---------
.../CamelRouteDiagramActionMultiFileTest.java | 12 ++++++----
.../action/CamelRouteTopologyActionTest.java | 19 ++++++++--------
.../commands/action/RouteActionJavaSourceTest.java | 20 ++++++++++++-----
8 files changed, 57 insertions(+), 57 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 2cf91c597b2e..da9774f9f335 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -213,6 +213,9 @@ A script that checks the exit code of a command must take
these changes into acc
* `camel transform dataweave` exits with `2` without `--input` or
`--expression`, and with `3` when the input path does
not exist or holds no `.dwl` files, instead of `1`.
* `camel cmd sql` exits with `124` instead of `1` when the query does not
answer in time.
+* A source file of `camel cmd route-diagram`, `camel cmd route-structure` and
`camel cmd route-topology` that does not
+ exist exits with `3` instead of `1`. The parts of a Java route the parser
could not read are printed as a warning on
+ stderr (also with `--json`, which lists them in `unread` as before).
* A file or a name that does not exist exits with `3` instead of `1`: a source
file or `--source-dir` of `camel run`, the
route file of `camel explain` and `camel harden` (checked before the LLM is
contacted), the `pom.xml` of
`camel dependency runtime`, the Kamelet of `camel init --from-kamelet`, the
service of `camel infra run`, the key of
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
index 029e2982f3c6..8adf0526fccd 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
@@ -30,6 +30,7 @@ import
org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.dsl.jbang.core.commands.Run;
import org.apache.camel.dsl.jbang.core.commands.TransformJavaRoutes;
import org.apache.camel.dsl.jbang.core.common.CamelJBangConstants;
+import org.apache.camel.dsl.jbang.core.common.CliException;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
@@ -205,8 +206,9 @@ abstract class ActionBaseCommand extends CamelCommand {
* @param workDirPrefix prefix for the (pid-suffixed) temporary work
directory name
* @param ignoreLoadingError whether to ignore route loading and
compilation errors
* @param compile whether to compile Java sources and boot
them, instead of reading them with the parser
- * @return the dump location and the instant the dump
started, or {@code null} if a file does not
- * exist or the dump failed (an error is
already printed to the user in that case)
+ * @return the dump location and the instant the dump
started, or {@code null} if the dump failed
+ * (an error is already printed to the user in
that case)
+ * @throws CliException when a source file does not exist (exit code
3)
*/
protected SourceDump dumpRoutesFromSource(
List<String> sourceFiles, String workDirPrefix, boolean
ignoreLoadingError, boolean compile)
@@ -214,8 +216,7 @@ abstract class ActionBaseCommand extends CamelCommand {
for (String name : sourceFiles) {
File f = new File(name);
if (!f.isFile() || !f.exists()) {
- printer().printErr("File does not exist: " + name);
- return null;
+ throw CliException.notFound("File does not exist: " + name);
}
}
@@ -269,22 +270,15 @@ abstract class ActionBaseCommand extends CamelCommand {
return false;
}
unreadSourceParts = result.unread();
- if (!unreadSourceParts.isEmpty() && printSourceNotes()) {
- printer().println("Routes read without compiling them; the parser
could not work out these parts "
- + "(use --compile to compile the source
instead):");
- unreadSourceParts.forEach(u -> printer().println(" " + u));
+ if (!unreadSourceParts.isEmpty()) {
+ // a warning on stderr: the JSON output stays one document, and
lists them too (see addUnreadSourceParts)
+ printer().printWarn("Routes read without compiling them; the
parser could not work out these parts "
+ + "(use --compile to compile the source
instead):");
+ unreadSourceParts.forEach(u -> printer().printDiagnostic(" " +
u));
}
return true;
}
- /**
- * Whether to print the parts of Java sources the parser could not read:
not when the output is JSON, which lists
- * them instead (see {@link #addUnreadSourceParts(JsonObject)}).
- */
- protected boolean printSourceNotes() {
- return true;
- }
-
/**
* Adds the parts of Java sources the parser could not read to the JSON
output, as an {@code unread} array, so a
* tool reading the JSON can tell that routes may be missing from it.
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
index 08a5ef1cfad9..ad7f9701ac20 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
@@ -28,6 +28,7 @@ import
org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.dsl.jbang.core.commands.Run;
import org.apache.camel.dsl.jbang.core.commands.TransformJavaRoutes;
import org.apache.camel.dsl.jbang.core.common.CamelJBangConstants;
+import org.apache.camel.dsl.jbang.core.common.CliException;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
import org.apache.camel.main.KameletMain;
@@ -226,8 +227,7 @@ public class CamelRouteStructureAction extends
ActionBaseCommand {
private int doCallSource(String name) throws Exception {
File f = new File(name);
if (!f.isFile() || !f.exists()) {
- printer().printErr("File does not exist: " + name);
- return 1;
+ throw CliException.notFound("File does not exist: " + name);
}
final String target = CommandLineHelper.CAMEL_JBANG_WORK_DIR +
"/structure-output.json";
@@ -348,9 +348,4 @@ public class CamelRouteStructureAction extends
ActionBaseCommand {
String code;
String description;
}
-
- @Override
- protected boolean printSourceNotes() {
- return !jsonOutput;
- }
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
index 281e23e578f0..c6c040c2c2d4 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
@@ -374,9 +374,4 @@ public class CamelRouteTopologyAction extends
ActionBaseCommand {
cmd.files.add(arg);
}
}
-
- @Override
- protected boolean printSourceNotes() {
- return !jsonOutput;
- }
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionDispatchTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionDispatchTest.java
index a278b47721a7..f312e8d8bc73 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionDispatchTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionDispatchTest.java
@@ -20,13 +20,15 @@ import java.util.List;
import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mockStatic;
/**
@@ -46,11 +48,10 @@ class CamelRouteDiagramActionDispatchTest extends
ActionCommandTestSupport {
// "doesNotExist" matches no running process, so it is tried as a
source file next
command.files = List.of("doesNotExist");
- int exit = callWatchWithSingleProcess(command);
+ CliException e = assertThrows(CliException.class, () ->
callWatchWithSingleProcess(command));
- assertEquals(1, exit);
- assertTrue(printer.getOutput().contains("File does not exist:
doesNotExist"),
- "should report the missing source file, was: " +
printer.getOutput());
+ assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+ assertEquals("File does not exist: doesNotExist", e.getMessage(),
"should report the missing source file");
}
@Test
@@ -63,13 +64,12 @@ class CamelRouteDiagramActionDispatchTest extends
ActionCommandTestSupport {
CamelRouteDiagramAction command = new CamelRouteDiagramAction(new
CamelJBangMain().withPrinter(printer));
command.files = List.of("route1", "route2");
- int exit = command.doWatchCall();
+ CliException e = assertThrows(CliException.class,
command::doWatchCall);
- assertEquals(1, exit);
- assertTrue(printer.getOutput().contains("File does not exist: route1"),
+ assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+ assertEquals("File does not exist: route1", e.getMessage(),
"with 2+ file args, dispatch must go straight to source-file
handling and skip the "
-
+ "running-integration lookup entirely, was: "
-
+ printer.getOutput());
+ +
"running-integration lookup entirely");
}
private static int callWatchWithSingleProcess(CamelRouteDiagramAction
command) throws Exception {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionMultiFileTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionMultiFileTest.java
index 73c32e46f1fe..b510c6d381ed 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionMultiFileTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramActionMultiFileTest.java
@@ -26,12 +26,15 @@ import java.util.List;
import org.apache.camel.diagram.RouteDiagramLayoutEngine.RouteInfo;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -52,11 +55,12 @@ class CamelRouteDiagramActionMultiFileTest {
StringPrinter printer = new StringPrinter();
CamelRouteDiagramAction command = new CamelRouteDiagramAction(new
CamelJBangMain().withPrinter(printer));
- List<RouteInfo> routes =
command.doCallSource(List.of(existing.toString(), "does-not-exist.yaml"));
+ // the missing file ends the command (exit code 3) before Camel is
booted
+ CliException e = assertThrows(CliException.class,
+ () -> command.doCallSource(List.of(existing.toString(),
"does-not-exist.yaml")));
- assertNull(routes, "should report an error and not attempt to boot
Camel when a file is missing");
- assertTrue(printer.getOutput().contains("File does not exist:
does-not-exist.yaml"),
- "should report the missing file, was: " + printer.getOutput());
+ assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+ assertEquals("File does not exist: does-not-exist.yaml",
e.getMessage(), "should report the missing file");
}
@Test
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyActionTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyActionTest.java
index 85fd4701d1c7..fd8b22c689fc 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyActionTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyActionTest.java
@@ -19,6 +19,8 @@ package org.apache.camel.dsl.jbang.core.commands.action;
import java.util.List;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.junit.jupiter.api.Test;
@@ -27,6 +29,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(MockitoExtension.class)
@@ -74,11 +77,10 @@ class CamelRouteTopologyActionTest extends
ActionCommandTestSupport {
// "doesNotExist" matches no running process, so it is tried as a
source file next
command.files = List.of("doesNotExist");
- int exit = callWithSingleProcess(command);
+ CliException e = assertThrows(CliException.class, () ->
callWithSingleProcess(command));
- assertEquals(1, exit);
- assertTrue(printer.getOutput().contains("File does not exist:
doesNotExist"),
- "should report the missing source file, was: " +
printer.getOutput());
+ assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+ assertEquals("File does not exist: doesNotExist", e.getMessage(),
"should report the missing source file");
}
@Test
@@ -91,13 +93,12 @@ class CamelRouteTopologyActionTest extends
ActionCommandTestSupport {
CamelRouteTopologyAction command = new CamelRouteTopologyAction(new
CamelJBangMain().withPrinter(printer));
command.files = List.of("route1", "route2");
- int exit = command.doCall();
+ CliException e = assertThrows(CliException.class, command::doCall);
- assertEquals(1, exit);
- assertTrue(printer.getOutput().contains("File does not exist: route1"),
+ assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+ assertEquals("File does not exist: route1", e.getMessage(),
"with 2+ file args, dispatch must go straight to source-file
handling and skip the "
-
+ "running-integration lookup entirely, was: "
-
+ printer.getOutput());
+ +
"running-integration lookup entirely");
}
// Note: the actual doCallSource -> Run.runTransform spawn path
(successfully rendering topology from a real
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
index 40969d254666..fc7d6eb95deb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
@@ -22,6 +22,8 @@ import java.util.List;
import org.apache.camel.diagram.RouteDiagramLayoutEngine.RouteInfo;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
import org.apache.camel.dsl.jbang.core.common.StringPrinter;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
@@ -30,6 +32,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* route-diagram, route-topology and route-structure read Java source files
with the Java DSL parser (CAMEL-25565): the
@@ -93,8 +96,9 @@ class RouteActionJavaSourceTest {
RouteInfo intake = routes.stream().filter(r ->
"intake".equals(r.routeId)).findFirst().orElseThrow();
// the lambda is a placeholder step, so the route keeps all its steps
assertThat(intake.nodes).extracting(n -> n.type).contains("from",
"setBody", "process", "to");
- assertThat(printer.getOutput())
- .contains("Routes read without compiling them")
+ // a warning on stderr, not part of the output
+ assertThat(printer.getStderr())
+ .contains("WARN: Routes read without compiling them")
.contains("Orders.java:")
.contains("a lambda or method reference");
}
@@ -107,8 +111,10 @@ class RouteActionJavaSourceTest {
command.compile = true;
// the existence check comes first, also when compiling
- assertThat(command.doCallSource(List.of(missing.toString()))).isNull();
- assertThat(printer.getOutput()).contains("File does not exist: " +
missing);
+ assertThatThrownBy(() ->
command.doCallSource(List.of(missing.toString())))
+ .isInstanceOf(CliException.class)
+ .hasMessage("File does not exist: " + missing)
+ .extracting(e -> ((CliException)
e).getExitCode()).isEqualTo(CliExitCode.NOT_FOUND);
}
@Test
@@ -142,8 +148,10 @@ class RouteActionJavaSourceTest {
int exit = command.doCall();
assertThat(exit).isZero();
- // with --json only the JSON is printed, and the parts the parser
could not read are listed in it
- JsonObject topology = (JsonObject)
Jsoner.deserialize(printer.getOutput());
+ // with --json only the JSON is on stdout, and the parts the parser
could not read are listed in it (and on
+ // stderr, as a warning)
+ JsonObject topology = (JsonObject)
Jsoner.deserialize(printer.getStdout());
+ assertThat(printer.getStderr()).contains("WARN: Routes read without
compiling them");
JsonArray edges = (JsonArray) topology.get("edges");
assertThat(edges).hasSize(1);
JsonObject edge = (JsonObject) edges.get(0);