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

davsclaus pushed a commit to branch fix/CAMEL-25900-followup
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 00dd15cad77fae83f55eb8f5e7b8806263dfd832
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Oct 21 22:01:54 2026 +0200

    CAMEL-25900: camel-jbang - AI tools: the jsonpath filter hints fit a path 
that goes on after the filter, and a when or filter
    
    Follow-up of #28210, from the review of oscerd:
    
    - an index after a filter that ends the path is answered with resultType 
java.util.Map; one before a field, as in
      $[?(...)][0].qty, with the path without the index and the type of the 
value; the eval note does the same
    - in a when or filter, the filter alone is the condition, for an index or 
&& and || outside the filter
    - the one-element texts say null also when several elements match
    - ${body[qty]} on a List leads to the jsonpath filter first, as ${body.qty} 
does
    - tests for the note of an evaluation without the download, [0].qty, || and 
$.orders[?(@.id == 1)].items[0]
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/language/bean/BeanExpression.java | 20 ++++--
 .../language/simple/SimpleSyntaxHintsTest.java     |  4 +-
 .../apache/camel/language/simple/SimpleTest.java   | 12 +++-
 .../dsl/jbang/core/commands/ai/CatalogDocs.java    |  4 +-
 .../core/commands/ai/ExpressionEvaluator.java      | 25 +++++--
 .../dsl/jbang/core/commands/ai/JsonPathChecks.java | 50 +++++++++----
 .../jbang/core/commands/ai/CatalogDocsTest.java    |  2 +-
 .../ai/ExpressionEvaluatorLanguageTest.java        | 24 ++++++-
 .../jbang/core/commands/ai/JsonPathChecksTest.java | 82 +++++++++++++++++++++-
 9 files changed, 190 insertions(+), 33 deletions(-)

diff --git 
a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
 
b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
index 1a5e368d6f26..2a36a7927074 100644
--- 
a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
+++ 
b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
@@ -568,11 +568,18 @@ public class BeanExpression implements Expression, 
Predicate {
         String type = bean instanceof List ? "a List" : bean instanceof 
Object[] ? "an array" : "a Collection";
         boolean field = methodName != null && 
FIELD_NAME.matcher(methodName).matches();
         String name = field ? methodName : "field";
-        // the lookup by a field first: given [0] first, models wrote 
${body[0].orderId} and took the first element
-        return " (" + type + " has no " + (field ? "method or key " + 
methodName : "such method")
-               + ": to pick the element whose " + name + " has a given value, 
use jsonpath: \"$[?(@." + name
-               + " == '${header." + name + "}')]\" with resultType 
java.util.Map (the element, or null when none"
-               + " matches); [0]." + name + " reads the first element; or 
split it)";
+        return " (" + type + " has no " + (field ? "method or key " + 
methodName : "such method") + ": "
+               + pickElementHint(name, "[0]." + name) + ")";
+    }
+
+    /**
+     * How to pick an element of a list by a field, and then the first 
element: given [0] first, models wrote
+     * ${body[0].orderId} and took the first element.
+     */
+    private static String pickElementHint(String name, String first) {
+        return "to pick the element whose " + name + " has a given value, use 
jsonpath: \"$[?(@." + name + " == '${header."
+               + name + "}')]\" with resultType java.util.Map (the element, or 
null when none or several match); " + first
+               + " reads the first element; or split it";
     }
 
     private static final Pattern FIELD_NAME = 
Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
@@ -787,7 +794,8 @@ public class BeanExpression implements Expression, 
Predicate {
         }
         if (value instanceof Collection || value instanceof Object[]) {
             String type = value instanceof List ? "a List" : value instanceof 
Object[] ? "an array" : "a Collection";
-            return " (" + type + " has no keys: pick an element first, such as 
[0][" + key + "], or split it)";
+            String name = FIELD_NAME.matcher(key).matches() ? key : "field";
+            return " (" + type + " has no keys: " + pickElementHint(name, 
"[0][" + key + "]") + ")";
         }
         return "";
     }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
