From 343b9609dee6ae95d91b961e42b2feeb801b372e Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Thu, 13 Nov 2025 19:23:08 -0500 Subject: [PATCH 1/3] Fix #973 - Refine support for http calls on func Signed-off-by: Ricardo Zanini --- .../fluent/agentic/AgentDoTaskBuilder.java | 4 +- .../agentic/AgentTaskItemListBuilder.java | 6 +- .../fluent/agentic/LoopAgentsBuilder.java | 2 +- .../fluent/agentic/dsl/AgenticDSL.java | 4 +- .../fluent/agentic/AgentDslWorkflowTest.java | 2 +- .../agentic/AgentWorkflowBuilderTest.java | 4 +- .../fluent/agentic/EmailDrafterIT.java | 4 +- .../fluent/func/FuncCallHttpTaskBuilder.java | 48 +++ .../fluent/func/FuncDoTaskBuilder.java | 10 +- .../fluent/func/FuncTaskItemListBuilder.java | 23 +- .../configurers/FuncCallHttpConfigurer.java | 22 ++ .../fluent/func/dsl/ConsumeStep.java | 4 +- .../fluent/func/dsl/FuncCallHttpSpec.java | 34 ++ .../fluent/func/dsl/FuncCallStep.java | 4 +- .../fluent/func/dsl/FuncDSL.java | 304 ++++++++++++++++++ .../fluent/func/spi/CallFnFluent.java | 6 +- .../fluent/func/spi/FuncDoFluent.java | 7 +- .../fluent/func/FuncDSLConsumeTest.java | 2 +- .../fluent/func/FuncDSLTest.java | 231 ++++++++++++- .../workflow/impl/FluentDSLCallTest.java | 4 +- .../fluent/spec/CallHTTPTaskBuilder.java | 202 ------------ .../fluent/spec/CallHttpTaskBuilder.java | 42 +++ .../fluent/spec/DoTaskBuilder.java | 4 +- ...renceableAuthenticationPolicyBuilder.java} | 30 +- .../fluent/spec/TaskItemListBuilder.java | 13 +- .../spec/UseAuthenticationsBuilder.java | 7 +- .../configurers/AuthenticationConfigurer.java | 5 +- ...onfigurer.java => CallHttpConfigurer.java} | 4 +- ...allHTTPSpec.java => BaseCallHttpSpec.java} | 72 +++-- .../fluent/spec/dsl/CallHttpSpec.java | 33 ++ .../fluent/spec/dsl/DSL.java | 10 +- ...allHTTPFluent.java => CallHttpFluent.java} | 8 +- .../fluent/spec/spi/CallHttpTaskFluent.java | 205 ++++++++++++ .../fluent/spec/spi/DoFluent.java | 4 +- .../fluent/spec/WorkflowBuilderTest.java | 14 +- .../fluent/spec/dsl/CallHttpAuthDslTest.java | 15 +- .../fluent/spec/dsl/DSLTest.java | 4 +- .../fluent/spec/dsl/TryCatchDslTest.java | 7 +- 38 files changed, 1083 insertions(+), 321 deletions(-) create mode 100644 experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java create mode 100644 experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallHttpConfigurer.java create mode 100644 experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallHttpSpec.java delete mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHTTPTaskBuilder.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java rename fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/{AuthenticationPolicyUnionBuilder.java => ReferenceableAuthenticationPolicyBuilder.java} (71%) rename fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/{CallHTTPConfigurer.java => CallHttpConfigurer.java} (85%) rename fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/{CallHTTPSpec.java => BaseCallHttpSpec.java} (55%) create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpSpec.java rename fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/{CallHTTPFluent.java => CallHttpFluent.java} (76%) create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java diff --git a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java index 5d7861d8d..4d7c42a22 100644 --- a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java +++ b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java @@ -70,8 +70,8 @@ public AgentDoTaskBuilder parallel(String name, Object... agents) { } @Override - public AgentDoTaskBuilder callFn(String name, Consumer cfg) { - this.listBuilder().callFn(name, cfg); + public AgentDoTaskBuilder function(String name, Consumer cfg) { + this.listBuilder().function(name, cfg); return self(); } diff --git a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java index 7ce0ea8e3..d48203120 100644 --- a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java +++ b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java @@ -56,7 +56,7 @@ public AgentTaskItemListBuilder agent(String name, Object agent) { AgentAdapters.toExecutors(agent) .forEach( exec -> - this.delegate.callFn( + this.delegate.function( name, fn -> fn.function(AgentAdapters.toFunction(exec), DefaultAgenticScope.class))); return self(); @@ -103,8 +103,8 @@ public AgentTaskItemListBuilder parallel(String name, Object... agents) { } @Override - public AgentTaskItemListBuilder callFn(String name, Consumer cfg) { - this.delegate.callFn(name, cfg); + public AgentTaskItemListBuilder function(String name, Consumer cfg) { + this.delegate.function(name, cfg); return self(); } diff --git a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java index 9cea18de3..dd1d6c00d 100644 --- a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java +++ b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java @@ -49,7 +49,7 @@ public LoopAgentsBuilder subAgents(String baseName, Object... agents) { forEachIndexed( execs, (exec, idx) -> - funcDelegate.callFn( + funcDelegate.function( baseName + "-" + idx, fn -> fn.function(AgentAdapters.toFunction(exec)))); return this; } diff --git a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java index df377403f..fbaa7e00c 100644 --- a/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java +++ b/experimental/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/dsl/AgenticDSL.java @@ -143,12 +143,12 @@ public static Consumer doTasks(AgentTaskConfigurer... steps) } public static AgentTaskConfigurer function(Function function, Class argClass) { - return list -> list.callFn(fn(function, argClass)); + return list -> list.function(fn(function, argClass)); } public static AgentTaskConfigurer function(Function function) { Class clazz = ReflectionUtils.inferInputType(function); - return list -> list.callFn(fn(function, clazz)); + return list -> list.function(fn(function, clazz)); } public static AgentTaskConfigurer agent(Object agent) { diff --git a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentDslWorkflowTest.java b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentDslWorkflowTest.java index c8be5f7a7..2b6947fd2 100644 --- a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentDslWorkflowTest.java +++ b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentDslWorkflowTest.java @@ -68,7 +68,7 @@ private void assertSequentialAgents(Workflow wf) { void dslCallFnBare() { Workflow wf = workflow("beanCall") - .tasks(tasks -> tasks.callFn("plainCall", fn -> fn.function(ctx -> "pong"))) + .tasks(tasks -> tasks.function("plainCall", fn -> fn.function(ctx -> "pong"))) .build(); List items = wf.getDo(); diff --git a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java index b659f5709..621007deb 100644 --- a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java +++ b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java @@ -177,7 +177,7 @@ void parallelAgents() { void testWorkflowCallFnBare() { Workflow wf = AgentWorkflowBuilder.workflow() - .tasks(d -> d.callFn("myCall", fn -> fn.function(ctx -> "hello"))) + .tasks(d -> d.function("myCall", fn -> fn.function(ctx -> "hello"))) .build(); assertThat(wf.getDo()).hasSize(1); @@ -193,7 +193,7 @@ void testWorkflowCallFnWithPredicate() { Workflow wf = AgentWorkflowBuilder.workflow() - .tasks(d -> d.callFn("guarded", fn -> fn.function(ctx -> "x").when(guard))) + .tasks(d -> d.function("guarded", fn -> fn.function(ctx -> "x").when(guard))) .build(); TaskItem ti = wf.getDo().get(0); diff --git a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/EmailDrafterIT.java b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/EmailDrafterIT.java index 4925ed649..e0eb6e544 100644 --- a/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/EmailDrafterIT.java +++ b/experimental/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/EmailDrafterIT.java @@ -62,8 +62,8 @@ void email_drafter_agent() { tasks -> tasks .agent("agentEmailDrafter", emailDrafter) - .callFn("parseDraft", fn(EmailDrafts::parse, String.class)) - .callFn("policyCheck", fn(EmailPolicies::policyCheck, EmailDraft.class)) + .function("parseDraft", fn(EmailDrafts::parse, String.class)) + .function("policyCheck", fn(EmailPolicies::policyCheck, EmailDraft.class)) .switchCase( "needsHumanReview?", cases( diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java new file mode 100644 index 000000000..22cccc4a2 --- /dev/null +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java @@ -0,0 +1,48 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncTaskTransformations; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import io.serverlessworkflow.fluent.spec.spi.CallHttpTaskFluent; + +public class FuncCallHttpTaskBuilder extends TaskBaseBuilder + implements CallHttpTaskFluent, + FuncTaskTransformations, + ConditionalTaskBuilder { + + private final CallHTTP callHTTP; + + protected FuncCallHttpTaskBuilder() { + callHTTP = new CallHTTP(); + callHTTP.setWith(new HTTPArguments()); + callHTTP.getWith().setOutput(HTTPArguments.HTTPOutput.CONTENT); + super.setTask(this.callHTTP); + } + + @Override + public CallHTTP build() { + return callHTTP; + } + + @Override + public FuncCallHttpTaskBuilder self() { + return this; + } +} diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java index 2b03dedf1..ee21061fe 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java @@ -73,8 +73,8 @@ public FuncDoTaskBuilder switchCase( } @Override - public FuncDoTaskBuilder callFn(String name, Consumer cfg) { - this.listBuilder().callFn(name, cfg); + public FuncDoTaskBuilder function(String name, Consumer cfg) { + this.listBuilder().function(name, cfg); return this; } @@ -83,4 +83,10 @@ public FuncDoTaskBuilder fork(String name, Consumer itemsCo this.listBuilder().fork(name, itemsConfigurer); return this; } + + @Override + public FuncDoTaskBuilder http(String name, Consumer itemsConfigurer) { + this.listBuilder().http(name, itemsConfigurer); + return this; + } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java index e31faf60f..dbc232753 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java @@ -15,6 +15,8 @@ */ package io.serverlessworkflow.fluent.func; +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.fluent.func.spi.FuncDoFluent; @@ -45,7 +47,7 @@ protected FuncTaskItemListBuilder newItemListBuilder() { } @Override - public FuncTaskItemListBuilder callFn(String name, Consumer consumer) { + public FuncTaskItemListBuilder function(String name, Consumer consumer) { name = this.defaultNameAndRequireConfig(name, consumer); final FuncCallTaskBuilder callTaskJavaBuilder = new FuncCallTaskBuilder(); consumer.accept(callTaskJavaBuilder); @@ -53,8 +55,8 @@ public FuncTaskItemListBuilder callFn(String name, Consumer } @Override - public FuncTaskItemListBuilder callFn(Consumer consumer) { - return this.callFn(UUID.randomUUID().toString(), consumer); + public FuncTaskItemListBuilder function(Consumer consumer) { + return this.function(UUID.randomUUID().toString(), consumer); } @Override @@ -116,4 +118,19 @@ public FuncTaskItemListBuilder fork(String name, Consumer i return this.addTaskItem( new TaskItem(name, new Task().withForkTask(forkTaskJavaBuilder.build()))); } + + @Override + public FuncTaskItemListBuilder http( + String name, Consumer itemsConfigurer) { + name = this.defaultNameAndRequireConfig(name, itemsConfigurer); + final FuncCallHttpTaskBuilder httpTaskJavaBuilder = new FuncCallHttpTaskBuilder(); + itemsConfigurer.accept(httpTaskJavaBuilder); + final CallHTTP callHTTP = httpTaskJavaBuilder.build(); + final CallTask callTask = new CallTask(); + callTask.setCallHTTP(callHTTP); + final Task task = new Task(); + task.setCallTask(callTask); + + return this.addTaskItem(new TaskItem(name, task)); + } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallHttpConfigurer.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallHttpConfigurer.java new file mode 100644 index 000000000..d852f82ca --- /dev/null +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallHttpConfigurer.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.fluent.func.configurers; + +import io.serverlessworkflow.fluent.func.FuncCallHttpTaskBuilder; +import java.util.function.Consumer; + +@FunctionalInterface +public interface FuncCallHttpConfigurer extends Consumer {} diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/ConsumeStep.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/ConsumeStep.java index 34339f0e2..b9a99ac96 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/ConsumeStep.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/ConsumeStep.java @@ -38,7 +38,7 @@ public final class ConsumeStep extends Step, FuncCallTaskBuild protected void configure( FuncTaskItemListBuilder list, java.util.function.Consumer post) { if (name == null) { - list.callFn( + list.function( cb -> { // prefer the typed consumer if your builder supports it; otherwise fallback: if (argClass != null) cb.consumer(consumer, argClass); @@ -46,7 +46,7 @@ protected void configure( post.accept(cb); }); } else { - list.callFn( + list.function( name, cb -> { if (argClass != null) cb.consumer(consumer, argClass); diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallHttpSpec.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallHttpSpec.java new file mode 100644 index 000000000..58259b801 --- /dev/null +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallHttpSpec.java @@ -0,0 +1,34 @@ +/* + * 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.fluent.func.dsl; + +import io.serverlessworkflow.fluent.func.FuncCallHttpTaskBuilder; +import io.serverlessworkflow.fluent.func.configurers.FuncCallHttpConfigurer; +import io.serverlessworkflow.fluent.spec.dsl.BaseCallHttpSpec; + +public class FuncCallHttpSpec extends BaseCallHttpSpec + implements FuncCallHttpConfigurer { + + @Override + protected FuncCallHttpSpec self() { + return this; + } + + @Override + public void accept(FuncCallHttpTaskBuilder funcCallHttpTaskBuilder) { + super.accept(funcCallHttpTaskBuilder); + } +} diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallStep.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallStep.java index 2e828e3be..8575efe17 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallStep.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallStep.java @@ -87,9 +87,9 @@ protected void configure(FuncTaskItemListBuilder list, Consumer FuncTaskConfigurer switchWhen( return list -> list.switchCase(cases(caseOf(pred, predClass).then(thenTask))); } + /** + * JQ-based condition: if the JQ expression evaluates truthy → jump to {@code thenTask}. + * + *
+   *   switchWhen(".approved == true", "approveOrder")
+   * 
+ * + *

