Skip to content

Commit f671a1c

Browse files
committed
feat(openapi): add redirect rules
Signed-off-by: Matheus Cruz <[email protected]>
1 parent ad8db5f commit f671a1c

File tree

4 files changed

+161
-26
lines changed

4 files changed

+161
-26
lines changed

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

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,31 @@
2424
import io.serverlessworkflow.impl.TaskContext;
2525
import io.serverlessworkflow.impl.WorkflowApplication;
2626
import io.serverlessworkflow.impl.WorkflowContext;
27+
import io.serverlessworkflow.impl.WorkflowError;
28+
import io.serverlessworkflow.impl.WorkflowException;
2729
import io.serverlessworkflow.impl.WorkflowModel;
2830
import io.serverlessworkflow.impl.resources.ResourceLoader;
2931
import io.swagger.v3.oas.models.OpenAPI;
3032
import io.swagger.v3.parser.OpenAPIV3Parser;
33+
import jakarta.ws.rs.WebApplicationException;
3134
import jakarta.ws.rs.client.Client;
3235
import jakarta.ws.rs.client.ClientBuilder;
3336
import jakarta.ws.rs.client.Invocation;
3437
import jakarta.ws.rs.client.WebTarget;
3538
import jakarta.ws.rs.core.MultivaluedMap;
39+
import jakarta.ws.rs.core.Response;
3640
import java.net.URI;
41+
import java.util.List;
42+
import java.util.Map;
43+
import java.util.Optional;
3744
import java.util.concurrent.CompletableFuture;
38-
import java.util.concurrent.atomic.AtomicReference;
3945

