diff --git a/fluent/spec/pom.xml b/fluent/spec/pom.xml
index 5ec62a98..45051baf 100644
--- a/fluent/spec/pom.xml
+++ b/fluent/spec/pom.xml
@@ -27,6 +27,11 @@
junit-jupiter-api
test
+
+ org.assertj
+ assertj-core
+ test
+
\ No newline at end of file
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractEventConsumptionStrategyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractEventConsumptionStrategyBuilder.java
index 4c12d199..2857cd7e 100644
--- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractEventConsumptionStrategyBuilder.java
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AbstractEventConsumptionStrategyBuilder.java
@@ -58,13 +58,22 @@ public SELF one(Consumer c) {
protected abstract void setOne(OneEventConsumptionStrategy strategy);
- public SELF all(Consumer c) {
+ @SafeVarargs
+ public final SELF all(Consumer... consumers) {
ensureNoneSet();
allSet = true;
- F fb = this.newEventFilterBuilder();
- c.accept(fb);
+
+ List built = new ArrayList<>(consumers.length);
+
+ for (Consumer super F> c : consumers) {
+ Objects.requireNonNull(c, "consumer");
+ F fb = this.newEventFilterBuilder();
+ c.accept(fb);
+ built.add(fb.build());
+ }
+
AllEventConsumptionStrategy strat = new AllEventConsumptionStrategy();
- strat.setAll(List.of(fb.build()));
+ strat.setAll(built);
this.setAll(strat);
return this.self();
}
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 95819162..21b84fe9 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
@@ -17,11 +17,13 @@
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;
@@ -55,12 +57,44 @@ public CallHTTPTaskBuilder endpoint(URI 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;
}
- // TODO: add endpoint configuration to support authentication
+ 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));
@@ -70,7 +104,18 @@ public CallHTTPTaskBuilder headers(String expr) {
public CallHTTPTaskBuilder headers(Consumer consumer) {
HTTPHeadersBuilder hb = new HTTPHeadersBuilder();
consumer.accept(hb);
- callHTTP.getWith().setHeaders(hb.build());
+ 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;
}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java
index ced59032..ede8f202 100644
--- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java
@@ -40,7 +40,7 @@ public SetTaskBuilder expr(String expression) {
return this;
}
- public SetTaskBuilder put(String key, String value) {
+ public SetTaskBuilder put(String key, Object value) {
setTaskConfiguration.withAdditionalProperty(key, value);
return this;
}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TryTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TryTaskBuilder.java
index a707e916..f8ad386a 100644
--- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TryTaskBuilder.java
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TryTaskBuilder.java
@@ -57,8 +57,9 @@ public TryTaskBuilder tryHandler(Consumer consumer) {
return this;
}
- public TryTaskBuilder catchHandler(Consumer consumer) {
- final TryTaskCatchBuilder catchBuilder = new TryTaskCatchBuilder();
+ public TryTaskBuilder catchHandler(Consumer> consumer) {
+ final TryTaskCatchBuilder catchBuilder =
+ new TryTaskCatchBuilder<>(this.doTaskBuilderFactory);
consumer.accept(catchBuilder);
this.tryTask.setCatch(catchBuilder.build());
return this;
@@ -68,42 +69,51 @@ public TryTask build() {
return tryTask;
}
- public static final class TryTaskCatchBuilder {
+ public static final class TryTaskCatchBuilder> {
private final TryTaskCatch tryTaskCatch;
+ private final T doTaskBuilderFactory;
- TryTaskCatchBuilder() {
+ TryTaskCatchBuilder(T doTaskBuilderFactory) {
+ this.doTaskBuilderFactory = doTaskBuilderFactory;
this.tryTaskCatch = new TryTaskCatch();
}
- public TryTaskCatchBuilder as(final String as) {
+ public TryTaskCatchBuilder as(final String as) {
this.tryTaskCatch.setAs(as);
return this;
}
- public TryTaskCatchBuilder when(final String when) {
+ public TryTaskCatchBuilder when(final String when) {
this.tryTaskCatch.setWhen(when);
return this;
}
- public TryTaskCatchBuilder exceptWhen(final String exceptWhen) {
+ public TryTaskCatchBuilder exceptWhen(final String exceptWhen) {
this.tryTaskCatch.setExceptWhen(exceptWhen);
return this;
}
- public TryTaskCatchBuilder retry(Consumer consumer) {
+ public TryTaskCatchBuilder retry(Consumer consumer) {
final RetryPolicyBuilder retryPolicyBuilder = new RetryPolicyBuilder();
consumer.accept(retryPolicyBuilder);
this.tryTaskCatch.setRetry(new Retry().withRetryPolicyDefinition(retryPolicyBuilder.build()));
return this;
}
- public TryTaskCatchBuilder errorsWith(Consumer consumer) {
+ public TryTaskCatchBuilder errorsWith(Consumer consumer) {
final CatchErrorsBuilder catchErrorsBuilder = new CatchErrorsBuilder();
consumer.accept(catchErrorsBuilder);
this.tryTaskCatch.setErrors(catchErrorsBuilder.build());
return this;
}
+ public TryTaskCatchBuilder doTasks(Consumer consumer) {
+ final T taskItemListBuilder = this.doTaskBuilderFactory.newItemListBuilder();
+ consumer.accept(taskItemListBuilder);
+ this.tryTaskCatch.setDo(taskItemListBuilder.build());
+ return this;
+ }
+
public TryTaskCatch build() {
return tryTaskCatch;
}
@@ -153,30 +163,30 @@ public static final class RetryPolicyJitterBuilder {
this.retryPolicyJitter = new RetryPolicyJitter();
}
- public RetryPolicyJitter to(Consumer consumer) {
+ public RetryPolicyJitterBuilder to(Consumer consumer) {
final DurationInlineBuilder durationInlineBuilder = new DurationInlineBuilder();
consumer.accept(durationInlineBuilder);
this.retryPolicyJitter.setTo(
new TimeoutAfter().withDurationInline(durationInlineBuilder.build()));
- return retryPolicyJitter;
+ return this;
}
- public RetryPolicyJitter to(String expression) {
+ public RetryPolicyJitterBuilder to(String expression) {
this.retryPolicyJitter.setTo(new TimeoutAfter().withDurationExpression(expression));
- return retryPolicyJitter;
+ return this;
}
- public RetryPolicyJitter from(Consumer consumer) {
+ public RetryPolicyJitterBuilder from(Consumer consumer) {
final DurationInlineBuilder durationInlineBuilder = new DurationInlineBuilder();
consumer.accept(durationInlineBuilder);
this.retryPolicyJitter.setFrom(
new TimeoutAfter().withDurationInline(durationInlineBuilder.build()));
- return retryPolicyJitter;
+ return this;
}
- public RetryPolicyJitter from(String expression) {
+ public RetryPolicyJitterBuilder from(String expression) {
this.retryPolicyJitter.setFrom(new TimeoutAfter().withDurationExpression(expression));
- return retryPolicyJitter;
+ return this;
}
public RetryPolicyJitter build() {
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderConsumers.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderConsumers.java
deleted file mode 100644
index e0dbb54c..00000000
--- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderConsumers.java
+++ /dev/null
@@ -1,37 +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 java.util.function.Consumer;
-
-public final class WorkflowBuilderConsumers {
-
- private WorkflowBuilderConsumers() {}
-
- public static Consumer authBasic(
- final String username, final String password) {
- return auth -> auth.basic(b -> b.username(username).password(password));
- }
-
- public static Consumer authBearer(final String token) {
- return auth -> auth.bearer(b -> b.token(token));
- }
-
- public static Consumer authDigest(
- final String username, final String password) {
- return auth -> auth.digest(d -> d.username(username).password(password));
- }
-}
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
new file mode 100644
index 00000000..97b9bd4b
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/AuthenticationConfigurer.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.AuthenticationPolicyUnionBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+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
new file mode 100644
index 00000000..11799b47
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/CallHTTPConfigurer.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.CallHTTPTaskBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface CallHTTPConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/EmitConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/EmitConfigurer.java
new file mode 100644
index 00000000..4a969387
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/EmitConfigurer.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.EmitTaskBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface EmitConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/EventConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/EventConfigurer.java
new file mode 100644
index 00000000..3cb365f9
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/EventConfigurer.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.EventPropertiesBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface EventConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/ForEachConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/ForEachConfigurer.java
new file mode 100644
index 00000000..bec8ba3c
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/ForEachConfigurer.java
@@ -0,0 +1,23 @@
+/*
+ * 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.ForEachTaskBuilder;
+import io.serverlessworkflow.fluent.spec.TaskItemListBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface ForEachConfigurer extends Consumer> {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/ListenConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/ListenConfigurer.java
new file mode 100644
index 00000000..8cefae7f
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/ListenConfigurer.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.ListenTaskBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface ListenConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/RaiseConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/RaiseConfigurer.java
new file mode 100644
index 00000000..5c9897d7
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/RaiseConfigurer.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.RaiseTaskBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface RaiseConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/RetryConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/RetryConfigurer.java
new file mode 100644
index 00000000..2de6920e
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/RetryConfigurer.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2020-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.serverlessworkflow.fluent.spec.configurers;
+
+import io.serverlessworkflow.fluent.spec.TryTaskBuilder;
+import java.util.function.Consumer;
+
+public interface RetryConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/SetConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/SetConfigurer.java
new file mode 100644
index 00000000..e6a939f2
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/SetConfigurer.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.SetTaskBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface SetConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/SwitchConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/SwitchConfigurer.java
new file mode 100644
index 00000000..54778888
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/SwitchConfigurer.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.SwitchTaskBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface SwitchConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TasksConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TasksConfigurer.java
new file mode 100644
index 00000000..276019fc
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TasksConfigurer.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.TaskItemListBuilder;
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface TasksConfigurer extends Consumer {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TryCatchConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TryCatchConfigurer.java
new file mode 100644
index 00000000..e256cc30
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TryCatchConfigurer.java
@@ -0,0 +1,23 @@
+/*
+ * 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.TaskItemListBuilder;
+import io.serverlessworkflow.fluent.spec.TryTaskBuilder;
+import java.util.function.Consumer;
+
+public interface TryCatchConfigurer
+ extends Consumer> {}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TryConfigurer.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TryConfigurer.java
new file mode 100644
index 00000000..59b5f289
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/configurers/TryConfigurer.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.TaskItemListBuilder;
+import io.serverlessworkflow.fluent.spec.TryTaskBuilder;
+import java.util.function.Consumer;
+
+public interface TryConfigurer 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/CallHTTPSpec.java
new file mode 100644
index 00000000..83bec629
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/CallHTTPSpec.java
@@ -0,0 +1,103 @@
+/*
+ * 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.AuthenticationConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.CallHTTPConfigurer;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public final class CallHTTPSpec implements CallHTTPConfigurer {
+
+ private final List steps = new ArrayList<>();
+
+ public CallHTTPSpec GET() {
+ steps.add(c -> c.method("GET"));
+ return this;
+ }
+
+ public CallHTTPSpec POST() {
+ steps.add(c -> c.method("POST"));
+ return this;
+ }
+
+ public CallHTTPSpec acceptJSON() {
+ return header("Accept", "application/json");
+ }
+
+ public CallHTTPSpec endpoint(String urlExpr) {
+ steps.add(b -> b.endpoint(urlExpr));
+ return this;
+ }
+
+ public CallHTTPSpec endpoint(String urlExpr, AuthenticationConfigurer auth) {
+ steps.add(b -> b.endpoint(urlExpr, auth));
+ return this;
+ }
+
+ public CallHTTPSpec uri(String url) {
+ steps.add(b -> b.endpoint(URI.create(url)));
+ return this;
+ }
+
+ public CallHTTPSpec uri(String url, AuthenticationConfigurer auth) {
+ steps.add(b -> b.endpoint(URI.create(url), auth));
+ return this;
+ }
+
+ public CallHTTPSpec uri(URI uri) {
+ steps.add(b -> b.endpoint(uri));
+ return this;
+ }
+
+ public CallHTTPSpec uri(URI uri, AuthenticationConfigurer auth) {
+ steps.add(b -> b.endpoint(uri, auth));
+ return this;
+ }
+
+ public CallHTTPSpec body(String bodyExpr) {
+ steps.add(c -> c.body(bodyExpr));
+ return this;
+ }
+
+ public CallHTTPSpec body(Map body) {
+ steps.add(c -> c.body(body));
+ return this;
+ }
+
+ public CallHTTPSpec method(String method) {
+ steps.add(b -> b.method(method));
+ return this;
+ }
+
+ public CallHTTPSpec header(String name, String value) {
+ steps.add(c -> c.headers(h -> h.header(name, value)));
+ return this;
+ }
+
+ public CallHTTPSpec headers(Map headers) {
+ steps.add(b -> b.headers(headers));
+ return this;
+ }
+
+ @Override
+ public void accept(CallHTTPTaskBuilder b) {
+ for (var s : steps) s.accept(b);
+ }
+}
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
new file mode 100644
index 00000000..8da5bdbe
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java
@@ -0,0 +1,261 @@
+/*
+ * 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.OAuth2AuthenticationData;
+import io.serverlessworkflow.fluent.spec.EmitTaskBuilder;
+import io.serverlessworkflow.fluent.spec.ForkTaskBuilder;
+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.ForEachConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.ListenConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.RaiseConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.RetryConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.SetConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.SwitchConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.TasksConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.TryCatchConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.TryConfigurer;
+import io.serverlessworkflow.types.Errors;
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public final class DSL {
+
+ private DSL() {}
+
+ // ---- Convenient shortcuts ----//
+
+ public static CallHTTPSpec http() {
+ return new CallHTTPSpec();
+ }
+
+ public static SwitchSpec cases() {
+ return new SwitchSpec();
+ }
+
+ public static ListenSpec to() {
+ return new ListenSpec();
+ }
+
+ public static EventSpec event() {
+ return new EventSpec();
+ }
+
+ public static TrySpec tryCatch() {
+ return new TrySpec();
+ }
+
+ public static TryCatchConfigurer catchWhen(
+ String when, RetryConfigurer retry, TasksConfigurer... doTasks) {
+ return c -> c.when(when).retry(retry).doTasks(tasks(doTasks));
+ }
+
+ public static TryCatchConfigurer catchExceptWhen(
+ String when, RetryConfigurer retry, TasksConfigurer... doTasks) {
+ return c -> c.exceptWhen(when).retry(retry).doTasks(tasks(doTasks));
+ }
+
+ public static RetryConfigurer retryWhen(String when, String limitDuration) {
+ return r -> r.when(when).limit(l -> l.duration(limitDuration));
+ }
+
+ public static RetryConfigurer retryExceptWhen(String when, String limitDuration) {
+ return r -> r.exceptWhen(when).limit(l -> l.duration(limitDuration));
+ }
+
+ public static Consumer errorFilter(URI errType, int status) {
+ return e -> e.type(errType.toString()).status(status);
+ }
+
+ public static Consumer errorFilter(
+ Errors.Standard errType, int status) {
+ return e -> e.type(errType.toString()).status(status);
+ }
+
+ public static AuthenticationConfigurer basic(String username, String password) {
+ return a -> a.basic(b -> b.username(username).password(password));
+ }
+
+ public static AuthenticationConfigurer bearer(String token) {
+ return a -> a.bearer(b -> b.token(token));
+ }
+
+ public static AuthenticationConfigurer digest(String username, String password) {
+ return a -> a.digest(d -> d.username(username).password(password));
+ }
+
+ public static AuthenticationConfigurer oidc(
+ String authority, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant) {
+ return a -> a.openIDConnect(o -> o.authority(authority).grant(grant));
+ }
+
+ public static AuthenticationConfigurer oidc(
+ String authority,
+ OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant,
+ String clientId,
+ String clientSecret) {
+ return a ->
+ a.openIDConnect(
+ o ->
+ o.authority(authority)
+ .grant(grant)
+ .client(c -> c.id(clientId).secret(clientSecret)));
+ }
+
+ // TODO: we may create an OIDCSpec for chained builders if necessary
+
+ public static AuthenticationConfigurer oauth2(
+ String authority, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant) {
+ return a -> a.openIDConnect(o -> o.authority(authority).grant(grant));
+ }
+
+ public static AuthenticationConfigurer oauth2(
+ String authority,
+ OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant,
+ String clientId,
+ String clientSecret) {
+ return a ->
+ a.openIDConnect(
+ o ->
+ o.authority(authority)
+ .grant(grant)
+ .client(c -> c.id(clientId).secret(clientSecret)));
+ }
+
+ public static RaiseSpec error(String errExpr, int status) {
+ return new RaiseSpec().type(errExpr).status(status);
+ }
+
+ public static RaiseSpec error(String errExpr) {
+ return new RaiseSpec().type(errExpr);
+ }
+
+ public static RaiseSpec error(URI errType) {
+ return new RaiseSpec().type(errType);
+ }
+
+ public static RaiseSpec error(URI errType, int status) {
+ return new RaiseSpec().type(errType).status(status);
+ }
+
+ // --- Errors Recipes --- //
+ public static RaiseSpec error(Errors.Standard std) {
+ return error(std.uri(), std.status());
+ }
+
+ public static RaiseSpec error(Errors.Standard std, int status) {
+ return error(std.uri(), status);
+ }
+
+ public static RaiseSpec serverError() {
+ return error(Errors.RUNTIME);
+ }
+
+ public static RaiseSpec communicationError() {
+ return error(Errors.COMMUNICATION);
+ }
+
+ public static RaiseSpec notImplementedError() {
+ return error(Errors.NOT_IMPLEMENTED);
+ }
+
+ public static RaiseSpec unauthorizedError() {
+ return error(Errors.AUTHENTICATION);
+ }
+
+ public static RaiseSpec forbiddenError() {
+ return error(Errors.AUTHORIZATION);
+ }
+
+ public static RaiseSpec timeoutError() {
+ return error(Errors.TIMEOUT);
+ }
+
+ public static RaiseSpec dataError() {
+ return error(Errors.DATA);
+ }
+
+ // ---- Tasks ----//
+
+ public static TasksConfigurer call(CallHTTPConfigurer configurer) {
+ return list -> list.callHTTP(configurer);
+ }
+
+ public static TasksConfigurer set(SetConfigurer configurer) {
+ return list -> list.set(configurer);
+ }
+
+ public static TasksConfigurer set(String expr) {
+ return list -> list.set(expr);
+ }
+
+ public static TasksConfigurer emit(Consumer configurer) {
+ return list -> list.emit(configurer);
+ }
+
+ public static TasksConfigurer listen(ListenConfigurer configurer) {
+ return list -> list.listen(configurer);
+ }
+
+ public static TasksConfigurer forEach(ForEachConfigurer configurer) {
+ return list -> list.forEach(configurer);
+ }
+
+ public static TasksConfigurer fork(Consumer configurer) {
+ return list -> list.fork(configurer);
+ }
+
+ public static TasksConfigurer switchCase(SwitchConfigurer configurer) {
+ return list -> list.switchCase(configurer);
+ }
+
+ public static TasksConfigurer raise(RaiseConfigurer configurer) {
+ return list -> list.raise(configurer);
+ }
+
+ public static TasksConfigurer tryCatch(TryConfigurer configurer) {
+ return list -> list.tryCatch(configurer);
+ }
+
+ // ----- Tasks that requires tasks list --//
+ public static Consumer 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));
+ }
+
+ 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 */
+ 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 */
+ 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/EmitSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EmitSpec.java
new file mode 100644
index 00000000..8c4115bc
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EmitSpec.java
@@ -0,0 +1,38 @@
+/*
+ * 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.EmitTaskBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.EmitConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.EventConfigurer;
+
+public final class EmitSpec extends EventFilterSpec implements EmitConfigurer {
+
+ @Override
+ protected EmitSpec self() {
+ return this;
+ }
+
+ @Override
+ public void accept(EmitTaskBuilder emitTaskBuilder) {
+ emitTaskBuilder.event(
+ e -> {
+ for (EventConfigurer step : steps) {
+ step.accept(e);
+ }
+ });
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EventFilterSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EventFilterSpec.java
new file mode 100644
index 00000000..7c47a07b
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EventFilterSpec.java
@@ -0,0 +1,77 @@
+/*
+ * 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.configurers.EventConfigurer;
+import java.net.URI;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public abstract class EventFilterSpec {
+
+ protected final List steps = new ArrayList<>();
+
+ protected abstract SELF self();
+
+ public SELF type(String eventType) {
+ steps.add(e -> e.type(eventType));
+ return self();
+ }
+
+ /** Sets the CloudEvent id to a random UUID */
+ public SELF randomId() {
+ steps.add(e -> e.id(UUID.randomUUID().toString()));
+ return self();
+ }
+
+ /** Sets the CloudEvent time to the current system time */
+ public SELF now() {
+ steps.add(e -> e.time(Date.from(Instant.now())));
+ return self();
+ }
+
+ /** Sets the CloudEvent dataContentType to `application/json` */
+ public SELF JSON() {
+ steps.add(e -> e.dataContentType("application/json"));
+ return self();
+ }
+
+ /** Sets the event data and the contentType to `application/json` */
+ public SELF jsonData(String expr) {
+ steps.add(e -> e.data(expr));
+ return JSON();
+ }
+
+ /** Sets the event data and the contentType to `application/json` */
+ public SELF jsonData(Map data) {
+ steps.add(e -> e.data(data));
+ return JSON();
+ }
+
+ public SELF source(String source) {
+ steps.add(e -> e.source(source));
+ return self();
+ }
+
+ public SELF source(URI source) {
+ steps.add(e -> e.source(source));
+ return self();
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EventSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EventSpec.java
new file mode 100644
index 00000000..a7a41a80
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/EventSpec.java
@@ -0,0 +1,32 @@
+/*
+ * 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.EventPropertiesBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.EventConfigurer;
+
+public final class EventSpec extends EventFilterSpec implements EventConfigurer {
+
+ @Override
+ protected EventSpec self() {
+ return this;
+ }
+
+ @Override
+ public void accept(EventPropertiesBuilder eventPropertiesBuilder) {
+ steps.forEach(step -> step.accept(eventPropertiesBuilder));
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ListenSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ListenSpec.java
new file mode 100644
index 00000000..ca538911
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/ListenSpec.java
@@ -0,0 +1,71 @@
+/*
+ * 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.EventFilterBuilder;
+import io.serverlessworkflow.fluent.spec.ListenTaskBuilder;
+import io.serverlessworkflow.fluent.spec.ListenToBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.EventConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.ListenConfigurer;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public final class ListenSpec implements ListenConfigurer {
+
+ private Consumer strategyStep;
+ private Consumer untilStep;
+
+ @SuppressWarnings("unchecked")
+ private static Consumer[] asFilters(EventConfigurer[] events) {
+ Consumer[] filters = new Consumer[events.length];
+ for (int i = 0; i < events.length; i++) {
+ EventConfigurer ev = Objects.requireNonNull(events[i], "events[" + i + "]");
+ filters[i] = f -> f.with(ev);
+ }
+ return filters;
+ }
+
+ public ListenSpec all(EventConfigurer... events) {
+ strategyStep = t -> t.all(asFilters(events));
+ return this;
+ }
+
+ public ListenSpec one(EventConfigurer e) {
+ strategyStep = t -> t.one(f -> f.with(e));
+ return this;
+ }
+
+ public ListenSpec any(EventConfigurer... events) {
+ strategyStep = t -> t.any(asFilters(events));
+ return this;
+ }
+
+ public ListenSpec until(String expression) {
+ untilStep = t -> t.until(expression);
+ return this;
+ }
+
+ @Override
+ public void accept(ListenTaskBuilder listenTaskBuilder) {
+ listenTaskBuilder.to(
+ t -> {
+ strategyStep.accept(t);
+ if (untilStep != null) {
+ untilStep.accept(t);
+ }
+ });
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/RaiseSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/RaiseSpec.java
new file mode 100644
index 00000000..da42bb30
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/RaiseSpec.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.dsl;
+
+import io.serverlessworkflow.fluent.spec.RaiseTaskBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.RaiseConfigurer;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public final class RaiseSpec implements RaiseConfigurer {
+ private final List> steps = new ArrayList<>();
+
+ public RaiseSpec type(String expr) {
+ steps.add(e -> e.type(expr));
+ return this;
+ }
+
+ public RaiseSpec type(URI uri) {
+ steps.add(e -> e.type(uri));
+ return this;
+ }
+
+ public RaiseSpec status(int s) {
+ steps.add(e -> e.status(s));
+ return this;
+ }
+
+ public RaiseSpec title(String t) {
+ steps.add(e -> e.title(t));
+ return this;
+ }
+
+ public RaiseSpec detail(String d) {
+ steps.add(e -> e.detail(d));
+ return this;
+ }
+
+ public RaiseSpec then(Consumer step) {
+ steps.add(Objects.requireNonNull(step));
+ return this;
+ }
+
+ @Override
+ public void accept(RaiseTaskBuilder b) {
+ b.error(
+ err -> {
+ for (var s : steps) s.accept(err);
+ });
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/RetrySpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/RetrySpec.java
new file mode 100644
index 00000000..5649c696
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/RetrySpec.java
@@ -0,0 +1,77 @@
+/*
+ * 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.TryTaskBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.RetryConfigurer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public final class RetrySpec implements RetryConfigurer {
+
+ private final TryCatchSpec tryCatchSpec;
+
+ RetrySpec(final TryCatchSpec tryCatchSpec) {
+ this.tryCatchSpec = tryCatchSpec;
+ }
+
+ private final List steps = new LinkedList<>();
+
+ public RetrySpec when(String when) {
+ steps.add(t -> t.when(when));
+ return this;
+ }
+
+ public RetrySpec exceptWhen(String when) {
+ steps.add(t -> t.exceptWhen(when));
+ return this;
+ }
+
+ public RetrySpec limit(String duration) {
+ steps.add(r -> r.limit(l -> l.duration(duration)));
+ return this;
+ }
+
+ public RetrySpec limit(Consumer retry) {
+ steps.add(r -> r.limit(retry));
+ return this;
+ }
+
+ public RetrySpec backoff(Consumer backoff) {
+ steps.add(r -> r.backoff(backoff));
+ return this;
+ }
+
+ public RetrySpec jitter(Consumer jitter) {
+ steps.add(r -> r.jitter(jitter));
+ return this;
+ }
+
+ public RetrySpec jitter(String from, String to) {
+ steps.add(r -> r.jitter(j -> j.to(to).from(from)));
+ return this;
+ }
+
+ public TryCatchSpec done() {
+ return tryCatchSpec;
+ }
+
+ @Override
+ public void accept(TryTaskBuilder.RetryPolicyBuilder retryPolicyBuilder) {
+ steps.forEach(step -> step.accept(retryPolicyBuilder));
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/SwitchSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/SwitchSpec.java
new file mode 100644
index 00000000..2b68fd61
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/SwitchSpec.java
@@ -0,0 +1,82 @@
+/*
+ * 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.FlowDirectiveEnum;
+import io.serverlessworkflow.fluent.spec.SwitchTaskBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.SwitchConfigurer;
+import io.serverlessworkflow.fluent.spec.spi.SwitchTaskFluent;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public final class SwitchSpec implements SwitchConfigurer {
+
+ private final Map> cases =
+ new LinkedHashMap<>();
+ private boolean defaultSet = false;
+
+ public SwitchSpec on(String caseKey, String whenExpr, String thenKey) {
+ Objects.requireNonNull(caseKey);
+ Objects.requireNonNull(whenExpr);
+ Objects.requireNonNull(thenKey);
+ putCase(caseKey, c -> c.when(whenExpr).then(thenKey));
+ return this;
+ }
+
+ public SwitchSpec on(String caseKey, String whenExpr, FlowDirectiveEnum thenKey) {
+ Objects.requireNonNull(caseKey);
+ Objects.requireNonNull(whenExpr);
+ Objects.requireNonNull(thenKey);
+ putCase(caseKey, c -> c.when(whenExpr).then(thenKey));
+ return this;
+ }
+
+ public SwitchSpec onDefault(String thenKey) {
+ Objects.requireNonNull(thenKey);
+ setDefault(c -> c.then(thenKey));
+ return this;
+ }
+
+ public SwitchSpec onDefault(FlowDirectiveEnum thenKey) {
+ Objects.requireNonNull(thenKey);
+ setDefault(c -> c.then(thenKey));
+ return this;
+ }
+
+ private void putCase(String key, Consumer cfg) {
+ if (SwitchTaskFluent.DEFAULT_CASE.equals(key)) {
+ throw new IllegalArgumentException("Use onDefault(...) for the default case.");
+ }
+ if (cases.putIfAbsent(key, cfg) != null) {
+ throw new IllegalStateException("Duplicate switch case key: " + key);
+ }
+ }
+
+ private void setDefault(Consumer cfg) {
+ if (defaultSet) {
+ throw new IllegalStateException("Default case already defined.");
+ }
+ defaultSet = true;
+ cases.put(SwitchTaskFluent.DEFAULT_CASE, cfg);
+ }
+
+ @Override
+ public void accept(SwitchTaskBuilder switchTaskBuilder) {
+ cases.forEach(switchTaskBuilder::on);
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchSpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchSpec.java
new file mode 100644
index 00000000..27733fe9
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchSpec.java
@@ -0,0 +1,82 @@
+/*
+ * 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.TaskItemListBuilder;
+import io.serverlessworkflow.fluent.spec.TryTaskBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.TasksConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.TryCatchConfigurer;
+import io.serverlessworkflow.types.Errors;
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public final class TryCatchSpec implements TryCatchConfigurer {
+ private final List steps = new LinkedList<>();
+ private final RetrySpec retry = new RetrySpec(this);
+ private final TrySpec trySpec;
+
+ TryCatchSpec(final TrySpec trySpec) {
+ this.trySpec = trySpec;
+ }
+
+ public TryCatchSpec when(String when) {
+ steps.add(t -> t.when(when));
+ return this;
+ }
+
+ public TryCatchSpec exceptWhen(String when) {
+ steps.add(t -> t.exceptWhen(when));
+ return this;
+ }
+
+ public TryCatchSpec tasks(TasksConfigurer... tasks) {
+ steps.add(t -> t.doTasks(DSL.tasks(tasks)));
+ return this;
+ }
+
+ public TryCatchSpec errors(URI errType, int status) {
+ steps.add(t -> t.errorsWith(e -> e.type(errType.toString()).status(status)));
+ return this;
+ }
+
+ public TryCatchSpec errors(Errors.Standard errType, int status) {
+ steps.add(t -> t.errorsWith(e -> e.type(errType.toString()).status(status)));
+ return this;
+ }
+
+ public TryCatchSpec errors(Consumer errors) {
+ steps.add(t -> t.errorsWith(errors));
+ return this;
+ }
+
+ public RetrySpec retry() {
+ return retry;
+ }
+
+ public TrySpec done() {
+ return trySpec;
+ }
+
+ @Override
+ public void accept(
+ TryTaskBuilder.TryTaskCatchBuilder
+ taskItemListBuilderTryTaskCatchBuilder) {
+ taskItemListBuilderTryTaskCatchBuilder.retry(retry);
+ steps.forEach(step -> step.accept(taskItemListBuilderTryTaskCatchBuilder));
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/TrySpec.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/TrySpec.java
new file mode 100644
index 00000000..dc032958
--- /dev/null
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/TrySpec.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.spec.dsl;
+
+import io.serverlessworkflow.fluent.spec.TaskItemListBuilder;
+import io.serverlessworkflow.fluent.spec.TryTaskBuilder;
+import io.serverlessworkflow.fluent.spec.configurers.TasksConfigurer;
+import io.serverlessworkflow.fluent.spec.configurers.TryConfigurer;
+
+public final class TrySpec implements TryConfigurer {
+ private TryConfigurer tryConfigurer;
+ private final TryCatchSpec tryCatchSpec = new TryCatchSpec(this);
+
+ public TrySpec tasks(TasksConfigurer... tasks) {
+ tryConfigurer = t -> t.tryHandler(DSL.tasks(tasks));
+ return this;
+ }
+
+ public TryCatchSpec catches() {
+ return tryCatchSpec;
+ }
+
+ @Override
+ public void accept(TryTaskBuilder taskItemListBuilderTryTaskBuilder) {
+ taskItemListBuilderTryTaskBuilder.catchHandler(tryCatchSpec);
+ tryConfigurer.accept(taskItemListBuilderTryTaskBuilder);
+ }
+}
diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EventConsumptionStrategyFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EventConsumptionStrategyFluent.java
index 5ee72399..91ba41e0 100644
--- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EventConsumptionStrategyFluent.java
+++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EventConsumptionStrategyFluent.java
@@ -27,7 +27,9 @@ public interface EventConsumptionStrategyFluent<
SELF one(Consumer c);
- SELF all(Consumer c);
+ SELF all(Consumer... c);
+
+ SELF any(Consumer... c);
SELF any(Consumer c);
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 cfaeb260..38c751eb 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
@@ -15,7 +15,10 @@
*/
package io.serverlessworkflow.fluent.spec;
-import static io.serverlessworkflow.fluent.spec.WorkflowBuilderConsumers.authBasic;
+import static io.serverlessworkflow.fluent.spec.dsl.DSL.basic;
+import static io.serverlessworkflow.fluent.spec.dsl.DSL.cases;
+import static io.serverlessworkflow.fluent.spec.dsl.DSL.event;
+import static io.serverlessworkflow.fluent.spec.dsl.DSL.http;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -27,6 +30,7 @@
import io.serverlessworkflow.api.types.Document;
import io.serverlessworkflow.api.types.ErrorFilter;
import io.serverlessworkflow.api.types.EventFilter;
+import io.serverlessworkflow.api.types.FlowDirectiveEnum;
import io.serverlessworkflow.api.types.HTTPArguments;
import io.serverlessworkflow.api.types.HTTPHeaders;
import io.serverlessworkflow.api.types.HTTPQuery;
@@ -80,10 +84,7 @@ void testWorkflowDocumentExplicit() {
void testUseAuthenticationsBasic() {
Workflow wf =
WorkflowBuilder.workflow("flowAuth")
- .use(
- u ->
- u.authentications(
- a -> a.authentication("basicAuth", authBasic("admin", "pass"))))
+ .use(u -> u.authentications(a -> a.authentication("basicAuth", basic("admin", "pass"))))
.build();
Use use = wf.getUse();
@@ -128,11 +129,7 @@ void testDoTaskMultipleTypes() {
d ->
d.set("init", s -> s.expr("$.init = true"))
.forEach("items", f -> f.each("item").in("$.list"))
- .switchCase(
- "choice",
- sw -> {
- // no-op configuration
- })
+ .switchCase("choice", cases().onDefault(FlowDirectiveEnum.CONTINUE))
.raise(
"alert",
r -> {
@@ -217,15 +214,15 @@ void testDoTaskEmitEvent() {
"emitEvent",
e ->
e.event(
- p ->
- p.source(URI.create("https://petstore.com"))
- .type("com.petstore.order.placed.v1")
- .data(
- Map.of(
- "client",
+ event()
+ .type("com.petstore.order.placed.v1")
+ .source(URI.create("https://petstore.com"))
+ .jsonData(
+ Map.of(
+ "client",
Map.of(
"firstName", "Cruella", "lastName", "de Vil"),
- "items",
+ "items",
List.of(
Map.of(
"breed", "dalmatian", "quantity", 101)))))))
@@ -412,10 +409,10 @@ void testDoTaskCallHTTPBasic() {
d ->
d.callHTTP(
"basicCall",
- c ->
- c.method("POST")
- .endpoint(URI.create("http://example.com/api"))
- .body(Map.of("foo", "bar"))))
+ http()
+ .POST()
+ .uri(URI.create("http://example.com/api"))
+ .andThen(b -> b.body(Map.of("foo", "bar")))))
.build();
List items = wf.getDo();
assertEquals(1, items.size(), "Should have one HTTP call task");
@@ -438,10 +435,7 @@ void testDoTaskCallHTTPHeadersConsumerAndMap() {
d ->
d.callHTTP(
"hdrCall",
- c ->
- c.method("GET")
- .endpoint("${uriExpr}")
- .headers(h -> h.header("A", "1").header("B", "2"))))
+ http().GET().endpoint("${uriExpr}").headers(Map.of("A", "1", "B", "2"))))
.build();
CallHTTP call = wf.getDo().get(0).getTask().getCallTask().getCallHTTP();
HTTPHeaders hh = call.getWith().getHeaders().getHTTPHeaders();
@@ -452,9 +446,7 @@ void testDoTaskCallHTTPHeadersConsumerAndMap() {
WorkflowBuilder.workflow()
.tasks(
d ->
- d.callHTTP(
- c ->
- c.method("GET").endpoint("expr").headers(Map.of("X", "10", "Y", "20"))))
+ d.callHTTP(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();
@@ -470,10 +462,10 @@ void testDoTaskCallHTTPQueryConsumerAndMap() {
d ->
d.callHTTP(
"qryCall",
- c ->
- c.method("GET")
- .endpoint("exprUri")
- .query(q -> q.query("k1", "v1").query("k2", "v2"))))
+ http()
+ .GET()
+ .endpoint("exprUri")
+ .andThen(q -> q.query(Map.of("k1", "v1", "k2", "v2")))))
.build();
HTTPQuery hq =
wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith().getQuery().getHTTPQuery();
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
new file mode 100644
index 00000000..d9150b1c
--- /dev/null
+++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java
@@ -0,0 +1,265 @@
+/*
+ * 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.bearer;
+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;
+import static io.serverlessworkflow.fluent.spec.dsl.DSL.oidc;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.serverlessworkflow.api.types.OAuth2AuthenticationData;
+import io.serverlessworkflow.api.types.Workflow;
+import io.serverlessworkflow.fluent.spec.WorkflowBuilder;
+import java.net.URI;
+import org.junit.jupiter.api.Test;
+
+public class CallHttpAuthDslTest {
+
+ private static final String EXPR_ENDPOINT = "${ \"https://api.example.com/v1/resource\" }";
+
+ @Test
+ 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"))))
+ .build();
+
+ var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith();
+
+ // Endpoint expression is set
+ assertThat(args.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT);
+
+ // Auth populated: BASIC (others null)
+ var auth =
+ args.getEndpoint().getEndpointConfiguration().getAuthentication().getAuthenticationPolicy();
+ assertThat(auth).isNotNull();
+
+ assertThat(auth.getBasicAuthenticationPolicy()).isNotNull();
+ assertThat(auth.getBasicAuthenticationPolicy().getBasic()).isNotNull();
+ assertThat(
+ auth.getBasicAuthenticationPolicy()
+ .getBasic()
+ .getBasicAuthenticationProperties()
+ .getUsername())
+ .isEqualTo("alice");
+ assertThat(
+ auth.getBasicAuthenticationPolicy()
+ .getBasic()
+ .getBasicAuthenticationProperties()
+ .getPassword())
+ .isEqualTo("secret");
+
+ assertThat(auth.getBearerAuthenticationPolicy()).isNull();
+ assertThat(auth.getDigestAuthenticationPolicy()).isNull();
+ assertThat(auth.getOpenIdConnectAuthenticationPolicy()).isNull();
+ }
+
+ @Test
+ 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"))))
+ .build();
+
+ var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith();
+
+ assertThat(args.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT);
+
+ var auth =
+ args.getEndpoint().getEndpointConfiguration().getAuthentication().getAuthenticationPolicy();
+ assertThat(auth).isNotNull();
+
+ assertThat(auth.getBearerAuthenticationPolicy()).isNotNull();
+ assertThat(auth.getBearerAuthenticationPolicy().getBearer()).isNotNull();
+ assertThat(
+ auth.getBearerAuthenticationPolicy()
+ .getBearer()
+ .getBearerAuthenticationProperties()
+ .getToken())
+ .isEqualTo("token-123");
+
+ assertThat(auth.getBasicAuthenticationPolicy()).isNull();
+ assertThat(auth.getDigestAuthenticationPolicy()).isNull();
+ assertThat(auth.getOpenIdConnectAuthenticationPolicy()).isNull();
+ }
+
+ @Test
+ 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"))))
+ .build();
+
+ var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith();
+
+ assertThat(args.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT);
+
+ var auth =
+ args.getEndpoint().getEndpointConfiguration().getAuthentication().getAuthenticationPolicy();
+ assertThat(auth).isNotNull();
+
+ assertThat(auth.getDigestAuthenticationPolicy()).isNotNull();
+ assertThat(auth.getDigestAuthenticationPolicy().getDigest()).isNotNull();
+ assertThat(
+ auth.getDigestAuthenticationPolicy()
+ .getDigest()
+ .getDigestAuthenticationProperties()
+ .getUsername())
+ .isEqualTo("bob");
+ assertThat(
+ auth.getDigestAuthenticationPolicy()
+ .getDigest()
+ .getDigestAuthenticationProperties()
+ .getPassword())
+ .isEqualTo("p@ssw0rd");
+
+ assertThat(auth.getBasicAuthenticationPolicy()).isNull();
+ assertThat(auth.getBearerAuthenticationPolicy()).isNull();
+ assertThat(auth.getOpenIdConnectAuthenticationPolicy()).isNull();
+ }
+
+ @Test
+ void when_call_http_with_oidc_auth_on_endpoint_expr_with_client() {
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(
+ t ->
+ t.callHTTP(
+ 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();
+
+ assertThat(args.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT);
+
+ var auth =
+ args.getEndpoint().getEndpointConfiguration().getAuthentication().getAuthenticationPolicy();
+ assertThat(auth).isNotNull();
+
+ assertThat(auth.getOpenIdConnectAuthenticationPolicy()).isNotNull();
+ var oidc = auth.getOpenIdConnectAuthenticationPolicy().getOidc();
+ assertThat(oidc).isNotNull();
+
+ var props = oidc.getOpenIdConnectAuthenticationProperties();
+ assertThat(props).isNotNull();
+ assertThat(props.getAuthority().getLiteralUri())
+ .isEqualTo(URI.create("https://auth.example.com/"));
+ assertThat(props.getGrant())
+ .isEqualTo(OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS);
+
+ var client = props.getClient();
+ assertThat(client).isNotNull();
+ assertThat(client.getId()).isEqualTo("client-id");
+ assertThat(client.getSecret()).isEqualTo("client-secret");
+
+ assertThat(auth.getBasicAuthenticationPolicy()).isNull();
+ assertThat(auth.getBearerAuthenticationPolicy()).isNull();
+ assertThat(auth.getDigestAuthenticationPolicy()).isNull();
+ }
+
+ @Test
+ void when_call_http_with_oauth2_alias_on_endpoint_expr_without_client() {
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(
+ t ->
+ t.callHTTP(
+ 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();
+
+ assertThat(args.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT);
+
+ var auth =
+ args.getEndpoint().getEndpointConfiguration().getAuthentication().getAuthenticationPolicy();
+ assertThat(auth).isNotNull();
+
+ assertThat(auth.getOpenIdConnectAuthenticationPolicy()).isNotNull();
+ var oidc = auth.getOpenIdConnectAuthenticationPolicy().getOidc();
+ assertThat(oidc).isNotNull();
+
+ var props = oidc.getOpenIdConnectAuthenticationProperties();
+ assertThat(props).isNotNull();
+ assertThat(props.getAuthority().getLiteralUri())
+ .isEqualTo(URI.create("https://auth.example.com/"));
+ assertThat(props.getGrant())
+ .isEqualTo(OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS);
+
+ // no client provided
+ assertThat(props.getClient()).isNull();
+
+ assertThat(auth.getBasicAuthenticationPolicy()).isNull();
+ assertThat(auth.getBearerAuthenticationPolicy()).isNull();
+ assertThat(auth.getDigestAuthenticationPolicy()).isNull();
+ }
+
+ @Test
+ void when_call_http_with_basic_auth_on_uri_string() {
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(
+ t ->
+ t.callHTTP(
+ http().GET().uri("https://api.example.com/v1/resource", basic("u", "p"))))
+ .build();
+
+ var args = wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith();
+
+ assertThat(args.getEndpoint().getUriTemplate().getLiteralUri().toString())
+ .isEqualTo("https://api.example.com/v1/resource");
+
+ var auth =
+ args.getEndpoint().getEndpointConfiguration().getAuthentication().getAuthenticationPolicy();
+ assertThat(auth).isNotNull();
+ assertThat(auth.getBasicAuthenticationPolicy()).isNotNull();
+ assertThat(
+ auth.getBasicAuthenticationPolicy()
+ .getBasic()
+ .getBasicAuthenticationProperties()
+ .getUsername())
+ .isEqualTo("u");
+ assertThat(
+ auth.getBasicAuthenticationPolicy()
+ .getBasic()
+ .getBasicAuthenticationProperties()
+ .getPassword())
+ .isEqualTo("p");
+
+ assertThat(auth.getBearerAuthenticationPolicy()).isNull();
+ assertThat(auth.getDigestAuthenticationPolicy()).isNull();
+ assertThat(auth.getOAuth2AuthenticationPolicy()).isNull();
+ }
+}
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
new file mode 100644
index 00000000..894b9a6b
--- /dev/null
+++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/DSLTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.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.to;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.serverlessworkflow.api.types.HTTPArguments;
+import io.serverlessworkflow.api.types.ListenTaskConfiguration;
+import io.serverlessworkflow.api.types.Workflow;
+import io.serverlessworkflow.fluent.spec.WorkflowBuilder;
+import io.serverlessworkflow.types.Errors;
+import java.net.URI;
+import org.junit.jupiter.api.Test;
+
+public class DSLTest {
+
+ @Test
+ public void when_new_call_http_task() {
+ Workflow wf =
+ WorkflowBuilder.workflow("myFlow", "myNs", "1.2.3")
+ .tasks(
+ t ->
+ t.callHTTP(
+ 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();
+ assertThat(args).isNotNull();
+ assertThat(args.getMethod()).isEqualTo("POST");
+ assertThat(args.getHeaders().getHTTPHeaders().getAdditionalProperties().get("Accept"))
+ .isEqualTo("application/json");
+ assertThat(args.getEndpoint().getRuntimeExpression())
+ .isEqualTo("${ \"https://petstore.swagger.io/v2/pet/\\(.petId)\" }");
+ }
+
+ @Test
+ public void when_listen_all_then_emit() {
+ Workflow wf =
+ WorkflowBuilder.workflow("myFlow", "myNs", "1.2.3")
+ .tasks(
+ t ->
+ t.listen(
+ to().all(
+ event().type("org.acme.listen"),
+ event().type("org.example.listen")))
+ .emit(e -> e.event(event().type("org.example.emit"))))
+ .build();
+
+ // Sanity
+ assertThat(wf).isNotNull();
+ assertThat(wf.getDo()).hasSize(2);
+
+ // --- First task: LISTEN with ALL ---
+ var first = wf.getDo().get(0).getTask();
+ assertThat(first.getListenTask()).isNotNull();
+
+ var listen = first.getListenTask().getListen();
+ assertThat(listen).isNotNull();
+ var to = listen.getTo();
+ assertThat(to).isNotNull();
+
+ // Exclusivity: ALL set; ONE/ANY not set
+ assertThat(to.getAllEventConsumptionStrategy()).isNotNull();
+ assertThat(to.getOneEventConsumptionStrategy()).isNull();
+ assertThat(to.getAnyEventConsumptionStrategy()).isNull();
+
+ // ALL has exactly 2 EventFilters, in insertion order
+ var all = to.getAllEventConsumptionStrategy().getAll();
+ assertThat(all).hasSize(2);
+ assertThat(all.get(0).getWith()).isNotNull();
+ assertThat(all.get(1).getWith()).isNotNull();
+ assertThat(all.get(0).getWith().getType()).isEqualTo("org.acme.listen");
+ assertThat(all.get(1).getWith().getType()).isEqualTo("org.example.listen");
+
+ // --- Second task: EMIT ---
+ var second = wf.getDo().get(1).getTask();
+ assertThat(second.getEmitTask()).isNotNull();
+
+ var emit = second.getEmitTask().getEmit();
+ assertThat(emit).isNotNull();
+ // Event type set; id/time/contentType are unset unless explicitly configured
+ var evProps = emit.getEvent().getWith();
+ assertThat(evProps.getType()).isEqualTo("org.example.emit");
+ assertThat(evProps.getId()).isNull();
+ assertThat(evProps.getTime()).isNull();
+ assertThat(evProps.getDatacontenttype()).isNull();
+ }
+
+ @Test
+ public void when_listen_any_with_until() {
+ final String untilExpr = "$.count > 0";
+
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(
+ t ->
+ t.listen(
+ to().any(event().type("A"), event().type("B"))
+ .until(untilExpr)
+ .andThen(c -> c.read(ListenTaskConfiguration.ListenAndReadAs.RAW))))
+ .build();
+
+ var to = wf.getDo().get(0).getTask().getListenTask().getListen().getTo();
+
+ assertThat(to.getAnyEventConsumptionStrategy()).isNotNull();
+ assertThat(to.getOneEventConsumptionStrategy()).isNull();
+ assertThat(to.getAllEventConsumptionStrategy()).isNull();
+
+ assertThat(wf.getDo().get(0).getTask().getListenTask().getListen().getRead())
+ .isEqualTo(ListenTaskConfiguration.ListenAndReadAs.RAW);
+
+ var any = to.getAnyEventConsumptionStrategy();
+ assertThat(any.getAny()).hasSize(2);
+ assertThat(any.getAny().get(0).getWith().getType()).isEqualTo("A");
+ assertThat(any.getAny().get(1).getWith().getType()).isEqualTo("B");
+ assertThat(any.getUntil().getAnyEventUntilCondition()).isEqualTo(untilExpr);
+ }
+
+ @Test
+ public void when_listen_one() {
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(t -> t.listen(to().one(event().type("only-once"))))
+ .build();
+
+ var to = wf.getDo().get(0).getTask().getListenTask().getListen().getTo();
+
+ assertThat(to.getOneEventConsumptionStrategy()).isNotNull();
+ assertThat(to.getAllEventConsumptionStrategy()).isNull();
+ assertThat(to.getAnyEventConsumptionStrategy()).isNull();
+
+ var one = to.getOneEventConsumptionStrategy().getOne();
+ assertThat(one.getWith()).isNotNull();
+ assertThat(one.getWith().getType()).isEqualTo("only-once");
+ }
+
+ @Test
+ public void when_raise_with_string_type_and_status() {
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(
+ t ->
+ t.raise(
+ error("org.acme.Error", 422).title("Unprocessable").detail("Bad input")))
+ .build();
+
+ var err = wf.getDo().get(0).getTask().getRaiseTask().getRaise().getError();
+
+ assertThat(err).isNotNull();
+ assertThat(err.getRaiseErrorDefinition()).isNotNull();
+ assertThat(err.getRaiseErrorDefinition().getType().getExpressionErrorType())
+ .isEqualTo("org.acme.Error");
+ assertThat(err.getRaiseErrorDefinition().getStatus()).isEqualTo(422);
+ assertThat(err.getRaiseErrorDefinition().getTitle().getExpressionErrorTitle())
+ .isEqualTo("Unprocessable");
+ assertThat(err.getRaiseErrorDefinition().getDetail().getExpressionErrorDetails())
+ .isEqualTo("Bad input");
+ }
+
+ @Test
+ public void when_raise_with_string_type_only() {
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(t -> t.raise(error("org.acme.MinorError")))
+ .build();
+
+ var err = wf.getDo().get(0).getTask().getRaiseTask().getRaise().getError();
+
+ assertThat(err).isNotNull();
+ assertThat(err.getRaiseErrorDefinition()).isNotNull();
+ // type as expression
+ assertThat(err.getRaiseErrorDefinition().getType().getExpressionErrorType())
+ .isEqualTo("org.acme.MinorError");
+ // status/title/detail not set
+ assertThat(err.getRaiseErrorDefinition().getStatus()).isEqualTo(0);
+ assertThat(err.getRaiseErrorDefinition().getTitle()).isNull();
+ assertThat(err.getRaiseErrorDefinition().getDetail()).isNull();
+ }
+
+ @Test
+ public void when_raise_with_uri_type_and_status() throws Exception {
+ URI type = new URI("urn:example:error:bad-request");
+
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(t -> t.raise(error(type, 400).title("Bad Request").detail("Missing field")))
+ .build();
+
+ var err = wf.getDo().get(0).getTask().getRaiseTask().getRaise().getError();
+
+ assertThat(err).isNotNull();
+ assertThat(err.getRaiseErrorDefinition()).isNotNull();
+ // type as URI
+ assertThat(err.getRaiseErrorDefinition().getType().getLiteralErrorType().getLiteralUri())
+ .isEqualTo(type);
+ assertThat(err.getRaiseErrorDefinition().getStatus()).isEqualTo(400);
+ assertThat(err.getRaiseErrorDefinition().getTitle().getExpressionErrorTitle())
+ .isEqualTo("Bad Request");
+ assertThat(err.getRaiseErrorDefinition().getDetail().getExpressionErrorDetails())
+ .isEqualTo("Missing field");
+ }
+
+ @Test
+ public void when_raise_with_uri_type_only() throws Exception {
+ URI type = new URI("urn:example:error:temporary");
+
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(t -> t.raise(error(type).title("Temporary")))
+ .build();
+
+ var err = wf.getDo().get(0).getTask().getRaiseTask().getRaise().getError();
+
+ assertThat(err).isNotNull();
+ assertThat(err.getRaiseErrorDefinition()).isNotNull();
+ // type as URI
+ assertThat(err.getRaiseErrorDefinition().getType().getLiteralErrorType().getLiteralUri())
+ .isEqualTo(type);
+ // status not set
+ assertThat(err.getRaiseErrorDefinition().getStatus()).isEqualTo(0);
+ // title set, detail not set
+ assertThat(err.getRaiseErrorDefinition().getTitle().getExpressionErrorTitle())
+ .isEqualTo("Temporary");
+ assertThat(err.getRaiseErrorDefinition().getDetail()).isNull();
+ }
+
+ @Test
+ void serverError_uses_versioned_uri_and_default_code() {
+ Errors.setSpecVersion("1.1.0"); // flip version globally
+ var spec = DSL.serverError().title("Boom").detail("x");
+
+ var wf = WorkflowBuilder.workflow("f", "ns", "1").tasks(t -> t.raise(spec)).build();
+ var def =
+ wf.getDo().get(0).getTask().getRaiseTask().getRaise().getError().getRaiseErrorDefinition();
+
+ assertThat(def.getType().getLiteralErrorType().getLiteralUri().toString())
+ .isEqualTo("https://serverlessworkflow.io/spec/1.1.0/errors/runtime");
+ assertThat(def.getStatus()).isEqualTo(500);
+ assertThat(def.getTitle().getExpressionErrorTitle()).isEqualTo("Boom");
+ assertThat(def.getDetail().getExpressionErrorDetails()).isEqualTo("x");
+ }
+}
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
new file mode 100644
index 00000000..185b65aa
--- /dev/null
+++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.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;
+import static io.serverlessworkflow.fluent.spec.dsl.DSL.set;
+import static io.serverlessworkflow.fluent.spec.dsl.DSL.tryCatch;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.serverlessworkflow.api.types.Workflow;
+import io.serverlessworkflow.fluent.spec.WorkflowBuilder;
+import io.serverlessworkflow.types.Errors;
+import java.net.URI;
+import org.junit.jupiter.api.Test;
+
+public class TryCatchDslTest {
+
+ private static final String EXPR_ENDPOINT = "${ \"https://api.example.com/v1/resource\" }";
+
+ @Test
+ void when_try_with_tasks_and_catch_when_with_retry_and_tasks() {
+ Workflow wf =
+ WorkflowBuilder.workflow("f", "ns", "1")
+ .tasks(
+ t ->
+ t.tryCatch(
+ tryCatch()
+ // try block (one HTTP call)
+ .tasks(call(http().GET().endpoint(EXPR_ENDPOINT)))
+ // catch block
+ .catches()
+ .when("$.error == true")
+ .errors(Errors.RUNTIME, 500)
+ .tasks(emit(e -> e.event(event().type("org.acme.failed"))))
+ .retry()
+ .when("$.retries < 3")
+ .limit("PT5S")
+ .jitter("PT0.1S", "PT0.5S")
+ .done() // back to TryCatchSpec
+ .done() // back to TrySpec
+ ))
+ .build();
+
+ // --- locate Try task ---
+ var tryTask = wf.getDo().get(0).getTask().getTryTask();
+ assertThat(tryTask).isNotNull();
+
+ // --- assert try/do tasks ---
+ var tryDo = tryTask.getTry();
+ assertThat(tryDo).isNotNull();
+ assertThat(tryDo).hasSize(1);
+
+ // First inner task: Call HTTP GET to EXPR_ENDPOINT
+ var callArgs = tryDo.get(0).getTask().getCallTask().getCallHTTP().getWith();
+ assertThat(callArgs.getMethod()).isEqualTo("GET");
+ assertThat(callArgs.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT);
+
+ // --- assert catch (singular) ---
+ var cat = tryTask.getCatch();
+ assertThat(cat).isNotNull();
+
+ // when condition
+ assertThat(cat.getWhen()).isEqualTo("$.error == true");
+
+ // errors filter (Errors.RUNTIME + 500)
+ var errors = cat.getErrors();
+ assertThat(errors).isNotNull();
+ assertThat(errors.getWith().getType()).isEqualTo(Errors.RUNTIME.toString());
+ assertThat(errors.getWith().getStatus()).isEqualTo(500);
+
+ // catch/do tasks
+ var catchDo = cat.getDo();
+ assertThat(catchDo).isNotNull();
+ assertThat(catchDo).hasSize(1);
+ var emitted = catchDo.get(0).getTask().getEmitTask().getEmit().getEvent().getWith();
+ assertThat(emitted.getType()).isEqualTo("org.acme.failed");
+
+ // retry policy (attached to this catch)
+ var retry = cat.getRetry().getRetryPolicyDefinition();
+ assertThat(retry).isNotNull();
+ assertThat(retry.getWhen()).isEqualTo("$.retries < 3");
+ assertThat(retry.getLimit().getDuration().getDurationExpression()).isEqualTo("PT5S");
+
+ // jitter range if modeled with from/to
+ if (retry.getJitter() != null) {
+ assertThat(retry.getJitter().getFrom().getDurationExpression()).isEqualTo("PT0.1S");
+ assertThat(retry.getJitter().getTo().getDurationExpression()).isEqualTo("PT0.5S");
+ }
+ }
+
+ @Test
+ void when_try_with_multiple_tasks_and_catch_except_when_with_uri_error_filter() throws Exception {
+ URI errType = new URI("urn:example:error:downstream");
+
+ Workflow wf =
+ WorkflowBuilder.workflow("g", "ns", "1")
+ .tasks(
+ t ->
+ t.tryCatch(
+ tryCatch()
+ // try with two tasks
+ .tasks(
+ call(http().GET().endpoint(EXPR_ENDPOINT)),
+ set("$.status = \"IN_FLIGHT\""))
+ // catch with exceptWhen + explicit URI error filter + status
+ .catches()
+ .exceptWhen("$.code == 502")
+ .errors(errType, 502)
+ .tasks(emit(e -> e.event(event().type("org.acme.recover"))))
+ .done() // back to TrySpec
+ ))
+ .build();
+
+ var tryTask = wf.getDo().get(0).getTask().getTryTask();
+ assertThat(tryTask).isNotNull();
+
+ // try has two tasks
+ var tryDo = tryTask.getTry();
+ assertThat(tryDo).isNotNull();
+ assertThat(tryDo).hasSize(2);
+
+ // First is HTTP call (GET + endpoint)
+ var args0 = tryDo.get(0).getTask().getCallTask().getCallHTTP().getWith();
+ assertThat(args0.getMethod()).isEqualTo("GET");
+ assertThat(args0.getEndpoint().getRuntimeExpression()).isEqualTo(EXPR_ENDPOINT);
+
+ // Second is set (presence check)
+ var setTask = tryDo.get(1).getTask().getSetTask();
+ assertThat(setTask).isNotNull();
+
+ // catch (singular)
+ var cat = tryTask.getCatch();
+ assertThat(cat).isNotNull();
+ assertThat(cat.getExceptWhen()).isEqualTo("$.code == 502");
+
+ var errors = cat.getErrors();
+ assertThat(errors).isNotNull();
+ assertThat(errors.getWith().getType()).isEqualTo(errType.toString());
+ assertThat(errors.getWith().getStatus()).isEqualTo(502);
+
+ var catchDo = cat.getDo();
+ assertThat(catchDo).hasSize(1);
+ var ev = catchDo.get(0).getTask().getEmitTask().getEmit().getEvent().getWith();
+ assertThat(ev.getType()).isEqualTo("org.acme.recover");
+
+ // no retry configured here
+ assertThat(cat.getRetry().get()).isNull();
+ }
+
+ @Test
+ void when_try_with_catch_and_simple_retry_limit_only() {
+ Workflow wf =
+ WorkflowBuilder.workflow("r", "ns", "1")
+ .tasks(
+ t ->
+ t.tryCatch(
+ tryCatch()
+ .tasks(call(http().GET().endpoint(EXPR_ENDPOINT)))
+ .catches()
+ .when("$.fail == true")
+ .errors(Errors.COMMUNICATION, 503)
+ .retry()
+ .limit("PT2S")
+ .done()
+ .tasks(emit(e -> e.event(event().type("org.acme.retrying"))))
+ .done()))
+ .build();
+
+ var tryTask = wf.getDo().get(0).getTask().getTryTask();
+ assertThat(tryTask).isNotNull();
+
+ var tryDo = tryTask.getTry();
+ assertThat(tryDo).isNotNull();
+ assertThat(tryDo).hasSize(1);
+ assertThat(tryDo.get(0).getTask().getCallTask()).isNotNull();
+
+ var cat = tryTask.getCatch();
+ assertThat(cat).isNotNull();
+ assertThat(cat.getWhen()).isEqualTo("$.fail == true");
+
+ var err = cat.getErrors().getWith();
+ assertThat(err.getType()).isEqualTo(Errors.COMMUNICATION.toString());
+ assertThat(err.getStatus()).isEqualTo(503);
+
+ var retryDef = cat.getRetry().getRetryPolicyDefinition();
+ assertThat(retryDef).isNotNull();
+ assertThat(retryDef.getLimit().getDuration().getDurationExpression()).isEqualTo("PT2S");
+ // 'when' not set in this case
+ assertThat(retryDef.getWhen()).isNull();
+
+ var catchDo = cat.getDo();
+ assertThat(catchDo).hasSize(1);
+ var ev = catchDo.get(0).getTask().getEmitTask().getEmit().getEvent().getWith();
+ assertThat(ev.getType()).isEqualTo("org.acme.retrying");
+ }
+}
diff --git a/impl/core/pom.xml b/impl/core/pom.xml
index fcbca070..7c507613 100644
--- a/impl/core/pom.xml
+++ b/impl/core/pom.xml
@@ -1,4 +1,5 @@
-
+
4.0.0
io.serverlessworkflow
@@ -8,17 +9,17 @@
serverlessworkflow-impl-core
Serverless Workflow :: Impl :: Core
-
- io.serverlessworkflow
- serverlessworkflow-types
+
+ io.serverlessworkflow
+ serverlessworkflow-types
io.cloudevents
cloudevents-core
- org.slf4j
- slf4j-api
+ org.slf4j
+ slf4j-api
com.github.f4b6a3
diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java
index 7381d4d9..c0952c01 100644
--- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java
+++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java
@@ -15,12 +15,13 @@
*/
package io.serverlessworkflow.impl;
+import io.serverlessworkflow.types.Errors;
+
public record WorkflowError(
String type, int status, String instance, String title, String details) {
- private static final String ERROR_FORMAT = "https://serverlessworkflow.io/spec/1.0.0/errors/%s";
- public static final String RUNTIME_TYPE = String.format(ERROR_FORMAT, "runtime");
- public static final String COMM_TYPE = String.format(ERROR_FORMAT, "communication");
+ public static final Errors.Standard RUNTIME_TYPE = Errors.RUNTIME;
+ public static final Errors.Standard COMM_TYPE = Errors.COMMUNICATION;
public static Builder error(String type, int status) {
return new Builder(type, status);
@@ -31,15 +32,25 @@ public static Builder communication(int status, TaskContext context, Exception e
}
public static Builder communication(int status, TaskContext context, String title) {
- return new Builder(COMM_TYPE, status).instance(context.position().jsonPointer()).title(title);
+ return new Builder(COMM_TYPE.toString(), status)
+ .instance(context.position().jsonPointer())
+ .title(title);
+ }
+
+ public static Builder communication(TaskContext context, String title) {
+ return communication(COMM_TYPE.status(), context, title);
}
public static Builder runtime(int status, TaskContext context, Exception ex) {
- return new Builder(RUNTIME_TYPE, status)
+ return new Builder(RUNTIME_TYPE.toString(), status)
.instance(context.position().jsonPointer())
.title(ex.getMessage());
}
+ public static Builder runtime(TaskContext context, Exception ex) {
+ return runtime(RUNTIME_TYPE.status(), context, ex);
+ }
+
public static class Builder {
private final String type;
diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowException.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowException.java
index 685fc077..59b16d51 100644
--- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowException.java
+++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowException.java
@@ -19,7 +19,7 @@ public class WorkflowException extends RuntimeException {
private static final long serialVersionUID = 1L;
- private final WorkflowError worflowError;
+ private final WorkflowError workflowError;
public WorkflowException(WorkflowError error) {
this(error, null);
@@ -27,10 +27,10 @@ public WorkflowException(WorkflowError error) {
public WorkflowException(WorkflowError error, Throwable cause) {
super(error.toString(), cause);
- this.worflowError = error;
+ this.workflowError = error;
}
- public WorkflowError getWorflowError() {
- return worflowError;
+ public WorkflowError getWorkflowError() {
+ return workflowError;
}
}
diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java
index 13d0500b..5d303aff 100644
--- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java
+++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java
@@ -106,7 +106,7 @@ private CompletableFuture handleException(
}
if (e instanceof WorkflowException) {
WorkflowException exception = (WorkflowException) e;
- if (errorFilter.map(f -> f.test(exception.getWorflowError())).orElse(true)
+ if (errorFilter.map(f -> f.test(exception.getWorkflowError())).orElse(true)
&& whenFilter.map(w -> w.test(workflow, taskContext, taskContext.input())).orElse(true)
&& exceptFilter
.map(w -> !w.test(workflow, taskContext, taskContext.input()))
diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/WorkflowErrorCEData.java b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/WorkflowErrorCEData.java
index 776a0185..d74a6c12 100644
--- a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/WorkflowErrorCEData.java
+++ b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/WorkflowErrorCEData.java
@@ -47,7 +47,7 @@ private static WorkflowErrorCEData commonError(Throwable cause) {
}
private static WorkflowErrorCEData error(WorkflowException ex) {
- WorkflowError error = ex.getWorflowError();
+ WorkflowError error = ex.getWorkflowError();
return new WorkflowErrorCEData(
error.type(), error.status(), error.instance(), error.title(), error.details());
}
diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/WorkflowDefinitionTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/WorkflowDefinitionTest.java
index d47dd4c0..856d8dc0 100644
--- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/WorkflowDefinitionTest.java
+++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/WorkflowDefinitionTest.java
@@ -177,12 +177,12 @@ private static JsonNode createObjectNode(
}
private static void checkWorkflowException(WorkflowException ex) {
- assertThat(ex.getWorflowError().type())
+ assertThat(ex.getWorkflowError().type())
.isEqualTo("https://serverlessworkflow.io/errors/not-implemented");
- assertThat(ex.getWorflowError().status()).isEqualTo(500);
- assertThat(ex.getWorflowError().title()).isEqualTo("Not Implemented");
- assertThat(ex.getWorflowError().details()).contains("raise-not-implemented");
- assertThat(ex.getWorflowError().instance()).isEqualTo("do/0/notImplemented");
+ assertThat(ex.getWorkflowError().status()).isEqualTo(500);
+ assertThat(ex.getWorkflowError().title()).isEqualTo("Not Implemented");
+ assertThat(ex.getWorkflowError().details()).contains("raise-not-implemented");
+ assertThat(ex.getWorkflowError().instance()).isEqualTo("do/0/notImplemented");
}
private static void checkSpecialKeywords(Object obj) {
diff --git a/types/src/main/java/io/serverlessworkflow/types/Errors.java b/types/src/main/java/io/serverlessworkflow/types/Errors.java
new file mode 100644
index 00000000..e7b09056
--- /dev/null
+++ b/types/src/main/java/io/serverlessworkflow/types/Errors.java
@@ -0,0 +1,96 @@
+/*
+ * 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.types;
+
+import java.net.URI;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+/**
+ * Standard error types with a configurable spec version.
+ *
+ * @see DSL
+ * Reference - Standard Error Types
+ */
+public final class Errors {
+
+ private Errors() {}
+
+ private static final AtomicReference> VERSION_SUPPLIER =
+ new AtomicReference<>(() -> "1.0.0");
+
+ private static final AtomicReference BASE_PATTERN =
+ new AtomicReference<>("https://serverlessworkflow.io/spec/%s/errors/");
+
+ public static void setSpecVersion(String version) {
+ Objects.requireNonNull(version, "version");
+ VERSION_SUPPLIER.set(() -> version);
+ }
+
+ public static void setVersionSupplier(Supplier supplier) {
+ VERSION_SUPPLIER.set(Objects.requireNonNull(supplier, "supplier"));
+ }
+
+ public static void setBasePattern(String basePattern) {
+ if (basePattern == null || !basePattern.contains("%s")) {
+ throw new IllegalArgumentException("basePattern must include %s placeholder for the version");
+ }
+ BASE_PATTERN.set(basePattern);
+ }
+
+ private static URI uriFor(String slug) {
+ String base = String.format(BASE_PATTERN.get(), VERSION_SUPPLIER.get().get());
+ return URI.create(base + slug);
+ }
+
+ public static final class Standard {
+ private final String slug;
+ private final int defaultStatus;
+
+ private Standard(String slug, int defaultStatus) {
+ this.slug = slug;
+ this.defaultStatus = defaultStatus;
+ }
+
+ public URI uri() {
+ return uriFor(slug);
+ }
+
+ public int status() {
+ return defaultStatus;
+ }
+
+ public Standard withStatus(int override) {
+ return new Standard(slug, override);
+ }
+
+ @Override
+ public String toString() {
+ return uri().toString();
+ }
+ }
+
+ // ---- Standard catalog (defaults are conventional HTTP mappings; override if you prefer) ----
+ public static final Standard RUNTIME = new Standard("runtime", 500);
+ public static final Standard COMMUNICATION = new Standard("communication", 502);
+ public static final Standard AUTHENTICATION = new Standard("authentication", 401);
+ public static final Standard AUTHORIZATION = new Standard("authorization", 403);
+ public static final Standard DATA = new Standard("data", 422);
+ public static final Standard TIMEOUT = new Standard("timeout", 408);
+ public static final Standard NOT_IMPLEMENTED = new Standard("not-implemented", 501);
+}