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 68600099ae46 CAMEL-26295: camel-jbang - the source editor checks the 
message body of a YAML route as the file opens and while you type, not only on 
save
68600099ae46 is described below

commit 68600099ae4635e9f08f213d78994610253aafef
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 25 09:42:53 2026 +0100

    CAMEL-26295: camel-jbang - the source editor checks the message body of a 
YAML route as the file opens and while you type, not only on save
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel-tui-source-body-problem-on-open.png      | Bin 0 -> 224936 bytes
 .../ROOT/pages/camel-jbang-tui-source-editor.adoc  |  11 +++++--
 .../dsl/jbang/core/commands/tui/SourceChecks.java  |  30 +++++++++++++-----
 .../jbang/core/commands/tui/SourceChecksTest.java  |  28 +++++++++++++++++
 .../camel/dsl/yaml/validator/YamlValidator.java    |  34 ++++++++++++++++++---
 .../camel/dsl/yaml/validator/TextBodyFlowTest.java |  22 +++++++++++++
 6 files changed, 110 insertions(+), 15 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-body-problem-on-open.png
 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-body-problem-on-open.png
new file mode 100644
index 000000000000..93f84042d34e
Binary files /dev/null and 
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-body-problem-on-open.png
 differ
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
index be04781198d1..050c0e96b304 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
@@ -95,8 +95,15 @@ route of the file, and `camel get route --checksum` the 
checksum of each running
 == Problems as You Type
 
 The editor checks your routes while you type: endpoint URIs and their options 
against the Camel catalog, Simple
-expressions, the YAML DSL schema, and a `to` whose URI holds `+${...}+` and 
should be a `toD`. Java routes are read
-without compiling them, so endpoints built with the endpoint DSL or from a 
constant are checked too.
+expressions, and a `to` whose URI holds `+${...}+` and should be a `toD`. Java 
routes are read without compiling them,
+so endpoints built with the endpoint DSL or from a constant are checked too.
+
+A YAML route is also followed through its steps with the message body each 
step gives and needs, as soon as the file
+passes the YAML DSL schema: a `to: file` after `unmarshal: json` cannot write 
the `Map` it gets, and the line says to add
+`marshal: json` before it (see xref:camel-jbang-validation.adoc[Validation]). 
The schema itself is checked when you
+save, as a file being typed is often not valid YAML yet.
+
+image::jbang/camel-tui-source-body-problem-on-open.png[A route that writes the 
parsed JSON to a file, marked as soon as the file opens: the file producer 
needs text, bytes, a stream or a file, and the Error panel says to add marshal 
json before it]
 
 The problems are marked as soon as a file opens, before you change anything: a 
red `✗` on the line, and the count in
 the title. The panel at the bottom says the problem of the selected line, and 
*F9* goes to the next one.
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
index 7d5c9141b2a6..12e8deedded9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecks.java
@@ -31,7 +31,9 @@ import org.apache.camel.dsl.yaml.validator.YamlValidator;
  * with its errors said. A note is something that works but is not right: 
marked and said, never blocking a save.
  * <p/>
  * The same checks run when a file opens, while typing and on save. A save 
