Skip to content

Commit 8e7cc3e

Browse files
committed
Add Swagger support to OpenAPI defition processor
Signed-off-by: Dmitrii Tikhomirov <chani.liet@gmail.com>
1 parent bf52277 commit 8e7cc3e

File tree

8 files changed

+1079
-62
lines changed

8 files changed

+1079
-62
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: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import io.serverlessworkflow.impl.executors.http.HttpExecutor;
2929
import io.serverlessworkflow.impl.executors.http.HttpExecutor.HttpExecutorBuilder;
3030
import io.serverlessworkflow.impl.resources.ResourceLoaderUtils;
31-
import io.swagger.v3.oas.models.parameters.Parameter;
3231
import java.util.Collection;
3332
import java.util.HashMap;
3433
import java.util.Iterator;
@@ -96,7 +95,7 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio
9695
Map<String, Object> pathParameters = new HashMap<String, Object>();
9796

9897
Map<String, Object> bodyParameters = new HashMap<>(parameters);
99-
for (Parameter parameter : operation.getParameters()) {
98+
for (ParameterDefinition parameter : operation.getParameters()) {
10099
switch (parameter.getIn()) {
101100
case "header":
102101
param(parameter.getName(), bodyParameters, headersMap);

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

Lines changed: 14 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,21 @@ 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(OpenAPI openAPI, boolean emulateSwaggerV2BodyParameters) {
4250
if (openAPI == null || openAPI.getPaths() == null) {
4351
throw new IllegalArgumentException("Invalid OpenAPI document");
4452
}
@@ -50,7 +58,7 @@ private OperationDefinition getOperation(OpenAPI openAPI) {
5058
OperationAndMethod operationAndMethod = findInPathItem(pathItem, operationId);
5159
if (operationAndMethod != null) {
5260
return new OperationDefinition(
53-
openAPI, operationAndMethod.operation, path, operationAndMethod.method);
61+
openAPI, operationAndMethod.operation, path, operationAndMethod.method, emulateSwaggerV2BodyParameters);
5462
}
5563
}
5664
throw new IllegalArgumentException(

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

Lines changed: 46 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,32 @@
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+
24+
import java.util.ArrayList;
25+
import java.util.HashSet;
2626
import java.util.List;
2727
import java.util.Map;
28+
import java.util.Set;
2829

2930
class OperationDefinition {
3031
private final Operation operation;
3132
private final String method;
3233
private final OpenAPI openAPI;
3334
private final String path;
35+
private final boolean emulateSwaggerV2BodyParameters;
36+
37+
private static final String APPLICATION_JSON = "application/json";
3438

35-
OperationDefinition(OpenAPI openAPI, Operation operation, String path, String method) {
39+
OperationDefinition(
40+
OpenAPI openAPI, Operation operation, String path, String method, boolean emulateSwaggerV2BodyParameters) {
3641
this.openAPI = openAPI;
3742
this.operation = operation;
3843
this.path = path;
3944
this.method = method;
45+
this.emulateSwaggerV2BodyParameters = emulateSwaggerV2BodyParameters;
4046
}
4147

4248
String getMethod() {
@@ -47,72 +53,58 @@ String getPath() {
4753
return path;
4854
}
4955

50-
Operation getOperation() {
51-
return operation;
52-
}
53-
5456
List<String> getServers() {
57+
if (openAPI.getServers() == null) {
58+
return List.of();
59+
}
5560
return openAPI.getServers().stream().map(Server::getUrl).toList();
5661
}
5762

58-
List<Parameter> getParameters() {
63+
List<ParameterDefinition> getParameters() {
64+
return emulateSwaggerV2BodyParameters ? getSwaggerV2Parameters() : getOpenApiParameters();
65+
}
66+
67+
private List<ParameterDefinition> getOpenApiParameters() {
5968
if (operation.getParameters() == null) {
6069
return List.of();
6170
}
62-
return operation.getParameters();
71+
return operation.getParameters().stream().map(ParameterDefinition::new).toList();
6372
}
6473

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-
}
74+
@SuppressWarnings({"rawtypes"})
75+
private List<ParameterDefinition> getSwaggerV2Parameters() {
76+
if (operation.getParameters() != null && !operation.getParameters().isEmpty()) {
77+
return operation.getParameters().stream().map(ParameterDefinition::new).toList();
8378
}
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-
}
79+
if (operation.getRequestBody() != null) {
80+
Schema<?> schema = null;
81+
if (operation.getRequestBody().getContent() != null && operation.getRequestBody().getContent().containsKey(APPLICATION_JSON)) {
82+
MediaType mt = operation.getRequestBody().getContent().get(APPLICATION_JSON);
83+
schema = mt.getSchema();
84+
} else if (operation.getRequestBody().get$ref() != null) {
85+
schema = resolveSchema(operation.getRequestBody().get$ref());
9686
}
97-
}
9887

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-
}
88+
if (schema == null) {
89+
return List.of();
10590
}
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();
91+
92+
Set<String> required = schema.getRequired() != null
93+
? new HashSet<>(schema.getRequired())
94+
: new HashSet<>();
95+
96+
Map<String, Schema> properties = schema.getProperties();
97+
if (properties != null) {
98+
List<ParameterDefinition> result = new ArrayList<>();
99+
for (Map.Entry<String, Schema> prop : properties.entrySet()) {
100+
String fieldName = prop.getKey();
101+
ParameterDefinition fieldParam = new ParameterDefinition(fieldName, "body", required.contains(fieldName));
102+
result.add(fieldParam);
110103
}
104+
return result;
111105
}
112106
}
113-
114-
throw new IllegalStateException(
115-
"No content type found for operation " + operation.getOperationId() + " [" + method + "]");
107+
return List.of();
116108
}
117109

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

0 commit comments

Comments
 (0)