diff --git a/api/src/test/java/io/serverlessworkflow/api/ApiTest.java b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java index 3505d645b..7c05099a9 100644 --- a/api/src/test/java/io/serverlessworkflow/api/ApiTest.java +++ b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java @@ -23,11 +23,10 @@ import io.serverlessworkflow.api.types.CallHTTP; import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.HTTPArguments; -import io.serverlessworkflow.api.types.OAuth2AuthenticationData; import io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant; import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy; -import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicyConfiguration; import io.serverlessworkflow.api.types.OAuth2AuthenticationPropertiesEndpoints; +import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.Workflow; import java.io.IOException; @@ -99,20 +98,18 @@ void testOauth2Auth() throws IOException { .getAuthenticationPolicy() .getOAuth2AuthenticationPolicy(); assertThat(oauthPolicy).isNotNull(); - OAuth2AuthenticationPolicyConfiguration oauth2Props = + OAuth2ConnectAuthenticationProperties oauth2Props = oauthPolicy.getOauth2().getOAuth2ConnectAuthenticationProperties(); assertThat(oauth2Props).isNotNull(); - OAuth2AuthenticationPropertiesEndpoints endpoints = - oauth2Props.getOAuth2ConnectAuthenticationProperties().getEndpoints(); + OAuth2AuthenticationPropertiesEndpoints endpoints = oauth2Props.getEndpoints(); assertThat(endpoints.getToken()).isEqualTo("/auth/token"); assertThat(endpoints.getIntrospection()).isEqualTo("/auth/introspect"); - OAuth2AuthenticationData oauth2Data = oauth2Props.getOAuth2AuthenticationData(); - assertThat(oauth2Data.getAuthority().getLiteralUri()) + assertThat(oauth2Props.getAuthority().getLiteralUri()) .isEqualTo(URI.create("http://keycloak/realms/fake-authority")); - assertThat(oauth2Data.getGrant()).isEqualTo(OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS); - assertThat(oauth2Data.getClient().getId()).isEqualTo("workflow-runtime-id"); - assertThat(oauth2Data.getClient().getSecret()).isEqualTo("workflow-runtime-secret"); + assertThat(oauth2Props.getGrant()).isEqualTo(OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS); + assertThat(oauth2Props.getClient().getId()).isEqualTo("workflow-runtime-id"); + assertThat(oauth2Props.getClient().getSecret()).isEqualTo("workflow-runtime-secret"); } @Test diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OAuth2AuthenticationPolicyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OAuth2AuthenticationPolicyBuilder.java index 6a4d68354..b62962d7a 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OAuth2AuthenticationPolicyBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OAuth2AuthenticationPolicyBuilder.java @@ -17,18 +17,13 @@ import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy; import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicyConfiguration; -import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties; -import io.serverlessworkflow.api.types.Oauth2; import java.util.function.Consumer; public final class OAuth2AuthenticationPolicyBuilder extends OIDCBuilder { - private final OAuth2ConnectAuthenticationProperties properties; - OAuth2AuthenticationPolicyBuilder() { super(); - this.properties = new OAuth2ConnectAuthenticationProperties(); } public OAuth2AuthenticationPolicyBuilder endpoints( @@ -36,22 +31,16 @@ public OAuth2AuthenticationPolicyBuilder endpoints( final OAuth2AuthenticationPropertiesEndpointsBuilder builder = new OAuth2AuthenticationPropertiesEndpointsBuilder(); endpointsConsumer.accept(builder); - this.properties.setEndpoints(builder.build()); + this.authenticationData.setEndpoints(builder.build()); return this; } public OAuth2AuthenticationPolicy build() { final OAuth2AuthenticationPolicyConfiguration configuration = new OAuth2AuthenticationPolicyConfiguration(); - configuration.setOAuth2AuthenticationData(this.getAuthenticationData()); - configuration.setOAuth2ConnectAuthenticationProperties(this.properties); - - final Oauth2 oauth2 = new Oauth2(); - oauth2.setOAuth2ConnectAuthenticationProperties(configuration); - + configuration.setOAuth2ConnectAuthenticationProperties(this.authenticationData); final OAuth2AuthenticationPolicy policy = new OAuth2AuthenticationPolicy(); - policy.setOauth2(oauth2); - + policy.setOauth2(configuration); return policy; } } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OIDCBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OIDCBuilder.java index 5518b296a..e442ed437 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OIDCBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OIDCBuilder.java @@ -19,16 +19,17 @@ import io.serverlessworkflow.api.types.OAuth2AuthenticationData; import io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient; import io.serverlessworkflow.api.types.OAuth2AuthenticationPropertiesEndpoints; +import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties; import io.serverlessworkflow.api.types.OAuth2TokenDefinition; import io.serverlessworkflow.api.types.OAuth2TokenRequest; import java.util.List; import java.util.function.Consumer; public abstract class OIDCBuilder { - private final OAuth2AuthenticationData authenticationData; + protected final OAuth2ConnectAuthenticationProperties authenticationData; OIDCBuilder() { - this.authenticationData = new OAuth2AuthenticationData(); + this.authenticationData = new OAuth2ConnectAuthenticationProperties(); this.authenticationData.setRequest(new OAuth2TokenRequest()); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index 76bcf23d3..138256c45 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -170,17 +170,15 @@ public static boolean whenExceptTest( public static WorkflowValueResolver fromTimeoutAfter( WorkflowApplication application, TimeoutAfter timeout) { if (timeout.getDurationExpression() != null) { - if (ExpressionUtils.isExpr(timeout.getDurationExpression())) { - return (w, f, t) -> - Duration.parse( - application - .expressionFactory() - .resolveString(ExpressionDescriptor.from(timeout.getDurationExpression())) - .apply(w, f, t)); - } else { - Duration duration = Duration.parse(timeout.getDurationExpression()); - return (w, f, t) -> duration; - } + return (w, f, t) -> + Duration.parse( + application + .expressionFactory() + .resolveString(ExpressionDescriptor.from(timeout.getDurationExpression())) + .apply(w, f, t)); + } else if (timeout.getDurationLiteral() != null) { + Duration duration = Duration.parse(timeout.getDurationLiteral()); + return (w, f, t) -> duration; } else if (timeout.getDurationInline() != null) { DurationInline inlineDuration = timeout.getDurationInline(); return (w, t, f) -> diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java index fa8a1d33b..46bf35797 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java @@ -16,7 +16,7 @@ package io.serverlessworkflow.impl.executors.http; import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy; -import io.serverlessworkflow.api.types.Oauth2; +import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicyConfiguration; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; @@ -36,9 +36,10 @@ public class OAuth2AuthProvider implements AuthProvider { public OAuth2AuthProvider( WorkflowApplication application, Workflow workflow, OAuth2AuthenticationPolicy authPolicy) { - Oauth2 oauth2 = authPolicy.getOauth2(); + OAuth2AuthenticationPolicyConfiguration oauth2 = authPolicy.getOauth2(); if (oauth2.getOAuth2ConnectAuthenticationProperties() != null) { - this.requestBuilder = new OAuthRequestBuilder(application, oauth2); + this.requestBuilder = + new OAuthRequestBuilder(application, oauth2.getOAuth2ConnectAuthenticationProperties()); } else if (oauth2.getOAuth2AuthenticationPolicySecret() != null) { throw new UnsupportedOperationException("Secrets are still not supported"); } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java index 2c557aad4..6ef16fcdc 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java @@ -16,35 +16,29 @@ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; import io.serverlessworkflow.api.types.OAuth2AuthenticationPropertiesEndpoints; -import io.serverlessworkflow.api.types.Oauth2; +import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties; import io.serverlessworkflow.impl.WorkflowApplication; import java.net.URI; import java.util.Map; public class OAuthRequestBuilder extends AbstractAuthRequestBuilder { - private final Oauth2 oauth2; - private final Map defaults = Map.of( "endpoints.token", "oauth2/token", "endpoints.revocation", "oauth2/revoke", "endpoints.introspection", "oauth2/introspect"); - public OAuthRequestBuilder(WorkflowApplication application, Oauth2 oauth2) { - super( - oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AuthenticationData(), - application); - this.oauth2 = oauth2; + public OAuthRequestBuilder( + WorkflowApplication application, + OAuth2ConnectAuthenticationProperties oAuth2ConnectAuthenticationProperties) { + super(oAuth2ConnectAuthenticationProperties, application); } @Override protected void authenticationURI(HttpRequestBuilder requestBuilder) { OAuth2AuthenticationPropertiesEndpoints endpoints = - oauth2 - .getOAuth2ConnectAuthenticationProperties() - .getOAuth2ConnectAuthenticationProperties() - .getEndpoints(); + ((OAuth2ConnectAuthenticationProperties) authenticationData).getEndpoints(); String baseUri = authenticationData.getAuthority().getLiteralUri().toString().replaceAll("/$", ""); diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTest.java index a963c9f87..8c5726fd4 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTest.java @@ -77,9 +77,11 @@ void testRetry(String path) throws IOException { .setHeader("Content-Type", "application/json") .setBody(JsonUtils.mapper().writeValueAsString(result))); CompletableFuture future = - app.workflowDefinition(readWorkflowFromClasspath(path)).instance(Map.of()).start(); + app.workflowDefinition(readWorkflowFromClasspath(path)) + .instance(Map.of("delay", 0.01)) + .start(); Awaitility.await() - .atMost(Duration.ofSeconds(1)) + .atMost(Duration.ofSeconds(100)) .until(() -> future.join().as(JsonNode.class).orElseThrow().equals(result)); } } diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml index cdf9aa760..0dc028aed 100644 --- a/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml +++ b/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml @@ -17,7 +17,7 @@ do: type: https://serverlessworkflow.io/spec/1.0.0/errors/communication status: 404 retry: - delay: "PT0.01S" + delay: ${"PT\(.delay)S"} backoff: exponential: {} limit: diff --git a/types/src/main/resources/schema/workflow.yaml b/types/src/main/resources/schema/workflow.yaml index 912375723..299c33fe2 100644 --- a/types/src/main/resources/schema/workflow.yaml +++ b/types/src/main/resources/schema/workflow.yaml @@ -312,10 +312,10 @@ $defs: type: string title: WithGRPCServiceHost description: The hostname of the GRPC service to call. - pattern: ^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$ + pattern: ^[a-zA-Z0-9](?:[a-zA-Z0-9-.]{0,61}[a-zA-Z0-9])?$ port: type: integer - title: WithGRPCServicePost + title: WithGRPCServicePort description: The port number of the GRPC service to call. minimum: 0 maximum: 65535 @@ -434,6 +434,45 @@ $defs: description: Specifies whether redirection status codes (`300–399`) should be treated as errors. required: [ document, operationId ] unevaluatedProperties: false + - title: CallA2A + description: Defines the A2A call to perform. + type: object + unevaluatedProperties: false + required: [ call, with ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + call: + type: string + const: a2a + with: + type: object + title: A2AArguments + description: The A2A call arguments. + properties: + agentCard: + $ref: '#/$defs/externalResource' + title: WithA2AAgentCard + description: The Agent Card that defines the agent to call. + server: + title: A2AServer + description: The server endpoint to send the request to. + $ref: '#/$defs/endpoint' + method: + type: string + title: WithA2AMethod + description: The A2A method to send. + enum: [ 'message/send', 'message/stream', 'tasks/get', 'tasks/list', 'tasks/cancel', 'tasks/resubscribe', 'tasks/pushNotificationConfig/set', 'tasks/pushNotificationConfig/get', 'tasks/pushNotificationConfig/list', 'tasks/pushNotificationConfig/delete', 'agent/getAuthenticatedExtendedCard' ] + parameters: + oneOf: + - type: object + minProperties: 1 + additionalProperties: true + - type: string + title: WithA2AParameters + description: The parameters object to send with the A2A method. + required: [ method ] + unevaluatedProperties: false - title: CallFunction description: Defines the function call to perform. type: object @@ -445,7 +484,7 @@ $defs: call: type: string not: - enum: ["asyncapi", "grpc", "http", "openapi"] + enum: ["asyncapi", "grpc", "http", "openapi", "a2a"] description: The name of the function to call. with: type: object @@ -1001,37 +1040,35 @@ $defs: description: The configuration of the OAuth2 authentication policy. unevaluatedProperties: false oneOf: - - type: object + - $ref: '#/$defs/oauth2AuthenticationProperties' + type: object title: OAuth2ConnectAuthenticationProperties description: The inline configuration of the OAuth2 authentication policy. unevaluatedProperties: false - allOf: - - $ref: '#/$defs/oauth2AuthenticationProperties' - - type: object + properties: + endpoints: + type: object + title: OAuth2AuthenticationPropertiesEndpoints + description: The endpoint configurations for OAuth2. properties: - endpoints: - type: object - title: OAuth2AuthenticationPropertiesEndpoints - description: The endpoint configurations for OAuth2. - properties: - token: - type: string - format: uri-template - default: /oauth2/token - title: OAuth2TokenEndpoint - description: The relative path to the token endpoint. Defaults to `/oauth2/token`. - revocation: - type: string - format: uri-template - default: /oauth2/revoke - title: OAuth2RevocationEndpoint - description: The relative path to the revocation endpoint. Defaults to `/oauth2/revoke`. - introspection: - type: string - format: uri-template - default: /oauth2/introspect - title: OAuth2IntrospectionEndpoint - description: The relative path to the introspection endpoint. Defaults to `/oauth2/introspect`. + token: + type: string + format: uri-template + default: /oauth2/token + title: OAuth2TokenEndpoint + description: The relative path to the token endpoint. Defaults to `/oauth2/token`. + revocation: + type: string + format: uri-template + default: /oauth2/revoke + title: OAuth2RevocationEndpoint + description: The relative path to the revocation endpoint. Defaults to `/oauth2/revoke`. + introspection: + type: string + format: uri-template + default: /oauth2/introspect + title: OAuth2IntrospectionEndpoint + description: The relative path to the introspection endpoint. Defaults to `/oauth2/introspect`. - $ref: '#/$defs/secretBasedAuthenticationPolicy' title: OAuth2AuthenticationPolicySecret description: Secret based configuration of the OAuth2 authentication policy. @@ -1178,10 +1215,13 @@ $defs: description: Number of milliseconds, if any. title: DurationInline description: The inline definition of a duration. + - $ref: '#/$defs/runtimeExpression' + title: DurationExpression + description: Runtime expression that generates an ISO 8601 - type: string pattern: '^P(?!$)(\d+(?:\.\d+)?Y)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?W)?(\d+(?:\.\d+)?D)?(T(?=\d)(\d+(?:\.\d+)?H)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?S)?)?$' - title: DurationExpression - description: The ISO 8601 expression of a duration. + title: DurationLiteral + description: The Literal ISO 8601 representation of a duration. error: type: object title: Error @@ -1697,23 +1737,20 @@ $defs: type: object title: AsyncApiMessagePayload description: The message's payload, if any. - additionalProperties: true headers: type: object title: AsyncApiMessageHeaders description: The message's headers, if any. - additionalProperties: true asyncApiInboundMessage: - type: object title: AsyncApiInboundMessage description: Represents a message counsumed by an AsyncAPI subscription. allOf: - - $ref: '#/$defs/asyncApiOutboundMessage' - properties: - correlationId: - type: string - title: AsyncApiMessageCorrelationId - description: The message's correlation id, if any. + - $ref: '#/$defs/asyncApiOutboundMessage' + - properties: + correlationId: + type: string + title: AsyncApiMessageCorrelationId + description: The message's correlation id, if any. asyncApiSubscription: type: object title: AsyncApiSubscription