also checks a YAML file against the YAML DSL
- * schema, and leaves out the notes of a file whose errors keep it from being 
saved.
+ * schema, and leaves out the notes of a file whose errors keep it from being 
saved. The checks of the routes of a YAML
+ * file that passes the schema, such as a step that cannot take the message 
body it gets, run while typing too
+ * (CAMEL-26295): a file being typed is often not valid YAML yet, so its 
schema errors wait for the save.
  */
 final class SourceChecks {
 
@@ -138,9 +140,8 @@ final class SourceChecks {
     private Found check(Kind kind, String content, boolean save) {
         List<String> errors = new ArrayList<>();
         if (kind == Kind.YAML) {
-            if (save) {
-                
errors.addAll(SourceValidationSupport.formatSchemaErrors(validateYaml(content), 
content));
-            }
+            errors.addAll(SourceValidationSupport.formatSchemaErrors(
+                    save ? validateYaml(content) : 
validateYamlRoutes(content), content));
             addAll(errors, endpointValidator, content);
             addAll(errors, simpleValidator, content);
             addAll(errors, kameletValidator, content);
@@ -170,12 +171,25 @@ final class SourceChecks {
 
     private List<Error> validateYaml(String content) {
         try {
-            if (yamlValidator == null) {
-                yamlValidator = new YamlValidator();
-            }
-            return yamlValidator.validate(content);
+            return yamlValidator().validate(content);
         } catch (Exception e) {
             return List.of();
         }
     }
+
+    /** The checks of the routes of a YAML file that passes the schema; none 
while it does not. */
+    private List<Error> validateYamlRoutes(String content) {
+        try {
+            return yamlValidator().validateRoutes(content);
+        } catch (Exception e) {
+            return List.of();
+        }
+    }
+
+    private YamlValidator yamlValidator() {
+        if (yamlValidator == null) {
+            yamlValidator = new YamlValidator();
+        }
+        return yamlValidator;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
index f6cc0ba1c4e3..fe020f9a5b21 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceChecksTest.java
@@ -69,6 +69,34 @@ class SourceChecksTest {
         assertThat(valid.blocksSave(SourceChecks.Kind.YAML)).isFalse();
     }
 
+    @Test
+    void aStepThatCannotTakeTheBodyWhileTyping() {
+        // CAMEL-26295: the body through the steps of a route is checked as 
soon as the file passes the schema
+        SourceChecks checks = new SourceChecks();
+        String yaml = """
+                - route:
+                    from:
+                      uri: file:inbox
+                      steps:
+                        - unmarshal:
+                            json: {}
+                        - to:
+                            uri: file:outbox
+                """;
+        SourceChecks.Found found = checks.check(SourceChecks.Kind.YAML, yaml);
+        assertThat(found.errors()).singleElement().satisfies(e -> 
assertThat(e).startsWith("Line 8: ")
+                .contains("to: file needs the body as text, bytes, a stream or 
a file")
+                .contains("add marshal: json before this step"));
+        assertThat(found.blocksSave(SourceChecks.Kind.YAML)).isTrue();
+        // the save says the same
+        assertThat(checks.checkOnSave(SourceChecks.Kind.YAML, 
yaml).errors()).isEqualTo(found.errors());
+        // with the marshal the body is text again
+        String fixed = yaml.replace("        - to:", "        - marshal:\n     
       json: {}\n        - to:");
+        assertThat(checks.check(SourceChecks.Kind.YAML, 
fixed).errors()).isEmpty();
+        // a file that does not pass the schema yet, as while typing: its 
routes wait for the save as well
+        assertThat(checks.check(SourceChecks.Kind.YAML, yaml.replace("steps:", 
"stepz:")).errors()).isEmpty();
+    }
+
     @Test
     void theErrorsOfAJavaFileDoNotBlockItsSave() {
         SourceChecks checks = new SourceChecks();
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
index 576624c766dc..1ae0eb101002 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
@@ -192,16 +192,33 @@ public class YamlValidator {
      *              (CAMEL-25762)
      */
     public List<Error> validate(String content, ProjectFacts facts) throws 
Exception {
+        return validate(content, facts, false);
+    }
+
+    /**
+     * The checks of the routes of a file whose YAML passes the schema: Simple 
syntax in a script, a dynamic uri, and
+     * the message body through the steps (CAMEL-24844). Empty when the YAML 
does not pass the schema, as for a file
+     * being typed: an editor says the errors of the schema when the file is 
saved, and these as soon as they are there
+     * (CAMEL-26295).
+     */
+    public List<Error> validateRoutes(String content) throws Exception {
+        return validate(content, ProjectFacts.NONE, true);
+    }
+
+    private List<Error> validate(String content, ProjectFacts facts, boolean 
routesOnly) throws Exception {
         if (schema == null) {
             init();
         }
+        if (routesOnly && (content == null || content.isBlank())) {
+            return List.of();
+        }
         Error extra = extraDocument(content);
         if (extra != null) {
-            return List.of(extra);
+            return routesOnly ? List.of() : List.of(extra);
         }
         Error deps = jbangDirective(content);
         if (deps != null) {
-            return List.of(deps);
+            return routesOnly ? List.of() : List.of(deps);
         }
         if (content == null || content.isBlank() || content.lines().allMatch(l 
-> l.isBlank() || l.trim().startsWith("#"))) {
             return List.of(Error.builder().messageKey("empty").format(new 
MessageFormat("{0}"))
@@ -211,7 +228,10 @@ public class YamlValidator {
         }
         try {
             var target = mapper.readTree(content);
-            List<Error> errors = validate(target, facts != null ? facts : 
ProjectFacts.NONE);
+            List<Error> errors = validate(target, facts != null ? facts : 
ProjectFacts.NONE, routesOnly);
+            if (routesOnly) {
+                return errors;
+            }
             // CAMEL-25560: the tree keeps the last of two equal keys, so a 
key given twice is only seen in the YAML nodes
             List<String> duplicates = 
DuplicateKeys.find(YamlPointerLines.root(content));
             if (duplicates.isEmpty()) {
@@ -222,7 +242,7 @@ public class YamlValidator {
             answer.addAll(errors);
             return answer;
         } catch (Exception e) {
-            return List.of(parseError(e, content));
+            return routesOnly ? List.of() : List.of(parseError(e, content));
         }
     }
 
@@ -788,7 +808,7 @@ public class YamlValidator {
         return kind != null && kind.isTextual() && 
"Kamelet".equals(kind.asText());
     }
 
-    private List<Error> validate(JsonNode target, ProjectFacts facts) {
+    private List<Error> validate(JsonNode target, ProjectFacts facts, boolean 
routesOnly) {
         Schema against = isKamelet(target) ? kameletSchema : schema;
         // A branch only matches after scalar conversion if it has no other 
errors, including missing required fields.
         var errors = filterOneOfNoise(new 
ArrayList<>(against.validate(target)));
@@ -848,6 +868,10 @@ public class YamlValidator {
             errors.removeIf(e -> 
hinted.contains(String.valueOf(e.getInstanceLocation()))
                     && ("oneOf".equals(e.getKeyword()) || 
"required".equals(e.getKeyword())));
         }
+        if (routesOnly && !errors.isEmpty()) {
+            // the YAML does not pass the schema, whose errors are not asked 
for: the routes are not checked either
+            return List.of();
+        }
         if (errors.isEmpty()) {
             checkSimpleSyntaxInScripts(camelOnly, new 
NodePath(PathType.JSON_POINTER), errors);
             checkDynamicUri(camelOnly, new NodePath(PathType.JSON_POINTER), 
errors);
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
index 6625ddc6867d..50107d90e188 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/TextBodyFlowTest.java
@@ -46,6 +46,28 @@ public class TextBodyFlowTest {
         validator.init();
     }
 
+    @Test
+    public void testTheRoutesOfAFileThatPassesTheSchema() throws Exception {
+        // CAMEL-26295: what an editor checks while typing, the routes, and 
only once the YAML passes the schema
+        String yaml = """
+                - route:
+                    from:
+                      uri: file:inbox
+                      steps:
+                        - unmarshal:
+                            json: {}
+                        - to:
+                            uri: file:outbox
+                """;
+        assertThat(validator.validateRoutes(yaml)).singleElement()
+                .satisfies(e -> assertThat(e.getMessage()).contains("to: file 
needs the body as text"));
+        // not the schema: a misspelled key, YAML that does not parse, or no 
YAML at all
+        assertThat(validator.validateRoutes(yaml.replace("steps:", 
"stepz:"))).isEmpty();
+        assertThat(validator.validateRoutes(yaml + "  - : [")).isEmpty();
+        assertThat(validator.validateRoutes("")).isEmpty();
+        assertThat(validator.validate(yaml.replace("steps:", 
"stepz:"))).isNotEmpty();
+    }
+
     @Test
     public void testMarshalInsteadOfUnmarshal() {
         // what the benchmark wrote: the file is loaded, then marshalled, then 
read with Groovy

Reply via email to