From 81ced95d27c8f7fa07a10196e2f0eaac2e892298 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Mon, 17 Nov 2025 17:12:56 +0100 Subject: [PATCH] [Fix #982] HttpExecutor should not assume body is map Signed-off-by: fjtirado --- .../impl/WorkflowUtils.java | 45 ++++++++++--------- .../impl/executors/RunScriptExecutor.java | 4 +- .../impl/executors/RunShellExecutor.java | 17 +++---- .../impl/executors/http/HttpExecutor.java | 20 +++------ .../executors/http/HttpModelConverter.java | 5 +-- .../openapi/OperationPathResolver.java | 4 +- .../impl/test/HTTPWorkflowDefinitionTest.java | 11 ++--- .../workflows-samples/callPostHttpAsExpr.yaml | 15 +++++++ 8 files changed, 61 insertions(+), 60 deletions(-) create mode 100644 impl/test/src/test/resources/workflows-samples/callPostHttpAsExpr.yaml diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index 4ebfcc8e3..45156cdf2 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -68,22 +68,33 @@ public static boolean isValid(String str) { public static Optional buildWorkflowFilter( WorkflowApplication app, InputFrom from) { return from != null - ? Optional.of(buildWorkflowFilter(app, from.getString(), from.getObject())) + ? Optional.of(buildFilterFromStrObject(app, from.getString(), from.getObject())) : Optional.empty(); } public static Optional buildWorkflowFilter(WorkflowApplication app, OutputAs as) { return as != null - ? Optional.of(buildWorkflowFilter(app, as.getString(), as.getObject())) + ? Optional.of(buildFilterFromStrObject(app, as.getString(), as.getObject())) : Optional.empty(); } public static Optional buildWorkflowFilter(WorkflowApplication app, ExportAs as) { return as != null - ? Optional.of(buildWorkflowFilter(app, as.getString(), as.getObject())) + ? Optional.of(buildFilterFromStrObject(app, as.getString(), as.getObject())) : Optional.empty(); } + public static WorkflowFilter buildWorkflowFilter(WorkflowApplication app, Object obj) { + return obj instanceof String str + ? buildWorkflowFilter(app, str) + : app.expressionFactory().buildFilter(ExpressionDescriptor.object(obj), app.modelFactory()); + } + + public static WorkflowFilter buildWorkflowFilter( + WorkflowApplication app, String str, Map object) { + return buildFilterFromStrObject(app, str, object); + } + public static WorkflowValueResolver buildStringFilter( WorkflowApplication app, String expression, String literal) { return expression != null ? toExprString(app, expression) : toString(literal); @@ -108,29 +119,26 @@ private static WorkflowValueResolver toString(String literal) { return (w, t, m) -> literal; } - public static WorkflowFilter buildWorkflowFilter( + private static WorkflowFilter buildFilterFromStrObject( WorkflowApplication app, String str, Object object) { return app.expressionFactory() .buildFilter(new ExpressionDescriptor(str, object), app.modelFactory()); } - public static WorkflowValueResolver buildStringResolver( - WorkflowApplication app, String str) { - return app.expressionFactory().resolveString(ExpressionDescriptor.from(str)); - } - - public static WorkflowValueResolver buildStringResolver( - WorkflowApplication app, String str, Object obj) { - return app.expressionFactory().resolveString(new ExpressionDescriptor(str, obj)); + public static WorkflowValueResolver> buildMapResolver( + WorkflowApplication app, Map map) { + return app.expressionFactory().resolveMap(ExpressionDescriptor.object(map)); } public static WorkflowValueResolver> buildMapResolver( - WorkflowApplication app, String str, Object obj) { - return app.expressionFactory().resolveMap(new ExpressionDescriptor(str, obj)); + WorkflowApplication app, String expr, Map map) { + return app.expressionFactory().resolveMap(new ExpressionDescriptor(expr, map)); } public static WorkflowFilter buildWorkflowFilter(WorkflowApplication app, String str) { - return app.expressionFactory().buildFilter(ExpressionDescriptor.from(str), app.modelFactory()); + return ExpressionUtils.isExpr(str) + ? app.expressionFactory().buildFilter(ExpressionDescriptor.from(str), app.modelFactory()) + : (w, t, m) -> app.modelFactory().from(str); } public static WorkflowPredicate buildPredicate(WorkflowApplication app, String str) { @@ -145,13 +153,6 @@ public static Optional optionalPredicate(WorkflowApplication return str != null ? Optional.of(buildPredicate(app, str)) : Optional.empty(); } - public static Optional optionalFilter( - WorkflowApplication app, Object obj, String str) { - return str != null || obj != null - ? Optional.of(buildWorkflowFilter(app, str, obj)) - : Optional.empty(); - } - public static String toString(UriTemplate template) { URI uri = template.getLiteralUri(); return uri != null ? uri.toString() : template.getLiteralUriTemplate(); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutor.java index 1f456e108..5bc4d4941 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunScriptExecutor.java @@ -72,14 +72,14 @@ public void init(RunScript taskConfiguration, WorkflowDefinition definition) { script.getEnvironment() != null && script.getEnvironment().getAdditionalProperties() != null ? Optional.of( WorkflowUtils.buildMapResolver( - application, null, script.getEnvironment().getAdditionalProperties())) + application, script.getEnvironment().getAdditionalProperties())) : Optional.empty(); this.argumentExpr = script.getArguments() != null && script.getArguments().getAdditionalProperties() != null ? Optional.of( WorkflowUtils.buildMapResolver( - application, null, script.getArguments().getAdditionalProperties())) + application, script.getArguments().getAdditionalProperties())) : Optional.empty(); this.codeSupplier = diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java index efa611dad..4820ae40b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java @@ -72,8 +72,7 @@ public void init(RunShell taskConfiguration, WorkflowDefinition definition) { StringBuilder commandBuilder = new StringBuilder( ExpressionUtils.isExpr(shellCommand) - ? WorkflowUtils.buildStringResolver( - application, shellCommand, taskContext.input().asJavaObject()) + ? WorkflowUtils.buildStringFilter(application, shellCommand) .apply(workflowContext, taskContext, taskContext.input()) : shellCommand); @@ -85,8 +84,7 @@ public void init(RunShell taskConfiguration, WorkflowDefinition definition) { .append(" ") .append( ExpressionUtils.isExpr(entry.getKey()) - ? WorkflowUtils.buildStringResolver( - application, entry.getKey(), taskContext.input().asJavaObject()) + ? WorkflowUtils.buildStringFilter(application, entry.getKey()) .apply(workflowContext, taskContext, taskContext.input()) : entry.getKey()); if (entry.getValue() != null) { @@ -95,10 +93,8 @@ public void init(RunShell taskConfiguration, WorkflowDefinition definition) { .append("=") .append( ExpressionUtils.isExpr(entry.getValue()) - ? WorkflowUtils.buildStringResolver( - application, - entry.getValue().toString(), - taskContext.input().asJavaObject()) + ? WorkflowUtils.buildStringFilter( + application, entry.getValue().toString()) .apply(workflowContext, taskContext, taskContext.input()) : entry.getValue().toString()); } @@ -113,10 +109,7 @@ public void init(RunShell taskConfiguration, WorkflowDefinition definition) { shell.getEnvironment().getAdditionalProperties().entrySet()) { String value = ExpressionUtils.isExpr(entry.getValue()) - ? WorkflowUtils.buildStringResolver( - application, - entry.getValue().toString(), - taskContext.input().asJavaObject()) + ? WorkflowUtils.buildStringFilter(application, entry.getValue().toString()) .apply(workflowContext, taskContext, taskContext.input()) : entry.getValue().toString(); diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java index 420931a4b..8cbd23c7b 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java @@ -28,10 +28,11 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowError; import io.serverlessworkflow.impl.WorkflowException; +import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.WorkflowValueResolver; import io.serverlessworkflow.impl.executors.CallableTask; -import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.client.Invocation.Builder; @@ -95,19 +96,11 @@ public HttpExecutorBuilder withQueryMap(WorkflowValueResolver headersMap) { - return withHeaders( - definition - .application() - .expressionFactory() - .resolveMap(ExpressionDescriptor.object(headersMap))); + return withHeaders(WorkflowUtils.buildMapResolver(definition.application(), headersMap)); } - public HttpExecutorBuilder withQueryMap(Map headersMap) { - return withQueryMap( - definition - .application() - .expressionFactory() - .resolveMap(ExpressionDescriptor.object(headersMap))); + public HttpExecutorBuilder withQueryMap(Map queryMap) { + return withQueryMap(WorkflowUtils.buildMapResolver(definition.application(), queryMap)); } public HttpExecutorBuilder withMethod(String method) { @@ -193,8 +186,7 @@ private static RequestSupplier buildRequestSupplier( String method, Object body, WorkflowApplication application, HttpModelConverter converter) { switch (method.toUpperCase()) { case HttpMethod.POST: - WorkflowValueResolver> bodyFilter = - buildMapResolver(application, null, body); + WorkflowFilter bodyFilter = WorkflowUtils.buildWorkflowFilter(application, body); return (request, w, context, node) -> converter.toModel( application.modelFactory(), diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java index 1d7b7c9f8..42155ac1e 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java @@ -18,7 +18,6 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowModelFactory; import jakarta.ws.rs.client.Entity; -import java.util.Map; public interface HttpModelConverter { @@ -26,7 +25,7 @@ default WorkflowModel toModel(WorkflowModelFactory factory, WorkflowModel model, return factory.fromAny(model, entity); } - default Entity toEntity(Map model) { - return Entity.json(model); + default Entity toEntity(WorkflowModel model) { + return Entity.json(model.as(model.objectClass()).orElseThrow()); } } diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationPathResolver.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationPathResolver.java index 5ca95d316..70e952c33 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationPathResolver.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationPathResolver.java @@ -19,8 +19,8 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.WorkflowValueResolver; -import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import jakarta.ws.rs.core.UriBuilder; import java.net.URI; import java.util.Map; @@ -31,7 +31,7 @@ class OperationPathResolver implements WorkflowValueResolver { OperationPathResolver(String path, WorkflowApplication application, Map args) { this.path = path; - this.asMap = application.expressionFactory().resolveMap(ExpressionDescriptor.object(args)); + this.asMap = WorkflowUtils.buildMapResolver(application, args); } @Override diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/HTTPWorkflowDefinitionTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/HTTPWorkflowDefinitionTest.java index 08a8648a2..ab95b236f 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/HTTPWorkflowDefinitionTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/HTTPWorkflowDefinitionTest.java @@ -91,6 +91,10 @@ private static Stream provideParameters() { .get("title") .equals("Star Trek"), "StartTrek"); + Condition postCondition = + new Condition( + o -> o.asText().orElseThrow().equals("Javierito"), "CallHttpPostCondition"); + Map postMap = Map.of("name", "Javierito", "surname", "Unknown"); return Stream.of( Arguments.of("workflows-samples/callGetHttp.yaml", petInput, petCondition), Arguments.of( @@ -106,10 +110,7 @@ private static Stream provideParameters() { "workflows-samples/call-http-query-parameters-external-schema.yaml", starTrekInput, starTrekCondition), - Arguments.of( - "workflows-samples/callPostHttp.yaml", - Map.of("name", "Javierito", "surname", "Unknown"), - new Condition( - o -> o.asText().orElseThrow().equals("Javierito"), "CallHttpPostCondition"))); + Arguments.of("workflows-samples/callPostHttp.yaml", postMap, postCondition), + Arguments.of("workflows-samples/callPostHttpAsExpr.yaml", postMap, postCondition)); } } diff --git a/impl/test/src/test/resources/workflows-samples/callPostHttpAsExpr.yaml b/impl/test/src/test/resources/workflows-samples/callPostHttpAsExpr.yaml new file mode 100644 index 000000000..69b7faac3 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/callPostHttpAsExpr.yaml @@ -0,0 +1,15 @@ +document: + dsl: 1.0.0-alpha1 + namespace: test + name: http-call-with-response-output-expr + version: 1.0.0 +do: + - postPet: + call: http + with: + method: post + endpoint: + uri: https://fakerestapi.azurewebsites.net/api/v1/Authors + body: "${{firstName: .name, lastName:.surname}}" + output: + as: .firstName \ No newline at end of file