Skip to content

Commit ce5f356

Browse files
committed
Add Swagger support to OpenAPI defition processor
Signed-off-by: Dmitrii Tikhomirov <[email protected]>
1 parent 25bfdf5 commit ce5f356

File tree

8 files changed

+1103
-59
lines changed

8 files changed

+1103
-59
lines changed

impl/openapi/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,15 @@
2525
<artifactId>swagger-parser</artifactId>
2626
<version>${version.io.swagger.parser.v3}</version>
2727
</dependency>
28+
<dependency>
29+
<groupId>org.junit.jupiter</groupId>
30+
<artifactId>junit-jupiter-engine</artifactId>
31+
<scope>test</scope>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.junit.jupiter</groupId>
35+
<artifactId>junit-jupiter-params</artifactId>
36+
<scope>test</scope>
37+
</dependency>
2838
</dependencies>
2939
</project>

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import io.serverlessworkflow.impl.executors.http.HttpExecutor.HttpExecutorBuilder;
3030
import io.serverlessworkflow.impl.resources.ResourceLoaderUtils;
3131
import io.swagger.v3.oas.models.media.Schema;
32-
import io.swagger.v3.oas.models.parameters.Parameter;
3332
import java.util.Collection;
3433
import java.util.HashMap;
3534
import java.util.HashSet;
@@ -110,7 +109,7 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio
110109
Set<String> missingParams = new HashSet<>();
111110

