Skip to content

feat: release for version 2.4.0 of the spec #501

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

Merged
merged 17 commits into from
Apr 27, 2022
Merged
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
43 changes: 43 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@
* [.messageTraits()](#module_@asyncapi/parser+Components+messageTraits) ⇒ <code>Object.&lt;string, MessageTrait&gt;</code>
* [.hasMessageTraits()](#module_@asyncapi/parser+Components+hasMessageTraits) ⇒ <code>boolean</code>
* [.messageTrait(name)](#module_@asyncapi/parser+Components+messageTrait) ⇒ <code>MessageTrait</code>
* [.serverVariables()](#module_@asyncapi/parser+Components+serverVariables) ⇒ <code>Object.&lt;string, ServerVariable&gt;</code>
* [.hasServerVariables()](#module_@asyncapi/parser+Components+hasServerVariables) ⇒ <code>boolean</code>
* [.serverVariable(name)](#module_@asyncapi/parser+Components+serverVariable) ⇒ <code>ServerVariable</code>
* [.hasExtensions()](#module_@asyncapi/parser+Components+hasExtensions) ⇒ <code>boolean</code>
* [.extensions()](#module_@asyncapi/parser+Components+extensions) ⇒ <code>Object.&lt;string, any&gt;</code>
* [.extensionKeys()](#module_@asyncapi/parser+Components+extensionKeys) ⇒ <code>Array.&lt;string&gt;</code>
Expand Down Expand Up @@ -184,6 +187,7 @@
* [.MessageTraitable](#module_@asyncapi/parser+MessageTraitable) ⇐ <code>Base</code>
* [.headers()](#module_@asyncapi/parser+MessageTraitable+headers) ⇒ <code>Schema</code>
* [.header(name)](#module_@asyncapi/parser+MessageTraitable+header) ⇒ <code>Schema</code>
* [.id()](#module_@asyncapi/parser+MessageTraitable+id) ⇒ <code>string</code>
* [.correlationId()](#module_@asyncapi/parser+MessageTraitable+correlationId) ⇒ <code>CorrelationId</code>
* [.schemaFormat()](#module_@asyncapi/parser+MessageTraitable+schemaFormat) ⇒ <code>string</code>
* [.contentType()](#module_@asyncapi/parser+MessageTraitable+contentType) ⇒ <code>string</code>
Expand Down Expand Up @@ -233,6 +237,7 @@
* [.extension(key)](#module_@asyncapi/parser+OAuthFlow+extension) ⇒ <code>any</code>
* [.hasExt(key)](#module_@asyncapi/parser+OAuthFlow+hasExt) ⇒ <code>boolean</code>
* [.ext(key)](#module_@asyncapi/parser+OAuthFlow+ext) ⇒ <code>any</code>
* [.OperationSecurityRequirement](#module_@asyncapi/parser+OperationSecurityRequirement) ⇐ <code>Base</code>
* [.OperationTrait](#module_@asyncapi/parser+OperationTrait) ⇐ <code>OperationTraitable</code>
* [.OperationTraitable](#module_@asyncapi/parser+OperationTraitable) ⇐ <code>Base</code>
* [.id()](#module_@asyncapi/parser+OperationTraitable+id) ⇒ <code>string</code>
Expand Down Expand Up @@ -265,6 +270,7 @@
* [.hasTraits()](#module_@asyncapi/parser+Operation+hasTraits) ⇒ <code>boolean</code>
* [.messages()](#module_@asyncapi/parser+Operation+messages) ⇒ <code>Array.&lt;Message&gt;</code>
* [.message()](#module_@asyncapi/parser+Operation+message) ⇒ <code>Message</code>
* [.security()](#module_@asyncapi/parser+Operation+security) ⇒ <code>Array.&lt;OperationSecurityRequirement&gt;</code>
* [.PublishOperation](#module_@asyncapi/parser+PublishOperation) ⇐ <code>Operation</code>
* [.isPublish()](#module_@asyncapi/parser+PublishOperation+isPublish) ⇒ <code>boolean</code>
* [.isSubscribe()](#module_@asyncapi/parser+PublishOperation+isSubscribe) ⇒ <code>boolean</code>
Expand Down Expand Up @@ -820,6 +826,9 @@ Implements functions to deal with a Components object.
* [.messageTraits()](#module_@asyncapi/parser+Components+messageTraits) ⇒ <code>Object.&lt;string, MessageTrait&gt;</code>
* [.hasMessageTraits()](#module_@asyncapi/parser+Components+hasMessageTraits) ⇒ <code>boolean</code>
* [.messageTrait(name)](#module_@asyncapi/parser+Components+messageTrait) ⇒ <code>MessageTrait</code>
* [.serverVariables()](#module_@asyncapi/parser+Components+serverVariables) ⇒ <code>Object.&lt;string, ServerVariable&gt;</code>
* [.hasServerVariables()](#module_@asyncapi/parser+Components+hasServerVariables) ⇒ <code>boolean</code>
* [.serverVariable(name)](#module_@asyncapi/parser+Components+serverVariable) ⇒ <code>ServerVariable</code>
* [.hasExtensions()](#module_@asyncapi/parser+Components+hasExtensions) ⇒ <code>boolean</code>
* [.extensions()](#module_@asyncapi/parser+Components+extensions) ⇒ <code>Object.&lt;string, any&gt;</code>
* [.extensionKeys()](#module_@asyncapi/parser+Components+extensionKeys) ⇒ <code>Array.&lt;string&gt;</code>
Expand Down Expand Up @@ -982,6 +991,23 @@ Implements functions to deal with a Components object.
| --- | --- | --- |
| name | <code>string</code> | Name of the message trait. |

<a name="module_@asyncapi/parser+Components+serverVariables"></a>

#### components.serverVariables() ⇒ <code>Object.&lt;string, ServerVariable&gt;</code>
**Kind**: instance method of [<code>Components</code>](#module_@asyncapi/parser+Components)
<a name="module_@asyncapi/parser+Components+hasServerVariables"></a>

#### components.hasServerVariables() ⇒ <code>boolean</code>
**Kind**: instance method of [<code>Components</code>](#module_@asyncapi/parser+Components)
<a name="module_@asyncapi/parser+Components+serverVariable"></a>

#### components.serverVariable(name) ⇒ <code>ServerVariable</code>
**Kind**: instance method of [<code>Components</code>](#module_@asyncapi/parser+Components)

| Param | Type | Description |
| --- | --- | --- |
| name | <code>string</code> | Name of the server variable. |

<a name="module_@asyncapi/parser+Components+hasExtensions"></a>

#### components.hasExtensions() ⇒ <code>boolean</code>
Expand Down Expand Up @@ -1552,6 +1578,7 @@ Implements functions to deal with a the common properties that Message and Messa
* [.MessageTraitable](#module_@asyncapi/parser+MessageTraitable) ⇐ <code>Base</code>
* [.headers()](#module_@asyncapi/parser+MessageTraitable+headers) ⇒ <code>Schema</code>
* [.header(name)](#module_@asyncapi/parser+MessageTraitable+header) ⇒ <code>Schema</code>
* [.id()](#module_@asyncapi/parser+MessageTraitable+id) ⇒ <code>string</code>
* [.correlationId()](#module_@asyncapi/parser+MessageTraitable+correlationId) ⇒ <code>CorrelationId</code>
* [.schemaFormat()](#module_@asyncapi/parser+MessageTraitable+schemaFormat) ⇒ <code>string</code>
* [.contentType()](#module_@asyncapi/parser+MessageTraitable+contentType) ⇒ <code>string</code>
Expand Down Expand Up @@ -1595,6 +1622,10 @@ Implements functions to deal with a the common properties that Message and Messa
| --- | --- | --- |
| name | <code>string</code> | Name of the header. |

<a name="module_@asyncapi/parser+MessageTraitable+id"></a>

#### messageTraitable.id() ⇒ <code>string</code>
**Kind**: instance method of [<code>MessageTraitable</code>](#module_@asyncapi/parser+MessageTraitable)
<a name="module_@asyncapi/parser+MessageTraitable+correlationId"></a>

#### messageTraitable.correlationId() ⇒ <code>CorrelationId</code>
Expand Down Expand Up @@ -1912,6 +1943,13 @@ Implements functions to deal with a OAuthFlow object.
| --- | --- | --- |
| key | <code>string</code> | Extension key. |

<a name="module_@asyncapi/parser+OperationSecurityRequirement"></a>

### @asyncapi/parser.OperationSecurityRequirement ⇐ <code>Base</code>
Implements functions to deal with a OperationSecurityRequirement object.

**Kind**: instance class of [<code>@asyncapi/parser</code>](#module_@asyncapi/parser)
**Extends**: <code>Base</code>
<a name="module_@asyncapi/parser+OperationTrait"></a>

### @asyncapi/parser.OperationTrait ⇐ <code>OperationTraitable</code>
Expand Down Expand Up @@ -2126,6 +2164,7 @@ Implements functions to deal with an Operation object.
* [.hasTraits()](#module_@asyncapi/parser+Operation+hasTraits) ⇒ <code>boolean</code>
* [.messages()](#module_@asyncapi/parser+Operation+messages) ⇒ <code>Array.&lt;Message&gt;</code>
* [.message()](#module_@asyncapi/parser+Operation+message) ⇒ <code>Message</code>
* [.security()](#module_@asyncapi/parser+Operation+security) ⇒ <code>Array.&lt;OperationSecurityRequirement&gt;</code>

<a name="module_@asyncapi/parser+Operation+hasMultipleMessages"></a>

Expand All @@ -2147,6 +2186,10 @@ Implements functions to deal with an Operation object.

#### operation.message() ⇒ <code>Message</code>
**Kind**: instance method of [<code>Operation</code>](#module_@asyncapi/parser+Operation)
<a name="module_@asyncapi/parser+Operation+security"></a>

#### operation.security() ⇒ <code>Array.&lt;OperationSecurityRequirement&gt;</code>
**Kind**: instance method of [<code>Operation</code>](#module_@asyncapi/parser+Operation)
<a name="module_@asyncapi/parser+PublishOperation"></a>

### @asyncapi/parser.PublishOperation ⇐ <code>Operation</code>
Expand Down
2 changes: 1 addition & 1 deletion dist/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/asyncapiSchemaFormatParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function getMimeTypes() {
'application/schema+json;version=draft-07',
'application/schema+yaml;version=draft-07',
];
['2.0.0', '2.1.0', '2.2.0', '2.3.0'].forEach(version => {
['2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0'].forEach(version => {
mimeTypes.push(
`application/vnd.aai.asyncapi;version=${version}`,
`application/vnd.aai.asyncapi+json;version=${version}`,
Expand Down
65 changes: 65 additions & 0 deletions lib/customValidators.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,70 @@ function validateOperationId(
return true;
}

/**
* Validates if messageIds are duplicated in the document
*
* @private
* @param {Object} parsedJSON parsed AsyncAPI document
* @param {String} asyncapiYAMLorJSON AsyncAPI document in string
* @param {String} initialFormat information of the document was originally JSON or YAML
* @returns {Boolean} true in case the document is valid, otherwise throws {@link ParserError}
*/
function validateMessageId(
parsedJSON,
asyncapiYAMLorJSON,
initialFormat,
operations
) {
const chnls = parsedJSON.channels;
if (!chnls) return true;
const chnlsMap = new Map(Object.entries(chnls));
//it is a map of paths, the one that is a duplicate and the one that is duplicated
const duplicatedMessages = new Map();
//is is a 2-dimensional array that holds information with messageId value and its path
const allMessages = [];

const addDuplicateToMap = (msg, channelName, opName, oneOf = '') => {
const messageId = msg.messageId;
if (!messageId) return;

const messagePath = `${tilde(channelName)}/${opName}/message${oneOf}/messageId`;
const isMessageIdDuplicated = allMessages.find(v => v[0] === messageId);
if (!isMessageIdDuplicated)
return allMessages.push([messageId, messagePath]);

//isMessageIdDuplicated always holds one record and it is an array of paths, the one that is a duplicate and the one that is duplicated
duplicatedMessages.set(messagePath, isMessageIdDuplicated[1]);
};

chnlsMap.forEach((chnlObj, chnlName) => {
operations.forEach((opName) => {
const op = chnlObj[String(opName)];
if (op && op.message) {
if (op.message.oneOf) op.message.oneOf.forEach((msg, index) => addDuplicateToMap(msg, chnlName, opName , `/oneOf/${index}`));
else addDuplicateToMap(op.message, chnlName, opName);
}
});
});

if (duplicatedMessages.size) {
throw new ParserError({
type: validationError,
title: 'messageId must be unique across all the messages.',
parsedJSON,
validationErrors: groupValidationErrors(
'channels',
'is a duplicate of',
duplicatedMessages,
asyncapiYAMLorJSON,
initialFormat
),
});
}

return true;
}

/**
* Validates if server security is declared properly and the name has a corresponding security schema definition in components with the same name
*
Expand Down Expand Up @@ -611,6 +675,7 @@ function getDuplicateTagNames(tags) {
module.exports = {
validateServerVariables,
validateOperationId,
validateMessageId,
validateServerSecurity,
validateChannels,
validateTags,
Expand Down
26 changes: 26 additions & 0 deletions lib/models/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const ChannelParameter = require('./channel-parameter');
const CorrelationId = require('./correlation-id');
const OperationTrait = require('./operation-trait');
const MessageTrait = require('./message-trait');
const ServerVariable = require('./server-variable');

const MixinSpecificationExtensions = require('../mixins/specification-extensions');

Expand Down Expand Up @@ -219,6 +220,31 @@ class Components extends Base {
messageTrait(name) {
return getMapValueOfType(this._json.messageTraits, name, MessageTrait);
}

/**
*
* @returns {Object<string, ServerVariable>}
*/
serverVariables() {
return createMapOfType(this._json.serverVariables, ServerVariable);
}

/**
*
* @returns {boolean}
*/
hasServerVariables() {
return !!this._json.serverVariables;
}

/**
*
* @param {string} name - Name of the server variable.
* @returns {ServerVariable}
*/
serverVariable(name) {
return getMapValueOfType(this._json.serverVariables, name, ServerVariable);
}
}

module.exports = mix(Components, MixinSpecificationExtensions);
7 changes: 7 additions & 0 deletions lib/models/message-traitable.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class MessageTraitable extends Base {
return getMapValueOfType(this._json.headers.properties, name, Schema);
}

/**
* @returns {string}
*/
id() {
return this._json.messageId;
}

/**
* @returns {CorrelationId}
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/models/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Message extends MessageTraitable {
* @returns {string}
*/
uid() {
return this.name() || this.ext('x-parser-message-name') || Buffer.from(JSON.stringify(this._json)).toString('base64');
return this.id() || this.name() || this.ext('x-parser-message-name') || Buffer.from(JSON.stringify(this._json)).toString('base64');
}

/**
Expand Down
13 changes: 13 additions & 0 deletions lib/models/operation-security-requirement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const Base = require('./base');

/**
* Implements functions to deal with a OperationSecurityRequirement object.
* @class
* @alias module:@asyncapi/parser#OperationSecurityRequirement
* @extends Base
* @returns {OperationSecurityRequirement}
*/
class OperationSecurityRequirement extends Base {
}

module.exports = OperationSecurityRequirement;
9 changes: 9 additions & 0 deletions lib/models/operation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const OperationTraitable = require('./operation-traitable');
const Message = require('./message');
const OperationTrait = require('./operation-trait');
const OperationSecurityRequirement = require('./operation-security-requirement');

/**
* Implements functions to deal with an Operation object.
Expand Down Expand Up @@ -54,6 +55,14 @@ class Operation extends OperationTraitable {
if (index > this._json.message.oneOf.length - 1) return null;
return new Message(this._json.message.oneOf[+index]);
}

/**
* @returns {OperationSecurityRequirement[]}
*/
security() {
if (!this._json.security) return null;
return this._json.security.map(sec => new OperationSecurityRequirement(sec));
}
}

module.exports = Operation;
3 changes: 2 additions & 1 deletion lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const asyncapi = require('@asyncapi/specs');
const $RefParser = require('@apidevtools/json-schema-ref-parser');
const mergePatch = require('tiny-merge-patch').apply;
const ParserError = require('./errors/parser-error');
const { validateChannels, validateTags, validateServerVariables, validateOperationId, validateServerSecurity } = require('./customValidators.js');
const { validateChannels, validateTags, validateServerVariables, validateOperationId, validateServerSecurity, validateMessageId } = require('./customValidators.js');
const { toJS, findRefs, getLocationOf, improveAjvErrors, getDefaultSchemaFormat } = require('./utils');
const AsyncAPIDocument = require('./models/asyncapi');

Expand Down Expand Up @@ -201,6 +201,7 @@ async function customDocumentOperations(parsedJSON, asyncapiYAMLorJSON, initialF
validateTags(parsedJSON, asyncapiYAMLorJSON, initialFormat);
validateChannels(parsedJSON, asyncapiYAMLorJSON, initialFormat);
validateOperationId(parsedJSON, asyncapiYAMLorJSON, initialFormat, OPERATIONS);
validateMessageId(parsedJSON, asyncapiYAMLorJSON, initialFormat, OPERATIONS);

await customComponentsMsgOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options);
await customChannelsOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options);
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@asyncapi/parser",
"version": "1.14.1",
"version": "1.15.0-2022-04-release.4",
"description": "JavaScript AsyncAPI parser.",
"main": "lib/index.js",
"types": "types.d.ts",
Expand Down Expand Up @@ -67,7 +67,7 @@
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
"@asyncapi/specs": "^2.13.0",
"@asyncapi/specs": "^2.14.0",
"@fmvilas/pseudo-yaml-ast": "^0.3.1",
"ajv": "^6.10.1",
"js-yaml": "^3.13.1",
Expand Down
Loading