Skip to content

Commit c0d395a

Browse files
committed
Remove io.swagger.parser.v3
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent 5e0916c commit c0d395a

File tree

8 files changed

+368
-167
lines changed

8 files changed

+368
-167
lines changed

impl/openapi/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@
2020
<groupId>io.serverlessworkflow</groupId>
2121
<artifactId>serverlessworkflow-impl-http</artifactId>
2222
</dependency>
23-
<dependency>
24-
<groupId>io.swagger.parser.v3</groupId>
25-
<artifactId>swagger-parser</artifactId>
26-
<version>${version.io.swagger.parser.v3}</version>
27-
</dependency>
2823
<dependency>
2924
<groupId>org.junit.jupiter</groupId>
3025
<artifactId>junit-jupiter-engine</artifactId>
3126
<scope>test</scope>
3227
</dependency>
28+
<dependency>
29+
<groupId>com.fasterxml.jackson.dataformat</groupId>
30+
<artifactId>jackson-dataformat-yaml</artifactId>
31+
</dependency>
3332
<dependency>
3433
<groupId>org.junit.jupiter</groupId>
3534
<artifactId>junit-jupiter-params</artifactId>
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.executors.openapi;
17+
18+
import com.fasterxml.jackson.databind.JsonNode;
19+
import com.fasterxml.jackson.databind.node.ArrayNode;
20+
import com.fasterxml.jackson.databind.node.ObjectNode;
21+
import java.util.Iterator;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Objects;
25+
import java.util.Set;
26+
27+
public class OpenAPI {
28+
29+
public enum SwaggerVersion {
30+
SWAGGER_V2,
31+
OPENAPI_V3
32+
}
33+
34+
private final JsonNode root;
35+
private final boolean isSwaggerV2;
36+
37+
public OpenAPI(JsonNode root) {
38+
this.root = Objects.requireNonNull(root, "root cannot be null");
39+
this.isSwaggerV2 = isSwaggerV2();
40+
this.validatePaths();
41+
this.moveRequestInBodyToRequestBody();
42+
}
43+
44+
private void moveRequestInBodyToRequestBody() {
45+
if (!isSwaggerV2) {
46+
return;
47+
}
48+
49+
JsonNode pathsNode = root.get("paths");
50+
if (pathsNode == null || !pathsNode.isObject()) {
51+
return;
52+
}
53+
54+
Iterator<String> pathNames = pathsNode.fieldNames();
55+
pathNames.forEachRemaining(
56+
pathName -> {
57+
JsonNode pathNode = pathsNode.get(pathName);
58+
if (pathNode == null || !pathNode.isObject()) {
59+
return;
60+
}
61+
Iterator<String> methodNames = pathNode.fieldNames();
62+
methodNames.forEachRemaining(
63+
methodName -> {
64+
JsonNode operationNode = pathNode.get(methodName);
65+
if (operationNode != null && operationNode.isObject()) {
66+
processOperationNode((ObjectNode) operationNode);
67+
}
68+
});
69+
});
70+
}
71+
72+
private void processOperationNode(ObjectNode operationNode) {
73+
JsonNode parametersNode = operationNode.get("parameters");
74+
if (parametersNode == null || !parametersNode.isArray()) {
75+
return;
76+
}
77+
78+
ArrayNode originalParameters = (ArrayNode) parametersNode;
79+
ArrayNode filteredParameters = originalParameters.arrayNode();
80+
boolean requestBodyCreated = false;
81+
82+
for (JsonNode parameterNode : originalParameters) {
83+
if (parameterNode == null || !parameterNode.isObject()) {
84+
filteredParameters.add(parameterNode);
85+
continue;
86+
}
87+
88+
JsonNode inNode = parameterNode.get("in");
89+
if (inNode != null && "body".equals(inNode.asText())) {
90+
if (!requestBodyCreated) {
91+
ObjectNode requestBodyNode = operationNode.putObject("requestBody");
92+
ObjectNode contentNode = requestBodyNode.putObject("content");
93+
ObjectNode mediaTypeNode = contentNode.putObject("application/json");
94+
ObjectNode schemaNode = mediaTypeNode.putObject("schema");
95+
JsonNode schemaFromParam = parameterNode.get("schema");
96+
if (schemaFromParam != null && schemaFromParam.isObject()) {
97+
schemaNode.setAll((ObjectNode) schemaFromParam);
98+
}
99+
requestBodyCreated = true;
100+
}
101+
} else {
102+
filteredParameters.add(parameterNode);
103+
}
104+
}
105+
106+
operationNode.set("parameters", filteredParameters);
107+
}
108+
109+
private void validatePaths() {
110+
if (!root.has("paths")) {
111+
throw new IllegalArgumentException("OpenAPI document must contain 'paths' field");
112+
}
113+
}
114+
115+
private boolean isSwaggerV2() {
116+
JsonNode swaggerNode = root.get("swagger");
117+
return swaggerNode != null && swaggerNode.asText().startsWith("2.0");
118+
}
119+
120+
public PathItemInfo findOperationById(String operationId) {
121+
JsonNode paths = root.get("paths");
122+
123+
Set<Map.Entry<String, JsonNode>> properties = paths.properties();
124+
125+
for (Map.Entry<String, JsonNode> path : properties) {
126+
JsonNode pathNode = path.getValue();
127+
Set<Map.Entry<String, JsonNode>> methods = pathNode.properties();
128+
for (Map.Entry<String, JsonNode> method : methods) {
129+
JsonNode operationNode = method.getValue().get("operationId");
130+
if (operationNode != null && operationNode.asText().equals(operationId)) {
131+
return new PathItemInfo(path.getKey(), path.getValue(), method.getKey());
132+
}
133+
}
134+
}
135+
throw new IllegalArgumentException("Operation with ID " + operationId + " not found");
136+
}
137+
138+
public List<String> getServers() {
139+
140+
if (isSwaggerV2) {
141+
if (root.has("host")) {
142+
String host = root.get("host").asText();
143+
String basePath = root.has("basePath") ? root.get("basePath").asText() : "";
144+
String scheme = "http";
145+
if (root.has("schemes")
146+
&& root.get("schemes").isArray()
147+
&& !root.get("schemes").isEmpty()) {
148+
scheme = root.get("schemes").get(0).asText();
149+
}
150+
return List.of(scheme + "://" + host + basePath);
151+
} else {
152+
return List.of();
153+
}
154+
}
155+
156+
return root.has("servers") ? List.of(root.get("servers").findPath("url").asText()) : List.of();
157+
}
158+
159+
public SwaggerVersion getSwaggerVersion() {
160+
return isSwaggerV2 ? SwaggerVersion.SWAGGER_V2 : SwaggerVersion.OPENAPI_V3;
161+
}
162+
163+
public JsonNode resolveSchema(String ref) {
164+
if (!ref.startsWith("#/")) {
165+
throw new IllegalArgumentException("Only local references are supported");
166+
}
167+
String[] parts = ref.substring(2).split("/");
168+
JsonNode currentNode = root;
169+
for (String part : parts) {
170+
currentNode = currentNode.get(part);
171+
if (currentNode == null) {
172+
throw new IllegalArgumentException("Reference " + ref + " could not be resolved");
173+
}
174+
}
175+
return currentNode;
176+
}
177+
178+
public record PathItemInfo(String path, JsonNode operation, String method) {}
179+
}

impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.serverlessworkflow.impl.executors.openapi;
1717

18+
import com.fasterxml.jackson.databind.JsonNode;
1819
import io.serverlessworkflow.api.types.ExternalResource;
1920
import io.serverlessworkflow.impl.TaskContext;
2021
import io.serverlessworkflow.impl.WorkflowApplication;
@@ -24,7 +25,6 @@
2425
import io.serverlessworkflow.impl.executors.http.HttpExecutor;
2526
import io.serverlessworkflow.impl.executors.http.HttpExecutorBuilder;
2627
import io.serverlessworkflow.impl.resources.ResourceLoaderUtils;
27-
import io.swagger.v3.oas.models.media.Schema;
2828
import java.util.Collection;
2929
import java.util.HashMap;
3030
import java.util.HashSet;
@@ -113,8 +113,8 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio
113113
if (!missingParams.isEmpty()) {
114114
throw new IllegalArgumentException(
115115
"Missing required OpenAPI parameters for operation '"
116-
+ (operation.getOperation().getOperationId() != null
117-
? operation.getOperation().getOperationId()
116+
+ (operation.getOperation().get("operationId") != null
117+
? operation.getOperation().get("operationId").asText()
118118
: "<unknown>" + "': ")
119119
+ missingParams);
120120
}
@@ -135,8 +135,9 @@ private void param(
135135
if (origMap.containsKey(name)) {
136136
collectorMap.put(parameter.getName(), origMap.remove(name));
137137
} else if (parameter.getRequired()) {
138-
Schema<?> schema = parameter.getSchema();
139-
Object defaultValue = schema != null ? schema.getDefault() : null;
138+
139+
JsonNode schema = parameter.getSchema();
140+
Object defaultValue = schema != null ? schema.get("default") : null;
140141
if (defaultValue != null) {
141142
collectorMap.put(name, defaultValue);
142143
} else {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.executors.openapi;
17+
18+
import com.fasterxml.jackson.databind.JsonNode;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import com.fasterxml.jackson.databind.json.JsonMapper;
21+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
22+
import java.util.Objects;
23+
24+
/**
25+
* Parses OpenAPI content (JSON or YAML) into a {@link OpenAPI} using Jackson.
26+
*
27+
* <p>This class detects JSON if the first non-whitespace character is '{'; otherwise it treats the
28+
* content as YAML.
29+
*/
30+
public final class OpenAPIParser {
31+
32+
private static final ObjectMapper YAML_MAPPER = new YAMLMapper();
33+
private static final ObjectMapper JSON_MAPPER = new JsonMapper();
34+
35+
/**
36+
* Parse the provided OpenAPI content (JSON or YAML) and return a {@link OpenAPI}.
37+
*
38+
* @param content the OpenAPI document content (must not be null or blank)
39+
* @return parsed {@link OpenAPI}
40+
* @throws IllegalArgumentException if content is null/blank or cannot be parsed
41+
*/
42+
public OpenAPI parse(String content) {
43+
Objects.requireNonNull(content, "content must not be null");
44+
String trimmed = content.trim();
45+
if (trimmed.isEmpty()) {
46+
throw new IllegalArgumentException("content must not be blank");
47+
}
48+
49+
ObjectMapper mapper = selectMapper(trimmed);
50+
try {
51+
JsonNode root = mapper.readTree(content);
52+
return new OpenAPI(root);
53+
} catch (Exception e) {
54+
throw new IllegalArgumentException("Failed to parse content", e);
55+
}
56+
}
57+
58+
private ObjectMapper selectMapper(String trimmedContent) {
59+
char first = firstNonWhitespaceChar(trimmedContent);
60+
if (first == '{') {
61+
return JSON_MAPPER;
62+
}
63+
return YAML_MAPPER;
64+
}
65+
66+
private static char firstNonWhitespaceChar(String s) {
67+
for (int i = 0; i < s.length(); i++) {
68+
char c = s.charAt(i);
69+
if (!Character.isWhitespace(c)) {
70+
return c;
71+
}
72+
}
73+
return '\0';
74+
}
75+
}

0 commit comments

Comments
 (0)