diff --git a/lib/index.js b/lib/index.js index b7b62bb8..b8c91803 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,9 +11,10 @@ const util = require("./util"); const Options = require("./options"); const maybe = require("call-me-maybe"); +const supported32Versions = ["3.2.0"]; const supported31Versions = ["3.1.0", "3.1.1", "3.1.2"]; const supported30Versions = ["3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4"]; -const supportedVersions = [...supported31Versions, ...supported30Versions]; +const supportedVersions = [...supported32Versions, ...supported31Versions, ...supported30Versions]; /** * This class parses a Swagger 2.0 or 3.0 API, resolves its JSON references and their resolved values, @@ -53,7 +54,7 @@ class SwaggerParser extends $RefParser { } } else { if (schema.paths === undefined) { - if (supported31Versions.indexOf(schema.openapi) !== -1) { + if (supported32Versions.indexOf(schema.openapi) !== -1 || supported31Versions.indexOf(schema.openapi) !== -1) { if (schema.webhooks === undefined) { throw new SyntaxError(`${args.path || args.schema} is not a valid Openapi API definition`); } diff --git a/lib/validators/schema.js b/lib/validators/schema.js index 5b3ad7cc..f4c4683e 100644 --- a/lib/validators/schema.js +++ b/lib/validators/schema.js @@ -21,8 +21,12 @@ function validateSchema(api) { schema = openapi.v2; ajv = initializeAjv(); } else { - if (api.openapi.startsWith("3.1")) { - schema = openapi.v31; + if (api.openapi.startsWith("3.1") || api.openapi.startsWith("3.2")) { + schema = structuredClone(openapi.v31); + + if (api.openapi.startsWith("3.2")) { + applyOpenApi32Compat(schema); + } // There's a bug with Ajv in how it handles `$dynamicRef` in the way that it's used within the 3.1 schema so we // need to do some adhoc workarounds. @@ -34,6 +38,9 @@ function validateSchema(api) { schema.$defs.components.properties.schemas.additionalProperties = schemaDynamicRef; schema.$defs.header.dependentSchemas.schema.properties.schema = schemaDynamicRef; schema.$defs["media-type"].properties.schema = schemaDynamicRef; + if (schema.$defs["media-type"].properties.itemSchema) { + schema.$defs["media-type"].properties.itemSchema = schemaDynamicRef; + } schema.$defs.parameter.properties.schema = schemaDynamicRef; ajv = initializeAjv(false); @@ -54,6 +61,94 @@ function validateSchema(api) { } } +/** + * Applies a targeted compatibility layer so the bundled OpenAPI 3.1 schema can validate + * the most important OpenAPI 3.2 additions until @apidevtools/openapi-schemas ships v3.2. + * + * @param {object} schema + */ +function applyOpenApi32Compat(schema) { + schema.properties.openapi.pattern = "^3\\.2\\.\\d+(-.+)?$"; + + schema.$defs.components.properties.mediaTypes = { + type: "object", + additionalProperties: { + $ref: "#/$defs/media-type", + }, + }; + + schema.$defs["path-item"].patternProperties["^query$"] = { + $ref: "#/$defs/operation", + }; + schema.$defs["path-item"].properties.additionalOperations = { + type: "object", + additionalProperties: { + $ref: "#/$defs/operation", + }, + }; + + schema.$defs.response.properties.summary = { + type: "string", + }; + + schema.$defs["media-type"].properties.itemSchema = { + $dynamicRef: "#meta", + }; + schema.$defs["media-type"].properties.prefixEncoding = { + type: "array", + items: { + $ref: "#/$defs/encoding", + }, + }; + schema.$defs["media-type"].properties.itemEncoding = { + $ref: "#/$defs/encoding", + }; + + schema.$defs.tag.properties.summary = { + type: "string", + }; + schema.$defs.tag.properties.parent = { + type: "string", + }; + schema.$defs.tag.properties.kind = { + type: "string", + }; + + schema.$defs.parameter.properties.in.enum.push("querystring"); + + schema.$defs["security-scheme"].properties.oauth2MetadataUrl = { + $ref: "#/$defs/uri", + }; + schema.$defs["security-scheme"].properties.deprecated = { + default: false, + type: "boolean", + }; + + schema.$defs["oauth-flows"].properties.deviceAuthorization = { + $ref: "#/$defs/oauth-flows/$defs/device-authorization", + }; + schema.$defs["oauth-flows"].$defs["device-authorization"] = { + type: "object", + properties: { + deviceAuthorizationUrl: { + type: "string", + }, + tokenUrl: { + type: "string", + }, + refreshUrl: { + type: "string", + }, + scopes: { + $ref: "#/$defs/map-of-strings", + }, + }, + required: ["deviceAuthorizationUrl", "tokenUrl", "scopes"], + $ref: "#/$defs/specification-extensions", + unevaluatedProperties: false, + }; +} + /** * Determines which version of Ajv to load and prepares it for use. * diff --git a/test/specs/invalid/invalid.spec.js b/test/specs/invalid/invalid.spec.js index e973c034..e0d8d10a 100644 --- a/test/specs/invalid/invalid.spec.js +++ b/test/specs/invalid/invalid.spec.js @@ -65,4 +65,18 @@ describe("Invalid APIs (can't be parsed)", () => { expect(err.message).to.equal('API version number must be a string (e.g. "1.0.0") not a number.'); } }); + + it("supports OpenAPI 3.2 parsing", async () => { + const api = await SwaggerParser.parse({ + openapi: "3.2.0", + info: { + title: "Test API", + version: "1.0.0", + }, + paths: {}, + }); + + expect(api).to.be.an("object"); + expect(api.openapi).to.equal("3.2.0"); + }); }); diff --git a/test/specs/validate-schema/valid/openapi-3.2.yaml b/test/specs/validate-schema/valid/openapi-3.2.yaml new file mode 100644 index 00000000..b94def38 --- /dev/null +++ b/test/specs/validate-schema/valid/openapi-3.2.yaml @@ -0,0 +1,38 @@ +openapi: "3.2.0" +info: + title: OpenAPI 3.2 test + version: "1.0.0" +tags: + - name: products + summary: Products + kind: nav +components: + mediaTypes: + EventStream: + itemSchema: + type: string + securitySchemes: + oauth: + type: oauth2 + oauth2MetadataUrl: https://example.com/.well-known/oauth-authorization-server + flows: + deviceAuthorization: + deviceAuthorizationUrl: https://example.com/oauth/device + tokenUrl: https://example.com/oauth/token + scopes: {} +paths: + /events: + query: + responses: + "200": + description: ok + summary: success + content: + application/jsonl: + itemSchema: + type: string + additionalOperations: + COPY: + responses: + "200": + description: copied diff --git a/test/specs/validate-schema/validate-schema.spec.js b/test/specs/validate-schema/validate-schema.spec.js index ce79c19b..5dd707ef 100644 --- a/test/specs/validate-schema/validate-schema.spec.js +++ b/test/specs/validate-schema/validate-schema.spec.js @@ -93,6 +93,11 @@ describe("Invalid APIs (Swagger 2.0 schema validation)", () => { valid: true, file: "allof.yaml", }, + { + name: "OpenAPI 3.2 document", + valid: true, + file: "openapi-3.2.yaml", + }, { name: 'Schema with "anyOf"', valid: false,