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