index fcc8ac4567aa..44ee0e623216 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
@@ -245,8 +245,8 @@ public class SimpleSyntaxHintsTest extends 
ExchangeTestSupport {
         // the lookup by a field first: given [0] first, models took the first 
element
         assertThat(e.getMessage()).contains("(a List has no method or key 
orderId: to pick the element whose orderId"
                                             + " has a given value, use 
jsonpath: \"$[?(@.orderId == '${header.orderId}')]\""
-                                            + " with resultType java.util.Map 
(the element, or null when none matches);"
-                                            + " [0].orderId reads the first 
element; or split it)");
+                                            + " with resultType java.util.Map 
(the element, or null when none or several"
+                                            + " match); [0].orderId reads the 
first element; or split it)");
     }
 
     @Test
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index c77072ad8ea9..1ec074ef84cc 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -552,7 +552,11 @@ public class SimpleTest extends LanguageTestSupport {
                 () -> assertExpression("${body[qty]}", null));
 
         IndexOutOfBoundsException cause = 
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
-        assertTrue(cause.getMessage().contains("a List has no keys: pick an 
element first, such as [0][qty]"),
+        // the lookup by a field first, as for ${body.qty}
+        assertTrue(cause.getMessage().contains("a List has no keys: to pick 
the element whose qty has a given value, use"
+                                               + " jsonpath: \"$[?(@.qty == 
'${header.qty}')]\" with resultType"
+                                               + " java.util.Map (the element, 
or null when none or several match);"
+                                               + " [0][qty] reads the first 
element; or split it)"),
                 cause.getMessage());
     }
 
@@ -563,7 +567,11 @@ public class SimpleTest extends LanguageTestSupport {
                 () -> assertExpression("${body[qty]}", null));
 
         IndexOutOfBoundsException cause = 
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
-        assertTrue(cause.getMessage().contains("an array has no keys: pick an 
element first, such as [0][qty]"),
+        // the lookup by a field first, as for ${body.qty}
+        assertTrue(cause.getMessage().contains("an array has no keys: to pick 
the element whose qty has a given value, use"
+                                               + " jsonpath: \"$[?(@.qty == 
'${header.qty}')]\" with resultType"
+                                               + " java.util.Map (the element, 
or null when none or several match);"
+                                               + " [0][qty] reads the first 
element; or split it)"),
                 cause.getMessage());
         // [0] picks the element the hint points to
         assertExpression("${body[0][qty]}", 42);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
