-
Notifications
You must be signed in to change notification settings - Fork 228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
asyncapi 1.0.0 support. #175
Changes from all commits
1a911b6
073417a
1c48fa8
9cbe59a
0c6de8a
48a9fde
61e241d
ceb3fc0
b5a6f83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,9 +18,16 @@ function createSpecification(definition) { | |
'parameters', | ||
'securityDefinitions', | ||
]; | ||
const v3 = [...v2, 'components']; | ||
let v3 = [...v2, 'components']; | ||
|
||
if (specification.openapi) { | ||
if (specification.asyncapi) { | ||
specification.asyncapi = specification.asyncapi; | ||
v3 = [...v3, 'topics', 'baseTopic', 'servers', 'security', 'externalDocs']; | ||
v3.forEach(property => { | ||
specification[property] = specification[property] || {}; | ||
}); | ||
v3.baseTopic = ''; | ||
} else if (specification.openapi) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd take that and refactor it after the PR |
||
specification.openapi = specification.openapi; | ||
v3.forEach(property => { | ||
specification[property] = specification[property] || {}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
const parser = require('swagger-parser'); | ||
const { isEmpty } = require('lodash'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if lodash only import that particular function into context. Rather use something like |
||
const hasEmptyProperty = require('./hasEmptyProperty'); | ||
|
||
/** | ||
|
@@ -26,6 +27,32 @@ function cleanUselessProperties(inputSpec) { | |
return improvedSpec; | ||
} | ||
|
||
/** | ||
* AsyncAPI specification validator does not accept empty values for a few properties. | ||
* Solves validator error: "Schema error should NOT have additional properties" | ||
* @function | ||
* @param {object} inputSpec - The asyncapi specification | ||
* @param {object} improvedSpec - The cleaned version of the inputSpec | ||
*/ | ||
function cleanUselessPropertiesAsyncApi(inputSpec) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As noted in the comments, do not eagerly share parser and then move things back and forth |
||
const improvedSpec = JSON.parse(JSON.stringify(inputSpec)); | ||
const toClean = [ | ||
'definitions', | ||
'responses', | ||
'parameters', | ||
'securityDefinitions', | ||
'paths', | ||
]; | ||
|
||
toClean.forEach(unnecessaryProp => { | ||
if (hasEmptyProperty(improvedSpec[unnecessaryProp])) { | ||
delete improvedSpec[unnecessaryProp]; | ||
} | ||
}); | ||
|
||
return improvedSpec; | ||
} | ||
|
||
/** | ||
* Parse the swagger object and remove useless properties if necessary. | ||
* | ||
|
@@ -44,6 +71,12 @@ function finalizeSpecificationObject(swaggerObject) { | |
if (specification.openapi) { | ||
specification = cleanUselessProperties(specification); | ||
} | ||
if (specification.asyncapi) { | ||
specification = cleanUselessPropertiesAsyncApi(specification); | ||
if (isEmpty(specification.baseTopic)) { | ||
delete specification.baseTopic; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do not delete in JS |
||
} | ||
} | ||
|
||
return specification; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,6 +66,8 @@ function getSwaggerSchemaWrongProperties() { | |
'scheme', | ||
'response', | ||
'parameter', | ||
'topic', | ||
'server', | ||
]; | ||
} | ||
|
||
|
@@ -111,21 +113,32 @@ function organizeSwaggerProperties(swaggerObject, pathObject, propertyName) { | |
'parameters', | ||
'definition', | ||
'definitions', | ||
'topic', | ||
'topics', | ||
'baseTopic', | ||
'server', | ||
'servers', | ||
'security', | ||
'externalDocs', | ||
]; | ||
|
||
// Common properties. | ||
if (simpleProperties.indexOf(propertyName) !== -1) { | ||
const keyName = correctSwaggerKey(propertyName); | ||
const definitionNames = Object.getOwnPropertyNames( | ||
pathObject[propertyName] | ||
); | ||
for (let k = 0; k < definitionNames.length; k += 1) { | ||
const definitionName = definitionNames[k]; | ||
swaggerObject[keyName][definitionName] = Object.assign( | ||
{}, | ||
swaggerObject[keyName][definitionName], | ||
pathObject[propertyName][definitionName] | ||
if (propertyName === 'baseTopic') { | ||
swaggerObject[propertyName] = pathObject[propertyName]; | ||
} else { | ||
const definitionNames = Object.getOwnPropertyNames( | ||
pathObject[propertyName] | ||
); | ||
for (let k = 0; k < definitionNames.length; k += 1) { | ||
const definitionName = definitionNames[k]; | ||
swaggerObject[keyName][definitionName] = Object.assign( | ||
{}, | ||
swaggerObject[keyName][definitionName], | ||
pathObject[propertyName][definitionName] | ||
); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems a bit a too central change and an eager refactoring, due to sharing the parser |
||
} | ||
// Tags. | ||
} else if (propertyName === 'tag' || propertyName === 'tags') { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# AsyncApi specification tests | ||
|
||
Taken from https://www.asyncapi.com/docs/specifications/1.0.0/#A2SObject |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Imaginary API helper | ||
module.exports = function(consumer) { | ||
/** | ||
* @swagger | ||
* | ||
* baseTopic: 'command.service.foo' | ||
* components: | ||
* schemas: | ||
* QoS: | ||
* type: object | ||
* properties: | ||
* id: | ||
* type: integer | ||
* required: true | ||
* example: 3 | ||
* messages: | ||
* ICommand: | ||
* summary: 'send foo command' | ||
* tags: | ||
* - name: 'company' | ||
* - name: 'settings' | ||
* headers: | ||
* type: object | ||
* properties: | ||
* QoS: | ||
* $ref: '#/components/schemas/QoS' | ||
* payload: | ||
* type: object | ||
* properties: | ||
* param: | ||
* type: string | ||
* required: true | ||
* example: FooBarCommandParam | ||
* ICommandResult: | ||
* summary: 'receive foo command result after publish' | ||
* tags: | ||
* - name: 'company' | ||
* - name: 'settings' | ||
* headers: | ||
* type: object | ||
* properties: | ||
* QoS: | ||
* $ref: '#/components/schemas/QoS' | ||
* payload: | ||
* type: object | ||
* properties: | ||
* data: | ||
* type: object | ||
* error: | ||
* type: string | ||
* example: FooBar Not found | ||
* | ||
* topic: | ||
* foo_result: | ||
* subscribe: | ||
* $ref: '#/components/schemas/ICommandResult' | ||
* foo: | ||
* publish: | ||
* $ref: '#/components/schemas/ICommand' | ||
*/ | ||
consumer.subscribe('command.service.foo', () => {}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
{ | ||
"asyncapi": "1.0.0", | ||
"info": { | ||
"version": "1.0.0", | ||
"title": "Sample specification testing async-api-with-examples" | ||
}, | ||
"components": { | ||
"schemas": { | ||
"QoS": { | ||
"type": "object", | ||
"properties": { | ||
"id": { | ||
"type": "integer", | ||
"required": true, | ||
"example": 3 | ||
} | ||
} | ||
} | ||
}, | ||
"messages": { | ||
"ICommand": { | ||
"summary": "send foo command", | ||
"tags": [ | ||
{ | ||
"name": "company" | ||
}, | ||
{ | ||
"name": "settings" | ||
} | ||
], | ||
"headers": { | ||
"type": "object", | ||
"properties": { | ||
"QoS": { | ||
"$ref": "#/components/schemas/QoS" | ||
} | ||
} | ||
}, | ||
"payload": { | ||
"type": "object", | ||
"properties": { | ||
"param": { | ||
"type": "string", | ||
"required": true, | ||
"example": "FooBarCommandParam" | ||
} | ||
} | ||
} | ||
}, | ||
"ICommandResult": { | ||
"summary": "receive foo command result after publish", | ||
"tags": [ | ||
{ | ||
"name": "company" | ||
}, | ||
{ | ||
"name": "settings" | ||
} | ||
], | ||
"headers": { | ||
"type": "object", | ||
"properties": { | ||
"QoS": { | ||
"$ref": "#/components/schemas/QoS" | ||
} | ||
} | ||
}, | ||
"payload": { | ||
"type": "object", | ||
"properties": { | ||
"data": { | ||
"type": "object" | ||
}, | ||
"error": { | ||
"type": "string", | ||
"example": "FooBar Not found" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
"topics": { | ||
"foo_result": { | ||
"subscribe": { | ||
"$ref": "#/components/schemas/ICommandResult" | ||
} | ||
}, | ||
"foo": { | ||
"publish": { | ||
"$ref": "#/components/schemas/ICommand" | ||
} | ||
} | ||
}, | ||
"baseTopic": "command.service.foo", | ||
"servers": {}, | ||
"security": {}, | ||
"externalDocs": {}, | ||
"tags": [] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* global it, before, beforeEach, describe */ | ||
|
||
const path = require('path'); | ||
const chai = require('chai'); | ||
|
||
const { expect } = chai; | ||
const chaiJestSnapshot = require('chai-jest-snapshot'); | ||
|
||
const swaggerJsdoc = require('../../../lib'); | ||
|
||
chai.use(chaiJestSnapshot); | ||
|
||
before(() => { | ||
chaiJestSnapshot.resetSnapshotRegistry(); | ||
}); | ||
|
||
beforeEach(function() { | ||
chaiJestSnapshot.configureUsingMochaContext(this); | ||
}); | ||
|
||
const tests = ['async-api-with-examples']; | ||
|
||
describe('AsyncApi 1.0.0 examples', () => { | ||
tests.forEach(test => { | ||
it(`Example: ${test}`, done => { | ||
const title = `Sample specification testing ${test}`; | ||
|
||
// eslint-disable-next-line | ||
const referenceSpecification = require(path.resolve( | ||
`${__dirname}/${test}/asyncapi.json` | ||
)); | ||
|
||
const definition = { | ||
asyncapi: '1.0.0', | ||
info: { | ||
version: '1.0.0', | ||
title, | ||
}, | ||
}; | ||
|
||
const options = { | ||
definition, | ||
apis: [`${__dirname}/${test}/api.js`], | ||
}; | ||
|
||
const specification = swaggerJsdoc(options); | ||
expect(specification).to.matchSnapshot(); | ||
expect(specification).to.eql(referenceSpecification); | ||
done(); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as noted in the comments, do not eagerly share the parser. Also this mutation is not very nice