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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM openjdk:11-jdk
FROM openjdk:17-jdk

ARG JAR_FILE
COPY ${JAR_FILE} app.jar
Expand Down
55 changes: 29 additions & 26 deletions OPENAPI_VERSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,35 +80,36 @@ paths:
type: [string, null] # Nullable in 3.1
```

### OpenAPI 3.2.x ⏳
- **Status:** Not yet released
- **ETA:** Future (as of April 2026)
- **Planned Support:** When OpenAPI 3.2 is officially released and swagger-parser is updated
- **Expected Changes:** Refinements to JSON Schema integration, possible webhooks improvements
### OpenAPI 3.2.x ✅
- **Status:** Officially released and supported
- **Release:** OpenAPI 3.2.0 (September 2025)
- **Parser:** swagger-parser 2.1.41+ (current project version)
- **Current Compatibility:** Parsed and generated through project-level normalization of 3.2-specific fields
- **Highlights:** Additional HTTP method support, richer parameter modeling, and improved response metadata

## Version Detection

The OpenAPI version is automatically detected from the `openapi` field in your spec:

```yaml
openapi: 3.1.0 # Detected and handled appropriately
openapi: 3.2.0 # Detected and handled appropriately
```

No configuration is needed — just submit your spec and the tool will parse it correctly.

## Feature Compatibility Across Versions

All openapi2soapui features work with both OpenAPI 3.0 and 3.1:
All openapi2soapui features work with OpenAPI 3.0, 3.1, and 3.2:

| Feature | 3.0.x | 3.1.x |
|---------|-------|-------|
| `readOnly` | ✅ | ✅ |
| `serverPattern` | ✅ | ✅ |
| `minimalEndpoints` | ✅ | ✅ |
| `microcksHeaders` | ✅ | ✅ |
| `generateOneOfAnyOf` | ✅ | ✅ |
| `examples` | ✅ | ✅ |
| `validateSchema` | ✅ | ✅ |
| Feature | 3.0.x | 3.1.x | 3.2.x |
|---------|-------|-------|-------|
| `readOnly` | ✅ | ✅ | ✅ |
| `serverPattern` | ✅ | ✅ | ✅ |
| `minimalEndpoints` | ✅ | ✅ | ✅ |
| `microcksHeaders` | ✅ | ✅ | ✅ |
| `generateOneOfAnyOf` | ✅ | ✅ | ✅ |
| `examples` | ✅ | ✅ | ✅ |
| `validateSchema` | ✅ | ✅ | ✅ |

## Test Coverage

Expand Down Expand Up @@ -137,9 +138,9 @@ Comprehensive tests ensure compatibility across versions:
- Arrays of objects
- Schema composition (allOf)

- **Forward Compatibility Tests:** 2 tests
- 3.2 status documentation
- Graceful handling of future versions
- **OpenAPI 3.2.x Tests:** 2 tests
- Basic 3.2.0 parsing and generation
- Querystring parameter compatibility

**Total:** 15 version support tests, all passing ✅

Expand Down Expand Up @@ -194,9 +195,10 @@ If you're migrating from 3.0 to 3.1:
</dependency>
```

- **Version 2.1.19+** supports OpenAPI 3.0.x and 3.1.x
- **Version 2.1.41+** supports OpenAPI 3.0.x and 3.1.x natively
- **OpenAPI 3.2.x** is supported in this project through a compatibility normalization layer
- **Version 2.0.x** supports OpenAPI 3.0.x only (legacy)
- **Version 3.x** (future) may support OpenAPI 3.2.x
- **Version 2.1.x/3.x** can be evaluated for future parser enhancements

## Error Handling

Expand All @@ -217,26 +219,27 @@ If your OpenAPI spec has version-specific issues:

### "Parser returned null" error
- Verify your YAML/JSON is valid
- Ensure OpenAPI version is one of: 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.1.0+
- Ensure OpenAPI version is one of: 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.1.0+, 3.2.x
- Check that required fields (info, paths) are present

### "Unknown schema property" errors
- For OpenAPI 3.0: Some JSON Schema 2020-12 features not supported
- For OpenAPI 3.1: Verify you're using 3.1-compatible schemas
- For OpenAPI 3.1/3.2: Verify you're using 3.1+ compatible schemas

