davsclaus commented on code in PR #28047:
URL: https://github.com/apache/camel/pull/28047#discussion_r4306642464


##########
dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java:
##########
@@ -235,11 +327,21 @@ private static void walkBlock(
     }
 
     /** What is known about the body after a step. */
-    private static Text after(
-            String name, JsonNode value, JsonNode step, Text text, Map<String, 
JsonNode> byFrom, Set<String> seen) {
+    private static Body after(
+            String name, JsonNode value, JsonNode step, Body body, Map<String, 
JsonNode> byFrom, Set<String> seen) {
         switch (name) {
             case "setBody", "transform" -> {
-                return setBodyText(value);
+                Text text = setBodyText(value);
+                if (text != null || !(body instanceof Parsed parsed)) {
+                    return text;
+                }
+                String call = keepsData(value);
+                return call != null
+                        ? new Parsed(parsed.origin() + ", and groovy " + call 
+ " keeps it data", parsed.format())

Review Comment:
   Done in 702146daac99.
   
   _Claude Code on behalf of davsclaus_



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java:
##########
@@ -235,11 +327,21 @@ private static void walkBlock(
     }
 
     /** What is known about the body after a step. */
-    private static Text after(
-            String name, JsonNode value, JsonNode step, Text text, Map<String, 
JsonNode> byFrom, Set<String> seen) {
+    private static Body after(
+            String name, JsonNode value, JsonNode step, Body body, Map<String, 
JsonNode> byFrom, Set<String> seen) {
         switch (name) {
             case "setBody", "transform" -> {
-                return setBodyText(value);
+                Text text = setBodyText(value);
+                if (text != null || !(body instanceof Parsed parsed)) {
+                    return text;
+                }
+                String call = keepsData(value);
+                return call != null
+                        ? new Parsed(parsed.origin() + ", and groovy " + call 
+ " keeps it data", parsed.format())

Review Comment:
   Done in 702146daac99.
   
   _Claude Code on behalf of davsclaus_



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java:
##########
@@ -316,6 +417,136 @@ private static Text setBodyText(JsonNode value) {
         return null;
     }
 
+    /** The body after an unmarshal: parsed data for the data formats that 
make a Map or a List of the text. */
+    private static Parsed unmarshalled(JsonNode value) {
+        if (value == null || !value.isObject() || 
!value.fieldNames().hasNext()) {
+            return null;
+        }
+        String format = value.fieldNames().next();
+        if (!PARSES.contains(format)) {
+            return null;
+        }
+        JsonNode options = value.get(format);
+        for (String option : NOT_A_MAP) {
+            if (options != null && options.isObject() && options.has(option)) {
+                return null;
+            }
+        }
+        return new Parsed("unmarshal: " + format + " turns the text into 
data", format);
+    }
+
+    /**
+     * A Groovy setBody whose result is data again: one collection method on 
the body and nothing after it, such as
+     * body.findAll { ... }, or body.find { it.sku == ... }, which picks an 
element whose fields it reads. The call, or
+     * null.
+     */
+    static String keepsData(JsonNode value) {
+        JsonNode node = value != null && value.has("expression") ? 
value.get("expression") : value;
+        String script = node != null && node.isObject() ? 
expressionText(node.get("groovy")) : null;
+        if (script == null) {
+            return null;
+        }
+        String s = script.trim();
+        Matcher m = COLLECTION_CALL.matcher(s);
+        if (!m.lookingAt()) {
+            return null;
+        }
+        String method = m.group(1);
+        int depth = 1;
+        int close = m.end();
+        for (; close < s.length() && depth > 0; close++) {
+            char c = s.charAt(close);
+            depth += c == '{' ? 1 : c == '}' ? -1 : 0;
+        }
+        if (depth != 0 || close != s.length()) {
+            // the closure does not end the script: something is done with its 
result
+            return null;
+        }
+        String closure = s.substring(m.end(), close - 1);
+        Matcher p = CLOSURE_PARAMETER.matcher(closure);
+        String element = p.find() ? p.group(1) : "it";
+        boolean readsFields = Pattern.compile("(?<![\\w$])" + element + 
"\\s*(\\??\\.\\s*[A-Za-z_]|\\[)")
+                .matcher(closure).find();
+        return GIVES_DATA.contains(method) || PICKS.contains(method) && 
readsFields

Review Comment:
   Done in 702146daac99.
   
   _Claude Code on behalf of davsclaus_



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java:
##########
@@ -316,6 +417,136 @@ private static Text setBodyText(JsonNode value) {
         return null;
     }
 
+    /** The body after an unmarshal: parsed data for the data formats that 
make a Map or a List of the text. */
+    private static Parsed unmarshalled(JsonNode value) {
+        if (value == null || !value.isObject() || 
!value.fieldNames().hasNext()) {
+            return null;
+        }
+        String format = value.fieldNames().next();
+        if (!PARSES.contains(format)) {
+            return null;
+        }
+        JsonNode options = value.get(format);
+        for (String option : NOT_A_MAP) {
+            if (options != null && options.isObject() && options.has(option)) {
+                return null;
+            }
+        }
+        return new Parsed("unmarshal: " + format + " turns the text into 
data", format);
+    }
+
+    /**
+     * A Groovy setBody whose result is data again: one collection method on 
the body and nothing after it, such as
+     * body.findAll { ... }, or body.find { it.sku == ... }, which picks an 
element whose fields it reads. The call, or
+     * null.
+     */
+    static String keepsData(JsonNode value) {
+        JsonNode node = value != null && value.has("expression") ? 
value.get("expression") : value;
+        String script = node != null && node.isObject() ? 
expressionText(node.get("groovy")) : null;
+        if (script == null) {
+            return null;
+        }
+        String s = script.trim();
+        Matcher m = COLLECTION_CALL.matcher(s);
+        if (!m.lookingAt()) {
+            return null;
+        }
+        String method = m.group(1);
+        int depth = 1;
+        int close = m.end();
+        for (; close < s.length() && depth > 0; close++) {
+            char c = s.charAt(close);
+            depth += c == '{' ? 1 : c == '}' ? -1 : 0;
+        }
+        if (depth != 0 || close != s.length()) {
+            // the closure does not end the script: something is done with its 
result
+            return null;
+        }
+        String closure = s.substring(m.end(), close - 1);
+        Matcher p = CLOSURE_PARAMETER.matcher(closure);
+        String element = p.find() ? p.group(1) : "it";
+        boolean readsFields = Pattern.compile("(?<![\\w$])" + element + 
"\\s*(\\??\\.\\s*[A-Za-z_]|\\[)")
+                .matcher(closure).find();
+        return GIVES_DATA.contains(method) || PICKS.contains(method) && 
readsFields

Review Comment:
   Done in 702146daac99.
   
   _Claude Code on behalf of davsclaus_



##########
dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/TextBodyFlow.java:
##########
@@ -136,10 +201,28 @@ static void check(JsonNode target, NodePath path, 
List<Error> errors) {
                 at = at.append("from").append("steps");
             }
             String id = entry.has("route") && entry.get("route").has("id") ? 
entry.get("route").get("id").asText() : null;
-            walk(steps, consumerText(from), at, id, byFrom, errors, new 
HashSet<>());
+            String consumer = scheme(endpointOf(from));
+            boolean post = NO_HTTP_HEADERS.contains(consumer) && 
!setsHttpHeaders(steps);
+            Body end = walk(steps, consumerText(from), at, id, byFrom, errors, 
new HashSet<>(), post);
+            // the body at the end of a platform-http route is the reply, 
written as a stream
+            if ("platform-http".equals(consumer) && end instanceof Parsed 
parsed && steps != null && steps.isArray()
+                    && !steps.isEmpty() && steps.get(steps.size() - 
1).fieldNames().hasNext()) {
+                int last = steps.size() - 1;
+                
errors.add(error(at.append(last).append(steps.get(last).fieldNames().next()), 
id,
+                        new Need("the body at the end of the route is the 
reply to the HTTP request", "as the last step"),
+                        parsed));
+            }
         }
     }
 
+    /**
+     * Whether the steps set or read an HTTP control header (CamelHttpMethod, 
CamelHttpQuery...), which can make an http
+     * producer call with GET and leave the body out.
+     */
+    private static boolean setsHttpHeaders(JsonNode steps) {
+        return steps != null && 
steps.toString().toLowerCase(Locale.ROOT).contains("camelhttp");

Review Comment:
   A match does not make a report: it turns the http check off for that route, 
since the check only reports when nothing can make the call a GET. So a literal 
or an expression that names a CamelHttp header keeps it quiet, which is the 
safe side. Checking only the uri fields would miss `setHeader: name: 
CamelHttpMethod`, the case that matters, so the match over all the steps is on 
purpose. The javadoc now says it errs on the quiet side (702146daac99).
   
   _Claude Code on behalf of davsclaus_



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to