" + "': ")
+ missingParams);
}
@@ -135,8 +135,9 @@ private void param(
if (origMap.containsKey(name)) {
collectorMap.put(parameter.getName(), origMap.remove(name));
} else if (parameter.getRequired()) {
- Schema> schema = parameter.getSchema();
- Object defaultValue = schema != null ? schema.getDefault() : null;
+
+ JsonNode schema = parameter.getSchema();
+ Object defaultValue = schema != null ? schema.get("default") : null;
if (defaultValue != null) {
collectorMap.put(name, defaultValue);
} else {
diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIParser.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIParser.java
new file mode 100644
index 00000000..49f2464b
--- /dev/null
+++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIParser.java
@@ -0,0 +1,75 @@
+/*
+ * 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.impl.executors.openapi;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import java.util.Objects;
+
+/**
+ * Parses OpenAPI content (JSON or YAML) into a {@link OpenAPI} using Jackson.
+ *
+ * This class detects JSON if the first non-whitespace character is '{'; otherwise it treats the
+ * content as YAML.
+ */
+public final class OpenAPIParser {
+
+ private static final ObjectMapper YAML_MAPPER = new YAMLMapper();
+ private static final ObjectMapper JSON_MAPPER = new JsonMapper();
+
+ /**
+ * Parse the provided OpenAPI content (JSON or YAML) and return a {@link OpenAPI}.
+ *
+ * @param content the OpenAPI document content (must not be null or blank)
+ * @return parsed {@link OpenAPI}
+ * @throws IllegalArgumentException if content is null/blank or cannot be parsed
+ */
+ public OpenAPI parse(String content) {
+ Objects.requireNonNull(content, "content must not be null");
+ String trimmed = content.trim();
+ if (trimmed.isEmpty()) {
+ throw new IllegalArgumentException("content must not be blank");
+ }
+
+ ObjectMapper mapper = selectMapper(trimmed);
+ try {
+ JsonNode root = mapper.readTree(content);
+ return new OpenAPI(root);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to parse content", e);
+ }
+ }
+
+ private ObjectMapper selectMapper(String trimmedContent) {
+ char first = firstNonWhitespaceChar(trimmedContent);
+ if (first == '{') {
+ return JSON_MAPPER;
+ }
+ return YAML_MAPPER;
+ }
+
+ private static char firstNonWhitespaceChar(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ return c;
+ }
+ }
+ return '\0';
+ }
+}
diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java
index f8da9994..26428101 100644
--- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java
+++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java
@@ -15,89 +15,25 @@
*/
package io.serverlessworkflow.impl.executors.openapi;
-import io.swagger.parser.OpenAPIParser;
-import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.Operation;
-import io.swagger.v3.oas.models.PathItem;
-import io.swagger.v3.parser.core.models.ParseOptions;
-import io.swagger.v3.parser.core.models.SwaggerParseResult;
-import java.util.Set;
+import java.util.Objects;
-class OpenAPIProcessor {
+public class OpenAPIProcessor {
private final String operationId;
- OpenAPIProcessor(String operationId) {
- this.operationId = operationId;
+ public OpenAPIProcessor(String operationId) {
+ this.operationId = Objects.requireNonNull(operationId);
}
public OperationDefinition parse(String content) {
OpenAPIParser parser = new OpenAPIParser();
- ParseOptions opts = new ParseOptions();
- opts.setResolve(true);
- opts.setResolveFully(true);
-
- SwaggerParseResult result = parser.readContents(content, null, opts);
-
- if (result.getMessages() != null && !result.getMessages().isEmpty()) {
- throw new IllegalArgumentException(
- "Failed to parse OpenAPI document: " + String.join(", ", result.getMessages()));
- }
- return getOperation(result.getOpenAPI(), !result.isOpenapi31());
+ OpenAPI openAPI = parser.parse(content);
+ OpenAPI.PathItemInfo pathItemInfo = openAPI.findOperationById(this.operationId);
+ return new OperationDefinition(
+ openAPI,
+ pathItemInfo.operation().get(pathItemInfo.method()),
+ pathItemInfo.path(),
+ pathItemInfo.method().toUpperCase(),
+ openAPI.getSwaggerVersion());
}
-
- private OperationDefinition getOperation(
- OpenAPI openAPI, boolean emulateSwaggerV2BodyParameters) {
- if (openAPI == null || openAPI.getPaths() == null) {
- throw new IllegalArgumentException("Invalid OpenAPI document");
- }
-
- Set paths = openAPI.getPaths().keySet();
-
- for (String path : paths) {
- PathItem pathItem = openAPI.getPaths().get(path);
- OperationAndMethod operationAndMethod = findInPathItem(pathItem, operationId);
- if (operationAndMethod != null) {
- return new OperationDefinition(
- openAPI,
- operationAndMethod.operation,
- path,
- operationAndMethod.method,
- emulateSwaggerV2BodyParameters);
- }
- }
- throw new IllegalArgumentException(
- "No operation with id '" + operationId + "' found in OpenAPI document");
- }
-
- private OperationAndMethod findInPathItem(PathItem pathItem, String operationId) {
- if (pathItem == null) {
- return null;
- }
-
- if (matches(pathItem.getGet(), operationId))
- return new OperationAndMethod(pathItem.getGet(), "GET");
- if (matches(pathItem.getPost(), operationId))
- return new OperationAndMethod(pathItem.getPost(), "POST");
- if (matches(pathItem.getPut(), operationId))
- return new OperationAndMethod(pathItem.getPut(), "PUT");
- if (matches(pathItem.getDelete(), operationId))
- return new OperationAndMethod(pathItem.getDelete(), "DELETE");
- if (matches(pathItem.getPatch(), operationId))
- return new OperationAndMethod(pathItem.getPatch(), "PATCH");
- if (matches(pathItem.getHead(), operationId))
- return new OperationAndMethod(pathItem.getHead(), "HEAD");
- if (matches(pathItem.getOptions(), operationId))
- return new OperationAndMethod(pathItem.getOptions(), "OPTIONS");
- if (matches(pathItem.getTrace(), operationId))
- return new OperationAndMethod(pathItem.getTrace(), "TRACE");
-
- return null;
- }
-
- private boolean matches(Operation op, String operationId) {
- return op != null && operationId.equals(op.getOperationId());
- }
-
- private record OperationAndMethod(Operation operation, String method) {}
}
diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java
index 5111a38b..6ae20c02 100644
--- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java
+++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java
@@ -15,35 +15,33 @@
*/
package io.serverlessworkflow.impl.executors.openapi;
-import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.Operation;
-import io.swagger.v3.oas.models.media.MediaType;
-import io.swagger.v3.oas.models.media.Schema;
-import io.swagger.v3.oas.models.servers.Server;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
+import java.util.Objects;
import java.util.Set;
class OperationDefinition {
- private final Operation operation;
+
+ private final JsonNode operation;
private final String method;
private final OpenAPI openAPI;
private final String path;
- private final boolean emulateSwaggerV2BodyParameters;
+ private final OpenAPI.SwaggerVersion swaggerVersion;
OperationDefinition(
OpenAPI openAPI,
- Operation operation,
+ JsonNode operation,
String path,
String method,
- boolean emulateSwaggerV2BodyParameters) {
- this.openAPI = openAPI;
- this.operation = operation;
- this.path = path;
- this.method = method;
- this.emulateSwaggerV2BodyParameters = emulateSwaggerV2BodyParameters;
+ OpenAPI.SwaggerVersion swaggerVersion) {
+ this.openAPI = Objects.requireNonNull(openAPI, "openAPI must not be null");
+ this.operation = Objects.requireNonNull(operation, "operation must not be null");
+ this.path = Objects.requireNonNull(path, "path must not be null");
+ this.method = Objects.requireNonNull(method, "method must not be null");
+ this.swaggerVersion = Objects.requireNonNull(swaggerVersion, "swaggerVersion must not be null");
}
String getMethod() {
@@ -54,7 +52,7 @@ String getPath() {
return path;
}
- Operation getOperation() {
+ JsonNode getOperation() {
return operation;
}
@@ -62,77 +60,93 @@ List getServers() {
if (openAPI.getServers() == null) {
return List.of();
}
- return openAPI.getServers().stream().map(Server::getUrl).toList();
+
+ return openAPI.getServers();
}
List getParameters() {
- return emulateSwaggerV2BodyParameters ? getSwaggerV2Parameters() : getOpenApiParameters();
+ return swaggerVersion.equals(OpenAPI.SwaggerVersion.SWAGGER_V2)
+ ? getSwaggerV2Parameters()
+ : getOpenApiParameters();
}
private List getOpenApiParameters() {
- if (operation.getParameters() == null) {
- return List.of();
+ List paramDefinitions = new ArrayList<>();
+ for (JsonNode parameterItem : operation.withArray("parameters")) {
+ paramDefinitions.add(new ParameterDefinition(parameterItem));
}
- return operation.getParameters().stream().map(ParameterDefinition::new).toList();
+
+ if (operation.has("requestBody")) {
+ List fromBody = parametersFromRequestBody(operation.path("requestBody"));
+ if (!fromBody.isEmpty()) {
+ paramDefinitions.addAll(fromBody);
+ }
+ }
+
+ return paramDefinitions;
}
- @SuppressWarnings({"rawtypes"})
private List getSwaggerV2Parameters() {
- if (operation.getParameters() != null && !operation.getParameters().isEmpty()) {
- return operation.getParameters().stream().map(ParameterDefinition::new).toList();
+ if (operation.has("parameters") && !operation.withArray("parameters").isEmpty()) {
+ ArrayNode parameters = operation.withArray("parameters");
+ List parameterDefinitions = new ArrayList<>();
+ parameters.forEach(jsonNode -> parameterDefinitions.add(new ParameterDefinition(jsonNode)));
+ return parameterDefinitions;
}
- if (operation.getRequestBody() != null) {
- Schema> schema = null;
- if (operation.getRequestBody().getContent() != null
- && operation
- .getRequestBody()
- .getContent()
- .containsKey(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)) {
- MediaType mt =
- operation
- .getRequestBody()
- .getContent()
- .get(jakarta.ws.rs.core.MediaType.APPLICATION_JSON);
- schema = mt.getSchema();
- } else if (operation.getRequestBody().get$ref() != null) {
- schema = resolveSchema(operation.getRequestBody().get$ref());
- }
-
- if (schema == null) {
- return List.of();
- }
- Set required =
- schema.getRequired() != null ? new HashSet<>(schema.getRequired()) : new HashSet<>();
-
- Map properties = schema.getProperties();
- if (properties != null) {
- List result = new ArrayList<>();
- for (Map.Entry prop : properties.entrySet()) {
- String fieldName = prop.getKey();
- ParameterDefinition fieldParam =
- new ParameterDefinition(
- fieldName, "body", required.contains(fieldName), prop.getValue());
- result.add(fieldParam);
- }
- return result;
- }
+ if (operation.has("requestBody")) {
+ return parametersFromRequestBody(operation.path("requestBody"));
}
+
return List.of();
}
- Schema> resolveSchema(String ref) {
- if (ref == null || !ref.startsWith("#/components/schemas/")) {
- throw new IllegalArgumentException("Unsupported $ref format: " + ref);
+ private List parametersFromRequestBody(JsonNode requestBody) {
+ if (requestBody == null) {
+ return List.of();
}
- String name = ref.substring("#/components/schemas/".length());
- if (openAPI.getComponents() == null || openAPI.getComponents().getSchemas() == null) {
- throw new IllegalStateException("No components/schemas found in OpenAPI");
+
+ JsonNode content = requestBody.path("content");
+ if (!content.has("application/json")) {
+ return List.of();
+ }
+
+ JsonNode mediaType = content.path("application/json");
+ JsonNode schema = mediaType.path("schema");
+
+ // resolve $ref if present
+ if (schema != null && schema.has("$ref")) {
+ String ref = schema.path("$ref").asText();
+ schema = openAPI.resolveSchema(ref);
+ }
+
+ if (schema == null || !schema.has("properties")) {
+ return List.of();
}
- Schema> schema = openAPI.getComponents().getSchemas().get(name);
- if (schema == null) {
- throw new IllegalArgumentException("Schema not found: " + name);
+
+ JsonNode properties = schema.path("properties");
+ Set requiredFields = requiredFields(schema);
+
+ List result = new ArrayList<>();
+ properties
+ .fieldNames()
+ .forEachRemaining(
+ fieldName -> {
+ JsonNode fieldSchema = properties.path(fieldName);
+ boolean isRequired = requiredFields.contains(fieldName);
+ result.add(new ParameterDefinition(fieldName, "body", isRequired, fieldSchema));
+ });
+
+ return result;
+ }
+
+ private Set requiredFields(JsonNode schema) {
+ Set required = new HashSet<>();
+ if (schema != null && schema.has("required")) {
+ for (JsonNode req : schema.path("required")) {
+ required.add(req.asText());
+ }
}
- return schema;
+ return required;
}
}
diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java
index 54290096..7ae8c37f 100644
--- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java
+++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java
@@ -15,25 +15,24 @@
*/
package io.serverlessworkflow.impl.executors.openapi;
-import io.swagger.v3.oas.models.media.Schema;
-import io.swagger.v3.oas.models.parameters.Parameter;
+import com.fasterxml.jackson.databind.JsonNode;
class ParameterDefinition {
private final String name;
private final String in;
private final boolean required;
- private final Schema schema;
+ private final JsonNode schema;
- ParameterDefinition(Parameter parameter) {
+ ParameterDefinition(JsonNode parameter) {
this(
- parameter.getName(),
- parameter.getIn(),
- parameter.getRequired() != null && parameter.getRequired(),
- parameter.getSchema());
+ parameter.get("name").asText(),
+ parameter.get("in").asText(),
+ parameter.has("required") && parameter.get("required").asBoolean(),
+ parameter.get("schema"));
}
- ParameterDefinition(String name, String in, boolean required, Schema schema) {
+ ParameterDefinition(String name, String in, boolean required, JsonNode schema) {
this.name = name;
this.in = in;
this.required = required;
@@ -52,7 +51,7 @@ public boolean getRequired() {
return required;
}
- public Schema getSchema() {
+ public JsonNode getSchema() {
return schema;
}
}
diff --git a/pom.xml b/pom.xml
index 11800721..ac99765d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,8 +94,6 @@
2.0.17
9.1.0.Final
6.0.0
-
- 2.1.36
true
java