diff --git a/impl/pom.xml b/impl/pom.xml index 32ee86a..3907fb7 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -8,6 +8,7 @@ serverlessworkflow-impl 3.1.9 + 1.0.1 @@ -25,6 +26,11 @@ jersey-media-json-jackson ${version.org.glassfish.jersey} + + net.thisptr + jackson-jq + ${version.net.thisptr} + org.junit.jupiter junit-jupiter-api diff --git a/impl/src/main/java/io/serverlessworkflow/impl/AbstractTaskExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/AbstractTaskExecutor.java index f377b3f..1318160 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/AbstractTaskExecutor.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/AbstractTaskExecutor.java @@ -21,9 +21,11 @@ public abstract class AbstractTaskExecutor implements TaskExecutor { protected final T task; + protected final ExpressionFactory exprFactory; - protected AbstractTaskExecutor(T task) { + protected AbstractTaskExecutor(T task, ExpressionFactory exprFactory) { this.task = task; + this.exprFactory = exprFactory; } @Override diff --git a/impl/src/main/java/io/serverlessworkflow/impl/DefaultTaskExecutorFactory.java b/impl/src/main/java/io/serverlessworkflow/impl/DefaultTaskExecutorFactory.java index 806a320..fab07d8 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/DefaultTaskExecutorFactory.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/DefaultTaskExecutorFactory.java @@ -18,23 +18,32 @@ import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.jq.JQExpressionFactory; public class DefaultTaskExecutorFactory implements TaskExecutorFactory { - protected DefaultTaskExecutorFactory() {} + private final ExpressionFactory exprFactory; - private static TaskExecutorFactory instance = new DefaultTaskExecutorFactory(); + private static TaskExecutorFactory instance = + new DefaultTaskExecutorFactory(JQExpressionFactory.get()); public static TaskExecutorFactory get() { return instance; } - public TaskExecutor getTaskExecutor(Task task) { + public static TaskExecutorFactory get(ExpressionFactory factory) { + return new DefaultTaskExecutorFactory(factory); + } + protected DefaultTaskExecutorFactory(ExpressionFactory exprFactory) { + this.exprFactory = exprFactory; + } + + public TaskExecutor getTaskExecutor(Task task) { if (task.getCallTask() != null) { CallTask callTask = task.getCallTask(); if (callTask.getCallHTTP() != null) { - return new HttpExecutor(callTask.getCallHTTP()); + return new HttpExecutor(callTask.getCallHTTP(), exprFactory); } } throw new UnsupportedOperationException(task.get().getClass().getName() + " not supported yet"); diff --git a/impl/src/main/java/io/serverlessworkflow/impl/Expression.java b/impl/src/main/java/io/serverlessworkflow/impl/Expression.java new file mode 100644 index 0000000..b5bbfc0 --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/Expression.java @@ -0,0 +1,22 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl; + +import com.fasterxml.jackson.databind.JsonNode; + +public interface Expression { + JsonNode eval(JsonNode input); +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/ExpressionFactory.java b/impl/src/main/java/io/serverlessworkflow/impl/ExpressionFactory.java new file mode 100644 index 0000000..8f9c1dd --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/ExpressionFactory.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl; + +public interface ExpressionFactory { + + Expression getExpression(String expression); +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/ExpressionUtils.java b/impl/src/main/java/io/serverlessworkflow/impl/ExpressionUtils.java new file mode 100644 index 0000000..4500093 --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/ExpressionUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl; + +public class ExpressionUtils { + + private static final String EXPR_PREFIX = "${"; + private static final String EXPR_SUFFIX = "}"; + + private ExpressionUtils() {} + + public static String trimExpr(String expr) { + expr = expr.trim(); + if (expr.startsWith(EXPR_PREFIX)) { + expr = trimExpr(expr, EXPR_PREFIX, EXPR_SUFFIX); + } + return expr.trim(); + } + + private static String trimExpr(String expr, String prefix, String suffix) { + expr = expr.substring(prefix.length()); + if (expr.endsWith(suffix)) { + expr = expr.substring(0, expr.length() - suffix.length()); + } + return expr; + } +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/HttpExecutor.java b/impl/src/main/java/io/serverlessworkflow/impl/HttpExecutor.java index 203ca15..e2c2c42 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/HttpExecutor.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/HttpExecutor.java @@ -18,7 +18,10 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.EndpointUri; import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.api.types.UriTemplate; import io.serverlessworkflow.api.types.WithHTTPHeaders; import io.serverlessworkflow.api.types.WithHTTPQuery; import jakarta.ws.rs.HttpMethod; @@ -27,40 +30,33 @@ import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Invocation.Builder; import jakarta.ws.rs.client.WebTarget; +import java.net.URI; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Function; public class HttpExecutor extends AbstractTaskExecutor { private static final Client client = ClientBuilder.newClient(); - public HttpExecutor(CallHTTP task) { - super(task); + private final Function targetSupplier; + + public HttpExecutor(CallHTTP task, ExpressionFactory factory) { + super(task, factory); + this.targetSupplier = getTargetSupplier(task.getWith().getEndpoint()); } @Override protected JsonNode internalExecute(JsonNode node) { HTTPArguments httpArgs = task.getWith(); - // missing checks - String uri = - httpArgs - .getEndpoint() - .getEndpointConfiguration() - .getUri() - .getLiteralEndpointURI() - .getLiteralUriTemplate(); - WebTarget target = client.target(uri); WithHTTPQuery query = httpArgs.getQuery(); + WebTarget target = targetSupplier.apply(node); if (query != null) { for (Entry entry : query.getAdditionalProperties().entrySet()) { target = target.queryParam(entry.getKey(), entry.getValue()); } } - Builder request = - target - .resolveTemplates( - JsonUtils.mapper().convertValue(node, new TypeReference>() {})) - .request(); + Builder request = target.request(); WithHTTPHeaders headers = httpArgs.getHeaders(); if (headers != null) { headers.getAdditionalProperties().forEach(request::header); @@ -73,4 +69,71 @@ protected JsonNode internalExecute(JsonNode node) { return request.post(Entity.json(httpArgs.getBody()), JsonNode.class); } } + + private Function getTargetSupplier(Endpoint endpoint) { + if (endpoint.getEndpointConfiguration() != null) { + EndpointUri uri = endpoint.getEndpointConfiguration().getUri(); + if (uri.getLiteralEndpointURI() != null) { + return getURISupplier(uri.getLiteralEndpointURI()); + } else if (uri.getExpressionEndpointURI() != null) { + return new ExpressionURISupplier(uri.getExpressionEndpointURI()); + } + } else if (endpoint.getRuntimeExpression() != null) { + return new ExpressionURISupplier(endpoint.getRuntimeExpression()); + } else if (endpoint.getUriTemplate() != null) { + return getURISupplier(endpoint.getUriTemplate()); + } + throw new IllegalArgumentException("Invalid endpoint definition " + endpoint); + } + + private Function getURISupplier(UriTemplate template) { + if (template.getLiteralUri() != null) { + return new URISupplier(template.getLiteralUri()); + } else if (template.getLiteralUriTemplate() != null) { + return new URITemplateSupplier(template.getLiteralUriTemplate()); + } + throw new IllegalArgumentException("Invalid uritemplate definition " + template); + } + + private class URISupplier implements Function { + private final URI uri; + + public URISupplier(URI uri) { + this.uri = uri; + } + + @Override + public WebTarget apply(JsonNode input) { + return client.target(uri); + } + } + + private class URITemplateSupplier implements Function { + private final String uri; + + public URITemplateSupplier(String uri) { + this.uri = uri; + } + + @Override + public WebTarget apply(JsonNode input) { + return client + .target(uri) + .resolveTemplates( + JsonUtils.mapper().convertValue(input, new TypeReference>() {})); + } + } + + private class ExpressionURISupplier implements Function { + private Expression expr; + + public ExpressionURISupplier(String expr) { + this.expr = exprFactory.getExpression(expr); + } + + @Override + public WebTarget apply(JsonNode input) { + return client.target(expr.eval(input).asText()); + } + } } diff --git a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index 8c8dad4..f926a75 100644 --- a/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -33,22 +33,22 @@ public class WorkflowDefinition { private WorkflowDefinition( Workflow workflow, - TaskExecutorFactory factory, + TaskExecutorFactory taskFactory, Collection listeners) { this.workflow = workflow; - this.factory = factory; + this.taskFactory = taskFactory; this.listeners = listeners; } private final Workflow workflow; private final Collection listeners; - private final TaskExecutorFactory factory; + private final TaskExecutorFactory taskFactory; private final Map> taskExecutors = new ConcurrentHashMap<>(); public static class Builder { private final Workflow workflow; - private TaskExecutorFactory factory = DefaultTaskExecutorFactory.get(); + private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get(); private Collection listeners; private Builder(Workflow workflow) { @@ -64,14 +64,14 @@ public Builder withListener(WorkflowExecutionListener listener) { } public Builder withTaskExecutorFactory(TaskExecutorFactory factory) { - this.factory = factory; + this.taskFactory = factory; return this; } public WorkflowDefinition build() { return new WorkflowDefinition( workflow, - factory, + taskFactory, listeners == null ? Collections.emptySet() : Collections.unmodifiableCollection(listeners)); @@ -83,7 +83,7 @@ public static Builder builder(Workflow workflow) { } public WorkflowInstance execute(Object input) { - return new WorkflowInstance(factory, JsonUtils.fromValue(input)); + return new WorkflowInstance(taskFactory, JsonUtils.fromValue(input)); } enum State { @@ -97,7 +97,6 @@ public class WorkflowInstance { private final JsonNode input; private JsonNode output; private State state; - private TaskExecutorFactory factory; private JsonPointer currentPos; @@ -106,7 +105,6 @@ private WorkflowInstance(TaskExecutorFactory factory, JsonNode input) { this.output = object(); this.state = State.STARTED; this.currentPos = JsonPointer.compile("/"); - this.factory = factory; processDo(workflow.getDo()); } @@ -119,7 +117,7 @@ private void processDo(List tasks) { this.output = MergeUtils.merge( taskExecutors - .computeIfAbsent(currentPos, k -> factory.getTaskExecutor(task.getTask())) + .computeIfAbsent(currentPos, k -> taskFactory.getTaskExecutor(task.getTask())) .apply(input), output); listeners.forEach(l -> l.onTaskEnded(currentPos, task.getTask())); diff --git a/impl/src/main/java/io/serverlessworkflow/impl/jq/JQExpression.java b/impl/src/main/java/io/serverlessworkflow/impl/jq/JQExpression.java new file mode 100644 index 0000000..b77f34a --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/jq/JQExpression.java @@ -0,0 +1,251 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.jq; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import io.serverlessworkflow.impl.Expression; +import io.serverlessworkflow.impl.JsonUtils; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; +import net.thisptr.jackson.jq.Output; +import net.thisptr.jackson.jq.Scope; +import net.thisptr.jackson.jq.Version; +import net.thisptr.jackson.jq.exception.JsonQueryException; +import net.thisptr.jackson.jq.internal.javacc.ExpressionParser; +import net.thisptr.jackson.jq.internal.tree.FunctionCall; +import net.thisptr.jackson.jq.internal.tree.StringInterpolation; +import net.thisptr.jackson.jq.internal.tree.binaryop.BinaryOperatorExpression; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JQExpression implements Expression { + + private static final Logger logger = LoggerFactory.getLogger(JQExpression.class); + private final Map, Collection> + declaredFieldsMap = new ConcurrentHashMap<>(); + private final Map, Collection> + allFieldsMap = new ConcurrentHashMap<>(); + + private final Supplier scope; + private final String expr; + + private net.thisptr.jackson.jq.Expression internalExpr; + private static Field rhsField; + + static { + try { + rhsField = BinaryOperatorExpression.class.getDeclaredField("rhs"); + rhsField.setAccessible(true); + } catch (ReflectiveOperationException e) { + logger.warn("Unexpected exception while resolving rhs field", e); + } + } + + public JQExpression(Supplier scope, String expr, Version version) + throws JsonQueryException { + this.expr = expr; + this.scope = scope; + this.internalExpr = compile(version); + checkFunctionCall(internalExpr); + } + + private net.thisptr.jackson.jq.Expression compile(Version version) throws JsonQueryException { + net.thisptr.jackson.jq.Expression expression; + try { + expression = ExpressionParser.compile(expr, version); + } catch (JsonQueryException ex) { + expression = handleStringInterpolation(version).orElseThrow(() -> ex); + } + checkFunctionCall(expression); + return expression; + } + + private Optional handleStringInterpolation(Version version) { + if (!expr.startsWith("\"")) { + try { + net.thisptr.jackson.jq.Expression expression = + ExpressionParser.compile("\"" + expr + "\"", version); + if (expression instanceof StringInterpolation) { + return Optional.of(expression); + } + } catch (JsonQueryException ex) { + // ignoring it + } + } + return Optional.empty(); + } + + private interface TypedOutput extends Output { + T getResult(); + } + + @SuppressWarnings("unchecked") + private TypedOutput output(Class returnClass) { + TypedOutput out; + if (String.class.isAssignableFrom(returnClass)) { + out = (TypedOutput) new StringOutput(); + } else if (Collection.class.isAssignableFrom(returnClass)) { + out = (TypedOutput) new CollectionOutput(); + } else { + out = (TypedOutput) new JsonNodeOutput(); + } + return out; + } + + private static class StringOutput implements TypedOutput { + StringBuilder sb = new StringBuilder(); + + @Override + public void emit(JsonNode out) throws JsonQueryException { + if (sb.length() > 0) { + sb.append(' '); + } + if (!out.isNull() && out.asText() != null) { + sb.append(out.asText()); + } + } + + @Override + public String getResult() { + return sb.toString(); + } + } + + private static class CollectionOutput implements TypedOutput> { + Collection result = new ArrayList<>(); + + @SuppressWarnings("unchecked") + @Override + public void emit(JsonNode out) throws JsonQueryException { + Object obj = JsonUtils.toJavaValue(out); + if (obj instanceof Collection) result.addAll((Collection) obj); + else { + result.add(obj); + } + } + + @Override + public Collection getResult() { + return result; + } + } + + private static class JsonNodeOutput implements TypedOutput { + + private JsonNode result; + private boolean arrayCreated; + + @Override + public void emit(JsonNode out) throws JsonQueryException { + if (this.result == null) { + this.result = out; + } else if (!arrayCreated) { + ArrayNode newNode = JsonUtils.mapper().createArrayNode(); + newNode.add(this.result).add(out); + this.result = newNode; + arrayCreated = true; + } else { + ((ArrayNode) this.result).add(out); + } + } + + @Override + public JsonNode getResult() { + return result; + } + } + + @Override + public JsonNode eval(JsonNode context) { + TypedOutput output = output(JsonNode.class); + try { + internalExpr.apply(this.scope.get(), context, output); + return output.getResult(); + } catch (JsonQueryException e) { + throw new IllegalArgumentException( + "Unable to evaluate content " + context + " using expr " + expr, e); + } + } + + private void checkFunctionCall(net.thisptr.jackson.jq.Expression toCheck) + throws JsonQueryException { + if (toCheck instanceof FunctionCall) { + toCheck.apply(scope.get(), JsonUtils.mapper().createObjectNode(), out -> {}); + } else if (toCheck instanceof BinaryOperatorExpression) { + if (rhsField != null) { + try { + checkFunctionCall((net.thisptr.jackson.jq.Expression) rhsField.get(toCheck)); + } catch (ReflectiveOperationException e) { + logger.warn( + "Ignoring unexpected error {} while accesing field {} for class{} and expression {}", + e.getMessage(), + rhsField.getName(), + toCheck.getClass(), + expr); + } + } + } else if (toCheck != null) { + for (Field f : getAllExprFields(toCheck)) + try { + checkFunctionCall((net.thisptr.jackson.jq.Expression) f.get(toCheck)); + } catch (ReflectiveOperationException e) { + logger.warn( + "Ignoring unexpected error {} while accesing field {} for class{} and expression {}", + e.getMessage(), + f.getName(), + toCheck.getClass(), + expr); + } + } + } + + private Collection getAllExprFields(net.thisptr.jackson.jq.Expression toCheck) { + return allFieldsMap.computeIfAbsent(toCheck.getClass(), this::getAllExprFields); + } + + private Collection getAllExprFields( + Class clazz) { + Collection fields = new HashSet<>(); + Class currentClass = clazz; + do { + fields.addAll( + declaredFieldsMap.computeIfAbsent( + currentClass.asSubclass(net.thisptr.jackson.jq.Expression.class), + this::getDeclaredExprFields)); + currentClass = currentClass.getSuperclass(); + } while (net.thisptr.jackson.jq.Expression.class.isAssignableFrom(currentClass)); + return fields; + } + + private Collection getDeclaredExprFields( + Class clazz) { + Collection fields = new HashSet<>(); + for (Field f : clazz.getDeclaredFields()) { + if (net.thisptr.jackson.jq.Expression.class.isAssignableFrom(f.getType())) { + f.setAccessible(true); + fields.add(f); + } + } + return fields; + } +} diff --git a/impl/src/main/java/io/serverlessworkflow/impl/jq/JQExpressionFactory.java b/impl/src/main/java/io/serverlessworkflow/impl/jq/JQExpressionFactory.java new file mode 100644 index 0000000..787842d --- /dev/null +++ b/impl/src/main/java/io/serverlessworkflow/impl/jq/JQExpressionFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.jq; + +import io.serverlessworkflow.impl.Expression; +import io.serverlessworkflow.impl.ExpressionFactory; +import io.serverlessworkflow.impl.ExpressionUtils; +import java.util.function.Supplier; +import net.thisptr.jackson.jq.BuiltinFunctionLoader; +import net.thisptr.jackson.jq.Scope; +import net.thisptr.jackson.jq.Versions; +import net.thisptr.jackson.jq.exception.JsonQueryException; + +public class JQExpressionFactory implements ExpressionFactory { + + private JQExpressionFactory() {} + + private static final JQExpressionFactory instance = new JQExpressionFactory(); + + public static JQExpressionFactory get() { + return instance; + } + + private static Supplier scopeSupplier = new DefaultScopeSupplier(); + + private static class DefaultScopeSupplier implements Supplier { + private static class DefaultScope { + private static Scope scope; + + static { + scope = Scope.newEmptyScope(); + BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, scope); + } + } + + @Override + public Scope get() { + return DefaultScope.scope; + } + } + + @Override + public Expression getExpression(String expression) { + try { + return new JQExpression(scopeSupplier, ExpressionUtils.trimExpr(expression), Versions.JQ_1_6); + } catch (JsonQueryException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java b/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java index f5feb51..66ef5d8 100644 --- a/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java +++ b/impl/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java @@ -41,11 +41,12 @@ void testWorkflowExecution(String fileName, Object input, Condition cond } private static Stream provideParameters() { + Map petInput = Map.of("petId", 10); + Condition petCondition = + new Condition<>( + o -> ((Map) o).containsKey("photoUrls"), "callHttpCondition"); return Stream.of( - Arguments.of( - "callHttp.yaml", - Map.of("petId", 1), - new Condition<>( - o -> ((Map) o).containsKey("photoUrls"), "callHttpCondition"))); + Arguments.of("callHttp.yaml", petInput, petCondition), + Arguments.of("call-http-endpoint-interpolation.yaml", petInput, petCondition)); } } diff --git a/impl/src/test/resources/call-http-endpoint-interpolation.yaml b/impl/src/test/resources/call-http-endpoint-interpolation.yaml new file mode 100644 index 0000000..8380a9a --- /dev/null +++ b/impl/src/test/resources/call-http-endpoint-interpolation.yaml @@ -0,0 +1,13 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: call-http-shorthand-endpoint + version: '0.1.0' +do: + - getPet: + call: http + with: + headers: + content-type: application/json + method: get + endpoint: ${ "https://petstore.swagger.io/v2/pet/\(.petId)" } \ No newline at end of file diff --git a/impl/src/main/resources/callHttp.yaml b/impl/src/test/resources/callHttp.yaml similarity index 100% rename from impl/src/main/resources/callHttp.yaml rename to impl/src/test/resources/callHttp.yaml