Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,28 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio
Set<String> mandatoryParameters = new HashSet<>();
Set<String> missingParams = new HashSet<>();
boolean hasBody = operation.getOperation().hasRequestBody();
Map<String, Object> bodyParameters = new HashMap<>(parameters);

// Parameters are unique per (in, name)
Set<String> consumedParams = new HashSet<>();
for (ParameterDefinition parameter : operation.getParameters()) {
switch (parameter.in()) {
case "header":
param(parameter, bodyParameters, headers, missingParams, mandatoryParameters);
param(parameter, headers, consumedParams, missingParams, mandatoryParameters);
break;
case "path":
param(parameter, bodyParameters, pathParameters, missingParams, mandatoryParameters);
param(parameter, pathParameters, consumedParams, missingParams, mandatoryParameters);
break;
case "query":
param(parameter, bodyParameters, queryMap, missingParams, mandatoryParameters);
param(parameter, queryMap, consumedParams, missingParams, mandatoryParameters);
break;
case "body":
hasBody = true;
break;
}
}
Map<String, Object> bodyParameters = new HashMap<>(parameters);
bodyParameters.keySet().removeAll(consumedParams);

if (!missingParams.isEmpty()) {
throw new IllegalArgumentException(
"Missing required OpenAPI parameters for operation '"
Expand All @@ -130,17 +135,18 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio

private void param(
ParameterDefinition parameter,
Map<String, Object> origMap,
Map<String, Object> collectorMap,
Set<String> consumedParams,
Set<String> missingParams,
Set<String> mandatoryParams) {
String name = parameter.name();
boolean isRequired = parameter.required();
if (isRequired) {
mandatoryParams.add(name);
}
if (origMap.containsKey(name)) {
collectorMap.put(parameter.name(), origMap.remove(name));
if (parameters.containsKey(name)) {
collectorMap.put(name, parameters.get(name));
consumedParams.add(name);
} else if (isRequired) {
UnifiedOpenAPI.Schema schema = parameter.schema();
Object defaultValue = schema != null ? schema._default() : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,28 @@
package org.openworkflow.sdk.impl.executors.openapi;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

class OperationDefinition {

private final UnifiedOpenAPI.Operation operation;
private final UnifiedOpenAPI.PathItem pathItem;
private final String method;
private final UnifiedOpenAPI openAPI;
private final String path;

OperationDefinition(
UnifiedOpenAPI openAPI, UnifiedOpenAPI.Operation operation, String path, String method) {
UnifiedOpenAPI openAPI,
UnifiedOpenAPI.PathItem pathItem,
UnifiedOpenAPI.Operation operation,
String path,
String method) {

this.openAPI = Objects.requireNonNull(openAPI, "openAPI cannot be null");
this.pathItem = pathItem;
this.operation = Objects.requireNonNull(operation, "operation cannot be null");
this.path = Objects.requireNonNull(path, "path cannot be null");
this.method = Objects.requireNonNull(method, "method cannot be null");
Expand All @@ -49,30 +56,54 @@ UnifiedOpenAPI.Operation getOperation() {
}

List<String> getServers() {
if (openAPI.getServers() == null) {
return List.of();
if (openAPI.swaggerVersion() == UnifiedOpenAPI.SwaggerVersion.OPENAPI_V3) {
if (operation.servers() != null && !operation.servers().isEmpty()) {
return toUrls(operation.servers());
Comment thread
mathecruz marked this conversation as resolved.
Resolved
}
if (pathItem != null && pathItem.servers() != null && !pathItem.servers().isEmpty()) {
return toUrls(pathItem.servers());
}
}

List<String> servers = openAPI.getServers();
return servers == null ? List.of() : servers;
}

private static List<String> toUrls(List<UnifiedOpenAPI.Server> servers) {
return servers.stream().map(UnifiedOpenAPI.Server::url).toList();
Comment thread
mathecruz marked this conversation as resolved.
Resolved
}

private List<UnifiedOpenAPI.Parameter> mergedParameters() {
Map<String, UnifiedOpenAPI.Parameter> merged = new LinkedHashMap<>();
if (pathItem != null && pathItem.parameters() != null) {
pathItem.parameters().forEach(p -> merge(merged, p));
}
if (operation.hasParameters()) {
operation.parameters().forEach(p -> merge(merged, p));
}
return new ArrayList<>(merged.values());
}

return openAPI.getServers();
private void merge(Map<String, UnifiedOpenAPI.Parameter> merged, UnifiedOpenAPI.Parameter p) {
UnifiedOpenAPI.Parameter resolved = openAPI.resolveParameter(p);
merged.put(resolved.in() + ":" + resolved.name(), resolved);
}

List<ParameterDefinition> getParameters() {
List<UnifiedOpenAPI.Parameter> allParameters = mergedParameters();
List<ParameterDefinition> paramDefinitions = new ArrayList<>();
if (operation.hasParameters()) {
for (UnifiedOpenAPI.Parameter parameter : operation.parameters()) {
if ("body".equals(parameter.in())) {
continue; // body parameters are handled separately
}

paramDefinitions.add(
new ParameterDefinition(
parameter.name(), parameter.in(), parameter.required(), parameter.schema()));
for (UnifiedOpenAPI.Parameter parameter : allParameters) {
if ("body".equals(parameter.in())) {
continue; // body parameters are handled separately
}

paramDefinitions.add(
new ParameterDefinition(
parameter.name(), parameter.in(), parameter.required(), parameter.schema()));
}

if (openAPI.swaggerVersion() == UnifiedOpenAPI.SwaggerVersion.SWAGGER_V2
&& operation.hasParameters()) {
operation.parameters().stream()
if (openAPI.swaggerVersion() == UnifiedOpenAPI.SwaggerVersion.SWAGGER_V2) {
allParameters.stream()
.filter(p -> "body".equals(p.in()))
.forEach(p -> {
UnifiedOpenAPI.Schema schema = p.schema();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.openworkflow.sdk.impl.executors.openapi;

import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand All @@ -29,7 +30,8 @@ public record UnifiedOpenAPI(
List<String> schemes,
Map<String, PathItem> paths,
Components components,
Map<String, Schema> definitions) {
Map<String, Schema> definitions,
Map<String, Parameter> parameters) {

public enum SwaggerVersion {
SWAGGER_V2,
Expand Down Expand Up @@ -59,6 +61,7 @@ Optional<OperationDefinition> findOperationById(String operationId) {
return Optional.of(
new OperationDefinition(
this,
pathItem,
httpOperation.operation(),
path,
httpOperation.method().toUpperCase(Locale.ROOT)));
Expand All @@ -79,7 +82,8 @@ public List<String> getServers() {
base += basePath;
}

return List.of(schemes.stream().findFirst().orElse("https") + "://" + base);
String scheme = schemes == null ? "https" : schemes.stream().findFirst().orElse("https");
return List.of(scheme + "://" + base);
}

if (servers == null || servers.isEmpty()) {
Expand Down Expand Up @@ -129,9 +133,51 @@ private Schema resolveRefSwaggerV2(String ref) {
return definitions.get(name);
}

/**
* Resolves a parameter reference, following chained references.
*
* <p>Parameters that are not references are returned as-is.
*
* @throws IllegalArgumentException if the reference cannot be resolved or is circular
*/
public Parameter resolveParameter(Parameter parameter) {
Set<String> visited = new HashSet<>();
while (parameter != null && parameter.hasRef()) {
String ref = parameter.ref();
if (!visited.add(ref)) {
throw new IllegalArgumentException("Circular OpenAPI parameter reference: " + ref);
}
Parameter resolved =
isSwaggerV2() ? resolveParameterSwaggerV2(ref) : resolveParameterOpenAPI(ref);
if (resolved == null) {
throw new IllegalArgumentException("Unable to resolve OpenAPI parameter reference: " + ref);
}
parameter = resolved;
}
return parameter;
}

private Parameter resolveParameterOpenAPI(String ref) {
if (!ref.startsWith("#/components/parameters/")
|| components == null
|| components.parameters() == null) {
return null;
}
return components.parameters().get(ref.substring("#/components/parameters/".length()));
}

private Parameter resolveParameterSwaggerV2(String ref) {
if (!ref.startsWith("#/parameters/") || parameters == null) {
return null;
}
return parameters.get(ref.substring("#/parameters/".length()));
}

public record Server(String url) {}

public record PathItem(
List<Server> servers,
List<Parameter> parameters,
Operation get,
Operation post,
Operation put,
Expand All @@ -154,7 +200,11 @@ Set<HttpOperation> methods() {

public record HttpOperation(String method, Operation operation) {}

public record Operation(String operationId, List<Parameter> parameters, RequestBody requestBody) {
public record Operation(
String operationId,
List<Server> servers,
List<Parameter> parameters,
RequestBody requestBody) {

public boolean hasParameters() {
return parameters != null && !parameters.isEmpty();
Expand All @@ -165,7 +215,12 @@ public boolean hasRequestBody() {
}
}

public record Parameter(String name, String in, boolean required, Schema schema) {}
public record Parameter(String name, String in, boolean required, Schema schema, String ref) {

public boolean hasRef() {
return ref != null && !ref.isBlank();
}
}

public record RequestBody(Content content) {}

Expand All @@ -177,7 +232,7 @@ public boolean isApplicationJson() {

public record MediaType(Schema schema) {}

public record Components(Map<String, Schema> schemas) {}
public record Components(Map<String, Schema> schemas, Map<String, Parameter> parameters) {}

public record Schema(
String type,
Expand Down
Loading
Loading