112111
Map<String, Object> bodyParameters = new HashMap<>(parameters);
113-
for (Parameter parameter : operation.getParameters()) {
112+
for (ParameterDefinition parameter : operation.getParameters()) {
114113
switch (parameter.getIn()) {
115114
case "header":
116115
param(parameter, bodyParameters, headersMap, missingParams);
@@ -141,7 +140,7 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio
141140
}
142141

143142
private void param(
144-
Parameter parameter,
143+
ParameterDefinition parameter,
145144
Map<String, Object> origMap,
146145
Map<String, Object> collectorMap,
147146
Set<String> missingParams) {

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

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

18+
import io.swagger.parser.OpenAPIParser;
1819
import io.swagger.v3.oas.models.OpenAPI;
1920
import io.swagger.v3.oas.models.Operation;
2021
import io.swagger.v3.oas.models.PathItem;
21-
import io.swagger.v3.parser.OpenAPIV3Parser;
2222
import io.swagger.v3.parser.core.models.ParseOptions;
23+
import io.swagger.v3.parser.core.models.SwaggerParseResult;
2324
import java.util.Set;
2425

2526
class OpenAPIProcessor {
@@ -31,14 +32,22 @@ class OpenAPIProcessor {
3132
}
3233

3334
public OperationDefinition parse(String content) {
34-
OpenAPIV3Parser parser = new OpenAPIV3Parser();
35+
OpenAPIParser parser = new OpenAPIParser();
3536
ParseOptions opts = new ParseOptions();
3637
opts.setResolve(true);
37-
opts.setResolveFully(false);
38-
return getOperation(parser.readContents(content).getOpenAPI());
38+
opts.setResolveFully(true);
39+
40+
SwaggerParseResult result = parser.readContents(content, null, opts);
41+
42+
if (result.getMessages() != null && !result.getMessages().isEmpty()) {
43+
throw new IllegalArgumentException(
44+
"Failed to parse OpenAPI document: " + String.join(", ", result.getMessages()));
45+
}
46+
return getOperation(result.getOpenAPI(), !result.isOpenapi31());
3947
}
4048

41-
private OperationDefinition getOperation(OpenAPI openAPI) {
49+
private OperationDefinition getOperation(
50+
OpenAPI openAPI, boolean emulateSwaggerV2BodyParameters) {
4251
if (openAPI == null || openAPI.getPaths() == null) {
4352
throw new IllegalArgumentException("Invalid OpenAPI document");
4453
}
@@ -50,7 +59,11 @@ private OperationDefinition getOperation(OpenAPI openAPI) {
5059
OperationAndMethod operationAndMethod = findInPathItem(pathItem, operationId);
5160
if (operationAndMethod != null) {
5261
return new OperationDefinition(
53-
openAPI, operationAndMethod.operation, path, operationAndMethod.method);
62+
openAPI,
63+
operationAndMethod.operation,
64+
path,
65+
operationAndMethod.method,
66+
emulateSwaggerV2BodyParameters);
5467
}
5568
}
5669
throw new IllegalArgumentException(

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

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,33 @@
1717

1818
import io.swagger.v3.oas.models.OpenAPI;
1919
import io.swagger.v3.oas.models.Operation;
20-
import io.swagger.v3.oas.models.media.Content;
2120
import io.swagger.v3.oas.models.media.MediaType;
2221
import io.swagger.v3.oas.models.media.Schema;
23-
import io.swagger.v3.oas.models.parameters.Parameter;
24-
import io.swagger.v3.oas.models.responses.ApiResponse;
2522
import io.swagger.v3.oas.models.servers.Server;
23+
import java.util.ArrayList;
24+
import java.util.HashSet;
2625
import java.util.List;
2726
import java.util.Map;
27+
import java.util.Set;
2828

2929
class OperationDefinition {
3030
private final Operation operation;
3131
private final String method;
3232
private final OpenAPI openAPI;
3333
private final String path;
34+
private final boolean emulateSwaggerV2BodyParameters;
3435

35-
OperationDefinition(OpenAPI openAPI, Operation operation, String path, String method) {
36+
OperationDefinition(
37+
OpenAPI openAPI,
38+
Operation operation,
39+
String path,
40+
String method,
41+
boolean emulateSwaggerV2BodyParameters) {
3642
this.openAPI = openAPI;
3743
this.operation = operation;
3844
this.path = path;
3945
this.method = method;
46+
this.emulateSwaggerV2BodyParameters = emulateSwaggerV2BodyParameters;
4047
}
4148

4249
String getMethod() {
@@ -52,67 +59,66 @@ Operation getOperation() {
5259
}
5360

5461
List<String> getServers() {
62+
if (openAPI.getServers() == null) {
63+
return List.of();
64+
}
5565
return openAPI.getServers().stream().map(Server::getUrl).toList();
5666
}
5767

58-
List<Parameter> getParameters() {
68+
List<ParameterDefinition> getParameters() {
69+
return emulateSwaggerV2BodyParameters ? getSwaggerV2Parameters() : getOpenApiParameters();
70+
}
71+
72+
private List<ParameterDefinition> getOpenApiParameters() {
5973
if (operation.getParameters() == null) {
6074
return List.of();
6175
}
62-
return operation.getParameters();
76+
return operation.getParameters().stream().map(ParameterDefinition::new).toList();
6377
}
6478

65-
@SuppressWarnings({"rawtypes", "unchecked"})
66-
Map<String, Schema> getBody() {
67-
if (operation.getRequestBody() != null && operation.getRequestBody().getContent() != null) {
68-
Content content = operation.getRequestBody().getContent();
69-
if (content.containsKey("application/json")) {
70-
MediaType mt = content.get("application/json");
71-
if (mt.getSchema().get$ref() != null && !mt.getSchema().get$ref().isEmpty()) {
72-
Schema<?> schema = resolveSchema(mt.getSchema().get$ref());
73-
return schema.getProperties();
74-
} else if (mt.getSchema().getProperties() != null) {
75-
return mt.getSchema().getProperties();
76-
} else {
77-
throw new IllegalArgumentException(
78-
"Can't resolve schema for request body of operation " + operation.getOperationId());
79-
}
80-
} else {
81-
throw new IllegalArgumentException("Only 'application/json' content type is supported");
82-
}
79+
@SuppressWarnings({"rawtypes"})
80+
private List<ParameterDefinition> getSwaggerV2Parameters() {
81+
if (operation.getParameters() != null && !operation.getParameters().isEmpty()) {
82+
return operation.getParameters().stream().map(ParameterDefinition::new).toList();
8383
}
84-
return Map.of();
85-
}
86-
87-
String getContentType() {
88-
String method = getMethod().toUpperCase();
89-
90-
if (method.equals("POST") || method.equals("PUT") || method.equals("PATCH")) {
91-
if (operation.getRequestBody() != null && operation.getRequestBody().getContent() != null) {
92-
Content content = operation.getRequestBody().getContent();
93-
if (!content.isEmpty()) {
94-
return content.keySet().iterator().next();
95-
}
84+
if (operation.getRequestBody() != null) {
85+
Schema<?> schema = null;
86+
if (operation.getRequestBody().getContent() != null
87+
&& operation
88+
.getRequestBody()
89+
.getContent()
90+
.containsKey(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)) {
91+
MediaType mt =
92+
operation
93+
.getRequestBody()
94+
.getContent()
95+
.get(jakarta.ws.rs.core.MediaType.APPLICATION_JSON);
96+
schema = mt.getSchema();
97+
} else if (operation.getRequestBody().get$ref() != null) {
98+
schema = resolveSchema(operation.getRequestBody().get$ref());
9699
}
97-
}
98100

99-
if (operation.getResponses() != null) {
100-
for (String code : new String[] {"200", "201", "204"}) {
101-
ApiResponse resp = operation.getResponses().get(code);
102-
if (resp != null && resp.getContent() != null && !resp.getContent().isEmpty()) {
103-
return resp.getContent().keySet().iterator().next();
104-
}
101+
if (schema == null) {
102+
return List.of();
105103
}
106-
for (Map.Entry<String, ApiResponse> e : operation.getResponses().entrySet()) {
107-
Content content = e.getValue().getContent();
108-
if (content != null && !content.isEmpty()) {
109-
return content.keySet().iterator().next();
104+
105+
Set<String> required =
106+
schema.getRequired() != null ? new HashSet<>(schema.getRequired()) : new HashSet<>();
107+
108+
Map<String, Schema> properties = schema.getProperties();
109+
if (properties != null) {
110+
List<ParameterDefinition> result = new ArrayList<>();
111+
for (Map.Entry<String, Schema> prop : properties.entrySet()) {
112+
String fieldName = prop.getKey();
113+
ParameterDefinition fieldParam =
114+
new ParameterDefinition(
115+
fieldName, "body", required.contains(fieldName), prop.getValue());
116+
result.add(fieldParam);
110117
}
118+
return result;
111119
}
112120
}
113-
114-
throw new IllegalStateException(
115-
"No content type found for operation " + operation.getOperationId() + " [" + method + "]");
121+
return List.of();
116122
}
117123

118124
Schema<?> resolveSchema(String ref) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 io.swagger.v3.oas.models.media.Schema;
19+
import io.swagger.v3.oas.models.parameters.Parameter;
20+
21+
class ParameterDefinition {
22+
23+
private final String name;
24+
private final String in;
25+
private final boolean required;
26+
private final Schema schema;
27+
28+
ParameterDefinition(Parameter parameter) {
29+
this(
30+
parameter.getName(),
31+
parameter.getIn(),
32+
parameter.getRequired() != null && parameter.getRequired(),
33+
parameter.getSchema());
34+
}
35+
36+
ParameterDefinition(String name, String in, boolean required, Schema schema) {
37+
this.name = name;
38+
this.in = in;
39+
this.required = required;
40+
this.schema = schema;
41+
}
42+
43+
public String getIn() {
44+
return in;
45+
}
46+
47+
public String getName() {
48+
return name;
49+
}
50+
51+
public boolean getRequired() {
52+
return required;
53+
}
54+
55+
public Schema getSchema() {
56+
return schema;
57+
}
58+
}

0 commit comments

Comments
 (0)