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 7d93a74f9203 CAMEL-25529: camel-yaml-dsl-validator - an option of a 
step's EIP at the column of the EIP: say which key it lines up with (#27660)
7d93a74f9203 is described below

commit 7d93a74f9203af657c36eda840d078e243e9bd4d
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 10 15:17:23 2026 +0200

    CAMEL-25529: camel-yaml-dsl-validator - an option of a step's EIP at the 
column of the EIP: say which key it lines up with (#27660)
    
    The hint said "steps: is at the column of - split: ... indented under 
split:, two spaces more"; read from the dash, that is where steps: already was, 
and a second error said to move the items up a level. The hint now names the 
key the option lines up with and the one it belongs next to, and the repeated 
error is dropped.
    
    
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/dsl/yaml/validator/SchemaHints.java      | 65 +++++++++++++++++++++-
 .../camel/dsl/yaml/validator/YamlValidator.java    |  2 +
 .../dsl/yaml/validator/StructureHintTest.java      | 27 ++++++++-
 3 files changed, 91 insertions(+), 3 deletions(-)

diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
index 576430ae89d5..7f4c8edec0c8 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/SchemaHints.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml.validator;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
@@ -191,6 +192,10 @@ final class SchemaHints {
             return generic;
         }
         Set<String> options = m.validator().optionsOf(eip);
+        // the key the option lines up with once it is under the EIP: "the 
column of - split:" read as the column of
+        // the dash, and two spaces more than that is where the model had put 
it already (it wrote the same five times)
+        JsonNode value = node.get(eip);
+        String sibling = value != null && value.isObject() && value.size() > 0 
? value.fieldNames().next() : null;
         List<String> parts = new ArrayList<>();
         for (String k : keys) {
             if (k.equals(eip)) {
@@ -198,9 +203,14 @@ final class SchemaHints {
             }
             if (steps.contains(k)) {
                 parts.add(k + ": is another EIP: start it as its own item, - " 
+ k + ":");
+            } else if (options.contains(k) && sibling != null) {
+                parts.add(k + ": lines up with " + eip + ":, so it is read as 
a second key of the - item; it is an"
+                          + " option of " + eip + ": indent " + k + ": and the 
lines under it two spaces more, so " + k
+                          + ": lines up with " + sibling + ":");
             } else if (options.contains(k)) {
-                parts.add(k + ": is at the column of - " + eip + ": as an 
option of " + eip + " it is indented under "
-                          + eip + ":, two spaces more, next to its other 
options");
+                parts.add(k + ": lines up with " + eip + ":, so it is read as 
a second key of the - item; it is an"
+                          + " option of " + eip + ": write the options of " + 
eip + ": on the lines under it, two"
+                          + " spaces further right than " + eip + ":, with " + 
k + ": among them");
             } else {
                 // neither: say what a step is, without guessing where the key 
belongs
                 return generic;
@@ -209,6 +219,57 @@ final class SchemaHints {
         return parts.isEmpty() ? generic : String.join("; ", parts);
     }
 
+    /**
+     * The options of the step's EIP that are written as second keys of the 
step (at the column of the EIP), when the
+     * step has an EIP; empty otherwise.
+     */
+    static Set<String> misplacedOptions(JsonNode node, YamlValidator 
validator) {
+        if (node == null || !node.isObject() || node.size() < 2) {
+            return Set.of();
+        }
+        Set<String> steps = validator.stepNames();
+        List<String> keys = new ArrayList<>();
+        node.fieldNames().forEachRemaining(keys::add);
+        String eip = 
keys.stream().filter(steps::contains).findFirst().orElse(null);
+        if (eip == null) {
+            return Set.of();
+        }
+        Set<String> options = validator.optionsOf(eip);
+        Set<String> answer = new LinkedHashSet<>();
+        for (String k : keys) {
+            if (!k.equals(eip) && !steps.contains(k) && options.contains(k)) {
+                answer.add(k);
+            }
+        }
+        return answer;
+    }
+
+    /**
+     * An option of a step's EIP at the column of the EIP fails twice: "at 
most 1 properties" (the hint above says where
+     * it goes) and "property 'steps' is not defined" in the oneOf branch of 
the step, whose hint says to move the items
+     * up a level, the opposite. The second one is dropped.
+     */
+    static List<Error> dropMisplacedOptionRepeats(List<Error> errors, 
YamlValidator validator) {
+        Set<String> repeats = new HashSet<>();
+        for (Error e : errors) {
+            if ("maxProperties".equals(e.getKeyword())) {
+                String at = String.valueOf(e.getInstanceLocation());
+                if (at.matches(".*/steps/\\d+")) {
+                    for (String k : misplacedOptions(e.getInstanceNode(), 
validator)) {
+                        repeats.add(at + " " + k);
+                    }
+                }
+            }
+        }
+        if (repeats.isEmpty()) {
+            return errors;
+        }
+        List<Error> answer = new ArrayList<>(errors);
+        answer.removeIf(e -> "additionalProperties".equals(e.getKeyword()) && 
e.getMessage() != null
+                && repeats.contains(e.getInstanceLocation() + " " + 
between(e.getMessage(), "property '", "'")));
+        return answer;
+    }
+
     // 
-------------------------------------------------------------------------------------------------------------
     // expression hints
     // 
-------------------------------------------------------------------------------------------------------------
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 c1f60be2d4ec..5db8ff7df451 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
@@ -724,6 +724,8 @@ public class YamlValidator {
         errors = SchemaHints.apply(SchemaHints.EXPRESSION, errors, this);
         errors = SchemaHints.apply(SchemaHints.PROPERTY, errors, this);
         errors = SchemaHints.apply(SchemaHints.LIST, errors, this);
+        // before the step hints, which rewrite the error without its instance 
node
+        errors = SchemaHints.dropMisplacedOptionRepeats(errors, this);
         errors = SchemaHints.apply(SchemaHints.STEP, errors, this);
         // CAMEL-24707: the schema requires the expression, so a node without 
one fails its oneOf with "0 are valid"
         // plus one "required property <language> not found" per language; 
replace that with one line that says
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
index c58171d0700a..bc44197dcede 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/StructureHintTest.java
@@ -72,7 +72,32 @@ public class StructureHintTest {
                             allowNamedParameters: true
                 """;
         assertThat(lines(yaml)).anySatisfy(m -> assertThat(m)
-                .contains("parameters: is at the column of - to: as an option 
of to it is indented under to:"));
+                .contains("parameters: lines up with to:, so it is read as a 
second key of the - item")
+                .contains("indent parameters: and the lines under it two 
spaces more, so parameters: lines up with"
+                          + " uri:"));
+    }
+
+    @Test
+    public void stepsAtTheColumnOfSplit() {
+        // as qwen3.6 wrote it ten times in a row: the hint named "the column 
of - split:", and two spaces more than
+        // the dash is where steps: already was; the second error said to move 
the items up a level
+        String yaml = """
+                - route:
+                    from:
+                      uri: timer:orders
+                      steps:
+                        - split:
+                            expression:
+                              simple: "${body[lines]}"
+                          steps:
+                            - log:
+                                message: "pick ${body[qty]} x ${body[sku]}"
+                """;
+        assertThat(lines(yaml)).singleElement().satisfies(m -> assertThat(m)
+                .startsWith("Line 5: ")
+                .contains("steps: lines up with split:")
+                .contains("so steps: lines up with expression:")
+                .doesNotContain("move the items up"));
     }
 
     @Test

Reply via email to