From 8cfce9e922bcac40e80c1b361c2d63bf459c0f91 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Tue, 30 Dec 2025 17:11:18 -0300 Subject: [PATCH 1/3] Use Jackson for deserialization instead use io.swagger.parser.v3 Signed-off-by: Matheus Cruz --- impl/openapi/pom.xml | 33 +-- .../executors/openapi/OpenAPIExecutor.java | 35 +-- .../executors/openapi/OpenAPIProcessor.java | 85 +------ .../openapi/OperationDefinition.java | 159 ++++++------- .../openapi/ParameterDefinition.java | 19 +- .../executors/openapi/UnifiedOpenAPI.java | 218 ++++++++++++++++++ .../openapi/OpenAPIProcessorTest.java | 68 +++--- pom.xml | 2 - 8 files changed, 377 insertions(+), 242 deletions(-) create mode 100644 impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java diff --git a/impl/openapi/pom.xml b/impl/openapi/pom.xml index f9fa6e11..6641de14 100644 --- a/impl/openapi/pom.xml +++ b/impl/openapi/pom.xml @@ -7,13 +7,6 @@ serverlessworkflow-impl-openapi Serverless Workflow :: Impl :: OpenAPI - - - 1.9.0 - 3.20.0 - 1.20.0 - - jakarta.ws.rs @@ -25,32 +18,12 @@ io.serverlessworkflow - serverlessworkflow-impl-http - - - io.swagger.parser.v3 - swagger-parser - ${version.io.swagger.parser.v3} - - - - - - org.mozilla - rhino - ${version.org.mozilla.rhino} + serverlessworkflow-api - org.apache.commons - commons-lang3 - ${version.org.apache.commons.lang3} - - - commons-codec - commons-codec - ${version.commons.codec} + io.serverlessworkflow + serverlessworkflow-impl-http - org.junit.jupiter junit-jupiter-engine diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java index da3e369a..7f90677e 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java @@ -15,6 +15,8 @@ */ package io.serverlessworkflow.impl.executors.openapi; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.serverlessworkflow.api.WorkflowFormat; import io.serverlessworkflow.api.types.ExternalResource; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; @@ -23,8 +25,10 @@ import io.serverlessworkflow.impl.executors.CallableTask; import io.serverlessworkflow.impl.executors.http.HttpExecutor; import io.serverlessworkflow.impl.executors.http.HttpExecutorBuilder; -import io.serverlessworkflow.impl.resources.ResourceLoaderUtils; -import io.swagger.v3.oas.models.media.Schema; +import io.serverlessworkflow.impl.resources.ExternalResourceHandler; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -61,12 +65,7 @@ public CompletableFuture apply( workflowContext .definition() .resourceLoader() - .load( - resource, - ResourceLoaderUtils::readString, - workflowContext, - taskContext, - input)); + .load(resource, this::readUnifiedOpenAPI, workflowContext, taskContext, input)); fillHttpBuilder(workflowContext.definition().application(), operationDefinition); // One executor per operation, even if the document is the same @@ -74,7 +73,7 @@ public CompletableFuture apply( // but the path differs, although some use cases may require different client configurations for // different paths...) Collection executors = - operationDefinition.getServers().stream().map(s -> builder.build(s)).toList(); + operationDefinition.getServers().stream().map(builder::build).toList(); Iterator iter = executors.iterator(); if (!iter.hasNext()) { @@ -113,8 +112,8 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio if (!missingParams.isEmpty()) { throw new IllegalArgumentException( "Missing required OpenAPI parameters for operation '" - + (operation.getOperation().getOperationId() != null - ? operation.getOperation().getOperationId() + + (operation.getOperation().operationId() != null + ? operation.getOperation().operationId() : "" + "': ") + missingParams); } @@ -135,8 +134,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; + + UnifiedOpenAPI.Schema schema = parameter.getSchema(); + Object defaultValue = schema != null ? schema._default() : null; if (defaultValue != null) { collectorMap.put(name, defaultValue); } else { @@ -144,4 +144,13 @@ private void param( } } } + + private UnifiedOpenAPI readUnifiedOpenAPI(ExternalResourceHandler handler) { + ObjectMapper objectMapper = WorkflowFormat.fromFileName(handler.name()).mapper(); + try (InputStream is = handler.open()) { + return objectMapper.readValue(is, UnifiedOpenAPI.class); + } catch (IOException e) { + throw new UncheckedIOException("Error while reading OpenAPI document " + handler.name(), e); + } + } } 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..1b4bcca2 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,22 @@ */ 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; +import java.util.Optional; class OpenAPIProcessor { private final String operationId; OpenAPIProcessor(String operationId) { - this.operationId = 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()); + OperationDefinition parse(UnifiedOpenAPI unifiedOpenAPI) { + Optional operationDefinition = + unifiedOpenAPI.findOperationById(this.operationId); + return operationDefinition.orElseThrow( + () -> + new IllegalArgumentException("Operation with ID '" + this.operationId + "' not found")); } - - 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..d8fa0eec 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,25 @@ */ 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 java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.Objects; class OperationDefinition { - private final Operation operation; + + private final UnifiedOpenAPI.Operation operation; private final String method; - private final OpenAPI openAPI; + private final UnifiedOpenAPI openAPI; private final String path; - private final boolean emulateSwaggerV2BodyParameters; OperationDefinition( - OpenAPI openAPI, - Operation operation, - String path, - String method, - boolean emulateSwaggerV2BodyParameters) { - this.openAPI = openAPI; - this.operation = operation; - this.path = path; - this.method = method; - this.emulateSwaggerV2BodyParameters = emulateSwaggerV2BodyParameters; + UnifiedOpenAPI openAPI, UnifiedOpenAPI.Operation operation, String path, String method) { + + this.openAPI = Objects.requireNonNull(openAPI, "openAPI cannot be null"); + this.operation = Objects.requireNonNull(operation, "operation cannot be null"); + this.path = Objects.requireNonNull(path, "path cannot be null"); + this.method = Objects.requireNonNull(method, "method cannot be null"); } String getMethod() { @@ -54,7 +44,7 @@ String getPath() { return path; } - Operation getOperation() { + UnifiedOpenAPI.Operation getOperation() { return operation; } @@ -62,77 +52,90 @@ 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(); - } + List paramDefinitions = new ArrayList<>(); + if (operation.hasParameters()) { + for (UnifiedOpenAPI.Parameter parameter : operation.parameters()) { + if (parameter.in().equals("body")) { + continue; // body parameters are handled separately + } - private List getOpenApiParameters() { - if (operation.getParameters() == null) { - return List.of(); + paramDefinitions.add( + new ParameterDefinition( + parameter.name(), parameter.in(), parameter.required(), parameter.schema())); + } } - return operation.getParameters().stream().map(ParameterDefinition::new).toList(); - } - @SuppressWarnings({"rawtypes"}) - private List getSwaggerV2Parameters() { - if (operation.getParameters() != null && !operation.getParameters().isEmpty()) { - return operation.getParameters().stream().map(ParameterDefinition::new).toList(); - } - 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 (openAPI.swaggerVersion().equals(UnifiedOpenAPI.SwaggerVersion.SWAGGER_V2)) { + operation.parameters().stream() + .filter(p -> p.in().equals("body")) + .forEach( + p -> { + UnifiedOpenAPI.Schema schema = p.schema(); + if (schema.hasRef()) { + String ref = schema.ref(); + schema = openAPI.resolveSchema(ref); + } - if (schema == null) { - return List.of(); - } + if (schema != null && schema.hasProperties()) { + Map properties = schema.properties(); + for (String fieldName : properties.keySet()) { + UnifiedOpenAPI.Schema fieldSchema = properties.get(fieldName); + boolean isRequired = schema.requiredFields().contains(fieldName); + paramDefinitions.add( + new ParameterDefinition(fieldName, "body", isRequired, fieldSchema)); + } + } + }); + } - 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.hasRequestBody()) { + List fromBody = parametersFromRequestBody(operation.requestBody()); + if (!fromBody.isEmpty()) { + paramDefinitions.addAll(fromBody); } } - return List.of(); + + return paramDefinitions; } - Schema resolveSchema(String ref) { - if (ref == null || !ref.startsWith("#/components/schemas/")) { - throw new IllegalArgumentException("Unsupported $ref format: " + ref); + private List parametersFromRequestBody( + UnifiedOpenAPI.RequestBody 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"); + + UnifiedOpenAPI.Content content = requestBody.content(); + if (!content.isApplicationJson()) { + return List.of(); } - Schema schema = openAPI.getComponents().getSchemas().get(name); - if (schema == null) { - throw new IllegalArgumentException("Schema not found: " + name); + + UnifiedOpenAPI.MediaType mediaType = content.applicationJson(); + UnifiedOpenAPI.Schema schema = mediaType.schema(); + + // resolve $ref if present + if (schema != null && schema.hasRef()) { + String ref = schema.ref(); + schema = openAPI.resolveSchema(ref); } - return schema; + + if (schema == null || !schema.hasProperties()) { + return List.of(); + } + + Map properties = schema.properties(); + List paramDefinitions = new ArrayList<>(); + + for (String fieldName : properties.keySet()) { + UnifiedOpenAPI.Schema fieldSchema = properties.get(fieldName); + boolean isRequired = schema.requiredFields().contains(fieldName); + paramDefinitions.add(new ParameterDefinition(fieldName, "body", isRequired, fieldSchema)); + } + + return paramDefinitions; } } 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..5f8a949e 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 UnifiedOpenAPI.Schema 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(), + null); } - ParameterDefinition(String name, String in, boolean required, Schema schema) { + ParameterDefinition(String name, String in, boolean required, UnifiedOpenAPI.Schema schema) { this.name = name; this.in = in; this.required = required; @@ -52,7 +51,7 @@ public boolean getRequired() { return required; } - public Schema getSchema() { + public UnifiedOpenAPI.Schema getSchema() { return schema; } } diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java new file mode 100644 index 00000000..c2ad822a --- /dev/null +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java @@ -0,0 +1,218 @@ +/* + * 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.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record UnifiedOpenAPI( + String swagger, + List servers, + String host, + String basePath, + List schemes, + Map paths, + Components components, + Map definitions) { + + public enum SwaggerVersion { + SWAGGER_V2, + OPENAPI_V3 + } + + public SwaggerVersion swaggerVersion() { + return isSwaggerV2() ? SwaggerVersion.SWAGGER_V2 : SwaggerVersion.OPENAPI_V3; + } + + private boolean isSwaggerV2() { + return swagger != null && swagger.trim().startsWith("2."); + } + + Optional findOperationById(String operationId) { + if (paths == null || operationId == null) { + return Optional.empty(); + } + + for (var entry : paths.entrySet()) { + String path = entry.getKey(); + PathItem pathItem = entry.getValue(); + + for (var httpOperation : pathItem.methods()) { + if (httpOperation.operation() != null + && operationId.equals(httpOperation.operation().operationId())) { + return Optional.of( + new OperationDefinition( + this, + httpOperation.operation(), + path, + httpOperation.method().toUpperCase(Locale.ROOT))); + } + } + } + return Optional.empty(); + } + + public List getServers() { + if (swaggerVersion() == SwaggerVersion.SWAGGER_V2) { + if (host == null || host.isBlank()) { + return List.of(); + } + + String base = host; + if (basePath != null && !basePath.isBlank()) { + base += basePath; + } + + return List.of(schemes.stream().findFirst().orElse("https") + "://" + base); + } + + if (servers == null || servers.isEmpty()) { + return List.of(); + } + + return servers.stream().map(Server::url).toList(); + } + + /** + * Resolves a schema reference to a Schema object. + * + *