4046
public class OpenAPIExecutor implements CallableTask<CallOpenAPI> {
4147

4248
private static final Client client = ClientBuilder.newClient();
4349
private WebTargetSupplier webTargetSupplier;
4450
private RequestSupplier requestSupplier;
45-
private final OpenAPIModelConverter converter = new OpenAPIModelConverter() {};
51+
private OpenAPIModelConverter converter = new OpenAPIModelConverter() {};
4652

4753
@FunctionalInterface
4854
private interface WebTargetSupplier {
@@ -59,7 +65,6 @@ WorkflowModel apply(
5965
public void init(
6066
CallOpenAPI task, Workflow workflow, WorkflowApplication application, ResourceLoader loader) {
6167
OpenAPIArguments args = task.getWith();
62-
WithOpenAPIParameters withParams = args.getParameters();
6368

6469
URI uri = getOpenAPIDocumentURI(args.getDocument().getEndpoint().getUriTemplate());
6570

@@ -69,33 +74,75 @@ public void init(
6974

7075
OpenAPIOperationContext ctx = generateContext(openAPI, args, uri);
7176

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.buildQueryParams(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-
};
77+
WithOpenAPIParameters withParams =
78+
Optional.ofNullable(args.getParameters()).orElse(new WithOpenAPIParameters());
79+
80+
this.webTargetSupplier = getTargetSupplier(openAPI, ctx, withParams);
9181

9282
this.requestSupplier =
9383
(request, w, taskContext, node) -> {
94-
Object response = request.method(ctx.httpMethodName(), node.objectClass());
95-
return converter.toModel(application.modelFactory(), node, response);
84+
try {
85+
Response response = request.method(ctx.httpMethodName(), Response.class);
86+
87+
if (!args.isRedirect() && !is2xx(response)) {
88+
throw new WorkflowException(
89+
WorkflowError.communication(
90+
response.getStatus(),
91+
taskContext,
92+
"Received a non-2xx nor 3xx response but redirects are enabled")
93+
.build());
94+
}
95+
96+
if (args.isRedirect() && isNot2xxNor3xx(response)) {
97+
throw new WorkflowException(
98+
WorkflowError.communication(
99+
response.getStatus(),
100+
taskContext,
101+
"Received a non-2xx nor 3xx response but redirects are enabled")
102+
.build());
103+
}
104+
105+
return converter.toModel(
106+
application.modelFactory(), node, response.readEntity(node.objectClass()));
107+
} catch (WebApplicationException exception) {
108+
throw new WorkflowException(
109+
WorkflowError.communication(
110+
exception.getResponse().getStatus(), taskContext, exception)
111+
.build());
112+
}
96113
};
97114
}
98115

116+
private static WebTargetSupplier getTargetSupplier(
117+
OpenAPI openAPI, OpenAPIOperationContext ctx, WithOpenAPIParameters withParams) {
118+
return () -> {
119+
WebTarget webTarget =
120+
client
121+
.target(openAPI.getServers().get(0).getUrl())
122+
.path(ctx.buildPath(withParams.getAdditionalProperties()));
123+
124+
MultivaluedMap<String, Object> queryParams =
125+
ctx.buildQueryParams(withParams.getAdditionalProperties());
126+
127+
for (Map.Entry<String, List<Object>> queryParam : queryParams.entrySet()) {
128+
for (Object value : queryParam.getValue()) {
129+
webTarget = webTarget.queryParam(queryParam.getKey(), value);
130+
}
131+
}
132+
133+
return webTarget;
134+
};
135+
}
136+
137+
private static boolean is2xx(Response response) {
138+
return response.getStatusInfo().getFamily().equals(Response.Status.Family.SUCCESSFUL);
139+
}
140+
141+
private static boolean isNot2xxNor3xx(Response response) {
142+
return !(response.getStatusInfo().getFamily().equals(Response.Status.Family.SUCCESSFUL)
143+
|| response.getStatusInfo().getFamily().equals(Response.Status.Family.REDIRECTION));
144+
}
145+
99146
private static OpenAPIOperationContext generateContext(
100147
OpenAPI openAPI, OpenAPIArguments args, URI uri) {
101148
return openAPI.getPaths().entrySet().stream()
@@ -142,7 +189,6 @@ private static URI getOpenAPIDocumentURI(UriTemplate template) {
142189
if (template.getLiteralUri() != null) {
143190
return template.getLiteralUri();
144191
} else if (template.getLiteralUriTemplate() != null) {
145-
// TODO: Support
146192
// https://github.com/serverlessworkflow/specification/blob/main/dsl-reference.md#uri-template
147193
throw new UnsupportedOperationException(
148194
"URI templates with parameters are not supported yet");

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import jakarta.ws.rs.core.MultivaluedHashMap;
2222
import jakarta.ws.rs.core.MultivaluedMap;
2323
import java.util.Map;
24+
import java.util.Objects;
2425

2526
public record OpenAPIOperationContext(
2627
String operationId, String path, PathItem.HttpMethod httpMethod, Operation operation) {
@@ -31,6 +32,9 @@ public String httpMethodName() {
3132

3233
public String buildPath(Map<String, Object> replacements) {
3334
String finalPath = path;
35+
if (Objects.isNull(operation.getParameters())) {
36+
return "";
37+
}
3438
for (Parameter parameter : operation.getParameters()) {
3539
if ("path".equals(parameter.getIn())) {
3640
String name = parameter.getName();
@@ -44,6 +48,9 @@ public String buildPath(Map<String, Object> replacements) {
4448
}
4549

4650
public MultivaluedMap<String, Object> buildQueryParams(Map<String, Object> replacements) {
51+
if (Objects.isNull(operation.getParameters())) {
52+
return new MultivaluedHashMap<>();
53+
}
4754
MultivaluedMap<String, Object> queryParams = new MultivaluedHashMap<>();
4855
for (Parameter parameter : operation.getParameters()) {
4956
if ("query".equals(parameter.getIn())) {

impl/test/src/test/java/io/serverlessworkflow/impl/test/OpenAPIWorkflowDefinitionTest.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,38 @@
1717

1818
import io.serverlessworkflow.api.WorkflowReader;
1919
import io.serverlessworkflow.impl.WorkflowApplication;
20+
import io.serverlessworkflow.impl.WorkflowException;
2021
import io.serverlessworkflow.impl.WorkflowModel;
2122
import java.io.IOException;
2223
import java.util.List;
2324
import java.util.Map;
25+
import okhttp3.mockwebserver.MockResponse;
26+
import okhttp3.mockwebserver.MockWebServer;
2427
import org.assertj.core.api.Assertions;
28+
import org.junit.jupiter.api.AfterEach;
2529
import org.junit.jupiter.api.BeforeAll;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.DisplayName;
2632
import org.junit.jupiter.api.Test;
2733

2834
public class OpenAPIWorkflowDefinitionTest {
2935

3036
private static WorkflowApplication app;
37+
private MockWebServer mockServer;
38+
39+
@BeforeEach
40+
public void setUp() throws IOException {
41+
mockServer = new MockWebServer();
42+
mockServer.start(9999);
43+
}
44+
45+
@AfterEach
46+
void tearDown() throws IOException {
47+
mockServer.shutdown();
48+
}
3149

3250
@BeforeAll
33-
static void setUp() {
51+
static void setUpApp() {
3452
app = WorkflowApplication.builder().build();
3553
}
3654

@@ -53,4 +71,55 @@ void testOpenAPIWorkflowExecution() throws IOException {
5371
return pet.get("status").equals("available");
5472
});
5573
}
74+
75+
@Test
76+
@DisplayName(
77+
"must raise an error for response status codes outside the 200–299 range when redirect is set to false")
78+
void testOpenAPIRedirect() {
79+
mockServer.enqueue(
80+
new MockResponse()
81+
.setResponseCode(200)
82+
.setBody(
83+
"""
84+
{
85+
"openapi": "3.0.3",
86+
"info": {
87+
"title": "Redirect API",
88+
"version": "1.0.0"
89+
},
90+
"servers": [
91+
{
92+
"url": "http://localhost:9999"
93+
}
94+
],
95+
"paths": {
96+
"/redirect": {
97+
"get": {
98+
"operationId": "redirectToDocs",
99+
"summary": "Redirects to external documentation",
100+
"responses": {
101+
"302": {
102+
"description": "Redirecting to external documentation"
103+
}
104+
}
105+
}
106+
}
107+
}
108+
}
109+
}
110+
""")
111+
.setHeader("Content-Type", "application/json"));
112+
113+
mockServer.enqueue(new MockResponse().setResponseCode(301));
114+
115+
Assertions.assertThatThrownBy(
116+
() ->
117+
app.workflowDefinition(
118+
WorkflowReader.readWorkflowFromClasspath(
119+
"workflows-samples/openapi/findPetsByStatus-redirect.yaml"))
120+
.instance(List.of())
121+
.start()
122+
.join())
123+
.hasCauseInstanceOf(WorkflowException.class);
124+
}
56125
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
document:
2+
dsl: '1.0.1'
3+
namespace: test
4+
name: openapi-example-with-redirect
5+
version: '0.1.0'
6+
do:
7+
- findPet:
8+
call: openapi
9+
with:
10+
document:
11+
endpoint: http://localhost:9999
12+
operationId: redirectToDocs
13+
redirect: false

0 commit comments

Comments
 (0)