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 - * && 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 && 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