### Performance with large specs
- Both 3.0 and 3.1 handle large specs efficiently
- OpenAPI 3.0, 3.1, and 3.2 handle large specs efficiently
- No performance difference between versions

## References

- [OpenAPI 3.0 Specification](https://spec.openapis.org/oas/v3.0.3)
- [OpenAPI 3.1 Specification](https://spec.openapis.org/oas/v3.1.0)
- [OpenAPI 3.2 Specification](https://spec.openapis.org/oas/v3.2.0)
- [swagger-parser Releases](https://github.com/swagger-api/swagger-parser/releases)
- [JSON Schema 2020-12](https://json-schema.org/draft/2020-12/json-schema-core.html)

---

**Last Updated:** 2026-04-27
**Swagger Parser Version:** 2.1.19+
**Last Updated:** 2026-04-29
**Swagger Parser Version:** 2.1.41+ (with OpenAPI 3.2 normalization layer)
**Test Coverage:** 15 tests (100% passing)
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Given an OpenAPI Specification (v3.0.x, v3.1.x, or v3.2.x), a SoapUI project is generated with the _requests_ for each resource operation and a _test suite_. The response is the content of the SoapUI project in XML format to save as file and import into the SoapUI application.

**Supported OpenAPI Versions:** 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.1.0+, 3.2.x (forward compatible)
**Supported OpenAPI Versions:** 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.1.0+, 3.2.x (officially released and supported)

### This repository is intended for :octocat: **community** use, it can be modified and adapted without commercial use. If you need a version, support or help for your **enterprise** or project, please contact us 📧 devrel@apiaddicts.org

Expand Down Expand Up @@ -197,7 +197,7 @@ OpenAPI2SoapUI supports 7 optional parameters to customize SoapUI project genera
|[Hibernate Validator](https://hibernate.org/validator/)|Express validation rules in a standardized way using annotation-based constraints and benefit from transparent integration with a wide variety of frameworks.|
|[Springdoc OpenAPI UI](https://springdoc.org/)|OpenAPI 3 Library for spring boot projects. Is based on swagger-ui, to display the OpenAPI description.|
|[SoapUI core module](https://www.soapui.org/open-source/)|SoapUI is the world's leading Functional Testing tool for SOAP and REST testing.|
|[Swagger Parser 2.1.19+](https://github.com/swagger-api/swagger-parser)|Parses OpenAPI definitions (3.0.x, 3.1.x) in JSON or YAML format into swagger-core representation as Java POJO. Supports JSON Schema 2020-12 and nullable types.|
|[Swagger Parser 2.1.41+](https://github.com/swagger-api/swagger-parser)|Parses OpenAPI definitions (3.0.x, 3.1.x) in JSON or YAML format into swagger-core representation as Java POJO. OpenAPI 3.2 compatibility is provided in this project through normalization of 3.2-specific fields before parsing.|

# 📑 Getting started

Expand Down Expand Up @@ -399,7 +399,7 @@ OpenAPI2SoapUI supports multiple OpenAPI specification versions with full featur
| **OpenAPI 3.0.2** | ✅ Fully Supported | All features |
| **OpenAPI 3.0.3** | ✅ Fully Supported | All features |
| **OpenAPI 3.1.0+** | ✅ Fully Supported | All features + JSON Schema 2020-12, nullable types |
| **OpenAPI 3.2.x** | ✅ Forward Compatible | Ready for future releases |
| **OpenAPI 3.2.x** | ✅ Fully Supported | Officially released and supported |

### Key Features by Version

Expand Down Expand Up @@ -432,7 +432,7 @@ For detailed information, see [OpenAPI Version Support Documentation](./OPENAPI_

The project includes comprehensive test coverage with **88 tests** covering:
- ✅ 7 feature options
- ✅ OpenAPI 3.0.x and 3.1.x support
- ✅ OpenAPI 3.0.x, 3.1.x, and 3.2.x support
- ✅ All HTTP methods
- ✅ Parameter handling (path, query, header)
- ✅ Complex schema handling
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

<springdoc.version>2.0.2</springdoc.version>
<soapui.version>5.6.0</soapui.version>
<swagger-parser.version>2.1.19</swagger-parser.version>
<swagger-parser.version>2.1.41</swagger-parser.version>
</properties>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,22 @@
parameter.setStyle(ParameterStyle.HEADER);
} else if (openAPIParameter.getIn().equalsIgnoreCase(PATH)) {
parameter.setStyle(ParameterStyle.TEMPLATE);
} else if (openAPIParameter.getIn().equalsIgnoreCase(QUERY)) {
} else if (openAPIParameter.getIn().equalsIgnoreCase(QUERY) || openAPIParameter.getIn().equalsIgnoreCase("querystring")) {
parameter.setStyle(ParameterStyle.QUERY);
}
}
}

/**
* Determine if an HTTP method should be skipped when readOnly option is enabled.
* @param httpMethod OpenAPI HTTP method
* @return true when method is considered write operation
*/
private boolean isWriteOperation(HttpMethod httpMethod) {
String method = httpMethod.name();
return method.equals("POST") || method.equals("PUT") || method.equals("PATCH") || method.equals("DELETE");
}

/**
* Get OpenAPI Parameter Example
* Validate if the parameter has the examples, example or x-example property and if so, it returns its value
Expand Down Expand Up @@ -357,19 +367,21 @@
* Skip write operations (POST/PUT/PATCH/DELETE) if readOnly mode is enabled
* @param operations list of path operations
*/
private void setResourceMethods(RestResource restResource, Map<HttpMethod, Operation> operations) {

Check failure on line 370 in src/main/java/org/apiaddicts/apitools/openapi2soapui/model/SoapUIProject.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=apiaddicts_openapi2soapui&issues=AZ3YqoVq9qFZCZi9b3PP&open=AZ3YqoVq9qFZCZi9b3PP&pullRequest=4
if (operations != null && !operations.isEmpty()) {
operations.forEach((httpMethod, operation) -> {
// Feature 1: readOnly
if (options.isReadOnly()) {
String method = httpMethod.name();
if (method.equals("POST") || method.equals("PUT") || method.equals("PATCH") || method.equals("DELETE")) {
return;
}
if (options.isReadOnly() && isWriteOperation(httpMethod)) {
return;
}

RestMethod restMethod = restResource.addNewMethod((operation.getOperationId() != null) ? operation.getOperationId() : httpMethod.name());
restMethod.setMethod(RestRequestInterface.HttpMethod.valueOf(httpMethod.name()));
try {
restMethod.setMethod(RestRequestInterface.HttpMethod.valueOf(httpMethod.name()));
} catch (IllegalArgumentException ex) {
log.warn("HTTP method {} is not supported by current SoapUI version and will be skipped", httpMethod.name());
return;
}
restMethod.setDescription((operation.getDescription() != null) ? operation.getDescription() : "");

if (operation.getRequestBody() != null) {
Expand Down Expand Up @@ -432,17 +444,14 @@
* @param pathName path name to find Resource
* @param pathItem instance of OpenAPI Path to iterate its Operations
*/
private void setMethodsRequests(String pathName, PathItem pathItem) {

Check failure on line 447 in src/main/java/org/apiaddicts/apitools/openapi2soapui/model/SoapUIProject.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 19 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=apiaddicts_openapi2soapui&issues=AZ3YqoVq9qFZCZi9b3PQ&open=AZ3YqoVq9qFZCZi9b3PQ&pullRequest=4
RestResource restResource = restService.getResourceByFullPath(restService.getBasePath() + pathName);

if (restResource != null) {
pathItem.readOperationsMap().forEach((httpMethod, operation) -> {
// Feature 1: readOnly
if (options.isReadOnly()) {
String method = httpMethod.name();
if (method.equals("POST") || method.equals("PUT") || method.equals("PATCH") || method.equals("DELETE")) {
return;
}
if (options.isReadOnly() && isWriteOperation(httpMethod)) {
return;
}

RestMethod restMethod = restResource.getRestMethodByName((operation.getOperationId() != null) ? operation.getOperationId() : httpMethod.name());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.apiaddicts.apitools.openapi2soapui.util;

import java.util.Base64;
import java.util.List;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;
import org.apiaddicts.apitools.openapi2soapui.error.exceptions.DecodeBase64Exception;
Expand All @@ -19,6 +21,8 @@
*/
@Slf4j
public class SerializedDataUtils {
private static final String QUERY = "query";
private static final String GET = "get";

private SerializedDataUtils() {
// Intentional blank
Expand Down Expand Up @@ -78,10 +82,11 @@
*/
public static OpenAPI parseOpenAPIContent(String openAPIContent) {
try {
String normalizedContent = normalizeOpenAPI32Content(openAPIContent);
ParseOptions parseOptions = new ParseOptions();
parseOptions.setResolve(true);
parseOptions.setResolveFully(true);
OpenAPI openAPI = new OpenAPIParser().readContents(openAPIContent, null, parseOptions).getOpenAPI();
OpenAPI openAPI = new OpenAPIParser().readContents(normalizedContent, null, parseOptions).getOpenAPI();
validateRequiredOpenAPIProperties(openAPI);
return openAPI;
} catch (Exception e) {
Expand All @@ -90,6 +95,92 @@
}
}

/**
* Normalize OpenAPI 3.2 content so it can be parsed by the current parser stack.
* This keeps the runtime compatible while parser-level 3.2 support evolves.
* @param openAPIContent OpenAPI content as string
* @return normalized content for parser consumption
*/
private static String normalizeOpenAPI32Content(String openAPIContent) {
try {
Yaml yaml = new Yaml();
Object parsed = yaml.load(openAPIContent);
if (!(parsed instanceof Map)) {
return openAPIContent;
}

Map<String, Object> root = (Map<String, Object>) parsed;
Object version = root.get("openapi");
if (!(version instanceof String) || !((String) version).startsWith("3.2")) {

Check warning on line 114 in src/main/java/org/apiaddicts/apitools/openapi2soapui/util/SerializedDataUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this instanceof check and cast with 'instanceof String string'

See more on https://sonarcloud.io/project/issues?id=apiaddicts_openapi2soapui&issues=AZ3YqoTx9qFZCZi9b3PN&open=AZ3YqoTx9qFZCZi9b3PN&pullRequest=4
return openAPIContent;
}

root.put("openapi", "3.1.0");
normalizeNode(root);
return yaml.dump(root);
} catch (Exception e) {
log.debug("OpenAPI 3.2 normalization skipped", e);
return openAPIContent;
}
}

/**
* Recursively normalize known OpenAPI 3.2-only fields to 3.1-compatible fields.
* @param node current structure node
*/
@SuppressWarnings("unchecked")
private static void normalizeNode(Object node) {
if (node instanceof Map) {
Map<String, Object> map = (Map<String, Object>) node;
normalizeParameterLocation(map);
normalizeTopLevel32Fields(map);
normalizeQueryOperation(map);
normalizeComponentsMediaTypes(map);
map.values().forEach(SerializedDataUtils::normalizeNode);
} else if (node instanceof List) {
((List<?>) node).forEach(SerializedDataUtils::normalizeNode);
}
}

private static void normalizeParameterLocation(Map<String, Object> map) {
Object inValue = map.get("in");
if (inValue instanceof String && "querystring".equalsIgnoreCase((String) inValue)) {

Check warning on line 147 in src/main/java/org/apiaddicts/apitools/openapi2soapui/util/SerializedDataUtils.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this instanceof check and cast with 'instanceof String string'

See more on https://sonarcloud.io/project/issues?id=apiaddicts_openapi2soapui&issues=AZ3YqoTx9qFZCZi9b3PO&open=AZ3YqoTx9qFZCZi9b3PO&pullRequest=4
map.put("in", QUERY);
}
}

private static void normalizeTopLevel32Fields(Map<String, Object> map) {
if (map.containsKey("$self")) {
map.put("x-oas32-self", map.remove("$self"));
}

if (map.containsKey("additionalOperations")) {
map.put("x-oas32-additionalOperations", map.remove("additionalOperations"));
}
}

private static void normalizeQueryOperation(Map<String, Object> map) {
if (map.containsKey(QUERY)) {
Object queryOp = map.remove(QUERY);
if (!map.containsKey(GET)) {
map.put(GET, queryOp);
} else {
map.put("x-oas32-query-operation", queryOp);
}
}
}

@SuppressWarnings("unchecked")
private static void normalizeComponentsMediaTypes(Map<String, Object> map) {
Object componentsObj = map.get("components");
if (componentsObj instanceof Map) {
Map<String, Object> components = (Map<String, Object>) componentsObj;
if (components.containsKey("mediaTypes")) {
components.put("x-oas32-mediaTypes", components.remove("mediaTypes"));
}
}
}

/**
* Validates the mandatory properties of an Open API Spec
* @param openAPI instance of OpenAPI
Expand Down
Loading