Skip to content

Commit 4219842

Browse files
committed
feat(openapi): support documentation example for openapi
1 parent d79284a commit 4219842

File tree

9 files changed

+379
-3
lines changed

9 files changed

+379
-3
lines changed

impl/openapi/pom.xml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>io.serverlessworkflow</groupId>
6+
<artifactId>serverlessworkflow-impl</artifactId>
7+
<version>8.0.0-SNAPSHOT</version>
8+
</parent>
9+
<artifactId>serverlessworkflow-impl-openapi</artifactId>
10+
<name>Serverless Workflow :: Impl :: OpenAPI</name>
11+
<dependencies>
12+
<dependency>
13+
<groupId>org.glassfish.jersey.core</groupId>
14+
<artifactId>jersey-client</artifactId>
15+
</dependency>
16+
<dependency>
17+
<groupId>org.glassfish.jersey.media</groupId>
18+
<artifactId>jersey-media-json-jackson</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>io.serverlessworkflow</groupId>
22+
<artifactId>serverlessworkflow-impl-core</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>io.swagger.parser.v3</groupId>
26+
<artifactId>swagger-parser</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>jakarta.ws.rs</groupId>
30+
<artifactId>jakarta.ws.rs-api</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.junit.jupiter</groupId>
34+
<artifactId>junit-jupiter-api</artifactId>
35+
<scope>test</scope>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.junit.jupiter</groupId>
39+
<artifactId>junit-jupiter-engine</artifactId>
40+
<scope>test</scope>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.junit.jupiter</groupId>
44+
<artifactId>junit-jupiter-params</artifactId>
45+
<scope>test</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.assertj</groupId>
49+
<artifactId>assertj-core</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
</dependencies>
53+
</project>
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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;
17+
18+
import io.serverlessworkflow.api.types.CallOpenAPI;
19+
import io.serverlessworkflow.api.types.OpenAPIArguments;
20+
import io.serverlessworkflow.api.types.TaskBase;
21+
import io.serverlessworkflow.api.types.UriTemplate;
22+
import io.serverlessworkflow.api.types.WithOpenAPIParameters;
23+
import io.serverlessworkflow.api.types.Workflow;
24+
import io.serverlessworkflow.impl.TaskContext;
25+
import io.serverlessworkflow.impl.WorkflowApplication;
26+
import io.serverlessworkflow.impl.WorkflowContext;
27+
import io.serverlessworkflow.impl.WorkflowModel;
28+
import io.serverlessworkflow.impl.resources.ResourceLoader;
29+
import io.swagger.v3.oas.models.OpenAPI;
30+
import io.swagger.v3.parser.OpenAPIV3Parser;
31+
import jakarta.ws.rs.client.Client;
32+
import jakarta.ws.rs.client.ClientBuilder;
33+
import jakarta.ws.rs.client.Invocation;
34+
import jakarta.ws.rs.client.WebTarget;
35+
import jakarta.ws.rs.core.MultivaluedMap;
36+
import java.net.URI;
37+
import java.util.concurrent.CompletableFuture;
38+
import java.util.concurrent.atomic.AtomicReference;
39+
40+
public class OpenAPIExecutor implements CallableTask<CallOpenAPI> {
41+
42+
private static final Client client = ClientBuilder.newClient();
43+
private WebTargetSupplier webTargetSupplier;
44+
private RequestSupplier requestSupplier;
45+
private final OpenAPIModelConverter converter = new OpenAPIModelConverter() {};
46+
47+
@FunctionalInterface
48+
private interface WebTargetSupplier {
49+
WebTarget apply();
50+
}
51+
52+
@FunctionalInterface
53+
private interface RequestSupplier {
54+
WorkflowModel apply(
55+
Invocation.Builder request, WorkflowContext workflow, TaskContext task, WorkflowModel node);
56+
}
57+
58+
@Override
59+
public void init(
60+
CallOpenAPI task, Workflow workflow, WorkflowApplication application, ResourceLoader loader) {
61+
OpenAPIArguments args = task.getWith();
62+
WithOpenAPIParameters withParams = args.getParameters();
63+
64+
URI uri = getOpenAPIDocumentURI(args.getDocument().getEndpoint().getUriTemplate());
65+
66+
OpenAPIV3Parser apiv3Parser = new OpenAPIV3Parser();
67+
68+
OpenAPI openAPI = apiv3Parser.read(uri.toString());
69+
70+
OpenAPIOperationContext ctx = generateContext(openAPI, args, uri);
71+
72+
this.webTargetSupplier =
73+
() -> {
74+
final AtomicReference<WebTarget> webTarget =
75+
new AtomicReference<>(
76+
client
77+
.target(openAPI.getServers().get(0).getUrl())
78+
.path(ctx.buildPath(withParams.getAdditionalProperties())));
79+
80+
MultivaluedMap<String, Object> queryParams =
81+
ctx.buildQuery(withParams.getAdditionalProperties());
82+
queryParams.forEach(
83+
(key, value) -> {
84+
for (Object o : value) {
85+
webTarget.set(webTarget.get().queryParam(key, o));
86+
}
87+
});
88+
89+
return webTarget.get();
90+
};
91+
92+
this.requestSupplier =
93+
(request, w, taskContext, node) -> {
94+
Object response = request.method(ctx.httpMethodName(), node.objectClass());
95+
return converter.toModel(application.modelFactory(), node, response);
96+
};
97+
}
98+
99+
private static OpenAPIOperationContext generateContext(
100+
OpenAPI openAPI, OpenAPIArguments args, URI uri) {
101+
return openAPI.getPaths().entrySet().stream()
102+
.flatMap(
103+
pathEntry ->
104+
pathEntry.getValue().readOperationsMap().entrySet().stream()
105+
.map(
106+
operationEntry ->
107+
new OpenAPIOperationContext(
108+
operationEntry.getValue().getOperationId(),
109+
pathEntry.getKey(),
110+
operationEntry.getKey(),
111+
operationEntry.getValue())))
112+
.filter(c -> c.operationId().equals(args.getOperationId()))
113+
.findFirst()
114+
.orElseThrow(
115+
() ->
116+
new IllegalArgumentException(
117+
"Operation with id "
118+
+ args.getOperationId()
119+
+ " not found in OpenAPI document "
120+
+ uri));
121+
}
122+
123+
@Override
124+
public CompletableFuture<WorkflowModel> apply(
125+
WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) {
126+
127+
return CompletableFuture.supplyAsync(
128+
() -> {
129+
WebTarget target = webTargetSupplier.apply();
130+
Invocation.Builder request = target.request();
131+
return requestSupplier.apply(request, workflowContext, taskContext, input);
132+
},
133+
workflowContext.definition().application().executorService());
134+
}
135+
136+
@Override
137+
public boolean accept(Class<? extends TaskBase> clazz) {
138+
return clazz.equals(CallOpenAPI.class);
139+
}
140+
141+
private static URI getOpenAPIDocumentURI(UriTemplate template) {
142+
if (template.getLiteralUri() != null) {
143+
return template.getLiteralUri();
144+
} else if (template.getLiteralUriTemplate() != null) {
145+
// TODO: Support
146+
// https://github.com/serverlessworkflow/specification/blob/main/dsl-reference.md#uri-template
147+
throw new UnsupportedOperationException(
148+
"URI templates with parameters are not supported yet");
149+
}
150+
throw new IllegalArgumentException("Invalid UriTemplate definition " + template);
151+
}
152+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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;
17+
18+
import io.serverlessworkflow.impl.WorkflowModel;
19+
import io.serverlessworkflow.impl.WorkflowModelFactory;
20+
import jakarta.ws.rs.client.Entity;
21+
import java.util.Map;
22+
23+
public interface OpenAPIModelConverter {
24+
25+
default WorkflowModel toModel(WorkflowModelFactory factory, WorkflowModel model, Object entity) {
26+
return factory.fromAny(model, entity);
27+
}
28+
29+
default Entity toEntity(Map<String, Object> model) {
30+
return Entity.json(model);
31+
}
32+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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;
17+
18+
import io.swagger.v3.oas.models.Operation;
19+
import io.swagger.v3.oas.models.PathItem;
20+
import io.swagger.v3.oas.models.parameters.Parameter;
21+
import jakarta.ws.rs.core.MultivaluedHashMap;
22+
import jakarta.ws.rs.core.MultivaluedMap;
23+
import java.util.Map;
24+
25+
public record OpenAPIOperationContext(
26+
String operationId, String path, PathItem.HttpMethod httpMethod, Operation operation) {
27+
28+
public String httpMethodName() {
29+
return httpMethod.name();
30+
}
31+
32+
public String buildPath(Map<String, Object> replacements) {
33+
String finalPath = path;
34+
for (Parameter parameter : operation.getParameters()) {
35+
if ("path".equals(parameter.getIn())) {
36+
String name = parameter.getName();
37+
Object value = replacements.get(name);
38+
if (value != null) {
39+
finalPath = path.replace("{" + name + "}", value.toString());
40+
}
41+
}
42+
}
43+
return finalPath;
44+
}
45+
46+
public MultivaluedMap<String, Object> buildQuery(Map<String, Object> replacements) {
47+
MultivaluedMap<String, Object> queryParams = new MultivaluedHashMap<>();
48+
for (Parameter parameter : operation.getParameters()) {
49+
if ("query".equals(parameter.getIn())) {
50+
String name = parameter.getName();
51+
Object value = replacements.get(name);
52+
if (value != null) {
53+
queryParams.add(name, value.toString());
54+
}
55+
}
56+
}
57+
return queryParams;
58+
}
59+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.serverlessworkflow.impl.executors.OpenAPIExecutor

impl/pom.xml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<version.net.thisptr>1.6.0</version.net.thisptr>
1414
<version.com.github.f4b6a3>5.2.3</version.com.github.f4b6a3>
1515
<version.jakarta.ws.rs>4.0.0</version.jakarta.ws.rs>
16+
<version.io.swagger.parser.v3>2.1.34</version.io.swagger.parser.v3>
1617
</properties>
1718
<dependencyManagement>
1819
<dependencies>
@@ -36,6 +37,11 @@
3637
<artifactId>serverlessworkflow-impl-jackson</artifactId>
3738
<version>${project.version}</version>
3839
</dependency>
40+
<dependency>
41+
<groupId>io.serverlessworkflow</groupId>
42+
<artifactId>serverlessworkflow-impl-openapi</artifactId>
43+
<version>${project.version}</version>
44+
</dependency>
3945
<dependency>
4046
<groupId>net.thisptr</groupId>
4147
<artifactId>jackson-jq</artifactId>
@@ -47,9 +53,9 @@
4753
<version>${version.com.github.f4b6a3}</version>
4854
</dependency>
4955
<dependency>
50-
<groupId>jakarta.ws.rs</groupId>
51-
<artifactId>jakarta.ws.rs-api</artifactId>
52-
<version>${version.jakarta.ws.rs}</version>
56+
<groupId>jakarta.ws.rs</groupId>
57+
<artifactId>jakarta.ws.rs-api</artifactId>
58+
<version>${version.jakarta.ws.rs}</version>
5359
</dependency>
5460
<dependency>
5561
<groupId>org.glassfish.jersey.core</groupId>
@@ -63,6 +69,11 @@
6369
<version>${version.org.glassfish.jersey}</version>
6470
<scope>test</scope>
6571
</dependency>
72+
<dependency>
73+
<groupId>io.swagger.parser.v3</groupId>
74+
<artifactId>swagger-parser</artifactId>
75+
<version>${version.io.swagger.parser.v3}</version>
76+
</dependency>
6677
</dependencies>
6778
</dependencyManagement>
6879
<modules>
@@ -71,5 +82,6 @@
7182
<module>jackson</module>
7283
<module>jwt-impl</module>
7384
<module>test</module>
85+
<module>openapi</module>
7486
</modules>
7587
</project>

impl/test/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
<groupId>io.serverlessworkflow</groupId>
2525
<artifactId>serverlessworkflow-impl-jackson-jwt</artifactId>
2626
</dependency>
27+
<dependency>
28+
<groupId>io.serverlessworkflow</groupId>
29+
<artifactId>serverlessworkflow-impl-openapi</artifactId>
30+
</dependency>
2731
<dependency>
2832
<groupId>org.glassfish.jersey.media</groupId>
2933
<artifactId>jersey-media-json-jackson</artifactId>

0 commit comments

Comments
 (0)