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

davsclaus pushed a commit to branch feature/CAMEL-25208-code-assist
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 6cf6f6f154c3d56bf34bae0d7d231d380707cb35
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 30 22:51:52 2026 +0200

    CAMEL-25208: camel-jbang - camel validate and camel_validate_source check 
Java and XML routes
    
    SourceValidator.validate, shared by the TUI, the MCP tools
    (camel_validate_source, camel_write_file) and camel validate source, adds
    the Camel checks of RouteAssist to Java files (after javac) and to well
    formed XML DSL files: what the compiler cannot see inside the strings.
    camel_validate_source also lists the parts of a Java route the parser
    could not read (notChecked), so an agent knows what was not checked.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/ai/AuthoringTools.java |  15 ++-
 .../jbang/core/commands/ai/SourceValidator.java    |  36 ++++++-
 .../commands/ai/SourceValidatorRoutesTest.java     | 113 +++++++++++++++++++++
 .../jbang/core/commands/mcp/AuthoringTools.java    |   2 +-
 4 files changed, 162 insertions(+), 4 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
index eb0fce3fbe07..064ffd66967e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/AuthoringTools.java
@@ -146,7 +146,7 @@ public final class AuthoringTools {
                 }));
 
         registry.accept(tool("camel_validate_source",
-                "Validates Camel YAML DSL or .properties source without 
writing: schema (misspelled options such as "
+                "Validates Camel YAML/Java/XML DSL or .properties source 
without writing: schema (misspelled options such as "
                                                       + "logLevel instead of 
loggingLevel), endpoint URIs, simple expressions, "
                                                       + "camel.* options. Use 
on content before writing it, or on an existing "
                                                       + "file (no content) to 
explain a reload error.")
@@ -497,6 +497,19 @@ public final class AuthoringTools {
         result.put("valid", errors.isEmpty());
         result.put("file", file);
         result.put("errors", new JsonArray(errors));
+        if (RouteAssist.supports(file, content)) {
+            // the parts of a Java or XML route the parser could not read, so 
the checks did not see (CAMEL-25208)
+            JsonArray notChecked = new JsonArray();
+            for (RouteAssist.Diagnostic d : RouteAssist.diagnostics(file, 
content, ctx.catalog(), null,
+                    RouteAssist.javaSources(dir), false)) {
+                if (d.severity() == RouteAssist.Severity.INFO) {
+                    notChecked.add(d.format());
+                }
+            }
+            if (!notChecked.isEmpty()) {
+                result.put("notChecked", notChecked);
+            }
+        }
         result.put("message", errors.isEmpty()
                 ? "The source is valid"
                 : errors.size() + " problem(s) found; fix them before writing 
the file");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
index 459be17c7fe7..aac0dfa51683 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidator.java
@@ -140,17 +140,49 @@ public final class SourceValidator {
             if (directory != null && !msgs.isEmpty()) {
                 msgs = withSiblingClassHints(msgs, 
BeanDeclarations.scan(directory, fileName));
             }
-            return msgs;
+            // what the compiler cannot see: the endpoint uris and simple 
expressions inside the strings (CAMEL-25208)
+            return withRouteChecks(msgs, fileName, content, catalog, 
directory, checkConsumers);
         }
         if (name.endsWith(".xsl") || name.endsWith(".xslt")) {
             return validateXslt(content);
         }
         if (name.endsWith(".xml")) {
-            return validateXml(content);
+            List<String> msgs = validateXml(content);
+            // a well formed file of the XML DSL: its routes against the XML 
DSL and the catalog (CAMEL-25208)
+            return msgs.isEmpty() ? withRouteChecks(msgs, fileName, content, 
catalog, directory, checkConsumers) : msgs;
         }
         return List.of();
     }
 
+    /**
+     * Adds the errors of the Camel checks of a Java or XML DSL source ({@link 
RouteAssist}) to the messages, leaving
+     * out one on a line that already has a message.
+     */
+    private static List<String> withRouteChecks(
+            List<String> msgs, String fileName, String content, CamelCatalog 
catalog, Path directory,
+            boolean checkConsumers) {
+        if (!RouteAssist.supports(fileName, content)) {
+            return msgs;
+        }
+        List<String> answer = new ArrayList<>(msgs);
+        Set<String> linesWithMessages = new HashSet<>();
+        for (String m : msgs) {
+            Matcher lm = MESSAGE_LINE.matcher(m);
+            if (lm.find()) {
+                linesWithMessages.add(lm.group(1));
+            }
+        }
+        for (RouteAssist.Diagnostic d : RouteAssist.diagnostics(fileName, 
content, catalog, directory,
+                RouteAssist.javaSources(directory), checkConsumers && 
directory != null)) {
+            if (d.severity() == RouteAssist.Severity.ERROR && 
!linesWithMessages.contains(Integer.toString(d.line()))) {
+                answer.add(d.format());
+            }
+        }
+        return answer;
+    }
+
+    private static final Pattern MESSAGE_LINE = Pattern.compile("^Line 
(\\d+)\\b");
+
     /**
      * Validates Camel YAML DSL source: the YAML DSL schema first, then 
endpoint URIs and simple expressions against the
      * catalog. The schema is the one of the catalog's Camel version: the 
CLI's own, or for a catalog of another version
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorRoutesTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorRoutesTest.java
new file mode 100644
index 000000000000..f28bcf158b74
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorRoutesTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.dsl.jbang.core.commands.ai;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
+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.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The Camel checks of Java and XML DSL routes in the validation shared by the 
TUI, camel_validate_source and camel
+ * validate (CAMEL-25208).
+ */
+class SourceValidatorRoutesTest {
+
+    private static CamelCatalog catalog;
+
+    @BeforeAll
+    static void loadCatalog() {
+        catalog = new DefaultCamelCatalog();
+    }
+
+    private static final String JAVA = """
+            import org.apache.camel.builder.RouteBuilder;
+
+            public class MyRoute extends RouteBuilder {
+                @Override
+                public void configure() throws Exception {
+                    from("timer:tick?peroid=1000")
+                        .process(e -> e.getMessage().setBody("hello"))
+                        .to("seda:out");
+                }
+            }
+            """;
+
+    @Test
+    void theEndpointsOfAJavaRouteAreChecked() {
+        List<String> errors = SourceValidator.validate("MyRoute.java", JAVA, 
catalog, null, null);
+        assertThat(errors).containsExactly(
+                "Line 6: timer: Unknown option 'peroid'. Did you mean: 
[period]");
+    }
+
+    @Test
+    void aJavaCompileErrorKeepsItsLine() {
+        String src = JAVA.replace(".to(\"seda:out\");", ".to(\"seda:out\")");
+        List<String> errors = SourceValidator.validate("MyRoute.java", src, 
catalog, null, null);
+        assertThat(errors).isNotEmpty();
+        assertThat(errors.get(0)).contains("';' expected");
+        // the Camel checks still run on what the parser reads
+        assertThat(errors).anyMatch(e -> e.contains("Unknown option 
'peroid'"));
+    }
+
+    @Test
+    void theRoutesOfAnXmlFileAreChecked() {
+        String xml = """
+                <routes xmlns="http://camel.apache.org/schema/xml-io";>
+                    <route>
+                        <from uri="timer:tick"/>
+                        <to uri="seda:${header.queue}"/>
+                    </route>
+                </routes>
+                """;
+        List<String> errors = SourceValidator.validate("routes.xml", xml, 
catalog, null, null);
+        assertThat(errors).hasSize(1);
+        assertThat(errors.get(0)).startsWith("Line 4: ").contains("toD");
+    }
+
+    @Test
+    void anXmlFileThatIsNotWellFormedIsReportedAsSuch() {
+        String xml = "<routes><route><from uri=\"timer:tick\"/></routes>";
+        List<String> errors = SourceValidator.validate("routes.xml", xml, 
catalog, null, null);
+        assertThat(errors).hasSize(1);
+        assertThat(errors.get(0)).contains("XML is not well formed");
+    }
+
+    @Test
+    void theValidateToolSaysWhatWasNotChecked() throws Exception {
+        String src = JAVA.replace(".to(\"seda:out\");", ".to(\"seda:out?\" + 
options());")
+                .replace("    @Override", "    String options() { return 
\"size=1\"; }\n\n    @Override");
+        Map<String, String> args = new HashMap<>();
+        args.put("file", "MyRoute.java");
+        args.put("content", src);
+        JsonObject result = (JsonObject) Jsoner.deserialize(
+                String.valueOf(ToolRegistry.execute("camel_validate_source", 
new ToolContext(), args)));
+        assertThat((JsonArray) result.get("errors")).hasSize(1);
+        JsonArray notChecked = (JsonArray) result.get("notChecked");
+        assertThat(notChecked).hasSize(1);
+        assertThat(notChecked.getString(0)).startsWith("Line 10: not checked: 
").contains("options()");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
index 7f3b43bc2e86..2948c1e2fd00 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java
@@ -110,7 +110,7 @@ public class AuthoringTools {
     }
 
     @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
-          description = "Validates Camel YAML DSL or .properties source 
without writing: schema (misspelled options "
+          description = "Validates Camel YAML/Java/XML DSL or .properties 
source without writing: schema (misspelled options "
                         + "such as logLevel instead of loggingLevel), endpoint 
URIs, simple expressions, camel.* "
                         + "options, and how each bean under beans: is created 
(the properties a class built through "
                         + "its builder() accepts, the ways to create a class 
with no constructor). Use on content "

Reply via email to