It does not resolve nested references. + */ + public Schema resolveSchema(String ref) { + if (isSwaggerV2()) { + return resolveRefSwaggerV2(ref); + } else { + return resolveRefOpenAPI(ref); + } + } + + private Schema resolveRefOpenAPI(String ref) { + if (ref == null || !ref.startsWith("#/components/schemas/")) { + return null; + } + + if (components == null || components.schemas() == null) { + return null; + } + + String name = ref.substring("#/components/schemas/".length()); + return components.schemas().get(name); + } + + private Schema resolveRefSwaggerV2(String ref) { + if (ref == null || !ref.startsWith("#/definitions/")) { + return null; + } + + if (definitions == null) { + return null; + } + + String name = ref.substring("#/definitions/".length()); + + return definitions.get(name); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Server(String url) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record PathItem( + Operation get, + Operation post, + Operation put, + Operation delete, + Operation patch, + Operation head, + Operation options) { + + Set methods() { + return Set.of( + new HttpOperation("get", get), + new HttpOperation("post", post), + new HttpOperation("put", put), + new HttpOperation("delete", delete), + new HttpOperation("patch", patch), + new HttpOperation("head", head), + new HttpOperation("options", options)); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + record HttpOperation(String method, Operation operation) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Operation(String operationId, List parameters, RequestBody requestBody) { + + public boolean hasParameters() { + return parameters != null && !parameters.isEmpty(); + } + + public boolean hasRequestBody() { + return requestBody != null; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Parameter(String name, String in, Boolean required, Schema schema) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record RequestBody(Content content) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Content(@JsonProperty("application/json") MediaType applicationJson) { + public boolean isApplicationJson() { + return applicationJson != null; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record MediaType(Schema schema) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Components(Map schemas) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Definitions(Map definitions) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Schema( + String type, + Map properties, + List required, + @JsonProperty("$ref") String ref, + @JsonProperty("default") JsonNode _default) { + + public boolean hasRef() { + return ref != null && !ref.isBlank(); + } + + public boolean hasProperties() { + return properties != null && !properties.isEmpty(); + } + + public Set requiredFields() { + return required == null ? Set.of() : Set.copyOf(required); + } + } +} diff --git a/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java b/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java index c89df6b7..93752b1a 100644 --- a/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java +++ b/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java @@ -19,9 +19,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.serverlessworkflow.api.WorkflowFormat; +import io.serverlessworkflow.impl.resources.ClasspathResource; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.List; import org.junit.jupiter.api.Test; @@ -29,17 +31,17 @@ public class OpenAPIProcessorTest { @Test public void testGetPetByIdSwaggerV2() { - String json = readResource("schema/swagger/petstore.json"); - testGetPetById(json); + UnifiedOpenAPI openAPI = readResource("schema/swagger/petstore.json"); + testGetPetById(openAPI); } @Test public void testGetPetByIdOpenAPI() { - String json = readResource("schema/openapi/petstore.json"); - testGetPetById(json); + UnifiedOpenAPI openAPI = readResource("schema/openapi/petstore.json"); + testGetPetById(openAPI); } - public void testGetPetById(String json) { + public void testGetPetById(UnifiedOpenAPI json) { OperationDefinition definition = new OpenAPIProcessor("getPetById").parse(json); assertEquals("GET", definition.getMethod()); assertEquals("/pet/{petId}", definition.getPath()); @@ -53,17 +55,17 @@ public void testGetPetById(String json) { @Test public void testAddPetByIdSwaggerV2() { - String swaggerJson = readResource("schema/swagger/petstore.json"); - testAddPetById(swaggerJson); + UnifiedOpenAPI openAPI = readResource("schema/swagger/petstore.json"); + testAddPetById(openAPI); } @Test public void testAddPetByIdOpenAPI() { - String json = readResource("schema/openapi/petstore.json"); - testAddPetById(json); + UnifiedOpenAPI openAPI = readResource("schema/openapi/petstore.json"); + testAddPetById(openAPI); } - public void testAddPetById(String json) { + public void testAddPetById(UnifiedOpenAPI json) { OperationDefinition definition = new OpenAPIProcessor("addPet").parse(json); assertEquals("POST", definition.getMethod()); @@ -99,17 +101,17 @@ public void testAddPetById(String json) { @Test public void testGetInventorySwaggerV2() { - String swaggerJson = readResource("schema/swagger/petstore.json"); - testGetInventory(swaggerJson); + UnifiedOpenAPI openAPI = readResource("schema/swagger/petstore.json"); + testGetInventory(openAPI); } @Test public void testGetInventoryOpenAPI() { - String json = readResource("schema/openapi/petstore.json"); - testGetInventory(json); + UnifiedOpenAPI openAPI = readResource("schema/openapi/petstore.json"); + testGetInventory(openAPI); } - public void testGetInventory(String json) { + public void testGetInventory(UnifiedOpenAPI json) { OperationDefinition definition = new OpenAPIProcessor("getInventory").parse(json); assertEquals("GET", definition.getMethod()); @@ -120,17 +122,17 @@ public void testGetInventory(String json) { @Test public void testPlaceOrderSwaggerV2() { - String json = readResource("schema/swagger/petstore.json"); + UnifiedOpenAPI json = readResource("schema/swagger/petstore.json"); testPlaceOrder(json); } @Test public void testPlaceOrderOpenAPI() { - String json = readResource("schema/openapi/petstore.json"); - testPlaceOrder(json); + UnifiedOpenAPI openAPI = readResource("schema/openapi/petstore.json"); + testPlaceOrder(openAPI); } - public void testPlaceOrder(String json) { + public void testPlaceOrder(UnifiedOpenAPI json) { OperationDefinition definition = new OpenAPIProcessor("placeOrder").parse(json); assertEquals("POST", definition.getMethod()); @@ -166,17 +168,17 @@ public void testPlaceOrder(String json) { @Test public void testLoginUserSwaggerV2() { - String json = readResource("schema/swagger/petstore.json"); - testLoginUser(json); + UnifiedOpenAPI openAPI = readResource("schema/swagger/petstore.json"); + testLoginUser(openAPI); } @Test public void testLoginUserOpenAPI() { - String json = readResource("schema/openapi/petstore.json"); - testLoginUser(json); + UnifiedOpenAPI openAPI = readResource("schema/openapi/petstore.json"); + testLoginUser(openAPI); } - public void testLoginUser(String json) { + public void testLoginUser(UnifiedOpenAPI json) { OperationDefinition definition = new OpenAPIProcessor("loginUser").parse(json); assertEquals("GET", definition.getMethod()); @@ -202,15 +204,15 @@ private boolean checkServer(List servers, String expected) { return false; } - public static String readResource(String path) { - try (InputStream is = - Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { - if (is == null) { - throw new IllegalArgumentException("Resource not found: " + path); - } - return new String(is.readAllBytes(), StandardCharsets.UTF_8); + public static UnifiedOpenAPI readResource(String path) { + + ClasspathResource classpathResource = new ClasspathResource(path); + ObjectMapper mapper = WorkflowFormat.fromFileName(classpathResource.name()).mapper(); + + try (InputStream is = classpathResource.open()) { + return mapper.readValue(is, UnifiedOpenAPI.class); } catch (IOException e) { - throw new RuntimeException("Failed to read resource: " + path, e); + throw new RuntimeException("Failed to read OpenAPI resource: " + path, e); } } } diff --git a/pom.xml b/pom.xml index fd63d277..cf156e59 100644 --- a/pom.xml +++ b/pom.xml @@ -94,8 +94,6 @@ 2.0.17 9.1.0.Final 6.0.0 - - 2.1.37 true java From b8887d5828153f39b7935e3ff7d52d99413f03b0 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Tue, 30 Dec 2025 17:14:29 -0300 Subject: [PATCH 2/3] Change ParameterDefinition to be a Java record Signed-off-by: Matheus Cruz --- .../executors/openapi/OpenAPIExecutor.java | 10 +-- .../openapi/ParameterDefinition.java | 42 +-------- .../openapi/OpenAPIProcessorTest.java | 90 +++++++++---------- 3 files changed, 52 insertions(+), 90 deletions(-) diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java index 7f90677e..66a59e23 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java @@ -96,7 +96,7 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio Map bodyParameters = new HashMap<>(parameters); for (ParameterDefinition parameter : operation.getParameters()) { - switch (parameter.getIn()) { + switch (parameter.in()) { case "header": param(parameter, bodyParameters, headersMap, missingParams); break; @@ -130,12 +130,12 @@ private void param( Map origMap, Map collectorMap, Set missingParams) { - String name = parameter.getName(); + String name = parameter.name(); if (origMap.containsKey(name)) { - collectorMap.put(parameter.getName(), origMap.remove(name)); - } else if (parameter.getRequired()) { + collectorMap.put(parameter.name(), origMap.remove(name)); + } else if (parameter.required()) { - UnifiedOpenAPI.Schema schema = parameter.getSchema(); + UnifiedOpenAPI.Schema schema = parameter.schema(); Object defaultValue = schema != null ? schema._default() : null; if (defaultValue != null) { collectorMap.put(name, defaultValue); 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 5f8a949e..4733464d 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,43 +15,5 @@ */ package io.serverlessworkflow.impl.executors.openapi; -import com.fasterxml.jackson.databind.JsonNode; - -class ParameterDefinition { - - private final String name; - private final String in; - private final boolean required; - private final UnifiedOpenAPI.Schema schema; - - ParameterDefinition(JsonNode parameter) { - this( - parameter.get("name").asText(), - parameter.get("in").asText(), - parameter.has("required") && parameter.get("required").asBoolean(), - null); - } - - ParameterDefinition(String name, String in, boolean required, UnifiedOpenAPI.Schema schema) { - this.name = name; - this.in = in; - this.required = required; - this.schema = schema; - } - - public String getIn() { - return in; - } - - public String getName() { - return name; - } - - public boolean getRequired() { - return required; - } - - public UnifiedOpenAPI.Schema getSchema() { - return schema; - } -} +record ParameterDefinition( + String name, String in, boolean required, UnifiedOpenAPI.Schema schema) {} diff --git a/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java b/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java index 93752b1a..58d9164b 100644 --- a/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java +++ b/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java @@ -48,9 +48,9 @@ public void testGetPetById(UnifiedOpenAPI json) { assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); assertEquals(1, definition.getParameters().size()); ParameterDefinition param = definition.getParameters().get(0); - assertEquals("path", param.getIn()); - assertEquals("petId", param.getName()); - assertTrue(param.getRequired()); + assertEquals("path", param.in()); + assertEquals("petId", param.name()); + assertTrue(param.required()); } @Test @@ -74,29 +74,29 @@ public void testAddPetById(UnifiedOpenAPI json) { assertEquals(6, definition.getParameters().size()); ParameterDefinition param = definition.getParameters().get(0); - assertEquals("body", param.getIn()); - assertEquals("id", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("id", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(1); - assertEquals("body", param.getIn()); - assertEquals("category", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("category", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(2); - assertEquals("body", param.getIn()); - assertEquals("name", param.getName()); - assertTrue(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("name", param.name()); + assertTrue(param.required()); param = definition.getParameters().get(3); - assertEquals("body", param.getIn()); - assertEquals("photoUrls", param.getName()); - assertTrue(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("photoUrls", param.name()); + assertTrue(param.required()); param = definition.getParameters().get(4); - assertEquals("body", param.getIn()); - assertEquals("tags", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("tags", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(5); - assertEquals("body", param.getIn()); - assertEquals("status", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("status", param.name()); + assertFalse(param.required()); } @Test @@ -140,30 +140,30 @@ public void testPlaceOrder(UnifiedOpenAPI json) { assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); assertEquals(6, definition.getParameters().size()); ParameterDefinition param = definition.getParameters().get(0); - assertEquals("body", param.getIn()); - assertEquals("id", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("id", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(1); - assertEquals("body", param.getIn()); - assertEquals("petId", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("petId", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(2); - assertEquals("body", param.getIn()); - assertEquals("quantity", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("quantity", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(3); - assertEquals("body", param.getIn()); - assertEquals("shipDate", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("shipDate", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(4); - assertEquals("body", param.getIn()); - assertEquals("status", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("status", param.name()); + assertFalse(param.required()); param = definition.getParameters().get(5); - assertEquals("body", param.getIn()); - assertEquals("complete", param.getName()); - assertFalse(param.getRequired()); + assertEquals("body", param.in()); + assertEquals("complete", param.name()); + assertFalse(param.required()); } @Test @@ -186,13 +186,13 @@ public void testLoginUser(UnifiedOpenAPI json) { assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); assertEquals(2, definition.getParameters().size()); ParameterDefinition param1 = definition.getParameters().get(0); - assertEquals("query", param1.getIn()); - assertEquals("username", param1.getName()); - assertTrue(param1.getRequired()); + assertEquals("query", param1.in()); + assertEquals("username", param1.name()); + assertTrue(param1.required()); ParameterDefinition param2 = definition.getParameters().get(1); - assertEquals("query", param2.getIn()); - assertEquals("password", param2.getName()); - assertTrue(param2.getRequired()); + assertEquals("query", param2.in()); + assertEquals("password", param2.name()); + assertTrue(param2.required()); } private boolean checkServer(List servers, String expected) { From 2e8f50b05d0e6183b39c2e5e7f72943f24138176 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Tue, 30 Dec 2025 17:18:46 -0300 Subject: [PATCH 3/3] Remove Definition record Signed-off-by: Matheus Cruz --- .../impl/executors/openapi/UnifiedOpenAPI.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java index c2ad822a..bc413bf7 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/UnifiedOpenAPI.java @@ -192,9 +192,6 @@ public record MediaType(Schema schema) {} @JsonIgnoreProperties(ignoreUnknown = true) public record Components(Map schemas) {} - @JsonIgnoreProperties(ignoreUnknown = true) - public record Definitions(Map definitions) {} - @JsonIgnoreProperties(ignoreUnknown = true) public record Schema( String type,