The JQ expression is evaluated against the task input at runtime. When the predicate is + * false, the workflow follows the default flow directive for the switch task (as defined by the + * underlying implementation / spec). + * + * @param jqExpression JQ expression evaluated against the current task input + * @param thenTask task name to jump to when the expression evaluates truthy + * @return list configurer + */ public static FuncTaskConfigurer switchWhen(String jqExpression, String thenTask) { return list -> list.switchCase(sw -> sw.on(c -> c.when(jqExpression).then(thenTask))); } @@ -837,4 +855,290 @@ public static FuncTaskConfigurer set(String expr) { public static FuncTaskConfigurer set(Map map) { return list -> list.set(s -> s.expr(map)); } + + /** + * Low-level HTTP call entrypoint using a {@link FuncCallHttpConfigurer}. + * + *

This overload creates an unnamed HTTP task. + * + * @param configurer the configurer that mutates the underlying HTTP call builder + * @return a {@link FuncTaskConfigurer} that adds an HTTP task to the tasks list + */ + public static FuncTaskConfigurer call(FuncCallHttpConfigurer configurer) { + return call(null, configurer); + } + + /** + * Low-level HTTP call entrypoint using a {@link FuncCallHttpConfigurer}. + * + *

This overload allows assigning an explicit task name. + * + * @param name task name, or {@code null} for an anonymous task + * @param configurer the configurer that mutates the underlying HTTP call builder + * @return a {@link FuncTaskConfigurer} that adds an HTTP task to the tasks list + */ + public static FuncTaskConfigurer call(String name, FuncCallHttpConfigurer configurer) { + Objects.requireNonNull(configurer, "configurer"); + return list -> list.http(name, configurer); + } + + /** + * HTTP call using a fluent {@link FuncCallHttpSpec}. + * + *

This overload creates an unnamed HTTP task. + * + *

{@code
+   * tasks(
+   *   FuncDSL.call(
+   *     FuncDSL.http()
+   *       .GET()
+   *       .endpoint("http://service/api")
+   *   )
+   * );
+   * }
+ * + * @param spec fluent HTTP spec built via {@link #http()} + * @return a {@link FuncTaskConfigurer} that adds an HTTP task + */ + public static FuncTaskConfigurer call(FuncCallHttpSpec spec) { + return call(null, spec); + } + + /** + * HTTP call using a fluent {@link FuncCallHttpSpec} with explicit task name. + * + *
{@code
+   * tasks(
+   *   FuncDSL.call("fetchUsers",
+   *     FuncDSL.http()
+   *       .GET()
+   *       .endpoint("http://service/users")
+   *   )
+   * );
+   * }
+ * + * @param name task name, or {@code null} for an anonymous task + * @param spec fluent HTTP spec built via {@link #http()} + * @return a {@link FuncTaskConfigurer} that adds an HTTP task + */ + public static FuncTaskConfigurer call(String name, FuncCallHttpSpec spec) { + Objects.requireNonNull(spec, "spec"); + return call(name, spec::accept); + } + + /** + * Create a new, empty HTTP specification to be used with {@link #call(FuncCallHttpSpec)}. + * + *

Typical usage: + * + *

{@code
+   * FuncDSL.call(
+   *   FuncDSL.http()
+   *     .GET()
+   *     .endpoint("http://service/api")
+   *     .acceptJSON()
+   * );
+   * }
+ * + * @return a new {@link FuncCallHttpSpec} + */ + public static FuncCallHttpSpec http() { + return new FuncCallHttpSpec(); + } + + /** + * Create a new HTTP specification preconfigured with an endpoint expression and authentication. + * + *
{@code
+   * FuncDSL.call(
+   *   FuncDSL.http("http://service/api", auth -> auth.use("my-auth"))
+   *     .GET()
+   * );
+   * }
+ * + * @param urlExpr expression or literal string for the endpoint URL + * @param auth authentication configurer (e.g. {@code auth -> auth.use("my-auth")}) + * @return a {@link FuncCallHttpSpec} preconfigured with endpoint + auth + */ + public static FuncCallHttpSpec http(String urlExpr, AuthenticationConfigurer auth) { + return new FuncCallHttpSpec().endpoint(urlExpr, auth); + } + + /** + * Create a new HTTP specification preconfigured with a {@link URI} and authentication. + * + * @param url concrete URI to call + * @param auth authentication configurer + * @return a {@link FuncCallHttpSpec} preconfigured with URI + auth + */ + public static FuncCallHttpSpec http(URI url, AuthenticationConfigurer auth) { + return new FuncCallHttpSpec().uri(url, auth); + } + + /** + * Convenience for adding an unnamed {@code GET} HTTP task using a string endpoint. + * + *
{@code
+   * tasks(
+   *   FuncDSL.get("http://service/health")
+   * );
+   * }
+ * + * @param endpoint literal or expression for the endpoint URL + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get(String endpoint) { + return get(null, endpoint); + } + + /** + * Convenience for adding a named {@code GET} HTTP task using a string endpoint. + * + *
{@code
+   * tasks(
+   *   FuncDSL.get("checkHealth", "http://service/health")
+   * );
+   * }
+ * + * @param name task name + * @param endpoint literal or expression for the endpoint URL + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get(String name, String endpoint) { + return call(name, http().GET().endpoint(endpoint)); + } + + /** + * Convenience for adding an unnamed authenticated {@code GET} HTTP task using a string endpoint. + * + *
{@code
+   * tasks(
+   *   FuncDSL.get("http://service/api/users", auth -> auth.use("user-service-auth"))
+   * );
+   * }
+ * + * @param endpoint literal or expression for the endpoint URL + * @param auth authentication configurer + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get(String endpoint, AuthenticationConfigurer auth) { + return get(null, endpoint, auth); + } + + /** + * Convenience for adding a named authenticated {@code GET} HTTP task using a string endpoint. + * + * @param name task name + * @param endpoint literal or expression for the endpoint URL + * @param auth authentication configurer + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get( + String name, String endpoint, AuthenticationConfigurer auth) { + return call(name, http().GET().endpoint(endpoint, auth)); + } + + /** + * Convenience for adding an unnamed {@code GET} HTTP task using a {@link URI}. + * + * @param endpoint concrete URI to call + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get(URI endpoint) { + return get(null, endpoint); + } + + /** + * Convenience for adding a named {@code GET} HTTP task using a {@link URI}. + * + * @param name task name + * @param endpoint concrete URI to call + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get(String name, URI endpoint) { + return call(name, http().GET().uri(endpoint)); + } + + /** + * Convenience for adding an unnamed authenticated {@code GET} HTTP task using a {@link URI}. + * + * @param endpoint concrete URI to call + * @param auth authentication configurer + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get(URI endpoint, AuthenticationConfigurer auth) { + return get(null, endpoint, auth); + } + + /** + * Convenience for adding a named authenticated {@code GET} HTTP task using a {@link URI}. + * + * @param name task name + * @param endpoint concrete URI to call + * @param auth authentication configurer + * @return a {@link FuncTaskConfigurer} adding a {@code GET} HTTP task + */ + public static FuncTaskConfigurer get(String name, URI endpoint, AuthenticationConfigurer auth) { + return call(name, http().GET().uri(endpoint, auth)); + } + + /** + * Convenience for adding an unnamed {@code POST} HTTP task with a body and string endpoint. + * + *
{@code
+   * tasks(
+   *   FuncDSL.post(
+   *     Map.of("name", "Ricardo"),
+   *     "http://service/api/users"
+   *   )
+   * );
+   * }
+ * + * @param body HTTP request body (literal value or expression-compatible object) + * @param endpoint literal or expression for the endpoint URL + * @return a {@link FuncTaskConfigurer} adding a {@code POST} HTTP task + */ + public static FuncTaskConfigurer post(Object body, String endpoint) { + return post(null, body, endpoint); + } + + /** + * Convenience for adding a named {@code POST} HTTP task with a body and string endpoint. + * + * @param name task name + * @param body HTTP request body (literal value or expression-compatible object) + * @param endpoint literal or expression for the endpoint URL + * @return a {@link FuncTaskConfigurer} adding a {@code POST} HTTP task + */ + public static FuncTaskConfigurer post(String name, Object body, String endpoint) { + return call(name, http().POST().endpoint(endpoint).body(body)); + } + + /** + * Convenience for adding an unnamed authenticated {@code POST} HTTP task with body and endpoint. + * + * @param body HTTP request body + * @param endpoint literal or expression for the endpoint URL + * @param auth authentication configurer + * @return a {@link FuncTaskConfigurer} adding an authenticated {@code POST} HTTP task + */ + public static FuncTaskConfigurer post( + Object body, String endpoint, AuthenticationConfigurer auth) { + return post(null, body, endpoint, auth); + } + + /** + * Convenience for adding a named authenticated {@code POST} HTTP task with body and endpoint. + * + * @param name task name + * @param body HTTP request body + * @param endpoint literal or expression for the endpoint URL + * @param auth authentication configurer + * @return a {@link FuncTaskConfigurer} adding an authenticated {@code POST} HTTP task + */ + public static FuncTaskConfigurer post( + String name, Object body, String endpoint, AuthenticationConfigurer auth) { + + return call(name, http().POST().endpoint(endpoint, auth).body(body)); + } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/CallFnFluent.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/CallFnFluent.java index f576601b4..99695aa40 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/CallFnFluent.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/CallFnFluent.java @@ -21,9 +21,9 @@ public interface CallFnFluent, LIST> { - LIST callFn(String name, Consumer cfg); + LIST function(String name, Consumer cfg); - default LIST callFn(Consumer cfg) { - return this.callFn(UUID.randomUUID().toString(), cfg); + default LIST function(Consumer cfg) { + return this.function(UUID.randomUUID().toString(), cfg); } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java index 9eebb194b..7b2acb941 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java @@ -15,6 +15,7 @@ */ package io.serverlessworkflow.fluent.func.spi; +import io.serverlessworkflow.fluent.func.FuncCallHttpTaskBuilder; import io.serverlessworkflow.fluent.func.FuncCallTaskBuilder; import io.serverlessworkflow.fluent.func.FuncEmitTaskBuilder; import io.serverlessworkflow.fluent.func.FuncForTaskBuilder; @@ -22,6 +23,7 @@ import io.serverlessworkflow.fluent.func.FuncListenTaskBuilder; import io.serverlessworkflow.fluent.func.FuncSetTaskBuilder; import io.serverlessworkflow.fluent.func.FuncSwitchTaskBuilder; +import io.serverlessworkflow.fluent.spec.spi.CallHttpFluent; import io.serverlessworkflow.fluent.spec.spi.EmitFluent; import io.serverlessworkflow.fluent.spec.spi.ForEachFluent; import io.serverlessworkflow.fluent.spec.spi.ForkFluent; @@ -29,8 +31,6 @@ import io.serverlessworkflow.fluent.spec.spi.SetFluent; import io.serverlessworkflow.fluent.spec.spi.SwitchFluent; -// TODO: implement the other builders, e.g. CallHTTP - public interface FuncDoFluent> extends SetFluent, EmitFluent, @@ -38,4 +38,5 @@ public interface FuncDoFluent> SwitchFluent, ForkFluent, ListenFluent, - CallFnFluent {} + CallFnFluent, + CallHttpFluent {} diff --git a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLConsumeTest.java b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLConsumeTest.java index f7b12c9d9..fb3150a0c 100644 --- a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLConsumeTest.java +++ b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLConsumeTest.java @@ -32,7 +32,7 @@ class FuncDSLConsumeTest { @Test @DisplayName( "consume(name, Consumer, Class) produces CallTask and leaves output unchanged by contract") - void consume_produces_CallTask() { + void consume_produces_callTask() { AtomicReference sink = new AtomicReference<>(); Workflow wf = diff --git a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java index 843f6e6eb..403225746 100644 --- a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java +++ b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java @@ -20,9 +20,13 @@ import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.listen; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.toOne; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import io.cloudevents.core.data.BytesCloudEventData; +import io.serverlessworkflow.api.types.CallHTTP; import io.serverlessworkflow.api.types.Export; import io.serverlessworkflow.api.types.FlowDirectiveEnum; import io.serverlessworkflow.api.types.Task; @@ -33,6 +37,7 @@ import io.serverlessworkflow.fluent.func.dsl.FuncDSL; import io.serverlessworkflow.fluent.func.dsl.FuncEmitSpec; import io.serverlessworkflow.fluent.func.dsl.FuncListenSpec; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -201,4 +206,228 @@ void switchWhenOrElse_jq_to_directive() { assertEquals( FlowDirectiveEnum.END, items.get(1).getSwitchCase().getThen().getFlowDirectiveEnum()); } + + @Test + void http_spec_via_call_builds_call_http_task() { + Workflow wf = + FuncWorkflowBuilder.workflow("http-call-spec") + .tasks( + FuncDSL.call("checkHealth", FuncDSL.http().GET().endpoint("http://service/health"))) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + Task t = items.get(0).getTask(); + assertNotNull(t.getCallTask(), "CallTask expected for HTTP call"); + + // HTTP-specific call + assertInstanceOf( + CallHTTP.class, t.getCallTask().get(), "CallTask should be an instance of CallHTTP"); + + CallHTTP http = (CallHTTP) t.getCallTask().get(); + assertEquals("GET", http.getWith().getMethod(), "HTTP method should be GET"); + assertEquals( + "http://service/health", + http.getWith().getEndpoint().getRuntimeExpression(), + "endpoint should match the DSL endpoint"); + } + + @Test + @DisplayName("get(endpoint) convenience creates unnamed GET CallHTTP task") + void get_convenience_creates_http_get() { + Workflow wf = + FuncWorkflowBuilder.workflow("http-get-convenience") + .tasks(FuncDSL.get("http://service/status")) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + Task t = items.get(0).getTask(); + assertNotNull(t.getCallTask(), "CallTask expected"); + + CallHTTP http = (CallHTTP) t.getCallTask().get(); + assertEquals("GET", http.getWith().getMethod()); + assertEquals( + "http://service/status", + http.getWith().getEndpoint().getRuntimeExpression(), + "endpoint should be set from get(endpoint)"); + } + + @Test + @DisplayName("get(name, endpoint, auth -> auth.use(\"auth-id\")) wires authentication") + void get_named_with_authentication_uses_auth_policy() { + Workflow wf = + FuncWorkflowBuilder.workflow("http-get-auth") + .tasks( + FuncDSL.get( + "fetchUsers", + "http://service/api/users", + auth -> auth.use("user-service-auth"))) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + assertEquals("fetchUsers", items.get(0).getName(), "Task should use the provided name"); + Task t = items.get(0).getTask(); + assertNotNull(t.getCallTask(), "CallTask expected"); + + CallHTTP http = (CallHTTP) t.getCallTask().get(); + assertEquals("GET", http.getWith().getMethod()); + assertEquals( + "http://service/api/users", + http.getWith().getEndpoint().getRuntimeExpression(), + "endpoint should be set from get(name, endpoint, auth)"); + + assertNotNull( + http.getWith().getEndpoint().getEndpointConfiguration().getAuthentication(), + "authentication should be configured"); + assertEquals( + "user-service-auth", + http.getWith() + .getEndpoint() + .getEndpointConfiguration() + .getAuthentication() + .getAuthenticationPolicyReference() + .getUse(), + "auth.use(\"...\") should set authentication.use"); + } + + @Test + @DisplayName("get(URI endpoint, auth) uses URI and authentication") + void get_with_uri_and_authentication() { + URI endpoint = URI.create("https://service.example.com/api/health"); + + Workflow wf = + FuncWorkflowBuilder.workflow("http-get-uri-auth") + .tasks(FuncDSL.get("checkHealth", endpoint, auth -> auth.use("tls-auth"))) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + Task t = items.get(0).getTask(); + assertEquals("checkHealth", items.get(0).getName(), "Task should use the provided name"); + assertNotNull(t.getCallTask()); + + CallHTTP http = (CallHTTP) t.getCallTask().get(); + assertEquals("GET", http.getWith().getMethod()); + assertEquals( + endpoint.toString(), + http.getWith().getEndpoint().getUriTemplate().getLiteralUri().toString(), + "endpoint should be derived from URI"); + + assertNotNull(http.getWith().getEndpoint().getEndpointConfiguration().getAuthentication()); + assertEquals( + "tls-auth", + http.getWith() + .getEndpoint() + .getEndpointConfiguration() + .getAuthentication() + .getAuthenticationPolicyReference() + .getUse()); + } + + @Test + @DisplayName("post(body, endpoint) convenience creates POST CallHTTP with body") + void post_convenience_creates_http_post_with_body() { + Map body = Map.of("name", "Ricardo"); + + Workflow wf = + FuncWorkflowBuilder.workflow("http-post-convenience") + .tasks(FuncDSL.post(body, "http://service/api/users")) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + Task t = items.get(0).getTask(); + assertNotNull(t.getCallTask(), "CallTask expected"); + + CallHTTP http = (CallHTTP) t.getCallTask().get(); + assertEquals("POST", http.getWith().getMethod()); + assertEquals( + "http://service/api/users", + http.getWith().getEndpoint().getRuntimeExpression(), + "endpoint should be set from post(body, endpoint)"); + + assertNotNull(http.getWith().getBody(), "Body should be set on POST"); + assertEquals(body, http.getWith().getBody(), "Body should match the provided payload"); + } + + @Test + @DisplayName("post(name, body, endpoint, auth) wires name, method, endpoint, body and auth") + void post_named_with_authentication() { + Map body = Map.of("id", 123, "status", "NEW"); + + Workflow wf = + FuncWorkflowBuilder.workflow("http-post-named-auth") + .tasks( + FuncDSL.post( + "createOrder", + body, + "https://orders.example.com/api/orders", + auth -> auth.use("orders-auth"))) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + Task t = items.get(0).getTask(); + assertEquals("createOrder", items.get(0).getName(), "Task should use the provided name"); + assertNotNull(t.getCallTask()); + + CallHTTP http = (CallHTTP) t.getCallTask().get(); + assertEquals("POST", http.getWith().getMethod()); + assertEquals( + "https://orders.example.com/api/orders", + http.getWith().getEndpoint().getRuntimeExpression()); + assertEquals(body, http.getWith().getBody()); + + assertNotNull(http.getWith().getEndpoint().getEndpointConfiguration().getAuthentication()); + assertEquals( + "orders-auth", + http.getWith() + .getEndpoint() + .getEndpointConfiguration() + .getAuthentication() + .getAuthenticationPolicyReference() + .getUse()); + } + + @Test + @DisplayName("call(http(\"...\", auth)) reuses fluent HTTP spec in call(...)") + void call_with_preconfigured_http_spec() { + Workflow wf = + FuncWorkflowBuilder.workflow("http-call-preconfigured") + .tasks( + FuncDSL.call( + "preconfigured", + FuncDSL.http("http://service/api", auth -> auth.use("svc-auth")) + .POST() + .body(Map.of("foo", "bar")))) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + Task t = items.get(0).getTask(); + assertEquals("preconfigured", items.get(0).getName()); + assertNotNull(t.getCallTask()); + + CallHTTP http = (CallHTTP) t.getCallTask().get(); + assertEquals("POST", http.getWith().getMethod()); + assertEquals("http://service/api", http.getWith().getEndpoint().getRuntimeExpression()); + assertEquals( + "svc-auth", + http.getWith() + .getEndpoint() + .getEndpointConfiguration() + .getAuthentication() + .getAuthenticationPolicyReference() + .getUse()); + assertEquals(Map.of("foo", "bar"), http.getWith().getBody()); + } } diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/FluentDSLCallTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/FluentDSLCallTest.java index a3f08c1e0..503dc2530 100644 --- a/experimental/lambda/src/test/java/io/serverless/workflow/impl/FluentDSLCallTest.java +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/FluentDSLCallTest.java @@ -34,7 +34,7 @@ void testJavaFunction() throws InterruptedException, ExecutionException { try (WorkflowApplication app = WorkflowApplication.builder().build()) { final Workflow workflow = FuncWorkflowBuilder.workflow("testJavaCall") - .tasks(tasks -> tasks.callFn(f -> f.function(JavaFunctions::getName))) + .tasks(tasks -> tasks.function(f -> f.function(JavaFunctions::getName))) .build(); assertThat( app.workflowDefinition(workflow) @@ -85,7 +85,7 @@ void testSwitch() throws InterruptedException, ExecutionException { switchOdd.onPredicate( item -> item.when(CallTest::isOdd).then(FlowDirectiveEnum.END))) - .callFn(callJava -> callJava.function(CallTest::zero))) + .function(callJava -> callJava.function(CallTest::zero))) .build(); WorkflowDefinition definition = app.workflowDefinition(workflow); diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHTTPTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHTTPTaskBuilder.java deleted file mode 100644 index 21b84fe96..000000000 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHTTPTaskBuilder.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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.fluent.spec; - -import io.serverlessworkflow.api.types.CallHTTP; -import io.serverlessworkflow.api.types.Endpoint; -import io.serverlessworkflow.api.types.EndpointConfiguration; -import io.serverlessworkflow.api.types.HTTPArguments; -import io.serverlessworkflow.api.types.HTTPHeaders; -import io.serverlessworkflow.api.types.HTTPQuery; -import io.serverlessworkflow.api.types.Headers; -import io.serverlessworkflow.api.types.Query; -import io.serverlessworkflow.api.types.ReferenceableAuthenticationPolicy; -import io.serverlessworkflow.api.types.UriTemplate; -import java.net.URI; -import java.util.Map; -import java.util.function.Consumer; - -public class CallHTTPTaskBuilder extends TaskBaseBuilder { - - private final CallHTTP callHTTP; - - CallHTTPTaskBuilder() { - callHTTP = new CallHTTP(); - callHTTP.setWith(new HTTPArguments()); - callHTTP.getWith().setOutput(HTTPArguments.HTTPOutput.CONTENT); - super.setTask(this.callHTTP); - } - - @Override - protected CallHTTPTaskBuilder self() { - return this; - } - - public CallHTTPTaskBuilder method(String method) { - this.callHTTP.getWith().setMethod(method); - return this; - } - - public CallHTTPTaskBuilder endpoint(URI endpoint) { - this.callHTTP - .getWith() - .setEndpoint(new Endpoint().withUriTemplate(new UriTemplate().withLiteralUri(endpoint))); - return this; - } - - public CallHTTPTaskBuilder endpoint( - URI endpoint, Consumer auth) { - final AuthenticationPolicyUnionBuilder policy = new AuthenticationPolicyUnionBuilder(); - auth.accept(policy); - this.callHTTP - .getWith() - .setEndpoint( - new Endpoint() - .withEndpointConfiguration( - new EndpointConfiguration() - .withAuthentication( - new ReferenceableAuthenticationPolicy() - .withAuthenticationPolicy(policy.build()))) - .withUriTemplate(new UriTemplate().withLiteralUri(endpoint))); - return this; - } - - public CallHTTPTaskBuilder endpoint(String expr) { - this.callHTTP.getWith().setEndpoint(new Endpoint().withRuntimeExpression(expr)); - return this; - } - - public CallHTTPTaskBuilder endpoint( - String expr, Consumer auth) { - final AuthenticationPolicyUnionBuilder policy = new AuthenticationPolicyUnionBuilder(); - auth.accept(policy); - this.callHTTP - .getWith() - .setEndpoint( - new Endpoint() - .withEndpointConfiguration( - new EndpointConfiguration() - .withAuthentication( - new ReferenceableAuthenticationPolicy() - .withAuthenticationPolicy(policy.build()))) - .withRuntimeExpression(expr)); - return this; - } - - public CallHTTPTaskBuilder headers(String expr) { - this.callHTTP.getWith().setHeaders(new Headers().withRuntimeExpression(expr)); - return this; - } - - public CallHTTPTaskBuilder headers(Consumer consumer) { - HTTPHeadersBuilder hb = new HTTPHeadersBuilder(); - consumer.accept(hb); - if (callHTTP.getWith().getHeaders() != null - && callHTTP.getWith().getHeaders().getHTTPHeaders() != null) { - Headers h = callHTTP.getWith().getHeaders(); - Headers built = hb.build(); - built - .getHTTPHeaders() - .getAdditionalProperties() - .forEach((k, v) -> h.getHTTPHeaders().setAdditionalProperty(k, v)); - } else { - callHTTP.getWith().setHeaders(hb.build()); - } - - return this; - } - - public CallHTTPTaskBuilder headers(Map headers) { - HTTPHeadersBuilder hb = new HTTPHeadersBuilder(); - hb.headers(headers); - callHTTP.getWith().setHeaders(hb.build()); - return this; - } - - public CallHTTPTaskBuilder body(Object body) { - this.callHTTP.getWith().setBody(body); - return this; - } - - public CallHTTPTaskBuilder query(String expr) { - this.callHTTP.getWith().setQuery(new Query().withRuntimeExpression(expr)); - return this; - } - - public CallHTTPTaskBuilder query(Consumer consumer) { - HTTPQueryBuilder queryBuilder = new HTTPQueryBuilder(); - consumer.accept(queryBuilder); - callHTTP.getWith().setQuery(queryBuilder.build()); - return this; - } - - public CallHTTPTaskBuilder query(Map query) { - HTTPQueryBuilder httpQueryBuilder = new HTTPQueryBuilder(); - httpQueryBuilder.queries(query); - callHTTP.getWith().setQuery(httpQueryBuilder.build()); - return this; - } - - public CallHTTPTaskBuilder redirect(boolean redirect) { - callHTTP.getWith().setRedirect(redirect); - return this; - } - - public CallHTTPTaskBuilder output(HTTPArguments.HTTPOutput output) { - callHTTP.getWith().setOutput(output); - return this; - } - - public CallHTTP build() { - return callHTTP; - } - - public static class HTTPQueryBuilder { - private final HTTPQuery httpQuery = new HTTPQuery(); - - public HTTPQueryBuilder query(String name, String value) { - httpQuery.setAdditionalProperty(name, value); - return this; - } - - public HTTPQueryBuilder queries(Map headers) { - headers.forEach(httpQuery::setAdditionalProperty); - return this; - } - - public Query build() { - return new Query().withHTTPQuery(httpQuery); - } - } - - public static class HTTPHeadersBuilder { - private final HTTPHeaders httpHeaders = new HTTPHeaders(); - - public HTTPHeadersBuilder header(String name, String value) { - httpHeaders.setAdditionalProperty(name, value); - return this; - } - - public HTTPHeadersBuilder headers(Map headers) { - headers.forEach(httpHeaders::setAdditionalProperty); - return this; - } - - public Headers build() { - return new Headers().withHTTPHeaders(httpHeaders); - } - } -} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java new file mode 100644 index 000000000..49c3a9132 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java @@ -0,0 +1,42 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.fluent.spec.spi.CallHttpTaskFluent; + +public class CallHttpTaskBuilder extends TaskBaseBuilder + implements CallHttpTaskFluent { + + private final CallHTTP callHTTP; + + protected CallHttpTaskBuilder() { + callHTTP = new CallHTTP(); + callHTTP.setWith(new HTTPArguments()); + callHTTP.getWith().setOutput(HTTPArguments.HTTPOutput.CONTENT); + super.setTask(this.callHTTP); + } + + @Override + public CallHttpTaskBuilder self() { + return this; + } + + public CallHTTP build() { + return callHTTP; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java index 669b580fe..bbeb1d1de 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java @@ -31,8 +31,8 @@ protected DoTaskBuilder self() { } @Override - public DoTaskBuilder callHTTP(String name, Consumer itemsConfigurer) { - this.listBuilder().callHTTP(name, itemsConfigurer); + public DoTaskBuilder http(String name, Consumer itemsConfigurer) { + this.listBuilder().http(name, itemsConfigurer); return this; } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AuthenticationPolicyUnionBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ReferenceableAuthenticationPolicyBuilder.java similarity index 71% rename from fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AuthenticationPolicyUnionBuilder.java rename to fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ReferenceableAuthenticationPolicyBuilder.java index 82f84a748..9c3040238 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AuthenticationPolicyUnionBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ReferenceableAuthenticationPolicyBuilder.java @@ -15,17 +15,21 @@ */ package io.serverlessworkflow.fluent.spec; +import io.serverlessworkflow.api.types.AuthenticationPolicyReference; import io.serverlessworkflow.api.types.AuthenticationPolicyUnion; +import io.serverlessworkflow.api.types.ReferenceableAuthenticationPolicy; import java.util.function.Consumer; -public class AuthenticationPolicyUnionBuilder { +public class ReferenceableAuthenticationPolicyBuilder { final AuthenticationPolicyUnion authenticationPolicy; + final AuthenticationPolicyReference authenticationPolicyReference; - AuthenticationPolicyUnionBuilder() { + public ReferenceableAuthenticationPolicyBuilder() { this.authenticationPolicy = new AuthenticationPolicyUnion(); + this.authenticationPolicyReference = new AuthenticationPolicyReference(); } - public AuthenticationPolicyUnionBuilder basic( + public ReferenceableAuthenticationPolicyBuilder basic( Consumer basicConsumer) { final BasicAuthenticationPolicyBuilder basicAuthenticationPolicyBuilder = new BasicAuthenticationPolicyBuilder(); @@ -35,7 +39,7 @@ public AuthenticationPolicyUnionBuilder basic( return this; } - public AuthenticationPolicyUnionBuilder bearer( + public ReferenceableAuthenticationPolicyBuilder bearer( Consumer bearerConsumer) { final BearerAuthenticationPolicyBuilder bearerAuthenticationPolicyBuilder = new BearerAuthenticationPolicyBuilder(); @@ -45,7 +49,7 @@ public AuthenticationPolicyUnionBuilder bearer( return this; } - public AuthenticationPolicyUnionBuilder digest( + public ReferenceableAuthenticationPolicyBuilder digest( Consumer digestConsumer) { final DigestAuthenticationPolicyBuilder digestAuthenticationPolicyBuilder = new DigestAuthenticationPolicyBuilder(); @@ -55,7 +59,7 @@ public AuthenticationPolicyUnionBuilder digest( return this; } - public AuthenticationPolicyUnionBuilder oauth2( + public ReferenceableAuthenticationPolicyBuilder oauth2( Consumer oauth2Consumer) { final OAuth2AuthenticationPolicyBuilder oauth2AuthenticationPolicyBuilder = new OAuth2AuthenticationPolicyBuilder(); @@ -65,7 +69,7 @@ public AuthenticationPolicyUnionBuilder oauth2( return this; } - public AuthenticationPolicyUnionBuilder openIDConnect( + public ReferenceableAuthenticationPolicyBuilder openIDConnect( Consumer openIdConnectConsumer) { final OpenIdConnectAuthenticationPolicyBuilder builder = new OpenIdConnectAuthenticationPolicyBuilder(); @@ -74,7 +78,15 @@ public AuthenticationPolicyUnionBuilder openIDConnect( return this; } - public AuthenticationPolicyUnion build() { - return authenticationPolicy; + public ReferenceableAuthenticationPolicyBuilder use(String use) { + this.authenticationPolicyReference.setUse(use); + return this; + } + + public ReferenceableAuthenticationPolicy build() { + final ReferenceableAuthenticationPolicy policy = new ReferenceableAuthenticationPolicy(); + policy.setAuthenticationPolicy(this.authenticationPolicy); + policy.setAuthenticationPolicyReference(this.authenticationPolicyReference); + return policy; } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java index 5cfba36c5..836e6809d 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java @@ -117,12 +117,15 @@ public TaskItemListBuilder tryCatch( } @Override - public TaskItemListBuilder callHTTP(String name, Consumer itemsConfigurer) { + public TaskItemListBuilder http(String name, Consumer itemsConfigurer) { name = defaultNameAndRequireConfig(name, itemsConfigurer); - final CallHTTPTaskBuilder callHTTPBuilder = new CallHTTPTaskBuilder(); + final CallHttpTaskBuilder callHTTPBuilder = new CallHttpTaskBuilder(); itemsConfigurer.accept(callHTTPBuilder); - return addTaskItem( - new TaskItem( - name, new Task().withCallTask(new CallTask().withCallHTTP(callHTTPBuilder.build())))); + final CallTask callTask = new CallTask(); + callTask.setCallHTTP(callHTTPBuilder.build()); + final Task task = new Task(); + task.setCallTask(callTask); + + return addTaskItem(new TaskItem(name, task)); } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseAuthenticationsBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseAuthenticationsBuilder.java index d865c802a..0c958092b 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseAuthenticationsBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseAuthenticationsBuilder.java @@ -27,10 +27,11 @@ public class UseAuthenticationsBuilder { } public UseAuthenticationsBuilder authentication( - String name, Consumer authenticationConsumer) { - final AuthenticationPolicyUnionBuilder builder = new AuthenticationPolicyUnionBuilder(); + String name, Consumer authenticationConsumer) { + final ReferenceableAuthenticationPolicyBuilder builder = + new ReferenceableAuthenticationPolicyBuilder(); authenticationConsumer.accept(builder); - this.authentication.setAdditionalProperty(name, builder.build()); + this.authentication.setAdditionalProperty(name, builder.build().getAuthenticationPolicy()); return this; } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/AuthenticationConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/AuthenticationConfigurer.java index 97b9bd4bd..cdd481ceb 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/AuthenticationConfigurer.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/AuthenticationConfigurer.java @@ -15,8 +15,9 @@ */ package io.serverlessworkflow.fluent.spec.configurers; -import io.serverlessworkflow.fluent.spec.AuthenticationPolicyUnionBuilder; +import io.serverlessworkflow.fluent.spec.ReferenceableAuthenticationPolicyBuilder; import java.util.function.Consumer; @FunctionalInterface -public interface AuthenticationConfigurer extends Consumer {} +public interface AuthenticationConfigurer + extends Consumer {} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallHTTPConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallHttpConfigurer.java similarity index 85% rename from fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallHTTPConfigurer.java rename to fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallHttpConfigurer.java index 11799b477..d1397d053 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallHTTPConfigurer.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallHttpConfigurer.java @@ -15,8 +15,8 @@ */ package io.serverlessworkflow.fluent.spec.configurers; -import io.serverlessworkflow.fluent.spec.CallHTTPTaskBuilder; +import io.serverlessworkflow.fluent.spec.CallHttpTaskBuilder; import java.util.function.Consumer; @FunctionalInterface -public interface CallHTTPConfigurer extends Consumer {} +public interface CallHttpConfigurer extends Consumer {} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHTTPSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/BaseCallHttpSpec.java similarity index 55% rename from fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHTTPSpec.java rename to fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/BaseCallHttpSpec.java index 83bec6294..1348a0a3b 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHTTPSpec.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/BaseCallHttpSpec.java @@ -15,89 +15,95 @@ */ package io.serverlessworkflow.fluent.spec.dsl; -import io.serverlessworkflow.fluent.spec.CallHTTPTaskBuilder; import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer; -import io.serverlessworkflow.fluent.spec.configurers.CallHTTPConfigurer; +import io.serverlessworkflow.fluent.spec.spi.CallHttpTaskFluent; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Consumer; -public final class CallHTTPSpec implements CallHTTPConfigurer { +public abstract class BaseCallHttpSpec> { - private final List steps = new ArrayList<>(); + private final List>> steps = new ArrayList<>(); - public CallHTTPSpec GET() { + protected abstract SELF self(); + + public SELF GET() { steps.add(c -> c.method("GET")); - return this; + return self(); } - public CallHTTPSpec POST() { + public SELF POST() { steps.add(c -> c.method("POST")); - return this; + return self(); } - public CallHTTPSpec acceptJSON() { + public SELF acceptJSON() { return header("Accept", "application/json"); } - public CallHTTPSpec endpoint(String urlExpr) { + public SELF endpoint(String urlExpr) { steps.add(b -> b.endpoint(urlExpr)); - return this; + return self(); } - public CallHTTPSpec endpoint(String urlExpr, AuthenticationConfigurer auth) { + public SELF endpoint(String urlExpr, AuthenticationConfigurer auth) { steps.add(b -> b.endpoint(urlExpr, auth)); - return this; + return self(); } - public CallHTTPSpec uri(String url) { + public SELF uri(String url) { steps.add(b -> b.endpoint(URI.create(url))); - return this; + return self(); } - public CallHTTPSpec uri(String url, AuthenticationConfigurer auth) { + public SELF uri(String url, AuthenticationConfigurer auth) { steps.add(b -> b.endpoint(URI.create(url), auth)); - return this; + return self(); } - public CallHTTPSpec uri(URI uri) { + public SELF uri(URI uri) { steps.add(b -> b.endpoint(uri)); - return this; + return self(); } - public CallHTTPSpec uri(URI uri, AuthenticationConfigurer auth) { + public SELF uri(URI uri, AuthenticationConfigurer auth) { steps.add(b -> b.endpoint(uri, auth)); - return this; + return self(); } - public CallHTTPSpec body(String bodyExpr) { + public SELF body(String bodyExpr) { steps.add(c -> c.body(bodyExpr)); - return this; + return self(); } - public CallHTTPSpec body(Map body) { + public SELF body(Map body) { steps.add(c -> c.body(body)); - return this; + return self(); + } + + public SELF body(Object bodyExpr) { + steps.add(c -> c.body(bodyExpr)); + return self(); } - public CallHTTPSpec method(String method) { + public SELF method(String method) { steps.add(b -> b.method(method)); - return this; + return self(); } - public CallHTTPSpec header(String name, String value) { + public SELF header(String name, String value) { steps.add(c -> c.headers(h -> h.header(name, value))); - return this; + return self(); } - public CallHTTPSpec headers(Map headers) { + public SELF headers(Map headers) { steps.add(b -> b.headers(headers)); - return this; + return self(); } - @Override - public void accept(CallHTTPTaskBuilder b) { + public void accept(CallHttpTaskFluent b) { for (var s : steps) s.accept(b); } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpSpec.java new file mode 100644 index 000000000..4c9b85d39 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpSpec.java @@ -0,0 +1,33 @@ +/* + * 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.fluent.spec.dsl; + +import io.serverlessworkflow.fluent.spec.CallHttpTaskBuilder; +import io.serverlessworkflow.fluent.spec.configurers.CallHttpConfigurer; + +public final class CallHttpSpec extends BaseCallHttpSpec + implements CallHttpConfigurer { + + @Override + protected CallHttpSpec self() { + return this; + } + + @Override + public void accept(CallHttpTaskBuilder callHttpTaskBuilder) { + super.accept(callHttpTaskBuilder); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java index 0863ef256..42bad108f 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java @@ -22,7 +22,7 @@ import io.serverlessworkflow.fluent.spec.TaskItemListBuilder; import io.serverlessworkflow.fluent.spec.TryTaskBuilder; import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer; -import io.serverlessworkflow.fluent.spec.configurers.CallHTTPConfigurer; +import io.serverlessworkflow.fluent.spec.configurers.CallHttpConfigurer; import io.serverlessworkflow.fluent.spec.configurers.ForEachConfigurer; import io.serverlessworkflow.fluent.spec.configurers.ListenConfigurer; import io.serverlessworkflow.fluent.spec.configurers.RaiseConfigurer; @@ -44,8 +44,8 @@ private DSL() {} // ---- Convenient shortcuts ----// - public static CallHTTPSpec http() { - return new CallHTTPSpec(); + public static CallHttpSpec http() { + return new CallHttpSpec(); } public static SwitchSpec cases() { @@ -196,8 +196,8 @@ public static RaiseSpec dataError() { // ---- Tasks ----// - public static TasksConfigurer call(CallHTTPConfigurer configurer) { - return list -> list.callHTTP(configurer); + public static TasksConfigurer call(CallHttpConfigurer configurer) { + return list -> list.http(configurer); } public static TasksConfigurer set(SetConfigurer configurer) { diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHTTPFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpFluent.java similarity index 76% rename from fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHTTPFluent.java rename to fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpFluent.java index 485470577..11fd9e2ae 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHTTPFluent.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpFluent.java @@ -19,11 +19,11 @@ import java.util.UUID; import java.util.function.Consumer; -public interface CallHTTPFluent, LIST> { +public interface CallHttpFluent, LIST> { - LIST callHTTP(String name, Consumer itemsConfigurer); + LIST http(String name, Consumer itemsConfigurer); - default LIST callHTTP(Consumer itemsConfigurer) { - return this.callHTTP(UUID.randomUUID().toString(), itemsConfigurer); + default LIST http(Consumer itemsConfigurer) { + return this.http(UUID.randomUUID().toString(), itemsConfigurer); } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java new file mode 100644 index 000000000..e6a873582 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java @@ -0,0 +1,205 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.api.types.AuthenticationPolicyReference; +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.EndpointConfiguration; +import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.api.types.HTTPHeaders; +import io.serverlessworkflow.api.types.HTTPQuery; +import io.serverlessworkflow.api.types.Headers; +import io.serverlessworkflow.api.types.Query; +import io.serverlessworkflow.api.types.ReferenceableAuthenticationPolicy; +import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.fluent.spec.ReferenceableAuthenticationPolicyBuilder; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.net.URI; +import java.util.Map; +import java.util.function.Consumer; + +public interface CallHttpTaskFluent> { + + CallHTTP build(); + + SELF self(); + + default SELF method(String method) { + ((CallHTTP) this.self().getTask()).getWith().setMethod(method); + return self(); + } + + default SELF endpoint(URI endpoint) { + ((CallHTTP) this.self().getTask()) + .getWith() + .setEndpoint(new Endpoint().withUriTemplate(new UriTemplate().withLiteralUri(endpoint))); + return self(); + } + + default SELF endpoint(URI endpoint, Consumer auth) { + final ReferenceableAuthenticationPolicyBuilder policy = + new ReferenceableAuthenticationPolicyBuilder(); + auth.accept(policy); + ((CallHTTP) this.self().getTask()) + .getWith() + .setEndpoint( + new Endpoint() + .withEndpointConfiguration( + new EndpointConfiguration().withAuthentication(policy.build())) + .withUriTemplate(new UriTemplate().withLiteralUri(endpoint))); + return self(); + } + + default SELF endpoint(String expr) { + ((CallHTTP) this.self().getTask()) + .getWith() + .setEndpoint(new Endpoint().withRuntimeExpression(expr)); + return self(); + } + + default SELF endpoint(String expr, Consumer auth) { + final ReferenceableAuthenticationPolicyBuilder policy = + new ReferenceableAuthenticationPolicyBuilder(); + auth.accept(policy); + ((CallHTTP) this.self().getTask()) + .getWith() + .setEndpoint( + new Endpoint() + .withEndpointConfiguration( + new EndpointConfiguration().withAuthentication(policy.build())) + .withRuntimeExpression(expr)); + return self(); + } + + default SELF endpoint(String expr, String authUse) { + ((CallHTTP) this.self().getTask()) + .getWith() + .setEndpoint( + new Endpoint() + .withEndpointConfiguration( + new EndpointConfiguration() + .withAuthentication( + new ReferenceableAuthenticationPolicy() + .withAuthenticationPolicyReference( + new AuthenticationPolicyReference(authUse)))) + .withRuntimeExpression(expr)); + return self(); + } + + default SELF headers(String expr) { + ((CallHTTP) this.self().getTask()) + .getWith() + .setHeaders(new Headers().withRuntimeExpression(expr)); + return self(); + } + + default SELF headers(Consumer consumer) { + HTTPHeadersBuilder hb = new HTTPHeadersBuilder(); + consumer.accept(hb); + CallHTTP httpTask = ((CallHTTP) this.self().getTask()); + if (httpTask.getWith().getHeaders() != null + && httpTask.getWith().getHeaders().getHTTPHeaders() != null) { + Headers h = httpTask.getWith().getHeaders(); + Headers built = hb.build(); + built + .getHTTPHeaders() + .getAdditionalProperties() + .forEach((k, v) -> h.getHTTPHeaders().setAdditionalProperty(k, v)); + } else { + httpTask.getWith().setHeaders(hb.build()); + } + + return self(); + } + + default SELF headers(Map headers) { + HTTPHeadersBuilder hb = new HTTPHeadersBuilder(); + hb.headers(headers); + ((CallHTTP) this.self().getTask()).getWith().setHeaders(hb.build()); + return self(); + } + + default SELF body(Object body) { + ((CallHTTP) this.self().getTask()).getWith().setBody(body); + return self(); + } + + default SELF query(String expr) { + ((CallHTTP) this.self().getTask()).getWith().setQuery(new Query().withRuntimeExpression(expr)); + return self(); + } + + default SELF query(Consumer consumer) { + HTTPQueryBuilder queryBuilder = new HTTPQueryBuilder(); + consumer.accept(queryBuilder); + ((CallHTTP) this.self().getTask()).getWith().setQuery(queryBuilder.build()); + return self(); + } + + default SELF query(Map query) { + HTTPQueryBuilder httpQueryBuilder = new HTTPQueryBuilder(); + httpQueryBuilder.queries(query); + ((CallHTTP) this.self().getTask()).getWith().setQuery(httpQueryBuilder.build()); + return self(); + } + + default SELF redirect(boolean redirect) { + ((CallHTTP) this.self().getTask()).getWith().setRedirect(redirect); + return self(); + } + + default SELF output(HTTPArguments.HTTPOutput output) { + ((CallHTTP) this.self().getTask()).getWith().setOutput(output); + return self(); + } + + class HTTPQueryBuilder { + private final HTTPQuery httpQuery = new HTTPQuery(); + + public HTTPQueryBuilder query(String name, String value) { + httpQuery.setAdditionalProperty(name, value); + return this; + } + + public HTTPQueryBuilder queries(Map headers) { + headers.forEach(httpQuery::setAdditionalProperty); + return this; + } + + public Query build() { + return new Query().withHTTPQuery(httpQuery); + } + } + + class HTTPHeadersBuilder { + private final HTTPHeaders httpHeaders = new HTTPHeaders(); + + public HTTPHeadersBuilder header(String name, String value) { + httpHeaders.setAdditionalProperty(name, value); + return this; + } + + public HTTPHeadersBuilder headers(Map headers) { + headers.forEach(httpHeaders::setAdditionalProperty); + return this; + } + + public Headers build() { + return new Headers().withHTTPHeaders(httpHeaders); + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java index a18a08bfd..e3d5d3610 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java @@ -15,7 +15,7 @@ */ package io.serverlessworkflow.fluent.spec.spi; -import io.serverlessworkflow.fluent.spec.CallHTTPTaskBuilder; +import io.serverlessworkflow.fluent.spec.CallHttpTaskBuilder; import io.serverlessworkflow.fluent.spec.EmitTaskBuilder; import io.serverlessworkflow.fluent.spec.ForEachTaskBuilder; import io.serverlessworkflow.fluent.spec.ForkTaskBuilder; @@ -37,7 +37,7 @@ public interface DoFluent extends SetFluent, SwitchFluent, TryCatchFluent, T>, - CallHTTPFluent, + CallHttpFluent, EmitFluent, ForEachFluent, T>, ForkFluent, diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java index 38c751ebb..fe9125365 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java @@ -407,7 +407,7 @@ void testDoTaskCallHTTPBasic() { WorkflowBuilder.workflow("flowCallBasic") .tasks( d -> - d.callHTTP( + d.http( "basicCall", http() .POST() @@ -433,7 +433,7 @@ void testDoTaskCallHTTPHeadersConsumerAndMap() { WorkflowBuilder.workflow("flowCallHeaders") .tasks( d -> - d.callHTTP( + d.http( "hdrCall", http().GET().endpoint("${uriExpr}").headers(Map.of("A", "1", "B", "2")))) .build(); @@ -444,9 +444,7 @@ void testDoTaskCallHTTPHeadersConsumerAndMap() { Workflow wf2 = WorkflowBuilder.workflow() - .tasks( - d -> - d.callHTTP(http().GET().endpoint("expr").headers(Map.of("X", "10", "Y", "20")))) + .tasks(d -> d.http(http().GET().endpoint("expr").headers(Map.of("X", "10", "Y", "20")))) .build(); CallHTTP call2 = wf2.getDo().get(0).getTask().getCallTask().getCallHTTP(); HTTPHeaders hh2 = call2.getWith().getHeaders().getHTTPHeaders(); @@ -460,7 +458,7 @@ void testDoTaskCallHTTPQueryConsumerAndMap() { WorkflowBuilder.workflow("flowCallQuery") .tasks( d -> - d.callHTTP( + d.http( "qryCall", http() .GET() @@ -476,7 +474,7 @@ void testDoTaskCallHTTPQueryConsumerAndMap() { WorkflowBuilder.workflow() .tasks( d -> - d.callHTTP( + d.http( c -> c.method("GET").endpoint("uri").query(Map.of("q1", "x", "q2", "y")))) .build(); HTTPQuery hq2 = @@ -498,7 +496,7 @@ void testDoTaskCallHTTPRedirectAndOutput() { WorkflowBuilder.workflow("flowCallOpts") .tasks( d -> - d.callHTTP( + d.http( "optCall", c -> c.method("DELETE") diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java index d9150b1c2..09757d510 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java @@ -37,11 +37,14 @@ public class CallHttpAuthDslTest { void when_call_http_with_basic_auth_on_endpoint_expr() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") - .tasks(t -> t.callHTTP(http().GET().endpoint(EXPR_ENDPOINT, basic("alice", "secret")))) + .tasks(t -> t.http(http().GET().endpoint(EXPR_ENDPOINT, basic("alice", "secret")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); + assertThat(wf.getDo().get(0).getTask().get()).isNotNull(); + assertThat(wf.getDo().get(0).getTask().getCallTask().get()).isNotNull(); + // Endpoint expression is set assertThat(args.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT); @@ -74,7 +77,7 @@ void when_call_http_with_basic_auth_on_endpoint_expr() { void when_call_http_with_bearer_auth_on_endpoint_expr() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") - .tasks(t -> t.callHTTP(http().GET().endpoint(EXPR_ENDPOINT, bearer("token-123")))) + .tasks(t -> t.http(http().GET().endpoint(EXPR_ENDPOINT, bearer("token-123")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); @@ -103,7 +106,7 @@ void when_call_http_with_bearer_auth_on_endpoint_expr() { void when_call_http_with_digest_auth_on_endpoint_expr() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") - .tasks(t -> t.callHTTP(http().GET().endpoint(EXPR_ENDPOINT, digest("bob", "p@ssw0rd")))) + .tasks(t -> t.http(http().GET().endpoint(EXPR_ENDPOINT, digest("bob", "p@ssw0rd")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); @@ -140,7 +143,7 @@ void when_call_http_with_oidc_auth_on_endpoint_expr_with_client() { WorkflowBuilder.workflow("f", "ns", "1") .tasks( t -> - t.callHTTP( + t.http( http() .POST() .endpoint( @@ -188,7 +191,7 @@ void when_call_http_with_oauth2_alias_on_endpoint_expr_without_client() { WorkflowBuilder.workflow("f", "ns", "1") .tasks( t -> - t.callHTTP( + t.http( http() .POST() .endpoint( @@ -232,7 +235,7 @@ void when_call_http_with_basic_auth_on_uri_string() { WorkflowBuilder.workflow("f", "ns", "1") .tasks( t -> - t.callHTTP( + t.http( http().GET().uri("https://api.example.com/v1/resource", basic("u", "p")))) .build(); diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java index 894b9a6b7..f959d89f3 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java @@ -32,12 +32,12 @@ public class DSLTest { @Test - public void when_new_call_http_task() { + public void when_new_http_call_task() { Workflow wf = WorkflowBuilder.workflow("myFlow", "myNs", "1.2.3") .tasks( t -> - t.callHTTP( + t.http( http() .acceptJSON() .header("CustomKey", "CustomValue") diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java index 185b65aa2..1401b13ef 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java @@ -15,7 +15,6 @@ */ package io.serverlessworkflow.fluent.spec.dsl; -import static io.serverlessworkflow.fluent.spec.dsl.DSL.call; import static io.serverlessworkflow.fluent.spec.dsl.DSL.emit; import static io.serverlessworkflow.fluent.spec.dsl.DSL.event; import static io.serverlessworkflow.fluent.spec.dsl.DSL.http; @@ -42,7 +41,7 @@ void when_try_with_tasks_and_catch_when_with_retry_and_tasks() { t.tryCatch( tryCatch() // try block (one HTTP call) - .tasks(call(http().GET().endpoint(EXPR_ENDPOINT))) + .tasks(DSL.call(http().GET().endpoint(EXPR_ENDPOINT))) // catch block .catches() .when("$.error == true") @@ -116,7 +115,7 @@ void when_try_with_multiple_tasks_and_catch_except_when_with_uri_error_filter() tryCatch() // try with two tasks .tasks( - call(http().GET().endpoint(EXPR_ENDPOINT)), + DSL.call(http().GET().endpoint(EXPR_ENDPOINT)), set("$.status = \"IN_FLIGHT\"")) // catch with exceptWhen + explicit URI error filter + status .catches() @@ -171,7 +170,7 @@ void when_try_with_catch_and_simple_retry_limit_only() { t -> t.tryCatch( tryCatch() - .tasks(call(http().GET().endpoint(EXPR_ENDPOINT))) + .tasks(DSL.call(http().GET().endpoint(EXPR_ENDPOINT))) .catches() .when("$.fail == true") .errors(Errors.COMMUNICATION, 503) From d778461718d3adf2147bd2bceb258ce520a71561 Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Fri, 14 Nov 2025 14:39:56 -0500 Subject: [PATCH 2/3] Added openapi support Signed-off-by: Ricardo Zanini --- .../fluent/func/FuncCallHttpTaskBuilder.java | 14 +- .../func/FuncCallOpenAPITaskBuilder.java | 41 +++++ .../fluent/func/FuncDoTaskBuilder.java | 7 + .../fluent/func/FuncTaskItemListBuilder.java | 20 +++ .../FuncCallOpenAPIConfigurer.java | 22 +++ .../fluent/func/dsl/FuncCallOpenAPISpec.java | 90 +++++++++++ .../fluent/func/dsl/FuncDSL.java | 82 +++++++++- .../fluent/func/spi/FuncDoFluent.java | 5 +- .../fluent/func/FuncDSLTest.java | 26 ++-- .../fluent/spec/CallHttpTaskBuilder.java | 12 +- .../fluent/spec/CallOpenAPITaskBuilder.java | 36 +++++ .../fluent/spec/DoTaskBuilder.java | 6 + .../fluent/spec/TaskItemListBuilder.java | 18 +++ .../configurers/CallOpenAPIConfigurer.java | 22 +++ .../fluent/spec/dsl/CallOpenAPISpec.java | 89 +++++++++++ .../fluent/spec/dsl/DSL.java | 13 ++ .../fluent/spec/spi/CallHttpTaskFluent.java | 44 +++--- .../fluent/spec/spi/CallOpenAPIFluent.java | 29 ++++ .../spec/spi/CallOpenAPITaskFluent.java | 143 ++++++++++++++++++ .../fluent/spec/spi/DoFluent.java | 4 +- .../fluent/spec/spi/EndpointUtil.java | 66 ++++++++ .../fluent/spec/WorkflowBuilderTest.java | 14 +- .../fluent/spec/dsl/CallHttpAuthDslTest.java | 54 +++---- .../fluent/spec/dsl/CallOpenApiDslTest.java | 95 ++++++++++++ .../fluent/spec/dsl/DSLTest.java | 14 +- .../fluent/spec/dsl/TryCatchDslTest.java | 7 +- 26 files changed, 868 insertions(+), 105 deletions(-) create mode 100644 experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallOpenAPITaskBuilder.java create mode 100644 experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallOpenAPIConfigurer.java create mode 100644 experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallOpenAPISpec.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallOpenAPITaskBuilder.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallOpenAPIConfigurer.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenAPISpec.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallOpenAPIFluent.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallOpenAPITaskFluent.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EndpointUtil.java create mode 100644 fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenApiDslTest.java diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java index 22cccc4a2..bf06e5897 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallHttpTaskBuilder.java @@ -27,18 +27,8 @@ public class FuncCallHttpTaskBuilder extends TaskBaseBuilder, ConditionalTaskBuilder { - private final CallHTTP callHTTP; - - protected FuncCallHttpTaskBuilder() { - callHTTP = new CallHTTP(); - callHTTP.setWith(new HTTPArguments()); - callHTTP.getWith().setOutput(HTTPArguments.HTTPOutput.CONTENT); - super.setTask(this.callHTTP); - } - - @Override - public CallHTTP build() { - return callHTTP; + FuncCallHttpTaskBuilder() { + super.setTask(new CallHTTP().withWith(new HTTPArguments())); } @Override diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallOpenAPITaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallOpenAPITaskBuilder.java new file mode 100644 index 000000000..8c86af9b0 --- /dev/null +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallOpenAPITaskBuilder.java @@ -0,0 +1,41 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.api.types.CallOpenAPI; +import io.serverlessworkflow.api.types.OpenAPIArguments; +import io.serverlessworkflow.api.types.WithOpenAPIParameters; +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncTaskTransformations; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import io.serverlessworkflow.fluent.spec.spi.CallOpenAPITaskFluent; + +public class FuncCallOpenAPITaskBuilder extends TaskBaseBuilder + implements CallOpenAPITaskFluent, + FuncTaskTransformations, + ConditionalTaskBuilder { + + FuncCallOpenAPITaskBuilder() { + final CallOpenAPI callOpenAPI = new CallOpenAPI(); + callOpenAPI.setWith(new OpenAPIArguments().withParameters(new WithOpenAPIParameters())); + super.setTask(callOpenAPI); + } + + @Override + public FuncCallOpenAPITaskBuilder self() { + return this; + } +} diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java index ee21061fe..b2bdf5fab 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java @@ -89,4 +89,11 @@ public FuncDoTaskBuilder http(String name, Consumer ite this.listBuilder().http(name, itemsConfigurer); return this; } + + @Override + public FuncDoTaskBuilder openapi( + String name, Consumer itemsConfigurer) { + this.listBuilder().openapi(name, itemsConfigurer); + return this; + } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java index dbc232753..cbf954d11 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.fluent.func; import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.CallOpenAPI; import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskItem; @@ -123,8 +124,10 @@ public FuncTaskItemListBuilder fork(String name, Consumer i public FuncTaskItemListBuilder http( String name, Consumer itemsConfigurer) { name = this.defaultNameAndRequireConfig(name, itemsConfigurer); + final FuncCallHttpTaskBuilder httpTaskJavaBuilder = new FuncCallHttpTaskBuilder(); itemsConfigurer.accept(httpTaskJavaBuilder); + final CallHTTP callHTTP = httpTaskJavaBuilder.build(); final CallTask callTask = new CallTask(); callTask.setCallHTTP(callHTTP); @@ -133,4 +136,21 @@ public FuncTaskItemListBuilder http( return this.addTaskItem(new TaskItem(name, task)); } + + @Override + public FuncTaskItemListBuilder openapi( + String name, Consumer itemsConfigurer) { + name = this.defaultNameAndRequireConfig(name, itemsConfigurer); + + final FuncCallOpenAPITaskBuilder openAPITaskBuilder = new FuncCallOpenAPITaskBuilder(); + itemsConfigurer.accept(openAPITaskBuilder); + + final CallOpenAPI callOpenAPI = openAPITaskBuilder.build(); + final CallTask callTask = new CallTask(); + callTask.setCallOpenAPI(callOpenAPI); + final Task task = new Task(); + task.setCallTask(callTask); + + return this.addTaskItem(new TaskItem(name, task)); + } } diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallOpenAPIConfigurer.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallOpenAPIConfigurer.java new file mode 100644 index 000000000..210b5792b --- /dev/null +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/configurers/FuncCallOpenAPIConfigurer.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.fluent.func.configurers; + +import io.serverlessworkflow.fluent.func.FuncCallOpenAPITaskBuilder; +import java.util.function.Consumer; + +@FunctionalInterface +public interface FuncCallOpenAPIConfigurer extends Consumer {} diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallOpenAPISpec.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallOpenAPISpec.java new file mode 100644 index 000000000..5915920bc --- /dev/null +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncCallOpenAPISpec.java @@ -0,0 +1,90 @@ +/* + * 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.fluent.func.dsl; + +import io.serverlessworkflow.api.types.OpenAPIArguments; +import io.serverlessworkflow.fluent.func.FuncCallOpenAPITaskBuilder; +import io.serverlessworkflow.fluent.func.configurers.FuncCallOpenAPIConfigurer; +import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer; +import io.serverlessworkflow.fluent.spec.spi.CallOpenAPITaskFluent; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class FuncCallOpenAPISpec implements FuncCallOpenAPIConfigurer { + + private final List>> steps = new ArrayList<>(); + + public FuncCallOpenAPISpec document(String uri) { + steps.add(b -> b.document(uri)); + return this; + } + + public FuncCallOpenAPISpec document( + String uri, AuthenticationConfigurer authenticationConfigurer) { + steps.add(b -> b.document(uri, authenticationConfigurer)); + return this; + } + + public FuncCallOpenAPISpec document(URI uri) { + steps.add(b -> b.document(uri)); + return this; + } + + public FuncCallOpenAPISpec document(URI uri, AuthenticationConfigurer authenticationConfigurer) { + steps.add(b -> b.document(uri, authenticationConfigurer)); + return this; + } + + public FuncCallOpenAPISpec operation(String operationId) { + steps.add(b -> b.operation(operationId)); + return this; + } + + public FuncCallOpenAPISpec parameters(Map params) { + steps.add(b -> b.parameters(params)); + return this; + } + + public FuncCallOpenAPISpec parameter(String name, String value) { + steps.add(b -> b.parameter(name, value)); + return this; + } + + public FuncCallOpenAPISpec redirect(boolean redirect) { + steps.add(b -> b.redirect(redirect)); + return this; + } + + public FuncCallOpenAPISpec authentication(AuthenticationConfigurer authenticationConfigurer) { + steps.add(b -> b.authentication(authenticationConfigurer)); + return this; + } + + public FuncCallOpenAPISpec output(OpenAPIArguments.WithOpenAPIOutput output) { + steps.add(b -> b.output(output)); + return this; + } + + @Override + public void accept(FuncCallOpenAPITaskBuilder builder) { + for (var s : steps) { + s.accept(builder); + } + } +} diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java index bb82e3373..a98153fcc 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java @@ -926,6 +926,82 @@ public static FuncTaskConfigurer call(String name, FuncCallHttpSpec spec) { return call(name, spec::accept); } + /** + * OpenAPI call using a fluent {@link FuncCallOpenAPISpec}. + * + *

This overload creates an unnamed OpenAPI call task. + * + *

{@code
+   * FuncWorkflowBuilder.workflow("openapi-call")
+   *   .tasks(
+   *     FuncDSL.call(
+   *       FuncDSL.openapi()
+   *         .document("https://petstore.swagger.io/v2/swagger.json", DSL.auth("openapi-auth"))
+   *         .operation("getPetById")
+   *     )
+   *   )
+   *   .build();
+   * }
+ * + * @param spec fluent OpenAPI spec built via {@link #openapi()} + * @return a {@link FuncTaskConfigurer} that adds an OpenAPI call task to the workflow + */ + public static FuncTaskConfigurer call(FuncCallOpenAPISpec spec) { + return call(null, spec); + } + + /** + * OpenAPI call using a fluent {@link FuncCallOpenAPISpec} with an explicit task name. + * + *

Example: + * + *

{@code
+   * FuncWorkflowBuilder.workflow("openapi-call-named")
+   *   .tasks(
+   *     FuncDSL.call(
+   *       "fetchPet",
+   *       FuncDSL.openapi()
+   *         .document("https://petstore.swagger.io/v2/swagger.json", DSL.auth("openapi-auth"))
+   *         .operation("getPetById")
+   *         .parameter("id", 123)
+   *     )
+   *   )
+   *   .build();
+   * }
+ * + * @param name task name, or {@code null} for an anonymous task + * @param spec fluent OpenAPI spec built via {@link #openapi()} + * @return a {@link FuncTaskConfigurer} that adds a named OpenAPI call task + */ + public static FuncTaskConfigurer call(String name, FuncCallOpenAPISpec spec) { + Objects.requireNonNull(spec, "spec"); + return list -> list.openapi(name, spec); + } + + /** + * Create a new OpenAPI specification to be used with {@link #call(FuncCallOpenAPISpec)}. + * + *

Typical usage: + * + *

{@code
+   * FuncDSL.call(
+   *   FuncDSL.openapi()
+   *     .document("https://petstore.swagger.io/v2/swagger.json", DSL.auth("openapi-auth"))
+   *     .operation("getPetById")
+   *     .parameter("id", 123)
+   * );
+   * }
+ * + *

The returned spec is a fluent builder that records operations (document, operation, + * parameters, authentication, etc.) and applies them to the underlying OpenAPI call task at build + * time. + * + * @return a new {@link FuncCallOpenAPISpec} + */ + public static FuncCallOpenAPISpec openapi() { + return new FuncCallOpenAPISpec(); + } + /** * Create a new, empty HTTP specification to be used with {@link #call(FuncCallHttpSpec)}. * @@ -1095,11 +1171,11 @@ public static FuncTaskConfigurer get(String name, URI endpoint, AuthenticationCo * } * * @param body HTTP request body (literal value or expression-compatible object) - * @param endpoint literal or expression for the endpoint URL + * @param endpointExpr literal or expression for the endpoint URL * @return a {@link FuncTaskConfigurer} adding a {@code POST} HTTP task */ - public static FuncTaskConfigurer post(Object body, String endpoint) { - return post(null, body, endpoint); + public static FuncTaskConfigurer post(Object body, String endpointExpr) { + return post(null, body, endpointExpr); } /** diff --git a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java index 7b2acb941..ee9f857e0 100644 --- a/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java +++ b/experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.fluent.func.spi; import io.serverlessworkflow.fluent.func.FuncCallHttpTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncCallOpenAPITaskBuilder; import io.serverlessworkflow.fluent.func.FuncCallTaskBuilder; import io.serverlessworkflow.fluent.func.FuncEmitTaskBuilder; import io.serverlessworkflow.fluent.func.FuncForTaskBuilder; @@ -24,6 +25,7 @@ import io.serverlessworkflow.fluent.func.FuncSetTaskBuilder; import io.serverlessworkflow.fluent.func.FuncSwitchTaskBuilder; import io.serverlessworkflow.fluent.spec.spi.CallHttpFluent; +import io.serverlessworkflow.fluent.spec.spi.CallOpenAPIFluent; import io.serverlessworkflow.fluent.spec.spi.EmitFluent; import io.serverlessworkflow.fluent.spec.spi.ForEachFluent; import io.serverlessworkflow.fluent.spec.spi.ForkFluent; @@ -39,4 +41,5 @@ public interface FuncDoFluent> ForkFluent, ListenFluent, CallFnFluent, - CallHttpFluent {} + CallHttpFluent, + CallOpenAPIFluent {} diff --git a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java index 403225746..2425eb6d3 100644 --- a/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java +++ b/experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java @@ -18,8 +18,10 @@ import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.emit; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.event; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function; +import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.get; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.listen; import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.toOne; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.auth; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -229,7 +231,7 @@ void http_spec_via_call_builds_call_http_task() { assertEquals("GET", http.getWith().getMethod(), "HTTP method should be GET"); assertEquals( "http://service/health", - http.getWith().getEndpoint().getRuntimeExpression(), + http.getWith().getEndpoint().getUriTemplate().getLiteralUri().toString(), "endpoint should match the DSL endpoint"); } @@ -238,7 +240,7 @@ void http_spec_via_call_builds_call_http_task() { void get_convenience_creates_http_get() { Workflow wf = FuncWorkflowBuilder.workflow("http-get-convenience") - .tasks(FuncDSL.get("http://service/status")) + .tasks(get("http://service/status")) .build(); List items = wf.getDo(); @@ -251,7 +253,7 @@ void get_convenience_creates_http_get() { assertEquals("GET", http.getWith().getMethod()); assertEquals( "http://service/status", - http.getWith().getEndpoint().getRuntimeExpression(), + http.getWith().getEndpoint().getUriTemplate().getLiteralUri().toString(), "endpoint should be set from get(endpoint)"); } @@ -260,11 +262,7 @@ void get_convenience_creates_http_get() { void get_named_with_authentication_uses_auth_policy() { Workflow wf = FuncWorkflowBuilder.workflow("http-get-auth") - .tasks( - FuncDSL.get( - "fetchUsers", - "http://service/api/users", - auth -> auth.use("user-service-auth"))) + .tasks(get("fetchUsers", "http://service/api/users", auth("user-service-auth"))) .build(); List items = wf.getDo(); @@ -278,7 +276,7 @@ void get_named_with_authentication_uses_auth_policy() { assertEquals("GET", http.getWith().getMethod()); assertEquals( "http://service/api/users", - http.getWith().getEndpoint().getRuntimeExpression(), + http.getWith().getEndpoint().getUriTemplate().getLiteralUri().toString(), "endpoint should be set from get(name, endpoint, auth)"); assertNotNull( @@ -302,7 +300,7 @@ void get_with_uri_and_authentication() { Workflow wf = FuncWorkflowBuilder.workflow("http-get-uri-auth") - .tasks(FuncDSL.get("checkHealth", endpoint, auth -> auth.use("tls-auth"))) + .tasks(get("checkHealth", endpoint, auth -> auth.use("tls-auth"))) .build(); List items = wf.getDo(); @@ -350,7 +348,7 @@ void post_convenience_creates_http_post_with_body() { assertEquals("POST", http.getWith().getMethod()); assertEquals( "http://service/api/users", - http.getWith().getEndpoint().getRuntimeExpression(), + http.getWith().getEndpoint().getUriTemplate().getLiteralUri().toString(), "endpoint should be set from post(body, endpoint)"); assertNotNull(http.getWith().getBody(), "Body should be set on POST"); @@ -383,7 +381,7 @@ void post_named_with_authentication() { assertEquals("POST", http.getWith().getMethod()); assertEquals( "https://orders.example.com/api/orders", - http.getWith().getEndpoint().getRuntimeExpression()); + http.getWith().getEndpoint().getUriTemplate().getLiteralUri().toString()); assertEquals(body, http.getWith().getBody()); assertNotNull(http.getWith().getEndpoint().getEndpointConfiguration().getAuthentication()); @@ -419,7 +417,9 @@ void call_with_preconfigured_http_spec() { CallHTTP http = (CallHTTP) t.getCallTask().get(); assertEquals("POST", http.getWith().getMethod()); - assertEquals("http://service/api", http.getWith().getEndpoint().getRuntimeExpression()); + assertEquals( + "http://service/api", + http.getWith().getEndpoint().getUriTemplate().getLiteralUri().toString()); assertEquals( "svc-auth", http.getWith() diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java index 49c3a9132..0ff056dac 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHttpTaskBuilder.java @@ -22,21 +22,13 @@ public class CallHttpTaskBuilder extends TaskBaseBuilder implements CallHttpTaskFluent { - private final CallHTTP callHTTP; - protected CallHttpTaskBuilder() { - callHTTP = new CallHTTP(); - callHTTP.setWith(new HTTPArguments()); - callHTTP.getWith().setOutput(HTTPArguments.HTTPOutput.CONTENT); - super.setTask(this.callHTTP); + final CallHTTP callHTTP = new CallHTTP().withWith(new HTTPArguments()); + super.setTask(callHTTP); } @Override public CallHttpTaskBuilder self() { return this; } - - public CallHTTP build() { - return callHTTP; - } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallOpenAPITaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallOpenAPITaskBuilder.java new file mode 100644 index 000000000..cc7ebce99 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallOpenAPITaskBuilder.java @@ -0,0 +1,36 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.CallOpenAPI; +import io.serverlessworkflow.api.types.OpenAPIArguments; +import io.serverlessworkflow.api.types.WithOpenAPIParameters; +import io.serverlessworkflow.fluent.spec.spi.CallOpenAPITaskFluent; + +public class CallOpenAPITaskBuilder extends TaskBaseBuilder + implements CallOpenAPITaskFluent { + + CallOpenAPITaskBuilder() { + final CallOpenAPI callOpenAPI = new CallOpenAPI(); + callOpenAPI.setWith(new OpenAPIArguments().withParameters(new WithOpenAPIParameters())); + super.setTask(callOpenAPI); + } + + @Override + public CallOpenAPITaskBuilder self() { + return this; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java index bbeb1d1de..98f599a7b 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java @@ -91,4 +91,10 @@ public DoTaskBuilder tryCatch( this.listBuilder().tryCatch(name, itemsConfigurer); return this; } + + @Override + public DoTaskBuilder openapi(String name, Consumer itemsConfigurer) { + this.listBuilder().openapi(name, itemsConfigurer); + return this; + } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java index 836e6809d..6a0736b00 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java @@ -119,8 +119,10 @@ public TaskItemListBuilder tryCatch( @Override public TaskItemListBuilder http(String name, Consumer itemsConfigurer) { name = defaultNameAndRequireConfig(name, itemsConfigurer); + final CallHttpTaskBuilder callHTTPBuilder = new CallHttpTaskBuilder(); itemsConfigurer.accept(callHTTPBuilder); + final CallTask callTask = new CallTask(); callTask.setCallHTTP(callHTTPBuilder.build()); final Task task = new Task(); @@ -128,4 +130,20 @@ public TaskItemListBuilder http(String name, Consumer items return addTaskItem(new TaskItem(name, task)); } + + @Override + public TaskItemListBuilder openapi( + String name, Consumer itemsConfigurer) { + name = defaultNameAndRequireConfig(name, itemsConfigurer); + + final CallOpenAPITaskBuilder callOpenAPIBuilder = new CallOpenAPITaskBuilder(); + itemsConfigurer.accept(callOpenAPIBuilder); + + final CallTask callTask = new CallTask(); + callTask.setCallOpenAPI(callOpenAPIBuilder.build()); + final Task task = new Task(); + task.setCallTask(callTask); + + return addTaskItem(new TaskItem(name, task)); + } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallOpenAPIConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallOpenAPIConfigurer.java new file mode 100644 index 000000000..730b3ca1d --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallOpenAPIConfigurer.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.fluent.spec.configurers; + +import io.serverlessworkflow.fluent.spec.CallOpenAPITaskBuilder; +import java.util.function.Consumer; + +@FunctionalInterface +public interface CallOpenAPIConfigurer extends Consumer {} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenAPISpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenAPISpec.java new file mode 100644 index 000000000..8da74f1c4 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenAPISpec.java @@ -0,0 +1,89 @@ +/* + * 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.fluent.spec.dsl; + +import io.serverlessworkflow.api.types.OpenAPIArguments; +import io.serverlessworkflow.fluent.spec.CallOpenAPITaskBuilder; +import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer; +import io.serverlessworkflow.fluent.spec.configurers.CallOpenAPIConfigurer; +import io.serverlessworkflow.fluent.spec.spi.CallOpenAPITaskFluent; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public final class CallOpenAPISpec implements CallOpenAPIConfigurer { + + private final List>> steps = new ArrayList<>(); + + public CallOpenAPISpec document(String uri) { + steps.add(b -> b.document(uri)); + return this; + } + + public CallOpenAPISpec document(String uri, AuthenticationConfigurer authenticationConfigurer) { + steps.add(b -> b.document(uri, authenticationConfigurer)); + return this; + } + + public CallOpenAPISpec document(URI uri) { + steps.add(b -> b.document(uri)); + return this; + } + + public CallOpenAPISpec document(URI uri, AuthenticationConfigurer authenticationConfigurer) { + steps.add(b -> b.document(uri, authenticationConfigurer)); + return this; + } + + public CallOpenAPISpec operation(String operationId) { + steps.add(b -> b.operation(operationId)); + return this; + } + + public CallOpenAPISpec parameters(Map params) { + steps.add(b -> b.parameters(params)); + return this; + } + + public CallOpenAPISpec parameter(String name, String value) { + steps.add(b -> b.parameter(name, value)); + return this; + } + + public CallOpenAPISpec redirect(boolean redirect) { + steps.add(b -> b.redirect(redirect)); + return this; + } + + public CallOpenAPISpec authentication(AuthenticationConfigurer authenticationConfigurer) { + steps.add(b -> b.authentication(authenticationConfigurer)); + return this; + } + + public CallOpenAPISpec output(OpenAPIArguments.WithOpenAPIOutput output) { + steps.add(b -> b.output(output)); + return this; + } + + @Override + public void accept(CallOpenAPITaskBuilder builder) { + for (var s : steps) { + s.accept(builder); + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java index 42bad108f..e586db8c9 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java @@ -23,6 +23,7 @@ import io.serverlessworkflow.fluent.spec.TryTaskBuilder; import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer; import io.serverlessworkflow.fluent.spec.configurers.CallHttpConfigurer; +import io.serverlessworkflow.fluent.spec.configurers.CallOpenAPIConfigurer; import io.serverlessworkflow.fluent.spec.configurers.ForEachConfigurer; import io.serverlessworkflow.fluent.spec.configurers.ListenConfigurer; import io.serverlessworkflow.fluent.spec.configurers.RaiseConfigurer; @@ -48,6 +49,10 @@ public static CallHttpSpec http() { return new CallHttpSpec(); } + public static CallOpenAPISpec openapi() { + return new CallOpenAPISpec(); + } + public static SwitchSpec cases() { return new SwitchSpec(); } @@ -91,6 +96,10 @@ public static Consumer errorFilter( return e -> e.type(errType.toString()).status(status); } + public static AuthenticationConfigurer auth(String authName) { + return a -> a.use(authName); + } + public static AuthenticationConfigurer basic(String username, String password) { return a -> a.basic(b -> b.username(username).password(password)); } @@ -200,6 +209,10 @@ public static TasksConfigurer call(CallHttpConfigurer configurer) { return list -> list.http(configurer); } + public static TasksConfigurer call(CallOpenAPIConfigurer configurer) { + return list -> list.openapi(configurer); + } + public static TasksConfigurer set(SetConfigurer configurer) { return list -> list.set(configurer); } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java index e6a873582..5df3123ce 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHttpTaskFluent.java @@ -34,7 +34,13 @@ public interface CallHttpTaskFluent> { - CallHTTP build(); + default CallHTTP build() { + final CallHTTP callHTTP = ((CallHTTP) this.self().getTask()); + if (callHTTP.getWith().getOutput() == null) { + callHTTP.getWith().setOutput(HTTPArguments.HTTPOutput.CONTENT); + } + return callHTTP; + } SELF self(); @@ -65,9 +71,7 @@ default SELF endpoint(URI endpoint, Consumer, LIST> { + + LIST openapi(String name, Consumer itemsConfigurer); + + default LIST openapi(Consumer itemsConfigurer) { + return this.openapi(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallOpenAPITaskFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallOpenAPITaskFluent.java new file mode 100644 index 000000000..48b1a701e --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallOpenAPITaskFluent.java @@ -0,0 +1,143 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.api.types.CallOpenAPI; +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.EndpointConfiguration; +import io.serverlessworkflow.api.types.EndpointUri; +import io.serverlessworkflow.api.types.ExternalResource; +import io.serverlessworkflow.api.types.OpenAPIArguments; +import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.fluent.spec.ReferenceableAuthenticationPolicyBuilder; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer; +import java.net.URI; +import java.util.Map; + +public interface CallOpenAPITaskFluent> { + + default CallOpenAPI build() { + final CallOpenAPI task = ((CallOpenAPI) this.self().getTask()); + if (task.getWith().getOutput() == null) { + task.getWith().setOutput(OpenAPIArguments.WithOpenAPIOutput.CONTENT); + } + return task; + } + + SELF self(); + + default SELF document(String uri) { + ((CallOpenAPI) this.self().getTask()) + .getWith() + .withDocument( + new ExternalResource().withEndpoint(new Endpoint().withRuntimeExpression(uri))); + return self(); + } + + default SELF document(URI uri) { + ((CallOpenAPI) this.self().getTask()) + .getWith() + .withDocument( + new ExternalResource() + .withEndpoint( + new Endpoint().withUriTemplate(new UriTemplate().withLiteralUri(uri)))); + return self(); + } + + default SELF document(String uri, AuthenticationConfigurer authenticationConfigurer) { + final ReferenceableAuthenticationPolicyBuilder policy = + new ReferenceableAuthenticationPolicyBuilder(); + authenticationConfigurer.accept(policy); + ((CallOpenAPI) this.self().getTask()).getWith().setAuthentication(policy.build()); + ((CallOpenAPI) this.self().getTask()) + .getWith() + .setDocument( + new ExternalResource() + .withEndpoint( + new Endpoint() + .withRuntimeExpression(uri) + .withEndpointConfiguration( + new EndpointConfiguration() + .withUri(new EndpointUri().withExpressionEndpointURI(uri)) + .withAuthentication(policy.build())))); + return self(); + } + + default SELF document(URI uri, AuthenticationConfigurer authenticationConfigurer) { + final ReferenceableAuthenticationPolicyBuilder policy = + new ReferenceableAuthenticationPolicyBuilder(); + authenticationConfigurer.accept(policy); + + ((CallOpenAPI) this.self().getTask()).getWith().setAuthentication(policy.build()); + ((CallOpenAPI) this.self().getTask()) + .getWith() + .setDocument( + new ExternalResource() + .withEndpoint( + new Endpoint() + .withUriTemplate(new UriTemplate().withLiteralUri(uri)) + .withEndpointConfiguration( + new EndpointConfiguration() + .withUri( + new EndpointUri() + .withLiteralEndpointURI( + new UriTemplate().withLiteralUri(uri))) + .withAuthentication(policy.build())))); + return self(); + } + + default SELF operation(String operation) { + ((CallOpenAPI) this.self().getTask()).getWith().setOperationId(operation); + return self(); + } + + default SELF parameters(Map parameters) { + ((CallOpenAPI) this.self().getTask()) + .getWith() + .getParameters() + .getAdditionalProperties() + .putAll(parameters); + return self(); + } + + default SELF parameter(String name, String value) { + ((CallOpenAPI) this.self().getTask()) + .getWith() + .getParameters() + .getAdditionalProperties() + .put(name, value); + return self(); + } + + default SELF authentication(AuthenticationConfigurer authenticationConfigurer) { + final ReferenceableAuthenticationPolicyBuilder policy = + new ReferenceableAuthenticationPolicyBuilder(); + authenticationConfigurer.accept(policy); + ((CallOpenAPI) this.self().getTask()).getWith().setAuthentication(policy.build()); + return self(); + } + + default SELF output(OpenAPIArguments.WithOpenAPIOutput output) { + ((CallOpenAPI) this.self().getTask()).getWith().setOutput(output); + return self(); + } + + default SELF redirect(boolean redirect) { + ((CallOpenAPI) this.self().getTask()).getWith().setRedirect(redirect); + return self(); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java index e3d5d3610..c11c2d194 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.fluent.spec.spi; import io.serverlessworkflow.fluent.spec.CallHttpTaskBuilder; +import io.serverlessworkflow.fluent.spec.CallOpenAPITaskBuilder; import io.serverlessworkflow.fluent.spec.EmitTaskBuilder; import io.serverlessworkflow.fluent.spec.ForEachTaskBuilder; import io.serverlessworkflow.fluent.spec.ForkTaskBuilder; @@ -42,4 +43,5 @@ public interface DoFluent ForEachFluent, T>, ForkFluent, ListenFluent, - RaiseFluent {} + RaiseFluent, + CallOpenAPIFluent {} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EndpointUtil.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EndpointUtil.java new file mode 100644 index 000000000..d1b6a3efa --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EndpointUtil.java @@ -0,0 +1,66 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.UriTemplate; +import java.net.URI; +import java.util.Objects; + +public final class EndpointUtil { + + private EndpointUtil() {} + + public static Endpoint fromString(String expr) { + Objects.requireNonNull(expr, "Endpoint expression cannot be null"); + String trimmed = expr.trim(); + Endpoint endpoint = new Endpoint(); + if (isUrlLike(trimmed)) { + UriTemplate template = new UriTemplate(); + if (trimmed.indexOf('{') >= 0 || trimmed.indexOf('}') >= 0) { + template.setLiteralUriTemplate(trimmed); + } else { + template.setLiteralUri(URI.create(trimmed)); + } + endpoint.setUriTemplate(template); + return endpoint; + } + + // Let the runtime engine to verify if it's a valid jq expression since ${} it's not the only + // way of checking it. + endpoint.setRuntimeExpression(expr); + return endpoint; + } + + private static boolean isUrlLike(String value) { + // same idea as UriTemplate.literalUriTemplate_Pattern: ^[A-Za-z][A-Za-z0-9+\\-.]*://.* + int idx = value.indexOf("://"); + if (idx <= 0) { + return false; + } + char first = value.charAt(0); + if (!Character.isLetter(first)) { + return false; + } + for (int i = 1; i < idx; i++) { + char c = value.charAt(i); + if (!(Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.')) { + return false; + } + } + return true; + } +} diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java index fe9125365..d62fd22af 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java @@ -444,7 +444,10 @@ void testDoTaskCallHTTPHeadersConsumerAndMap() { Workflow wf2 = WorkflowBuilder.workflow() - .tasks(d -> d.http(http().GET().endpoint("expr").headers(Map.of("X", "10", "Y", "20")))) + .tasks( + d -> + d.http( + http().GET().endpoint("${ expr }").headers(Map.of("X", "10", "Y", "20")))) .build(); CallHTTP call2 = wf2.getDo().get(0).getTask().getCallTask().getCallHTTP(); HTTPHeaders hh2 = call2.getWith().getHeaders().getHTTPHeaders(); @@ -462,7 +465,7 @@ void testDoTaskCallHTTPQueryConsumerAndMap() { "qryCall", http() .GET() - .endpoint("exprUri") + .endpoint("${ exprUri }") .andThen(q -> q.query(Map.of("k1", "v1", "k2", "v2"))))) .build(); HTTPQuery hq = @@ -475,7 +478,10 @@ void testDoTaskCallHTTPQueryConsumerAndMap() { .tasks( d -> d.http( - c -> c.method("GET").endpoint("uri").query(Map.of("q1", "x", "q2", "y")))) + c -> + c.method("GET") + .endpoint("http://uri") + .query(Map.of("q1", "x", "q2", "y")))) .build(); HTTPQuery hq2 = wf2.getDo() @@ -500,7 +506,7 @@ void testDoTaskCallHTTPRedirectAndOutput() { "optCall", c -> c.method("DELETE") - .endpoint("expr") + .endpoint("${ expr }") .redirect(true) .output(HTTPArguments.HTTPOutput.RESPONSE))) .build(); diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java index 09757d510..874cce3b5 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java @@ -17,6 +17,7 @@ import static io.serverlessworkflow.fluent.spec.dsl.DSL.basic; import static io.serverlessworkflow.fluent.spec.dsl.DSL.bearer; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.call; import static io.serverlessworkflow.fluent.spec.dsl.DSL.digest; import static io.serverlessworkflow.fluent.spec.dsl.DSL.http; import static io.serverlessworkflow.fluent.spec.dsl.DSL.oauth2; @@ -37,7 +38,7 @@ public class CallHttpAuthDslTest { void when_call_http_with_basic_auth_on_endpoint_expr() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") - .tasks(t -> t.http(http().GET().endpoint(EXPR_ENDPOINT, basic("alice", "secret")))) + .tasks(call(http().GET().endpoint(EXPR_ENDPOINT, basic("alice", "secret")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); @@ -77,7 +78,7 @@ void when_call_http_with_basic_auth_on_endpoint_expr() { void when_call_http_with_bearer_auth_on_endpoint_expr() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") - .tasks(t -> t.http(http().GET().endpoint(EXPR_ENDPOINT, bearer("token-123")))) + .tasks(call(http().GET().endpoint(EXPR_ENDPOINT, bearer("token-123")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); @@ -106,7 +107,7 @@ void when_call_http_with_bearer_auth_on_endpoint_expr() { void when_call_http_with_digest_auth_on_endpoint_expr() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") - .tasks(t -> t.http(http().GET().endpoint(EXPR_ENDPOINT, digest("bob", "p@ssw0rd")))) + .tasks(call(http().GET().endpoint(EXPR_ENDPOINT, digest("bob", "p@ssw0rd")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); @@ -142,18 +143,17 @@ void when_call_http_with_oidc_auth_on_endpoint_expr_with_client() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") .tasks( - t -> - t.http( - http() - .POST() - .endpoint( - EXPR_ENDPOINT, - oidc( - "https://auth.example.com/", - OAuth2AuthenticationData.OAuth2AuthenticationDataGrant - .CLIENT_CREDENTIALS, - "client-id", - "client-secret")))) + call( + http() + .POST() + .endpoint( + EXPR_ENDPOINT, + oidc( + "https://auth.example.com/", + OAuth2AuthenticationData.OAuth2AuthenticationDataGrant + .CLIENT_CREDENTIALS, + "client-id", + "client-secret")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); @@ -190,16 +190,15 @@ void when_call_http_with_oauth2_alias_on_endpoint_expr_without_client() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") .tasks( - t -> - t.http( - http() - .POST() - .endpoint( - EXPR_ENDPOINT, - oauth2( - "https://auth.example.com/", - OAuth2AuthenticationData.OAuth2AuthenticationDataGrant - .CLIENT_CREDENTIALS)))) + call( + http() + .POST() + .endpoint( + EXPR_ENDPOINT, + oauth2( + "https://auth.example.com/", + OAuth2AuthenticationData.OAuth2AuthenticationDataGrant + .CLIENT_CREDENTIALS)))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); @@ -233,10 +232,7 @@ void when_call_http_with_oauth2_alias_on_endpoint_expr_without_client() { void when_call_http_with_basic_auth_on_uri_string() { Workflow wf = WorkflowBuilder.workflow("f", "ns", "1") - .tasks( - t -> - t.http( - http().GET().uri("https://api.example.com/v1/resource", basic("u", "p")))) + .tasks(call(http().GET().uri("https://api.example.com/v1/resource", basic("u", "p")))) .build(); var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenApiDslTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenApiDslTest.java new file mode 100644 index 000000000..70a91f282 --- /dev/null +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallOpenApiDslTest.java @@ -0,0 +1,95 @@ +/* + * 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.fluent.spec.dsl; + +import static io.serverlessworkflow.fluent.spec.dsl.DSL.basic; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.call; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.openapi; +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.types.OpenAPIArguments; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.spec.WorkflowBuilder; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class CallOpenApiDslTest { + + private static final String EXPR_DOCUMENT = "${ \"https://api.example.com/v1/openapi.yaml\" }"; + + @Test + void when_call_openapi_with_basic_auth_on_document_expr() { + Workflow wf = + WorkflowBuilder.workflow("f", "ns", "1") + .tasks( + call( + openapi() + .document(EXPR_DOCUMENT, basic("alice", "secret")) + .operation("getPetById") + .parameter("id", "123"))) + .build(); + + var taskItem = wf.getDo().get(0); + var callOpenAPI = taskItem.getTask().getCallTask().getCallOpenAPI(); + assertThat(callOpenAPI).isNotNull(); + + var with = callOpenAPI.getWith(); + assertThat(with).isNotNull(); + + // Default output is CONTENT if not explicitly set + assertThat(with.getOutput()).isEqualTo(OpenAPIArguments.WithOpenAPIOutput.CONTENT); + + // Document and endpoint expression + assertThat(with.getDocument()).isNotNull(); + assertThat(with.getDocument().getEndpoint()).isNotNull(); + assertThat(with.getDocument().getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_DOCUMENT); + + // Endpoint configuration URI expression + var endpointConfig = with.getDocument().getEndpoint().getEndpointConfiguration(); + assertThat(endpointConfig).isNotNull(); + assertThat(endpointConfig.getUri()).isNotNull(); + assertThat(endpointConfig.getUri().getExpressionEndpointURI()).isEqualTo(EXPR_DOCUMENT); + + // Parameters wired through DSL + assertThat(with.getParameters()).isNotNull(); + Map params = with.getParameters().getAdditionalProperties(); + assertThat(params).containsEntry("id", "123"); + + // Authentication attached to endpoint configuration + var endpointAuth = endpointConfig.getAuthentication().getAuthenticationPolicy(); + assertThat(endpointAuth).isNotNull(); + assertThat(endpointAuth.getBasicAuthenticationPolicy()).isNotNull(); + assertThat( + endpointAuth + .getBasicAuthenticationPolicy() + .getBasic() + .getBasicAuthenticationProperties() + .getUsername()) + .isEqualTo("alice"); + assertThat( + endpointAuth + .getBasicAuthenticationPolicy() + .getBasic() + .getBasicAuthenticationProperties() + .getPassword()) + .isEqualTo("secret"); + + // Authentication also attached at the top-level "with.authentication" + var topLevelAuth = with.getAuthentication().getAuthenticationPolicy(); + assertThat(topLevelAuth).isNotNull(); + assertThat(topLevelAuth.getBasicAuthenticationPolicy()).isNotNull(); + } +} diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java index f959d89f3..e053e3c70 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java @@ -15,6 +15,7 @@ */ package io.serverlessworkflow.fluent.spec.dsl; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.call; import static io.serverlessworkflow.fluent.spec.dsl.DSL.error; import static io.serverlessworkflow.fluent.spec.dsl.DSL.event; import static io.serverlessworkflow.fluent.spec.dsl.DSL.http; @@ -36,13 +37,12 @@ public void when_new_http_call_task() { Workflow wf = WorkflowBuilder.workflow("myFlow", "myNs", "1.2.3") .tasks( - t -> - t.http( - http() - .acceptJSON() - .header("CustomKey", "CustomValue") - .POST() - .endpoint("${ \"https://petstore.swagger.io/v2/pet/\\(.petId)\" }"))) + call( + http() + .acceptJSON() + .header("CustomKey", "CustomValue") + .POST() + .endpoint("${ \"https://petstore.swagger.io/v2/pet/\\(.petId)\" }"))) .build(); HTTPArguments args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith(); diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java index 1401b13ef..185b65aa2 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java @@ -15,6 +15,7 @@ */ package io.serverlessworkflow.fluent.spec.dsl; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.call; import static io.serverlessworkflow.fluent.spec.dsl.DSL.emit; import static io.serverlessworkflow.fluent.spec.dsl.DSL.event; import static io.serverlessworkflow.fluent.spec.dsl.DSL.http; @@ -41,7 +42,7 @@ void when_try_with_tasks_and_catch_when_with_retry_and_tasks() { t.tryCatch( tryCatch() // try block (one HTTP call) - .tasks(DSL.call(http().GET().endpoint(EXPR_ENDPOINT))) + .tasks(call(http().GET().endpoint(EXPR_ENDPOINT))) // catch block .catches() .when("$.error == true") @@ -115,7 +116,7 @@ void when_try_with_multiple_tasks_and_catch_except_when_with_uri_error_filter() tryCatch() // try with two tasks .tasks( - DSL.call(http().GET().endpoint(EXPR_ENDPOINT)), + call(http().GET().endpoint(EXPR_ENDPOINT)), set("$.status = \"IN_FLIGHT\"")) // catch with exceptWhen + explicit URI error filter + status .catches() @@ -170,7 +171,7 @@ void when_try_with_catch_and_simple_retry_limit_only() { t -> t.tryCatch( tryCatch() - .tasks(DSL.call(http().GET().endpoint(EXPR_ENDPOINT))) + .tasks(call(http().GET().endpoint(EXPR_ENDPOINT))) .catches() .when("$.fail == true") .errors(Errors.COMMUNICATION, 503) From 566ecec280bf16435b2745d55788a6ba9880e177 Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Fri, 14 Nov 2025 18:31:32 -0500 Subject: [PATCH 3/3] Add use, secrets, auth Signed-off-by: Ricardo Zanini --- .../fluent/spec/BaseWorkflowBuilder.java | 18 + .../fluent/spec/UseBuilder.java | 8 +- .../spec/configurers/UseConfigurer.java | 22 + .../fluent/spec/dsl/DSL.java | 491 +++++++++++++++++- .../fluent/spec/dsl/UseSpec.java | 47 ++ .../fluent/spec/dsl/DSLTest.java | 21 + 6 files changed, 602 insertions(+), 5 deletions(-) create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/UseConfigurer.java create mode 100644 fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/UseSpec.java diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java index b81b2c222..8b042f485 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java @@ -79,6 +79,24 @@ public SELF use(Consumer useBuilderConsumer) { return self(); } + @SafeVarargs + public final SELF use(Consumer... configurers) { + if (configurers == null || configurers.length == 0) { + return self(); + } + return use(List.of(configurers.clone())); + } + + private SELF use(List> configurers) { + final UseBuilder builder = new UseBuilder(); + configurers.forEach( + c -> { + if (c != null) c.accept(builder); + }); + this.workflow.setUse(builder.build()); + return self(); + } + public SELF tasks(Consumer doTaskConsumer) { return appendDo(doTaskConsumer); } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseBuilder.java index 668331116..d3b9bd552 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseBuilder.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.fluent.spec; import io.serverlessworkflow.api.types.Use; +import io.serverlessworkflow.api.types.UseAuthentications; import java.util.List; import java.util.function.Consumer; @@ -25,6 +26,7 @@ public class UseBuilder { UseBuilder() { this.use = new Use(); + this.use.setAuthentications(new UseAuthentications()); } public UseBuilder secrets(final String... secrets) { @@ -37,7 +39,11 @@ public UseBuilder secrets(final String... secrets) { public UseBuilder authentications(Consumer authenticationsConsumer) { final UseAuthenticationsBuilder builder = new UseAuthenticationsBuilder(); authenticationsConsumer.accept(builder); - this.use.setAuthentications(builder.build()); + final UseAuthentications useAuthentications = builder.build(); + this.use + .getAuthentications() + .getAdditionalProperties() + .putAll(useAuthentications.getAdditionalProperties()); return this; } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/UseConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/UseConfigurer.java new file mode 100644 index 000000000..5fe1781d3 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/UseConfigurer.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.fluent.spec.configurers; + +import io.serverlessworkflow.fluent.spec.UseBuilder; +import java.util.function.Consumer; + +@FunctionalInterface +public interface UseConfigurer extends Consumer {} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java index e586db8c9..955d9db53 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java @@ -39,84 +39,355 @@ import java.util.Objects; import java.util.function.Consumer; +/** + * High-level Java DSL shortcuts for building workflows. + * + *

This class exposes small, composable helpers that: + * + *

    + *
  • Build HTTP and OpenAPI call specs. + *
  • Configure {@code use} blocks (secrets and authentications). + *
  • Define switch, listen, event, try/catch and raise recipes. + *
  • Compose tasks lists and common task patterns. + *
+ * + *

All methods are static and designed to be used with static imports. + */ public final class DSL { private DSL() {} // ---- Convenient shortcuts ----// + /** + * Create a new HTTP call specification to be used with {@link #call(CallHttpConfigurer)} or + * {@link #call(io.serverlessworkflow.fluent.func.dsl.FuncCallHttpSpec)}. + * + *

Typical usage: + * + *

{@code
+   * tasks(
+   *   call(
+   *     http()
+   *       .GET()
+   *       .endpoint("http://service/api")
+   *   )
+   * );
+   * }
+ * + * @return a new {@link CallHttpSpec} instance + */ public static CallHttpSpec http() { return new CallHttpSpec(); } + /** + * Create a new OpenAPI call specification to be used with {@link #call(CallOpenAPIConfigurer)}. + * + *

Typical usage: + * + *

{@code
+   * tasks(
+   *   call(
+   *     openapi()
+   *       .document("http://service/openapi.json")
+   *       .operation("getUser")
+   *   )
+   * );
+   * }
+ * + * @return a new {@link CallOpenAPISpec} instance + */ public static CallOpenAPISpec openapi() { return new CallOpenAPISpec(); } + /** + * Convenience for defining a {@code use} block with a single secret. + * + *

Example: + * + *

{@code
+   * workflow()
+   *   .use(secret("db-password"))
+   *   .build();
+   * }
+ * + * @param secret secret identifier to add to the workflow + * @return a {@link UseSpec} preconfigured with the given secret + */ + public static UseSpec secret(String secret) { + return new UseSpec().secret(secret); + } + + /** + * Convenience for defining a {@code use} block with multiple secrets. + * + *

Example: + * + *

{@code
+   * workflow()
+   *   .use(secrets("db-password", "api-key"))
+   *   .build();
+   * }
+ * + * @param secret one or more secret identifiers + * @return a {@link UseSpec} preconfigured with the given secrets + */ + public static UseSpec secrets(String... secret) { + return new UseSpec().secrets(secret); + } + + /** + * Convenience for defining a single reusable authentication policy by name. + * + *

Example: + * + *

{@code
+   * workflow()
+   *   .use(auth("basic-auth", basic("user", "pass")));
+   * }
+ * + * @param name logical authentication policy name + * @param auth authentication configurer (e.g. {@link #basic(String, String)}) + * @return a {@link UseSpec} preconfigured with the given authentication + */ + public static UseSpec auth(String name, AuthenticationConfigurer auth) { + return new UseSpec().auth(name, auth); + } + + /** + * Create an empty {@link UseSpec} for incremental configuration. + * + *

Example: + * + *

{@code
+   * workflow()
+   *   .use(
+   *     use()
+   *       .secret("db-password")
+   *       .auth("basic-auth", basic("u", "p"))
+   *   );
+   * }
+ * + * @return a new, empty {@link UseSpec} + */ + public static UseSpec use() { + return new UseSpec(); + } + + /** + * Create an empty {@link SwitchSpec} for building switch cases. + * + *

Typical usage is via chaining methods on {@link SwitchSpec} and passing it to {@link + * #switchCase(SwitchConfigurer)}. + * + * @return a new {@link SwitchSpec} + */ public static SwitchSpec cases() { return new SwitchSpec(); } + /** + * Start building a {@code listen} specification without a predefined strategy. + * + *

Use methods on {@link ListenSpec} like {@code one()}, {@code any()} or {@code all()} and + * then pass the spec to {@link #listen(ListenConfigurer)}. + * + * @return a new {@link ListenSpec} + */ public static ListenSpec to() { return new ListenSpec(); } + /** + * Start building an event emission specification. + * + *

Use methods on {@link EventSpec} to define event type and payload, and pass it to {@link + * #emit(Consumer)}. + * + * @return a new {@link EventSpec} + */ public static EventSpec event() { return new EventSpec(); } + /** + * Start building a {@code try/catch} specification for use with {@link #tryCatch(TryConfigurer)}. + * + *

Example: + * + *

{@code
+   * tasks(
+   *   tryCatch(
+   *     tryCatch()
+   *       .doTasks(...)
+   *       .on(catchWhen("jqExpr", retryWhen(...), tasks(...)))
+   *   )
+   * );
+   * }
+ * + * @return a new {@link TrySpec} + */ public static TrySpec tryCatch() { return new TrySpec(); } + /** + * Build a {@link TryCatchConfigurer} that catches when a given expression matches, configures + * retry, and runs the given tasks as the catch body. + * + * @param when jq-style condition expression + * @param retry retry strategy configurer + * @param doTasks one or more task configurers to execute in the catch block + * @return a {@link TryCatchConfigurer} to be used inside {@link TrySpec} + */ public static TryCatchConfigurer catchWhen( String when, RetryConfigurer retry, TasksConfigurer... doTasks) { return c -> c.when(when).retry(retry).doTasks(tasks(doTasks)); } + /** + * Build a {@link TryCatchConfigurer} that catches when the given expression does not + * match, configures retry, and runs the given tasks as the catch body. + * + * @param when jq-style condition expression to exclude + * @param retry retry strategy configurer + * @param doTasks one or more task configurers to execute in the catch block + * @return a {@link TryCatchConfigurer} to be used inside {@link TrySpec} + */ public static TryCatchConfigurer catchExceptWhen( String when, RetryConfigurer retry, TasksConfigurer... doTasks) { return c -> c.exceptWhen(when).retry(retry).doTasks(tasks(doTasks)); } + /** + * Build a basic retry strategy that retries when the given condition is true and respects a + * duration limit. + * + * @param when jq-style condition expression + * @param limitDuration duration in ISO-8601 or spec-compatible format + * @return a {@link RetryConfigurer} representing the retry strategy + */ public static RetryConfigurer retryWhen(String when, String limitDuration) { return r -> r.when(when).limit(l -> l.duration(limitDuration)); } + /** + * Build a retry strategy that retries when the given condition does not hold and + * respects a duration limit. + * + * @param when jq-style condition expression to exclude + * @param limitDuration duration in ISO-8601 or spec-compatible format + * @return a {@link RetryConfigurer} representing the retry strategy + */ public static RetryConfigurer retryExceptWhen(String when, String limitDuration) { return r -> r.exceptWhen(when).limit(l -> l.duration(limitDuration)); } + /** + * Build an error filter for the {@code catch} clause based on a concrete error type URI and HTTP + * status. + * + * @param errType error type URI + * @param status expected HTTP status code + * @return a consumer to configure {@link TryTaskBuilder.CatchErrorsBuilder} + */ public static Consumer errorFilter(URI errType, int status) { return e -> e.type(errType.toString()).status(status); } + /** + * Build an error filter for the {@code catch} clause using a standard {@link Errors.Standard} + * error and HTTP status. + * + * @param errType standard error enum + * @param status expected HTTP status code + * @return a consumer to configure {@link TryTaskBuilder.CatchErrorsBuilder} + */ public static Consumer errorFilter( Errors.Standard errType, int status) { return e -> e.type(errType.toString()).status(status); } + /** + * Create an {@link AuthenticationConfigurer} that references a previously defined authentication + * policy by name in the {@code use} block. + * + *

Equivalent to setting {@code authentication.use(name)}. + * + * @param authName the name of a reusable authentication policy + * @return an {@link AuthenticationConfigurer} that sets {@code use(authName)} + */ public static AuthenticationConfigurer auth(String authName) { return a -> a.use(authName); } + /** + * Build a BASIC authentication configurer with username and password. + * + *

Typical usage: + * + *

{@code
+   * auth("basic-auth", basic("user", "pass"))
+   * }
+ * + * @param username BASIC auth username + * @param password BASIC auth password + * @return an {@link AuthenticationConfigurer} for BASIC authentication + */ public static AuthenticationConfigurer basic(String username, String password) { return a -> a.basic(b -> b.username(username).password(password)); } + /** + * Build a Bearer token authentication configurer. + * + *

Typical usage: + * + *

{@code
+   * auth("bearer-auth", bearer("token-123"))
+   * }
+ * + * @param token bearer token + * @return an {@link AuthenticationConfigurer} for Bearer authentication + */ public static AuthenticationConfigurer bearer(String token) { return a -> a.bearer(b -> b.token(token)); } + /** + * Build a Digest authentication configurer with username and password. + * + * @param username digest auth username + * @param password digest auth password + * @return an {@link AuthenticationConfigurer} for Digest authentication + */ public static AuthenticationConfigurer digest(String username, String password) { return a -> a.digest(d -> d.username(username).password(password)); } + /** + * Build an OpenID Connect (OIDC) authentication configurer without client credentials. + * + * @param authority OIDC authority/issuer URL + * @param grant OAuth2 grant type + * @return an {@link AuthenticationConfigurer} using OIDC + */ public static AuthenticationConfigurer oidc( String authority, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant) { return a -> a.openIDConnect(o -> o.authority(authority).grant(grant)); } + /** + * Build an OpenID Connect (OIDC) authentication configurer with client credentials. + * + * @param authority OIDC authority/issuer URL + * @param grant OAuth2 grant type + * @param clientId client identifier + * @param clientSecret client secret + * @return an {@link AuthenticationConfigurer} using OIDC with client credentials + */ public static AuthenticationConfigurer oidc( String authority, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant, @@ -132,11 +403,29 @@ public static AuthenticationConfigurer oidc( // TODO: we may create an OIDCSpec for chained builders if necessary + /** + * Alias for {@link #oidc(String, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant)} using + * OAuth2 semantics. + * + * @param authority OAuth2/OIDC authority URL + * @param grant OAuth2 grant type + * @return an {@link AuthenticationConfigurer} configured as OAuth2 + */ public static AuthenticationConfigurer oauth2( String authority, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant) { return a -> a.openIDConnect(o -> o.authority(authority).grant(grant)); } + /** + * Alias for {@link #oidc(String, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant, String, + * String)} using OAuth2 naming. + * + * @param authority OAuth2/OIDC authority URL + * @param grant OAuth2 grant type + * @param clientId client identifier + * @param clientSecret client secret + * @return an {@link AuthenticationConfigurer} configured as OAuth2 with client credentials + */ public static AuthenticationConfigurer oauth2( String authority, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant, @@ -150,132 +439,326 @@ public static AuthenticationConfigurer oauth2( .client(c -> c.id(clientId).secret(clientSecret))); } + /** + * Build a {@link RaiseSpec} for an error with a string type expression and HTTP status. + * + * @param errExpr error type expression (URI or logical id) + * @param status HTTP status code + * @return a {@link RaiseSpec} configured with type and status + */ public static RaiseSpec error(String errExpr, int status) { return new RaiseSpec().type(errExpr).status(status); } + /** + * Build a {@link RaiseSpec} for an error with a string type expression. + * + * @param errExpr error type expression (URI or logical id) + * @return a {@link RaiseSpec} configured with type only + */ public static RaiseSpec error(String errExpr) { return new RaiseSpec().type(errExpr); } + /** + * Build a {@link RaiseSpec} for an error using a concrete error type URI. + * + * @param errType error type URI + * @return a {@link RaiseSpec} configured with type only + */ public static RaiseSpec error(URI errType) { return new RaiseSpec().type(errType); } + /** + * Build a {@link RaiseSpec} for an error using a concrete error type URI and HTTP status. + * + * @param errType error type URI + * @param status HTTP status code + * @return a {@link RaiseSpec} configured with type and status + */ public static RaiseSpec error(URI errType, int status) { return new RaiseSpec().type(errType).status(status); } // --- Errors Recipes --- // + + /** + * Build a {@link RaiseSpec} based on a standard error enum. + * + * @param std standard error + * @return a {@link RaiseSpec} using the standard error URI and status + */ public static RaiseSpec error(Errors.Standard std) { return error(std.uri(), std.status()); } + /** + * Build a {@link RaiseSpec} based on a standard error enum with an overridden status. + * + * @param std standard error + * @param status HTTP status code + * @return a {@link RaiseSpec} using the standard URI and custom status + */ public static RaiseSpec error(Errors.Standard std, int status) { return error(std.uri(), status); } + /** + * Shortcut for a generic server runtime error according to {@link Errors#RUNTIME}. + * + * @return a {@link RaiseSpec} representing a runtime server error + */ public static RaiseSpec serverError() { return error(Errors.RUNTIME); } + /** + * Shortcut for a communication error according to {@link Errors#COMMUNICATION}. + * + * @return a {@link RaiseSpec} representing a communication error + */ public static RaiseSpec communicationError() { return error(Errors.COMMUNICATION); } + /** + * Shortcut for a "not implemented" error according to {@link Errors#NOT_IMPLEMENTED}. + * + * @return a {@link RaiseSpec} representing a not-implemented error + */ public static RaiseSpec notImplementedError() { return error(Errors.NOT_IMPLEMENTED); } + /** + * Shortcut for an unauthorized error according to {@link Errors#AUTHENTICATION}. + * + * @return a {@link RaiseSpec} representing an authentication error + */ public static RaiseSpec unauthorizedError() { return error(Errors.AUTHENTICATION); } + /** + * Shortcut for a forbidden error according to {@link Errors#AUTHORIZATION}. + * + * @return a {@link RaiseSpec} representing an authorization error + */ public static RaiseSpec forbiddenError() { return error(Errors.AUTHORIZATION); } + /** + * Shortcut for a timeout error according to {@link Errors#TIMEOUT}. + * + * @return a {@link RaiseSpec} representing a timeout error + */ public static RaiseSpec timeoutError() { return error(Errors.TIMEOUT); } + /** + * Shortcut for a data error according to {@link Errors#DATA}. + * + * @return a {@link RaiseSpec} representing a data error + */ public static RaiseSpec dataError() { return error(Errors.DATA); } // ---- Tasks ----// + /** + * Create a {@link TasksConfigurer} that adds an HTTP call task using a low-level HTTP configurer. + * + *

Example: + * + *

{@code
+   * tasks(
+   *   call(http -> http.method("GET").endpoint("..."))
+   * );
+   * }
+ * + * @param configurer low-level HTTP configurer + * @return a {@link TasksConfigurer} that adds a CallHTTP task + */ public static TasksConfigurer call(CallHttpConfigurer configurer) { return list -> list.http(configurer); } + /** + * Create a {@link TasksConfigurer} that adds an OpenAPI call task. + * + *

Example: + * + *

{@code
+   * tasks(
+   *   call(openapi -> openapi.document("...").operation("getUser"))
+   * );
+   * }
+ * + * @param configurer OpenAPI configurer + * @return a {@link TasksConfigurer} that adds a CallOpenAPI task + */ public static TasksConfigurer call(CallOpenAPIConfigurer configurer) { return list -> list.openapi(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code set} task using a low-level configurer. + * + * @param configurer configurer for the set task + * @return a {@link TasksConfigurer} that adds a SetTask + */ public static TasksConfigurer set(SetConfigurer configurer) { return list -> list.set(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code set} task using a raw expression. + * + * @param expr expression to apply in the set task + * @return a {@link TasksConfigurer} that adds a SetTask + */ public static TasksConfigurer set(String expr) { return list -> list.set(expr); } + /** + * Create a {@link TasksConfigurer} that adds an {@code emit} task. + * + * @param configurer consumer configuring {@link EmitTaskBuilder} + * @return a {@link TasksConfigurer} that adds an EmitTask + */ public static TasksConfigurer emit(Consumer configurer) { return list -> list.emit(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code listen} task. + * + * @param configurer listen configurer + * @return a {@link TasksConfigurer} that adds a ListenTask + */ public static TasksConfigurer listen(ListenConfigurer configurer) { return list -> list.listen(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code forEach} task. + * + * @param configurer for-each configurer + * @return a {@link TasksConfigurer} that adds a ForEachTask + */ public static TasksConfigurer forEach(ForEachConfigurer configurer) { return list -> list.forEach(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code fork} task. + * + * @param configurer consumer configuring {@link ForkTaskBuilder} + * @return a {@link TasksConfigurer} that adds a ForkTask + */ public static TasksConfigurer fork(Consumer configurer) { return list -> list.fork(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code switch} task. + * + * @param configurer switch configurer + * @return a {@link TasksConfigurer} that adds a SwitchTask + */ public static TasksConfigurer switchCase(SwitchConfigurer configurer) { return list -> list.switchCase(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code raise} task. + * + * @param configurer raise configurer + * @return a {@link TasksConfigurer} that adds a RaiseTask + */ public static TasksConfigurer raise(RaiseConfigurer configurer) { return list -> list.raise(configurer); } + /** + * Create a {@link TasksConfigurer} that adds a {@code try/catch} task. + * + * @param configurer try/catch configurer + * @return a {@link TasksConfigurer} that adds a TryTask + */ public static TasksConfigurer tryCatch(TryConfigurer configurer) { return list -> list.tryCatch(configurer); } // ----- Tasks that requires tasks list --// - /** Main task list to be used in `workflow().tasks()` consumer. */ + /** + * Main task list adapter to be used in {@code workflow().tasks()} consumers. + * + *

This wraps multiple {@link TasksConfigurer} into a single {@link Consumer} of {@link + * DoTaskBuilder}. + * + * @param steps ordered list of task configurers + * @return a consumer configuring {@link DoTaskBuilder} with the provided steps + */ public static Consumer doTasks(TasksConfigurer... steps) { final Consumer tasks = tasks(steps); return d -> d.tasks(tasks); } - /** Task list for tasks that requires it such as `for`, `try`, and so on. */ + /** + * Task list for tasks that require a nested tasks list such as {@code for}, {@code try}, etc. + * + *

The configurers are applied in order and cloned defensively. + * + * @param steps ordered list of task configurers + * @return a {@link TasksConfigurer} applying all given steps + */ public static TasksConfigurer tasks(TasksConfigurer... steps) { Objects.requireNonNull(steps, "Steps in a tasks are required"); final List snapshot = List.of(steps.clone()); return list -> snapshot.forEach(s -> s.accept(list)); } + /** + * Build a {@link ForEachConfigurer} that uses a nested tasks list as its body. + * + * @param steps task configurers that make up the body of the for-each + * @return a {@link ForEachConfigurer} with the given tasks + */ public static ForEachConfigurer forEach(TasksConfigurer... steps) { final Consumer tasks = DSL.tasks(steps); return f -> f.tasks(tasks); } - /** Recipe for {@link io.serverlessworkflow.api.types.ForkTask} branch that DO compete */ + /** + * Recipe for {@link io.serverlessworkflow.api.types.ForkTask} where branches compete (first to + * complete "wins"). + * + *

Configures {@code compete(true)} and sets the branches tasks list. + * + * @param steps task configurers shared by all branches + * @return a {@link Consumer} configuring {@link ForkTaskBuilder} + */ public static Consumer branchesCompete(TasksConfigurer... steps) { final Consumer tasks = DSL.tasks(steps); return f -> f.compete(true).branches(tasks); } - /** Recipe for {@link io.serverlessworkflow.api.types.ForkTask} branch that DO NOT compete */ + /** + * Recipe for {@link io.serverlessworkflow.api.types.ForkTask} where branches do not compete (all + * branches are executed). + * + *

Configures {@code compete(false)} and sets the branches tasks list. + * + * @param steps task configurers shared by all branches + * @return a {@link Consumer} configuring {@link ForkTaskBuilder} + */ public static Consumer branches(TasksConfigurer... steps) { final Consumer tasks = DSL.tasks(steps); return f -> f.compete(false).branches(tasks); diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/UseSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/UseSpec.java new file mode 100644 index 000000000..4f2b739b0 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/UseSpec.java @@ -0,0 +1,47 @@ +/* + * 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.fluent.spec.dsl; + +import io.serverlessworkflow.fluent.spec.UseBuilder; +import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer; +import io.serverlessworkflow.fluent.spec.configurers.UseConfigurer; +import java.util.LinkedList; +import java.util.List; + +public class UseSpec implements UseConfigurer { + + private final List steps = new LinkedList<>(); + + public UseSpec secrets(String... secrets) { + steps.add(u -> u.secrets(secrets)); + return this; + } + + public UseSpec secret(String secret) { + steps.add(u -> u.secrets(secret)); + return this; + } + + public UseSpec auth(String name, AuthenticationConfigurer auth) { + steps.add(u -> u.authentications(a -> a.authentication(name, auth))); + return this; + } + + @Override + public void accept(UseBuilder useBuilder) { + steps.forEach(step -> step.accept(useBuilder)); + } +} diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java index e053e3c70..80b9d1e1b 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java @@ -15,10 +15,14 @@ */ package io.serverlessworkflow.fluent.spec.dsl; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.auth; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.basic; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.bearer; import static io.serverlessworkflow.fluent.spec.dsl.DSL.call; import static io.serverlessworkflow.fluent.spec.dsl.DSL.error; import static io.serverlessworkflow.fluent.spec.dsl.DSL.event; import static io.serverlessworkflow.fluent.spec.dsl.DSL.http; +import static io.serverlessworkflow.fluent.spec.dsl.DSL.secrets; import static io.serverlessworkflow.fluent.spec.dsl.DSL.to; import static org.assertj.core.api.Assertions.assertThat; @@ -260,4 +264,21 @@ void serverError_uses_versioned_uri_and_default_code() { assertThat(def.getTitle().getExpressionErrorTitle()).isEqualTo("Boom"); assertThat(def.getDetail().getExpressionErrorDetails()).isEqualTo("x"); } + + @Test + void use_spec_accumulates_secrets_and_auths() { + Workflow wf = + WorkflowBuilder.workflow("id", "ns", "1") + .use( + auth("basic-auth", basic("u", "p")), + auth("bearer-auth", bearer("t")), + secrets("s1", "s2")) + .build(); + + var use = wf.getUse(); + assertThat(use).isNotNull(); + assertThat(use.getSecrets()).containsExactly("s1", "s2"); + assertThat(use.getAuthentications().getAdditionalProperties().keySet()) + .containsExactly("basic-auth", "bearer-auth"); + } }