diff --git a/api/src/main/java/io/serverlessworkflow/api/ValidationReader.java b/api/src/main/java/io/serverlessworkflow/api/ValidationReader.java index d3d3123f4..db9f0179b 100644 --- a/api/src/main/java/io/serverlessworkflow/api/ValidationReader.java +++ b/api/src/main/java/io/serverlessworkflow/api/ValidationReader.java @@ -15,6 +15,8 @@ */ package io.serverlessworkflow.api; +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.Error; import com.networknt.schema.InputFormat; @@ -65,12 +67,13 @@ public Workflow read(String input, WorkflowFormat format) throws IOException { return validate(format.mapper().readValue(input, JsonNode.class), format); } - private Workflow validate(JsonNode value, WorkflowFormat format) { + private Workflow validate(JsonNode value, WorkflowFormat format) + throws StreamReadException, DatabindException, IOException { Collection validationErrors = schemaObject.validate(value); if (!validationErrors.isEmpty()) { throw new IllegalArgumentException( validationErrors.stream().map(Error::toString).collect(Collectors.joining("\n"))); } - return format.mapper().convertValue(value, Workflow.class); + return format.mapper().treeToValue(value, Workflow.class); } } diff --git a/api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java b/api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java index b4401af0e..1bdb21839 100644 --- a/api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java +++ b/api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java @@ -216,12 +216,12 @@ private static class ValidationHolder { } /** - * Returns the default {@link WorkflowReaderOperations} instance (no validation). + * Returns the default {@link WorkflowReaderOperations} instance * * @return the default reader */ private static WorkflowReaderOperations defaultReader() { - return NoValidationHolder.instance; + return ValidationHolder.instance; } private WorkflowReader() {} 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 a107d9ca5..aa2fc410a 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 @@ -178,7 +178,7 @@ public CatchErrorsBuilder title(final String title) { } public CatchErrorsBuilder details(final String details) { - this.errorFilter.setDetails(details); + this.errorFilter.setDetail(details); return this; } diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java index d277876fd..196ed888e 100644 --- a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java @@ -17,6 +17,7 @@ import io.serverlessworkflow.api.types.ForTask; import io.serverlessworkflow.api.types.ForTaskConfiguration; +import io.serverlessworkflow.api.types.In; import io.serverlessworkflow.api.types.TaskItem; import io.serverlessworkflow.fluent.spec.spi.ForEachTaskFluent; import java.util.List; @@ -48,7 +49,7 @@ public ForEachTaskBuilder each(String each) { } public ForEachTaskBuilder in(String in) { - this.forTaskConfiguration.setIn(in); + this.forTaskConfiguration.setIn(new In().withForInExpression(in)); return this; } 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 d877e0d8f..7cf084620 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 @@ -329,7 +329,7 @@ void when_try_catch_match_details() { assertThat(tryTask).isNotNull(); var cat = tryTask.getCatch(); assertThat(cat).isNotNull(); - assertThat(cat.getErrors().getWith().getDetails()) + assertThat(cat.getErrors().getWith().getDetail()) .isEqualTo("Enforcement Failure - invalid email"); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java index 249ee0397..6022002e3 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.impl.executors; import io.serverlessworkflow.api.types.ForTask; +import io.serverlessworkflow.api.types.In; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowDefinition; @@ -50,9 +51,11 @@ protected Optional buildWhileFilter() { } protected WorkflowValueResolver> buildCollectionFilter() { + In in = task.getFor().getIn(); return application .expressionFactory() - .resolveCollection(ExpressionDescriptor.from(task.getFor().getIn())); + .resolveCollection( + new ExpressionDescriptor(in.getForInExpression(), in.getForInInlineArray())); } @Override 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 7ef9c64d7..38ebfa510 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 @@ -187,7 +187,7 @@ private CompletableFuture handleException( taskContext, workflow.definition().application().modelFactory().fromAny(error))) { if (errorVariable != null) { - taskContext.variables().put(errorVariable, WorkflowErrorExpr.from(error)); + taskContext.variables().put(errorVariable, error); } if (catchTaskExecutor.isPresent()) { completable = @@ -213,14 +213,6 @@ private CompletableFuture handleException( return CompletableFuture.failedFuture(e); } - private static record WorkflowErrorExpr( - String type, int status, String instance, String title, String details) { - static WorkflowErrorExpr from(WorkflowError error) { - return new WorkflowErrorExpr( - error.type(), error.status(), error.instance(), error.title(), error.detail()); - } - } - private static Optional> buildErrorFilter(CatchErrors errors) { return errors != null ? Optional.of(error -> filterError(error, errors.getWith())) @@ -232,7 +224,7 @@ private static boolean filterError(WorkflowError error, ErrorFilter errorFilter) && (errorFilter.getStatus() <= 0 || error.status() == errorFilter.getStatus()) && compareString(errorFilter.getInstance(), error.instance()) && compareString(errorFilter.getTitle(), error.title()) - && compareString(errorFilter.getDetails(), error.detail()); + && compareString(errorFilter.getDetail(), error.detail()); } private static boolean compareString(String one, String other) { diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-error-variable.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-error-variable.yaml index 5d741ef3e..8d9a1c930 100644 --- a/impl/test/src/test/resources/workflows-samples/try-catch-error-variable.yaml +++ b/impl/test/src/test/resources/workflows-samples/try-catch-error-variable.yaml @@ -17,4 +17,4 @@ do: do: - handleError: set: - errorMessage: ${$caughtError.details} \ No newline at end of file + errorMessage: ${$caughtError.detail} \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-match-details.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-match-details.yaml index 96dcdf662..ecee1d5e9 100644 --- a/impl/test/src/test/resources/workflows-samples/try-catch-match-details.yaml +++ b/impl/test/src/test/resources/workflows-samples/try-catch-match-details.yaml @@ -17,7 +17,7 @@ do: with: type: https://example.com/errors/transient status: 503 - details: Enforcement Failure - invalid email + detail: Enforcement Failure - invalid email do: - handleError: set: diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-not-match-details.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-details.yaml index d2a2ff627..50f106996 100644 --- a/impl/test/src/test/resources/workflows-samples/try-catch-not-match-details.yaml +++ b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-details.yaml @@ -17,4 +17,4 @@ do: with: type: https://example.com/errors/security status: 403 - details: User not found in tenant catalog \ No newline at end of file + detail: User not found in tenant catalog \ No newline at end of file diff --git a/types/src/main/resources/schema/workflow.yaml b/types/src/main/resources/schema/workflow.yaml index 299c33fe2..96b3fc24f 100644 --- a/types/src/main/resources/schema/workflow.yaml +++ b/types/src/main/resources/schema/workflow.yaml @@ -1,6 +1,6 @@ -$id: https://serverlessworkflow.io/schemas/1.0.1/workflow.yaml +$id: https://open-workflow-specification.org/schemas/1.0.3/workflow.yaml $schema: https://json-schema.org/draft/2020-12/schema -description: Serverless Workflow DSL - Workflow Schema. +description: Open Workflow DSL - Workflow Schema. type: object required: [ document, do ] properties: @@ -152,6 +152,31 @@ properties: $ref: '#/$defs/eventConsumptionStrategy' title: ScheduleOn description: Specifies the events that trigger the workflow execution. + read: + type: string + enum: [ data, envelope, raw ] + default: data + title: ScheduleOnReadAs + description: Specifies how consumed events are read when the workflow is triggered by events. Supported values are 'data' (reads the event's data), 'envelope' (reads the event's envelope, including context attributes), and 'raw' (reads the event's raw data). Defaults to 'data'. + dependentRequired: + read: [ on ] + evaluate: + type: object + title: Evaluate + description: Configures the workflow's runtime expression evaluation. + unevaluatedProperties: false + properties: + language: + type: string + default: jq + title: EvaluateLanguage + description: The language used for writing runtime expressions. Defaults to 'jq'. + mode: + type: string + enum: [ strict, loose ] + default: strict + title: EvaluateMode + description: The runtime expression evaluation mode. Defaults to 'strict'. $defs: taskList: title: TaskList @@ -473,6 +498,108 @@ $defs: description: The parameters object to send with the A2A method. required: [ method ] unevaluatedProperties: false + - title: CallMCP + description: Defines the MCP call to perform. + type: object + unevaluatedProperties: false + required: [ call, with ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + call: + type: string + const: mcp + with: + type: object + title: MCPArguments + description: The MCP call arguments. + properties: + protocolVersion: + type: string + default: '2025-06-18' + title: McpProtocolVersion + description: The version of the MCP protocol to use. + method: + type: string + enum: [ tools/list, tools/call, prompts/list, prompts/get, resources/list, resources/read, resources/templates/list ] + title: McpMethod + description: The MCP method to call. + parameters: + oneOf: + - type: object + additionalProperties: true + - type: string + title: McpMethodParameters + description: The MCP method parameters. + timeout: + $ref: '#/$defs/duration' + title: McpCallTimeout + description: The duration after which the MCP call times out. + transport: + type: object + title: McpCallTransport + description: The transport to use to perform the MCP call. + properties: + http: + type: object + title: McpHttpTransport + description: The definition of the HTTP transport to use. + properties: + endpoint: + $ref: '#/$defs/endpoint' + title: McpHttpTransportEndpoint + description: The MCP server endpoint to connect to. + headers: + type: object + additionalProperties: + type: string + title: McpHttpTransportHeaders + description: A key/value mapping of the HTTP headers to send with requests, if any. + required: [ endpoint ] + stdio: + type: object + title: McpStdioTransport + description: The definition of the STDIO transport to use. + properties: + command: + type: string + title: McpStdioTransportCommand + description: The command used to run the MCP server. + arguments: + type: array + items: + type: string + title: McpStdioTransportArguments + description: An optional list of arguments to pass to the command. + environment: + type: object + additionalProperties: + type: string + title: McpStdioTransportEnvironment + description: A key/value mapping, if any, of environment variables used to configure the MCP server. + required: [ command ] + options: + type: object + additionalProperties: + type: string + oneOf: + - required: [http] + - required: [stdio] + client: + type: object + title: McpClient + description: Describes the client used to perform the MCP call. + properties: + name: + type: string + title: McpClientName + description: The name of the client used to connect to the MCP server. + version: + type: string + title: McpClientVersion + description: The version of the client used to connect to the MCP server. + required: [ name, version ] + required: [ method, transport ] - title: CallFunction description: Defines the function call to perform. type: object @@ -484,7 +611,7 @@ $defs: call: type: string not: - enum: ["asyncapi", "grpc", "http", "openapi", "a2a"] + enum: ["asyncapi", "grpc", "http", "openapi", "a2a", "mcp"] description: The name of the function to call. with: type: object @@ -552,7 +679,7 @@ $defs: $ref: '#/$defs/eventProperties' title: EmitEventWith description: Defines the properties of event to emit. - required: [ source, type ] + required: [ type ] additionalProperties: true required: [ event ] forTask: @@ -576,9 +703,18 @@ $defs: description: The name of the variable used to store the current item being enumerated. default: item in: - type: string title: ForIn - description: A runtime expression used to get the collection to enumerate. + description: A runtime expression or an inline array used to get the collection to enumerate. + oneOf: + - type: string + title: ForInExpression + description: A runtime expression used to get the collection to enumerate. + - type: array + title: ForInInlineArray + description: An inline array of objects to enumerate. + items: + type: object + description: An item in the inline collection. at: type: string title: ForAt @@ -707,10 +843,25 @@ $defs: type: object title: ContainerEnvironment description: A key/value mapping of the environment variables, if any, to use when running the configured process. + stdin: + type: string + title: ContainerStdin + description: A runtime expression, if any, passed as standard input (stdin) to the command or default container CMD + arguments: + type: array + title: ContainerArguments + description: A list of the arguments, if any, passed as argv to the command or default container CMD + items: + type: string lifetime: $ref: '#/$defs/containerLifetime' title: ContainerLifetime description: An object, if any, used to configure the container's lifetime + pullPolicy: + type: string + title: ContainerPullPolicy + description: Policy that controls how the container's image should be pulled from the registry. Defaults to `ifNotPresent` + enum: [ ifNotPresent, always, never ] required: [ image ] required: [ container ] - title: RunScript @@ -768,6 +919,10 @@ $defs: type: string title: ShellCommand description: The shell command to run. + stdin: + type: string + title: ShellStdin + description: A runtime expression, if any, to the shell command as standard input (stdin). arguments: type: object title: ShellArguments @@ -910,6 +1065,10 @@ $defs: $ref: '#/$defs/taskList' title: TryTaskCatchDo description: The definition of the task(s) to run when catching an error. + then: + $ref: '#/$defs/flowDirective' + title: TryTaskCatchThen + description: The flow directive to execute for the error path after the error has been caught (and after executing any `do` tasks, if set). When set, this determines the next transition instead of the try task's top-level `then` or the default sequential flow. waitTask: type: object title: WaitTask @@ -1094,6 +1253,7 @@ $defs: type: object title: OAuth2AuthenticationData description: Inline configuration of the OAuth2 authentication policy. + required: [ authority, grant ] properties: authority: $ref: '#/$defs/uriTemplate' @@ -1285,23 +1445,23 @@ $defs: instance: type: string description: if present, means this value should be used for filtering - title: + title: type: string description: if present, means this value should be used for filtering - details: + detail: type: string description: if present, means this value should be used for filtering uriTemplate: title: UriTemplate anyOf: - - title: LiteralUriTemplate - type: string - format: uri-template - pattern: "^[A-Za-z][A-Za-z0-9+\\-.]*://.*" - - title: LiteralUri - type: string - format: uri - pattern: "^[A-Za-z][A-Za-z0-9+\\-.]*://.*" + - title: LiteralUriTemplate + type: string + format: uri-template + pattern: "^(?!\\s*\\$\\{)(?=\\S)(([^:/?#]+):)?(//([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(\\S*))?$" + - title: LiteralUri + type: string + format: uri + pattern: "^(?!\\s*\\$\\{)(?=\\S)(([^:/?#]+):)?(//([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(\\S*))?$" endpoint: title: Endpoint description: Represents an endpoint. @@ -1338,7 +1498,7 @@ $defs: description: The event's unique identifier. source: title: EventSource - description: Identifies the context in which an event happened. + description: Identifies the context in which an event happened. If not explicitly provided, runtime implementations generate it at emission time (e.g., from the workflow identity). If explicitly provided by the workflow author, the supplied value takes precedence. oneOf: - $ref: '#/$defs/uriTemplate' - $ref: '#/$defs/runtimeExpression' @@ -1826,4 +1986,4 @@ $defs: export: $ref: '#/$defs/export' title: SubscriptionIteratorExport - description: An object, if any, used to customize the content of the workflow context. \ No newline at end of file + description: An object, if any, used to customize the content of the workflow context.