index c8fee5255776..849de3ef5071 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
@@ -1691,8 +1691,8 @@ public final class CatalogDocs {
     static final String JSONPATH_ONE_ELEMENT = "to pick the one element of a 
list by a field, write a filter with"
                                                + " resultType: java.util.Map, 
such as expression: \"$[?(@.orderId =="
                                                + " '${header.id}')]\" and 
resultType: java.util.Map: the element when one"
-                                               + " matches, null when none 
does (an index after the filter gives an empty"
-                                               + " list); camel_catalog_sample 
jsonpath shows it in a route";
+                                               + " matches, null when none or 
several do (an index after the filter gives"
+                                               + " an empty list); 
camel_catalog_sample jsonpath shows it in a route";
 
     /** How many options of a component are listed in full when no filter is 
given (CAMEL-25541). */
     static final int DEFAULT_OPTIONS_LIMIT = 20;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluator.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluator.java
index 39739bf833b2..3f3d44227959 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluator.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluator.java
@@ -46,6 +46,9 @@ public final class ExpressionEvaluator {
             
"\\s(==|=~|!=|!=~|>|>=|<|<=|~~|!~~|contains|!contains|regex|!regex|in|!in|is|!is|range|!range"
                                                                       + 
"|startsWith|!startsWith|endsWith|!endsWith|equals|!equals|&&|\\|\\|)\\s");
 
+    // a path that ends with a filter: $[?(@.sku == 'CAMEL-MUG')]
+    private static final Pattern FILTER_ENDS_PATH = 
Pattern.compile("\\)]\\s*$");
+
     private ExpressionEvaluator() {
     }
 
@@ -83,23 +86,37 @@ public final class ExpressionEvaluator {
             result.put("evaluatedIn", "a local scratch context (no integration 
selected)");
             evaluateLocally(ctx, lang, expression, body, predicate, result);
         }
+        addFilterNote(result, lang, expression);
+        return result;
+    }
+
+    /** Adds the note about a jsonpath filter to the result of an evaluation, 
after the note it has already. */
+    static void addFilterNote(JsonObject result, String lang, String 
expression) {
         String filter = filterNote(lang, expression, 
result.getString("status"));
         if (filter != null) {
             result.put("note", result.containsKey("note") ? 
result.getString("note") + "; " + filter : filter);
         }
-        return result;
     }
 
     /**
      * A jsonpath filter gives a list, also for one match: models picked the 
element with an index after the filter,
-     * which gives an empty list, or a made-up syntax. In a route, resultType 
java.util.Map gives the one element.
+     * which gives an empty list, or a made-up syntax. In a route, resultType 
java.util.Map gives the one element when
+     * the path ends with the filter, and the type of the value gives the one 
value of a path that goes on after it.
      */
     static String filterNote(String lang, String expression, String status) {
         if (!"jsonpath".equals(lang) || expression == null || 
!expression.contains("[?(") || !"ok".equals(status)) {
             return null;
         }
-        return "a filter gives a list, also for one match; in a route, 
resultType: java.util.Map gives the one element"
-               + " it matches (null when none does), and an index after the 
filter gives an empty list";
+        String problem = JsonPathChecks.filterProblem(expression, false);
+        if (problem != null) {
+            return problem;
+        }
+        if (FILTER_ENDS_PATH.matcher(expression).find()) {
+            return "a filter gives a list, also for one match; in a route, 
resultType: java.util.Map gives the one"
+                   + " element it matches (null when none or several do)";
+        }
+        return "a filter gives a list, also for one match; in a route, a 
resultType that is not a list, such as"
+               + " java.lang.String or java.lang.Integer, gives the one value 
when one element matches";
     }
 
     private static void evaluateInProcess(
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecks.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecks.java
index 5db46d434043..64fbfdc891d3 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecks.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecks.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.ai;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.camel.catalog.CamelCatalog;
@@ -34,9 +35,12 @@ public final class JsonPathChecks {
 
     private static final Pattern COMPARISON = Pattern.compile(" 
(==|!=|<=|>=|<|>|=~|in|nin|size|empty) ");
     // an index after a filter: $[?(@.orderId == 'ORD-1')][0]
-    private static final Pattern INDEX_AFTER_FILTER = 
Pattern.compile("\\[\\?\\(.*\\)]\\s*\\[\\s*-?\\d+\\s*]");
+    private static final Pattern INDEX_AFTER_FILTER = 
Pattern.compile("(\\[\\?\\(.*\\)])\\s*\\[\\s*-?\\d+\\s*]");
     private static final String ONE_ELEMENT = "set resultType: java.util.Map 
(the element when one matches, null when"
-                                              + " none does)";
+                                              + " none or several do)";
+    // in a when or filter a filter is the condition: an empty list is false 
(ObjectHelper.evaluateValuePredicate)
+    private static final String FILTER_IS_THE_CONDITION = "in a when or 
filter, the filter alone is the condition (true"
+                                                          + " when an element 
matches)";
 
     private JsonPathChecks() {
     }
@@ -74,7 +78,7 @@ public final class JsonPathChecks {
             if (text == null || text.isEmpty()) {
                 continue;
             }
-            String filter = filterProblem(text);
+            String filter = filterProblem(text, 
SimpleChecks.isPredicate(catalog, lines, i, countLeadingSpaces(line)));
             if (filter != null) {
                 errors.add("Line " + lineNum + ": " + filter);
                 continue;
@@ -100,23 +104,43 @@ public final class JsonPathChecks {
     }
 
     /**
-     * What is wrong around the filter of a path that should give the one 
element it matches. Jayway applies an index
-     * after a filter to each element the filter matched, so $[?(@.orderId == 
'ORD-1')][0] gives an empty list; and
-     * &amp;&amp; or || outside the filter is no JsonPath (500 in a REST 
route). The one element comes with resultType
-     * java.util.Map: a single match is the element, and no match null.
+     * What is wrong around the filter of a path. Jayway applies an index 
after a filter to each element the filter
+     * matched, so $[?(@.orderId == 'ORD-1')][0] gives an empty list; and 
&amp;&amp; or || outside the filter is no
+     * JsonPath (500 in a REST route). As a value, the one element comes with 
resultType java.util.Map, and a field of
+     * it with the type of the field; in a when or filter, the filter alone is 
the condition.
+     *
+     * @param text      the jsonpath expression
+     * @param predicate whether it is the condition of a when or filter
      */
-    static String filterProblem(String text) {
+    static String filterProblem(String text, boolean predicate) {
         if (!text.contains("[?(")) {
             return null;
         }
-        if (INDEX_AFTER_FILTER.matcher(text).find()) {
-            return "an index after a filter gives an empty list in JsonPath 
([?(...)][0] applies the index to each element"
-                   + " the filter matched): to get the one element, leave the 
index out and " + ONE_ELEMENT;
+        Matcher index = INDEX_AFTER_FILTER.matcher(text);
+        if (index.find()) {
+            String problem = "an index after a filter gives an empty list in 
JsonPath ([?(...)][0] applies the index to"
+                             + " each element the filter matched): ";
+            if (predicate) {
+                return problem + "leave the index out: " + 
FILTER_IS_THE_CONDITION;
+            }
+            String rest = text.substring(index.end()).strip();
+            if (rest.isEmpty()) {
+                return problem + "to get the one element, leave the index out 
and " + ONE_ELEMENT;
+            }
+            // $[?(...)][0].qty: the path without the index gives the qty of 
each match, a single match unwrapped
+            return problem + "to get a value of the one element, leave the 
index out, as in "
+                   + text.substring(0, index.start()) + index.group(1) + rest
+                   + ", and set resultType to the type of that value, such as 
java.lang.Integer for a number (the"
+                   + " value when one element matches)";
         }
         String outside = outsideFilters(text);
         if (outside.contains("&&") || outside.contains("||")) {
-            return "&& and || work inside a filter only, such as $[?(@.status 
== 'paid' && @.qty > 0)]; to test whether"
-                   + " a filter matched, " + ONE_ELEMENT + ", and check 
${body} == null in a choice";
+            String problem = "&& and || work inside a filter only, such as 
$[?(@.status == 'paid' && @.qty > 0)]";
+            if (predicate) {
+                return problem + ": " + FILTER_IS_THE_CONDITION + ", so move 
the other conditions inside it";
+            }
+            return problem + "; to test whether an element matches, use the 
filter as the condition: "
+                   + FILTER_IS_THE_CONDITION;
         }
         return null;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
index 3e1b624b3443..16c2012053ea 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
@@ -278,7 +278,7 @@ class CatalogDocsTest {
 
     @Test
     void aWrongHeaderNameIsAnsweredWithTheRightOne() {
-        org.apache.camel.catalog.CamelCatalog catalog = new 
org.apache.camel.catalog.DefaultCamelCatalog();
+        CamelCatalog catalog = new DefaultCamelCatalog();
         JsonObject result = CatalogDocs.find(catalog, "CamelHttpStatusCode", 
null, 10);
         assertTrue(result.getString("header").startsWith("Camel has no header 
CamelHttpStatusCode"), result.toJson());
         assertTrue(result.getString("header").contains("CamelHttpResponseCode 
(Exchange.HTTP_RESPONSE_CODE)"));
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluatorLanguageTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluatorLanguageTest.java
index 0fd7655f1100..6388f2f87bad 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluatorLanguageTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ExpressionEvaluatorLanguageTest.java
@@ -35,12 +35,34 @@ class ExpressionEvaluatorLanguageTest {
     @Test
     void aJsonPathFilterSaysHowToGetTheOneElement() {
         assertThat(ExpressionEvaluator.filterNote("jsonpath", "$[?(@.sku == 
'CAMEL-MUG')]", "ok"))
-                .contains("resultType: java.util.Map gives the one 
element").contains("an index after the filter");
+                .contains("resultType: java.util.Map gives the one element it 
matches (null when none or several do)");
+        // a path that goes on after the filter: a Map would convert 42 to null
+        assertThat(ExpressionEvaluator.filterNote("jsonpath", "$[?(@.sku == 
'CAMEL-MUG')].qty", "ok"))
+                .contains("such as java.lang.String or java.lang.Integer, 
gives the one value").doesNotContain("Map");
+        assertThat(ExpressionEvaluator.filterNote("jsonpath", "$[?(@.sku == 
'CAMEL-MUG')][0].qty", "ok"))
+                .startsWith("an index after a filter gives an empty list")
+                .contains("leave the index out, as in $[?(@.sku == 
'CAMEL-MUG')].qty");
         assertThat(ExpressionEvaluator.filterNote("jsonpath", "$.sku", 
"ok")).isNull();
         assertThat(ExpressionEvaluator.filterNote("simple", "${body}", 
"ok")).isNull();
         assertThat(ExpressionEvaluator.filterNote("jsonpath", "$[?(@.sku == 
'X')", "error")).isNull();
     }
 
+    /** The note goes after the one the evaluation has already, as evaluate 
adds it for any language it runs. */
+    @Test
+    void theFilterNoteIsAddedToTheResult() {
+        JsonObject result = new JsonObject();
+        result.put("status", "ok");
+        result.put("note", "evaluated with an empty body; pass body to 
evaluate against a value");
+        ExpressionEvaluator.addFilterNote(result, "jsonpath", "$[?(@.sku == 
'CAMEL-MUG')]");
+        assertThat(result.getString("note")).startsWith("evaluated with an 
empty body; pass body to evaluate against a"
+                                                        + " value; a filter 
gives a list, also for one match");
+
+        JsonObject failed = new JsonObject();
+        failed.put("status", "error");
+        ExpressionEvaluator.addFilterNote(failed, "jsonpath", "$[?(@.sku == 
'CAMEL-MUG')]");
+        assertThat(failed.get("note")).isNull();
+    }
+
     @Test
     void aLanguageThatCannotBeLoadedSaysWhatToDo() {
         JsonObject result = ExpressionEvaluator.evaluate(new ToolContext(), 
"no-such-language", "x", null);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecksTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecksTest.java
index ffe399a499e1..a807583054dc 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecksTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/JsonPathChecksTest.java
@@ -71,7 +71,8 @@ class JsonPathChecksTest {
         List<String> errors = JsonPathChecks.validateYamlJsonPath(yaml, 
catalog);
         assertThat(errors).singleElement().satisfies(e -> assertThat(e)
                 .startsWith("Line 8: an index after a filter gives an empty 
list in JsonPath")
-                .contains("leave the index out and set resultType: 
java.util.Map"));
+                .contains("leave the index out and set resultType: 
java.util.Map (the element when one matches, null"
+                          + " when none or several do)"));
     }
 
     @Test
@@ -88,9 +89,86 @@ class JsonPathChecksTest {
                                     expression: "$[?(@.orderId == 
'${header.id}')] && size() > 0"
                 """;
         List<String> errors = JsonPathChecks.validateYamlJsonPath(yaml, 
catalog);
+        // in a when, the filter alone is the condition: a Map would be null 
also when several elements match
         assertThat(errors).singleElement().satisfies(e -> assertThat(e)
                 .startsWith("Line 9: && and || work inside a filter only")
-                .contains("set resultType: java.util.Map").contains("check 
${body} == null"));
+                .contains("in a when or filter, the filter alone is the 
condition (true when an element matches), so"
+                          + " move the other conditions inside it")
+                .doesNotContain("java.util.Map"));
+    }
+
+    @Test
+    void orOutsideAFilterAsAValue() {
+        String yaml = """
+                - route:
+                    from:
+                      uri: direct:get-order
+                      steps:
+                        - setHeader:
+                            name: found
+                            expression:
+                              jsonpath:
+                                expression: "$[?(@.orderId == '${header.id}')] 
|| $[?(@.sku == 'X')]"
+                """;
+        List<String> errors = JsonPathChecks.validateYamlJsonPath(yaml, 
catalog);
+        assertThat(errors).singleElement().satisfies(e -> assertThat(e)
+                .startsWith("Line 9: && and || work inside a filter only")
+                .contains("to test whether an element matches, use the filter 
as the condition"));
+    }
+
+    @Test
+    void anIndexAfterAFilterThenAFieldSaysToLeaveTheIndexOut() {
+        // the Map advice gives [42] converted to a Map here: the field wants 
the type of the field
+        String yaml = """
+                - route:
+                    from:
+                      uri: direct:stock
+                      steps:
+                        - setBody:
+                            expression:
+                              jsonpath:
+                                expression: "$[?(@.sku == 'X')][0].qty"
+                """;
+        List<String> errors = JsonPathChecks.validateYamlJsonPath(yaml, 
catalog);
+        assertThat(errors).singleElement().satisfies(e -> assertThat(e)
+                .startsWith("Line 8: an index after a filter gives an empty 
list in JsonPath")
+                .contains("leave the index out, as in $[?(@.sku == 'X')].qty, 
and set resultType to the type of that"
+                          + " value, such as java.lang.Integer")
+                .doesNotContain("java.util.Map"));
+    }
+
+    @Test
+    void anIndexAfterAFilterInAWhenSaysTheFilterIsTheCondition() {
+        String yaml = """
+                - route:
+                    from:
+                      uri: direct:get-order
+                      steps:
+                        - filter:
+                            expression:
+                              jsonpath:
+                                expression: "$[?(@.orderId == 
'${header.id}')][0]"
+                """;
+        List<String> errors = JsonPathChecks.validateYamlJsonPath(yaml, 
catalog);
+        assertThat(errors).singleElement().satisfies(e -> assertThat(e)
+                .startsWith("Line 8: an index after a filter gives an empty 
list in JsonPath")
+                .contains("leave the index out: in a when or filter, the 
filter alone is the condition")
+                .doesNotContain("java.util.Map"));
+    }
+
+    @Test
+    void anIndexInAFieldAfterAFilterIsFine() {
+        String yaml = """
+                - route:
+                    from:
+                      uri: direct:get-order
+                      steps:
+                        - setBody:
+                            expression:
+                              jsonpath:
+                                expression: "$.orders[?(@.id == 1)].items[0]"
+                """;
+        assertThat(JsonPathChecks.validateYamlJsonPath(yaml, 
catalog)).isEmpty();
     }
 
     @Test

Reply via email to