From 63baae78e62b52f56633df4fb70fee0257d55f31 Mon Sep 17 00:00:00 2001 From: Kyle Fuller Date: Tue, 23 Jun 2020 20:27:39 +0100 Subject: [PATCH] fix(oas3): support component aliases --- packages/openapi3-parser/CHANGELOG.md | 13 ++ .../lib/parser/oas/parseComponentsObject.js | 10 +- .../lib/parser/parseReference.js | 4 +- .../test/integration/components-test.js | 18 +- .../path-item-object-parameters-alias.json | 54 ++++++ .../path-item-object-parameters-alias.yaml | 15 ++ .../fixtures/components/schema-alias.json | 175 ++++++++++++++++++ .../fixtures/components/schema-alias.yaml | 23 +++ .../parser/oas/parseComponentsObject-test.js | 28 +++ 9 files changed, 331 insertions(+), 9 deletions(-) create mode 100644 packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.json create mode 100644 packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.yaml create mode 100644 packages/openapi3-parser/test/integration/fixtures/components/schema-alias.json create mode 100644 packages/openapi3-parser/test/integration/fixtures/components/schema-alias.yaml diff --git a/packages/openapi3-parser/CHANGELOG.md b/packages/openapi3-parser/CHANGELOG.md index 30d2c5eb7..3ab230b83 100644 --- a/packages/openapi3-parser/CHANGELOG.md +++ b/packages/openapi3-parser/CHANGELOG.md @@ -9,6 +9,19 @@ or any annotation. It is not supported in the case when one of is used in conjunction with other constraints in the same schema object. +### Bug Fixes + +- Supports using `$ref` in the root of a component, for example: + + ```yaml + components: + schemas: + UserAlias: + $ref: '#/components/schemas/User' + User: + type: object + ``` + ## 0.13.1 (2020-06-22) ### Bug Fixes diff --git a/packages/openapi3-parser/lib/parser/oas/parseComponentsObject.js b/packages/openapi3-parser/lib/parser/oas/parseComponentsObject.js index ab115a965..28098ec75 100644 --- a/packages/openapi3-parser/lib/parser/oas/parseComponentsObject.js +++ b/packages/openapi3-parser/lib/parser/oas/parseComponentsObject.js @@ -8,6 +8,7 @@ const { createInvalidMemberWarning, } = require('../annotations'); const parseObject = require('../parseObject'); +const parseReference = require('../parseReference'); const pipeParseResult = require('../../pipeParseResult'); const parseSchemaObject = require('./parseSchemaObject'); const parseParameterObject = require('./parseParameterObject'); @@ -130,12 +131,13 @@ function parseComponentsObject(context, element) { * @returns ParseResult * @private */ - const parseComponentObjectMember = (parser) => { + const parseComponentObjectMember = R.curry((parser, member) => { const parseMember = parseComponentMember(context, parser); + const parseMemberOrRef = m => parseReference(member.key.toValue(), () => parseMember(m), context, m.value, false, true); - return member => pipeParseResult(context.namespace, + return pipeParseResult(context.namespace, validateIsObject, - R.compose(parseObject(context, name, parseMember), getValue), + R.compose(parseObject(context, name, parseMemberOrRef), getValue), (object) => { const contextMember = context.state.components.getMember(member.key.toValue()); @@ -145,7 +147,7 @@ function parseComponentsObject(context, element) { return object; })(member); - }; + }); const setDataStructureId = (dataStructure, key) => { if (dataStructure) { diff --git a/packages/openapi3-parser/lib/parser/parseReference.js b/packages/openapi3-parser/lib/parser/parseReference.js index b694c064b..03a07674f 100644 --- a/packages/openapi3-parser/lib/parser/parseReference.js +++ b/packages/openapi3-parser/lib/parser/parseReference.js @@ -6,9 +6,9 @@ function isReferenceObject(element) { return isObject(element) && element.get('$ref') !== undefined; } -function parseReference(component, parser, context, element, isInsideSchema) { +function parseReference(component, parser, context, element, isInsideSchema, returnReferenceElement) { if (isReferenceObject(element)) { - const parseResult = parseReferenceObject(context, component, element, component === 'schemas'); + const parseResult = parseReferenceObject(context, component, element, component === 'schemas' || returnReferenceElement); // If we're referencing a schema object and we're not inside a schema // parser (subschema), then we want to wrap the object in a data structure element diff --git a/packages/openapi3-parser/test/integration/components-test.js b/packages/openapi3-parser/test/integration/components-test.js index 269ca4948..7c3c9f65f 100644 --- a/packages/openapi3-parser/test/integration/components-test.js +++ b/packages/openapi3-parser/test/integration/components-test.js @@ -19,6 +19,11 @@ describe('components', () => { const file = path.join(fixtures, 'path-item-object-parameters-unsupported-parameter'); return testParseFixture(file); }); + + it('handles parameter referencing with reference to alias', () => { + const file = path.join(fixtures, 'path-item-object-parameters-alias'); + return testParseFixture(file); + }); }); describe('Media Type Object', () => { @@ -72,9 +77,16 @@ describe('components', () => { }); }); - it("'Schema Object' circular references", () => { - const file = path.join(fixtures, 'schema-object-circular'); - return testParseFixture(file); + describe('Schema Object', () => { + it('handles circular references', () => { + const file = path.join(fixtures, 'schema-object-circular'); + return testParseFixture(file); + }); + + it('handles schema with reference to alias', () => { + const file = path.join(fixtures, 'schema-alias'); + return testParseFixture(file); + }); }); it("'Operation Object' requestBody references", () => { diff --git a/packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.json b/packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.json new file mode 100644 index 000000000..97906adc8 --- /dev/null +++ b/packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.json @@ -0,0 +1,54 @@ +{ + "element": "parseResult", + "content": [ + { + "element": "category", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "api" + } + ] + }, + "title": { + "element": "string", + "content": "Parameter Component with alias" + } + }, + "attributes": { + "version": { + "element": "string", + "content": "1.0.0" + } + }, + "content": [ + { + "element": "resource", + "attributes": { + "href": { + "element": "string", + "content": "/{?foo}" + }, + "hrefVariables": { + "element": "hrefVariables", + "content": [ + { + "element": "member", + "content": { + "key": { + "element": "string", + "content": "foo" + } + } + } + ] + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.yaml b/packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.yaml new file mode 100644 index 000000000..3222be95e --- /dev/null +++ b/packages/openapi3-parser/test/integration/fixtures/components/path-item-object-parameters-alias.yaml @@ -0,0 +1,15 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Parameter Component with alias +paths: + /: + parameters: + - $ref: '#/components/parameters/UserAlias' +components: + parameters: + User: + in: query + name: foo + UserAlias: + $ref: '#/components/parameters/User' diff --git a/packages/openapi3-parser/test/integration/fixtures/components/schema-alias.json b/packages/openapi3-parser/test/integration/fixtures/components/schema-alias.json new file mode 100644 index 000000000..b0f4ec45b --- /dev/null +++ b/packages/openapi3-parser/test/integration/fixtures/components/schema-alias.json @@ -0,0 +1,175 @@ +{ + "element": "parseResult", + "content": [ + { + "element": "category", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "api" + } + ] + }, + "title": { + "element": "string", + "content": "Schemas Component with alias" + } + }, + "attributes": { + "version": { + "element": "string", + "content": "1.0.0" + } + }, + "content": [ + { + "element": "resource", + "attributes": { + "href": { + "element": "string", + "content": "/" + } + }, + "content": [ + { + "element": "transition", + "content": [ + { + "element": "httpTransaction", + "content": [ + { + "element": "httpRequest", + "attributes": { + "method": { + "element": "string", + "content": "GET" + } + } + }, + { + "element": "httpResponse", + "attributes": { + "headers": { + "element": "httpHeaders", + "content": [ + { + "element": "member", + "content": { + "key": { + "element": "string", + "content": "Content-Type" + }, + "value": { + "element": "string", + "content": "application/json" + } + } + } + ] + }, + "statusCode": { + "element": "string", + "content": "200" + } + }, + "content": [ + { + "element": "asset", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "messageBody" + } + ] + } + }, + "attributes": { + "contentType": { + "element": "string", + "content": "application/json" + } + }, + "content": "{\"name\":\"\"}" + }, + { + "element": "dataStructure", + "content": { + "element": "UserAlias" + } + }, + { + "element": "copy", + "content": "" + } + ] + } + ] + } + ] + } + ] + }, + { + "element": "category", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "dataStructures" + } + ] + } + }, + "content": [ + { + "element": "dataStructure", + "content": { + "element": "object", + "meta": { + "id": { + "element": "string", + "content": "User" + } + }, + "content": [ + { + "element": "member", + "content": { + "key": { + "element": "string", + "content": "name" + }, + "value": { + "element": "string" + } + } + } + ] + } + }, + { + "element": "dataStructure", + "content": { + "element": "User", + "meta": { + "id": { + "element": "string", + "content": "UserAlias" + } + } + } + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/openapi3-parser/test/integration/fixtures/components/schema-alias.yaml b/packages/openapi3-parser/test/integration/fixtures/components/schema-alias.yaml new file mode 100644 index 000000000..31d37c216 --- /dev/null +++ b/packages/openapi3-parser/test/integration/fixtures/components/schema-alias.yaml @@ -0,0 +1,23 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Schemas Component with alias +paths: + /: + get: + responses: + '200': + description: '' + content: + 'application/json': + schema: + $ref: '#/components/schemas/UserAlias' +components: + schemas: + User: + type: object + properties: + name: + type: string + UserAlias: + $ref: '#/components/schemas/User' diff --git a/packages/openapi3-parser/test/unit/parser/oas/parseComponentsObject-test.js b/packages/openapi3-parser/test/unit/parser/oas/parseComponentsObject-test.js index cdcd8b462..9c2fbe611 100644 --- a/packages/openapi3-parser/test/unit/parser/oas/parseComponentsObject-test.js +++ b/packages/openapi3-parser/test/unit/parser/oas/parseComponentsObject-test.js @@ -91,6 +91,34 @@ describe('Components Object', () => { expect(userState).to.be.instanceof(namespace.elements.Member); expect(userState.value).to.be.undefined; }); + + it('parses valid schemas with alias into data structures', () => { + const components = new namespace.elements.Object({ + schemas: { + User: { + type: 'object', + }, + UserAlias: { $ref: '#/components/schemas/User' }, + }, + }); + + const parseResult = parse(context, components); + expect(parseResult.length).to.equal(1); + + const parsedComponents = parseResult.get(0); + expect(parsedComponents).to.be.instanceof(namespace.elements.Object); + + const schemas = parsedComponents.get('schemas'); + expect(schemas).to.be.instanceof(namespace.elements.Object); + + const userSchema = schemas.get('User'); + expect(userSchema).to.be.instanceof(namespace.elements.DataStructure); + expect(userSchema.content).to.be.instanceof(namespace.elements.Object); + + const userSchemaAlias = schemas.get('UserAlias'); + expect(userSchemaAlias).to.be.instanceof(namespace.elements.DataStructure); + expect(userSchemaAlias.content.element).to.equal('User'); + }); }); describe('#parameters', () => {