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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ SwaggerParseResult result = new OpenAPIParser().readContents("./path/to/swagger.

the Swagger/OpenAPI 2.0 document will be first converted into a comparable OpenAPI 3.0 one.

You can also directly use `OpenAPIV3Parser` which only handles OpenAPI 3.0 documents, and provides a convenience method to get directly the parsed `OpenAPI object:
You can also directly use `OpenAPIV3Parser` which handles OpenAPI 3.x documents, and provides a convenience method to get directly the parsed `OpenAPI` object.

Note: OpenAPI 3.2 documents are currently parsed through the OpenAPI 3.1 model. Elements introduced in 3.2 (e.g. the `query` operation, `additionalOperations`, `$self`, `in: querystring` parameters) are reported as validation messages but are not reproduced in the parsed model, so resolved output can differ from what a fully 3.2-aware parser would produce.

```java
import io.swagger.v3.parser.OpenAPIV3Parser;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ private SwaggerParseResult resolve(SwaggerParseResult result, List<Authorization
try {
if (options != null) {
if (options.isResolve() || options.isResolveFully()) {
if (result.getOpenAPI().getOpenapi() != null && result.getOpenAPI().getOpenapi().startsWith("3.1")) {
if (result.getOpenAPI().getOpenapi() != null && (result.getOpenAPI().getOpenapi().startsWith("3.1") || result.getOpenAPI().getOpenapi().startsWith("3.2"))) {
if (StringUtils.isBlank(System.getenv(DISABLE_OAS31_RESOLVE))) {
DereferencerContext dereferencerContext = new DereferencerContext(
result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String par
}

public ResolverCache(OpenAPI openApi, List<AuthorizationValue> auths, String parentFileLocation, Set<String> resolveValidationMessages, ParseOptions parseOptions) {
this.openapi31 = openApi != null && openApi.getOpenapi() != null && openApi.getOpenapi().startsWith("3.1");
this.openapi31 = openApi != null && openApi.getOpenapi() != null && (openApi.getOpenapi().startsWith("3.1") || openApi.getOpenapi().startsWith("3.2"));
this.openApi = openApi;
this.auths = auths;
this.rootPath = parentFileLocation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
public class OpenAPIDereferencer31 implements OpenAPIDereferencer {

public boolean canDereference(DereferencerContext context) {
if (context.openApi != null && context.openApi.getOpenapi().startsWith("3.1")) {
if (context.openApi != null && context.openApi.getOpenapi() != null && (context.openApi.getOpenapi().startsWith("3.1") || context.openApi.getOpenapi().startsWith("3.2"))) {
return true;
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,13 @@ public OpenAPI parseRoot(JsonNode node, ParseResult result, String path) {
String value = getString("openapi", rootNode, true, location, result);

// we don't even try if the version isn't there
if (value == null || (!value.startsWith("3.0") && !value.startsWith("3.1"))) {
if (value == null || (!value.startsWith("3.0") && !value.startsWith("3.1") && !value.startsWith("3.2"))) {
return null;
} else if (value.startsWith("3.1")) {
} else if (value.startsWith("3.1") || value.startsWith("3.2")) {
result.openapi31(true);
openAPI.setSpecVersion(SpecVersion.V31);
}
if (!value.startsWith("3.0.") && !value.startsWith("3.1.")){
if (!value.startsWith("3.0.") && !value.startsWith("3.1.") && !value.startsWith("3.2.")){
result.warning(location, "The provided definition does not specify a valid version field");
}
openAPI.setOpenapi(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package io.swagger.v3.parser.test;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

public class OAI32DeserializationTest {

@Test(description = "OpenAPI 3.2.0 version string is recognized and parsed")
public void testBasicOAS320() {
String yaml = "openapi: 3.2.0\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths:\n" +
" /pets:\n" +
" get:\n" +
" responses:\n" +
" '200':\n" +
" description: ok\n";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, null);
assertNotNull(result.getOpenAPI());
assertEquals(result.getOpenAPI().getOpenapi(), "3.2.0");
assertNotNull(result.getOpenAPI().getPaths().get("/pets").getGet());
// 3.2 shares the 3.1 data model code path
assertTrue(result.isOpenapi31());
}

@Test(description = "OpenAPI 3.2.1 version string is recognized and parsed")
public void testBasicOAS321() {
String json = "{\n" +
" \"openapi\": \"3.2.1\",\n" +
" \"info\": {\n" +
" \"title\": \"Swagger Petstore\",\n" +
" \"version\": \"1.0.0\"\n" +
" },\n" +
" \"paths\": {}\n" +
"}";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(json, null, null);
assertNotNull(result.getOpenAPI());
assertEquals(result.getOpenAPI().getOpenapi(), "3.2.1");
assertTrue(result.isOpenapi31());
}

@Test(description = "3.2-specific fields do not crash parsing and are reported as unexpected attributes")
public void testOAS32SpecificFieldsRecorded() {
String yaml = "openapi: 3.2.0\n" +
"$self: https://example.com/api/openapi.yaml\n" +
"jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths:\n" +
" /pets:\n" +
" get:\n" +
" responses:\n" +
" '200':\n" +
" description: ok\n" +
" query:\n" +
" responses:\n" +
" '200':\n" +
" description: ok\n";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, null);
OpenAPI openAPI = result.getOpenAPI();
assertNotNull(openAPI);
assertNotNull(openAPI.getPaths().get("/pets").getGet());
// 3.2-only members are not modeled yet: they surface as validation messages
assertTrue(result.getMessages().contains("attribute $self is unexpected"));
assertTrue(result.getMessages().contains("attribute paths.'/pets'.query is unexpected"));
// 3.1-family fields continue to work
assertEquals(openAPI.getJsonSchemaDialect(), "https://json-schema.org/draft/2020-12/schema");
}

@Test(description = "3.2 document parses with resolveFully option without crashing")
public void testOAS32ResolveFully() {
String yaml = "openapi: 3.2.0\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths:\n" +
" /pets:\n" +
" get:\n" +
" responses:\n" +
" '200':\n" +
" description: ok\n" +
" content:\n" +
" application/json:\n" +
" schema:\n" +
" $ref: '#/components/schemas/Pet'\n" +
"components:\n" +
" schemas:\n" +
" Pet:\n" +
" type: object\n" +
" properties:\n" +
" name:\n" +
" type: string\n";
ParseOptions options = new ParseOptions();
options.setResolveFully(true);
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options);
OpenAPI openAPI = result.getOpenAPI();
assertNotNull(openAPI);
assertEquals(openAPI.getOpenapi(), "3.2.0");
// the $ref is actually resolved through the 3.1-family dereference path
Schema schema = openAPI.getPaths().get("/pets").getGet().getResponses().get("200")
.getContent().get("application/json").getSchema();
assertNull(schema.get$ref());
assertNotNull(schema.getProperties().get("name"));
}

@Test(description = "3.2 resolve routes through the 3.1-family dereferencer: components.pathItems refs resolve")
public void testOAS32ResolveRoutesTo31Dereferencer() {
// components.pathItems is a 3.1+ feature the legacy (3.0) resolver cannot resolve:
// ResolverCache has no pattern for #/components/pathItems, so under the wrong
// routing this assertion fails (the pathItem stays an unresolved $ref with no ops)
String yaml = "openapi: 3.2.0\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths:\n" +
" /pets:\n" +
" $ref: '#/components/pathItems/PetOps'\n" +
"components:\n" +
" pathItems:\n" +
" PetOps:\n" +
" get:\n" +
" operationId: listPets\n" +
" responses:\n" +
" '200':\n" +
" description: ok\n";
ParseOptions options = new ParseOptions();
options.setResolveFully(true);
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, options);
OpenAPI openAPI = result.getOpenAPI();
assertNotNull(openAPI);
assertNotNull(openAPI.getPaths().get("/pets").getGet());
assertEquals(openAPI.getPaths().get("/pets").getGet().getOperationId(), "listPets");
}

@Test(description = "3.2 'in: querystring' parameter is dropped with a validation message (current best-effort)")
public void testOAS32QuerystringParamReported() {
String yaml = "openapi: 3.2.0\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths:\n" +
" /pets:\n" +
" get:\n" +
" parameters:\n" +
" - name: q\n" +
" in: querystring\n" +
" responses:\n" +
" '200':\n" +
" description: ok\n";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, null);
OpenAPI openAPI = result.getOpenAPI();
assertNotNull(openAPI);
// the parameter is not representable yet: it is dropped and reported
assertTrue(openAPI.getPaths().get("/pets").getGet().getParameters() == null
|| openAPI.getPaths().get("/pets").getGet().getParameters().isEmpty());
assertTrue(result.getMessages().stream()
.anyMatch(m -> m.contains("in is not of type `[query|header|path|cookie]`")));
}

@Test(description = "bare 3.2 (no patch version) is accepted per upstream's loose convention, with a warning")
public void testBareVersionString() {
// mirrors upstream's handling of bare "3.0"/"3.1" (see OpenAPIV3ParserTest#testIssue1780):
// accepted and routed to the 3.1-family model, but reported as not a valid version field
String yaml = "openapi: '3.2'\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths: {}\n";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, null);
assertNotNull(result.getOpenAPI());
assertEquals(result.getOpenAPI().getOpenapi(), "3.2");
assertTrue(result.isOpenapi31());
assertTrue(result.getMessages().contains("The provided definition does not specify a valid version field"));
}

@Test(description = "loose prefix: 3.20.x is accepted because it starts with \"3.2\" (prefix match, not true 3.x support -- 3.3.0 is rejected), and warned since it has no 3.2. dotted form")
public void testVersionPrefixBoundary() {
String yaml = "openapi: 3.20.0\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths: {}\n";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, null);
assertNotNull(result.getOpenAPI());
assertTrue(result.isOpenapi31());
assertTrue(result.getMessages().contains("The provided definition does not specify a valid version field"));
}

@Test(description = "malformed version 3.2. (trailing dot, no patch) still parses; no warning fires, matching the pre-existing 3.0./3.1. quirk this change extends rather than fixes")
public void testMalformedVersionString() {
// "3.2." satisfies startsWith("3.2."), so the dotted-form warning check does not
// fire for it, the same way "3.0." and "3.1." alone would not warn either. This is
// an inherited quirk of the pre-existing convention, not something new to 3.2 --
// asserted explicitly here so a future change to the 3.0/3.1 behavior doesn't
// silently leave 3.2 inconsistent with it.
String yaml = "openapi: '3.2.'\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths: {}\n";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, null);
assertNotNull(result.getOpenAPI());
assertEquals(result.getOpenAPI().getOpenapi(), "3.2.");
assertTrue(result.isOpenapi31());
assertFalse(result.getMessages().contains("The provided definition does not specify a valid version field"));
}

@Test(description = "3.3.0 is genuinely rejected (unlike 3.20.0, which is only accepted by prefix-match accident)")
public void testUnrecognizedMinorVersionRejected() {
String yaml = "openapi: 3.3.0\n" +
"info:\n" +
" title: Swagger Petstore\n" +
" version: 1.0.0\n" +
"paths: {}\n";
SwaggerParseResult result = new OpenAPIV3Parser().readContents(yaml, null, null);
assertNull(result.getOpenAPI());
}
}