Skip to content

Commit 8eb019a

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

File tree

8 files changed

+1088
-62
lines changed

8 files changed

+1088
-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: 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: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,35 @@
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+
private static final String APPLICATION_JSON = "application/json";
37+
38+
OperationDefinition(
39+
OpenAPI openAPI,
40+
Operation operation,
41+
String path,
42+
String method,
43+
boolean emulateSwaggerV2BodyParameters) {
3644
this.openAPI = openAPI;
3745
this.operation = operation;
3846
this.path = path;
3947
this.method = method;
48+
this.emulateSwaggerV2BodyParameters = emulateSwaggerV2BodyParameters;
4049
}
4150

4251
String getMethod() {
@@ -47,72 +56,59 @@ String getPath() {
4756
return path;
4857
}
4958

50-
Operation getOperation() {
51-
return operation;
52-
}
53-
5459
List<String> getServers() {
60+
if (openAPI.getServers() == null) {
61+
return List.of();
62+
}
5563
return openAPI.getServers().stream().map(Server::getUrl).toList();
5664
}
5765

58-
List<Parameter> getParameters() {
66+
List<ParameterDefinition> getParameters() {
67+
return emulateSwaggerV2BodyParameters ? getSwaggerV2Parameters() : getOpenApiParameters();
68+
}
69+
70+
private List<ParameterDefinition> getOpenApiParameters() {
5971
if (operation.getParameters() == null) {
6072
return List.of();
6173
}
62-
return operation.getParameters();
74+
return operation.getParameters().stream().map(ParameterDefinition::new).toList();
6375
}
6476

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

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-
}
92+
if (schema == null) {
93+
return List.of();
10594
}
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();
95+
96+
Set<String> required =
97+
schema.getRequired() != null ? new HashSet<>(schema.getRequired()) : new HashSet<>();
98+
99+
Map<String, Schema> properties = schema.getProperties();
100+
if (properties != null) {
101+
List<ParameterDefinition> result = new ArrayList<>();
102+
for (Map.Entry<String, Schema> prop : properties.entrySet()) {
103+
String fieldName = prop.getKey();
104+
ParameterDefinition fieldParam =
105+
new ParameterDefinition(fieldName, "body", required.contains(fieldName));
106+
result.add(fieldParam);
110107
}
108+
return result;
111109
}
112110
}
113-
114-
throw new IllegalStateException(
115-
"No content type found for operation " + operation.getOperationId() + " [" + method + "]");
111+
return List.of();
116112
}
117113

118114
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)