diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTryTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTryTaskBuilder.java index 1dc14b59d..a107d9ca5 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTryTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTryTaskBuilder.java @@ -342,40 +342,61 @@ public RetryLimitAttempt build() { public static final class BackoffBuilder { private final RetryBackoff retryBackoff; - private final ConstantBackoff constantBackoff; - private final ExponentialBackOff exponentialBackOff; - private final LinearBackoff linearBackoff; + private ConstantBackoff constantBackoff; + private ExponentialBackOff exponentialBackOff; + private LinearBackoff linearBackoff; BackoffBuilder() { this.retryBackoff = new RetryBackoff(); - - this.constantBackoff = new ConstantBackoff(); - this.constantBackoff.setConstant(new Constant()); - this.exponentialBackOff = new ExponentialBackOff(); - this.exponentialBackOff.setExponential(new Exponential()); - this.linearBackoff = new LinearBackoff(); - this.linearBackoff.setLinear(new Linear()); } public BackoffBuilder constant(String key, String value) { + ensureExclusive(this.constantBackoff, this.exponentialBackOff, this.linearBackoff); + if (this.constantBackoff == null) { + this.constantBackoff = new ConstantBackoff(); + this.constantBackoff.setConstant(new Constant()); + } this.constantBackoff.getConstant().withAdditionalProperty(key, value); return this; } public BackoffBuilder exponential(String key, String value) { + ensureExclusive(this.exponentialBackOff, this.constantBackoff, this.linearBackoff); + if (this.exponentialBackOff == null) { + this.exponentialBackOff = new ExponentialBackOff(); + this.exponentialBackOff.setExponential(new Exponential()); + } this.exponentialBackOff.getExponential().withAdditionalProperty(key, value); return this; } public BackoffBuilder linear(String key, String value) { + ensureExclusive(this.linearBackoff, this.constantBackoff, this.exponentialBackOff); + if (this.linearBackoff == null) { + this.linearBackoff = new LinearBackoff(); + this.linearBackoff.setLinear(new Linear()); + } this.linearBackoff.getLinear().withAdditionalProperty(key, value); return this; } + private void ensureExclusive(Object current, Object... others) { + for (Object other : others) { + if (other != null) { + throw new IllegalStateException( + "Only one backoff variant (constant, exponential, or linear) can be configured"); + } + } + } + public RetryBackoff build() { - this.retryBackoff.setConstantBackoff(constantBackoff); - this.retryBackoff.setExponentialBackOff(exponentialBackOff); - this.retryBackoff.setLinearBackoff(linearBackoff); + if (this.constantBackoff != null) { + this.retryBackoff.setConstantBackoff(constantBackoff); + } else if (this.exponentialBackOff != null) { + this.retryBackoff.setExponentialBackOff(exponentialBackOff); + } else if (this.linearBackoff != null) { + this.retryBackoff.setLinearBackoff(linearBackoff); + } return this.retryBackoff; } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ReferenceableAuthenticationPolicyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ReferenceableAuthenticationPolicyBuilder.java index 3136ce6c2..20f573f24 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ReferenceableAuthenticationPolicyBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ReferenceableAuthenticationPolicyBuilder.java @@ -79,8 +79,11 @@ public ReferenceableAuthenticationPolicyBuilder use(String use) { public ReferenceableAuthenticationPolicy build() { final ReferenceableAuthenticationPolicy policy = new ReferenceableAuthenticationPolicy(); - policy.setAuthenticationPolicy(this.authenticationPolicy); - policy.setAuthenticationPolicyReference(this.authenticationPolicyReference); + if (this.authenticationPolicyReference != null) { + policy.setAuthenticationPolicyReference(this.authenticationPolicyReference); + } else if (this.authenticationPolicy != null) { + policy.setAuthenticationPolicy(this.authenticationPolicy); + } return policy; } } diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java index ef89506ba..85c449d3e 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/CallHttpAuthDslTest.java @@ -233,6 +233,32 @@ void when_call_http_with_oauth2_alias_on_endpoint_expr_without_client() { assertThat(auth.getDigestAuthenticationPolicy()).isNull(); } + @Test + void when_call_http_with_basic_auth_then_oneof_value_is_set() { + Workflow wf = + WorkflowBuilder.workflow("f", "ns", "1") + .tasks(call(http().get().endpoint(EXPR_ENDPOINT, basic("alice", "secret")))) + .build(); + + var authentication = + wf.getDo() + .get(0) + .getTask() + .getCallTask() + .getCallHTTP() + .getWith() + .getEndpoint() + .getEndpointConfiguration() + .getAuthentication(); + + assertThat(authentication.get()) + .as("ReferenceableAuthenticationPolicy.get() must not be null for serialization") + .isNotNull(); + + assertThat(authentication.getAuthenticationPolicy()).isNotNull(); + assertThat(authentication.getAuthenticationPolicyReference()).isNull(); + } + @Test void when_call_http_with_basic_auth_on_uri_string() { Workflow wf = diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java index cf9d8eaac..d877e0d8f 100644 --- a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/dsl/TryCatchDslTest.java @@ -24,6 +24,7 @@ import static io.serverlessworkflow.fluent.spec.dsl.DSL.tryCatch; import static io.serverlessworkflow.fluent.spec.dsl.DSL.use; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.fluent.spec.WorkflowBuilder; @@ -622,4 +623,101 @@ void when_try_catch_backoff_constant() { assertThat(constant).isNotNull(); assertThat(constant.getAdditionalProperties()).containsEntry("c", "10"); } + + @Test + void when_try_catch_backoff_exponential_then_oneof_value_is_set() { + Workflow wf = + WorkflowBuilder.workflow("try-catch-backoff-oneof", "test", "0.1.0") + .tasks( + tryCatch( + "tryTask", + tryCatch() + .tasks(call(http().get().endpoint("http://localhost:9797"))) + .catches() + .errors(Errors.COMMUNICATION, 404) + .retry() + .backoffExponential() + .done() + .done())) + .build(); + + var backoff = + wf.getDo() + .get(0) + .getTask() + .getTryTask() + .getCatch() + .getRetry() + .getRetryPolicyDefinition() + .getBackoff(); + + assertThat(backoff.get()) + .as("RetryBackoff.get() must not be null for serialization") + .isNotNull(); + + assertThat(backoff.getExponentialBackOff()).isNotNull(); + assertThat(backoff.getConstantBackoff()).isNull(); + assertThat(backoff.getLinearBackoff()).isNull(); + } + + @Test + void when_try_catch_backoff_constant_then_oneof_value_is_set() { + Workflow wf = + WorkflowBuilder.workflow("try-catch-backoff-oneof-const", "test", "0.1.0") + .tasks( + tryCatch( + "tryTask", + tryCatch() + .tasks(call(http().get().endpoint("http://localhost:9797"))) + .catches() + .errors(Errors.COMMUNICATION, 404) + .retry() + .backoffConstant() + .done() + .done())) + .build(); + + var backoff = + wf.getDo() + .get(0) + .getTask() + .getTryTask() + .getCatch() + .getRetry() + .getRetryPolicyDefinition() + .getBackoff(); + + assertThat(backoff.get()) + .as("RetryBackoff.get() must not be null for serialization") + .isNotNull(); + + assertThat(backoff.getConstantBackoff()).isNotNull(); + assertThat(backoff.getExponentialBackOff()).isNull(); + assertThat(backoff.getLinearBackoff()).isNull(); + } + + @Test + void when_backoff_builder_mixes_variants_then_throws() { + assertThatThrownBy( + () -> + WorkflowBuilder.workflow("mixed-backoff", "test", "0.1.0") + .tasks( + tryCatch( + "tryTask", + tryCatch() + .tasks(call(http().get().endpoint("http://localhost:9797"))) + .catches() + .errors(Errors.COMMUNICATION, 404) + .retry() + .backoff( + b -> { + b.constant("c", "10"); + b.exponential("e", "1.5"); + }) + .done() + .done())) + .build()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Only one backoff variant"); + } }