Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into add_types
Browse files Browse the repository at this point in the history
jonaslagoni authored Jul 27, 2020
2 parents bae466a + 69ffef8 commit 4aaa5aa
Showing 14 changed files with 707 additions and 77 deletions.
94 changes: 90 additions & 4 deletions API.md
Original file line number Diff line number Diff line change
@@ -102,15 +102,30 @@
<dt><a href="#addNameToKey">addNameToKey(map)</a></dt>
<dd><p>Add anonymous name to key if no name provided.</p>
</dd>
<dt><a href="#recursiveSchema">recursiveSchema(schema, callback(schema))</a></dt>
<dt><a href="#isCircular">isCircular(schema, seenObjects)</a></dt>
<dd><p>Function that indicates that a circular reference was detected.</p>
</dd>
<dt><a href="#markCircular">markCircular(schema)</a></dt>
<dd><p>Mark schema as being a circular ref</p>
</dd>
<dt><a href="#recursiveSchema">recursiveSchema(schemaContent, callback(schema))</a></dt>
<dd><p>Recursively go through each schema and execute callback.</p>
</dd>
<dt><a href="#crawl">crawl(schemaContent, seenObj, callback(schema))</a></dt>
<dd><p>Schema crawler</p>
</dd>
<dt><a href="#schemaDocument">schemaDocument(doc, callback(schema))</a></dt>
<dd><p>Go through each channel and for each parameter, and message payload and headers recursively call the callback for each schema.</p>
</dd>
<dt><a href="#assignIdToAnonymousSchemas">assignIdToAnonymousSchemas(doc)</a></dt>
<dd><p>Gives schemas id to all anonymous schemas.</p>
</dd>
<dt><a href="#recursiveSchemaObject">recursiveSchemaObject(schema, seenObj, callback(schema))</a></dt>
<dd><p>Recursively go through schema of object type and execute callback.</p>
</dd>
<dt><a href="#recursiveSchemaArray">recursiveSchemaArray(schema, seenObj, callback(schema))</a></dt>
<dd><p>Recursively go through schema of array type and execute callback.</p>
</dd>
</dl>

<a name="module_Parser"></a>
@@ -137,7 +152,6 @@ Parses and validate an AsyncAPI document from YAML or JSON.
| [options.path] | <code>String</code> | | Path to the AsyncAPI document. It will be used to resolve relative references. Defaults to current working dir. |
| [options.parse] | <code>Object</code> | | Options object to pass to [json-schema-ref-parser](https://apidevtools.org/json-schema-ref-parser/docs/options.html). |
| [options.resolve] | <code>Object</code> | | Options object to pass to [json-schema-ref-parser](https://apidevtools.org/json-schema-ref-parser/docs/options.html). |
| [options.dereference] | <code>Object</code> | | Options object to pass to [json-schema-ref-parser](https://apidevtools.org/json-schema-ref-parser/docs/options.html). |
| [options.applyTraits] | <code>Object</code> | <code>true</code> | Whether to resolve and apply traits or not. |

<a name="module_Parser+parseFromUrl"></a>
@@ -246,6 +260,7 @@ Implements functions to deal with the AsyncAPI document.
* [.hasMessages()](#AsyncAPIDocument+hasMessages) ⇒ <code>boolean</code>
* [.allMessages()](#AsyncAPIDocument+allMessages)[<code>Map.&lt;Message&gt;</code>](#Message)
* [.allSchemas()](#AsyncAPIDocument+allSchemas)[<code>Map.&lt;Schema&gt;</code>](#Schema)
* [.hasCircular()](#AsyncAPIDocument+hasCircular) ⇒ <code>boolean</code>
* [.json()](#Base+json) ⇒ <code>Any</code>

<a name="AsyncAPIDocument+version"></a>
@@ -330,6 +345,10 @@ Implements functions to deal with the AsyncAPI document.

### asyncAPIDocument.allSchemas() ⇒ [<code>Map.&lt;Schema&gt;</code>](#Schema)
**Kind**: instance method of [<code>AsyncAPIDocument</code>](#AsyncAPIDocument)
<a name="AsyncAPIDocument+hasCircular"></a>

### asyncAPIDocument.hasCircular() ⇒ <code>boolean</code>
**Kind**: instance method of [<code>AsyncAPIDocument</code>](#AsyncAPIDocument)
<a name="Base+json"></a>

### asyncAPIDocument.json() ⇒ <code>Any</code>
@@ -1286,6 +1305,7 @@ Implements functions to deal with a Schema object.
* [.readOnly()](#Schema+readOnly) ⇒ <code>boolean</code>
* [.writeOnly()](#Schema+writeOnly) ⇒ <code>boolean</code>
* [.examples()](#Schema+examples) ⇒ <code>Array.&lt;any&gt;</code>
* [.isCircular()](#Schema+isCircular) ⇒ <code>boolean</code>
* [.json()](#Base+json) ⇒ <code>Any</code>

<a name="Schema+uid"></a>
@@ -1476,6 +1496,10 @@ Implements functions to deal with a Schema object.

### schema.examples() ⇒ <code>Array.&lt;any&gt;</code>
**Kind**: instance method of [<code>Schema</code>](#Schema)
<a name="Schema+isCircular"></a>

### schema.isCircular() ⇒ <code>boolean</code>
**Kind**: instance method of [<code>Schema</code>](#Schema)
<a name="Base+json"></a>

### schema.json() ⇒ <code>Any</code>
@@ -1840,16 +1864,52 @@ Add anonymous name to key if no name provided.
| --- | --- | --- |
| map | <code>messages</code> | of messages |

<a name="isCircular"></a>

## isCircular(schema, seenObjects)
Function that indicates that a circular reference was detected.

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| schema | [<code>Schema</code>](#Schema) | schema that is currently accessed and need to be checked if it is a first time |
| seenObjects | <code>Array</code> | list of objects that were already seen during recursion |

<a name="markCircular"></a>

## markCircular(schema)
Mark schema as being a circular ref

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| schema | [<code>Schema</code>](#Schema) | schema that should be marked as circular |

<a name="recursiveSchema"></a>

## recursiveSchema(schema, callback(schema))
## recursiveSchema(schemaContent, callback(schema))
Recursively go through each schema and execute callback.

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| schema | [<code>Schema</code>](#Schema) | found. |
| schemaContent | [<code>Schema</code>](#Schema) | schema. |
| callback(schema) | <code>function</code> | the function that is called foreach schema found. schema {Schema}: the found schema. |

<a name="crawl"></a>

## crawl(schemaContent, seenObj, callback(schema))
Schema crawler

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| schemaContent | [<code>Schema</code>](#Schema) | schema. |
| seenObj | <code>Array</code> | schema elements that crowler went through already. |
| callback(schema) | <code>function</code> | the function that is called foreach schema found. schema {Schema}: the found schema. |

<a name="schemaDocument"></a>
@@ -1875,3 +1935,29 @@ Gives schemas id to all anonymous schemas.
| --- | --- |
| doc | [<code>AsyncAPIDocument</code>](#AsyncAPIDocument) |

<a name="recursiveSchemaObject"></a>

## recursiveSchemaObject(schema, seenObj, callback(schema))
Recursively go through schema of object type and execute callback.

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| schema | [<code>Schema</code>](#Schema) | Object type. |
| seenObj | <code>Array</code> | schema elements that crawler went through already. |
| callback(schema) | <code>function</code> | the function that is called foreach schema found. schema {Schema}: the found schema. |

<a name="recursiveSchemaArray"></a>

## recursiveSchemaArray(schema, seenObj, callback(schema))
Recursively go through schema of array type and execute callback.

**Kind**: global function

| Param | Type | Description |
| --- | --- | --- |
| schema | [<code>Schema</code>](#Schema) | Array type. |
| seenObj | <code>Array</code> | schema elements that crowler went through already. |
| callback(schema) | <code>function</code> | the function that is called foreach schema found. schema {Schema}: the found schema. |

6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -130,6 +130,12 @@ This package throws a bunch of different error types. All errors contain a `type
For more information about the `ParserError` class, [check out the documentation](./API.md#new_ParserError_new).
### Circular references
Parser dereferences all circular references by default. In addition, to simplify interactions with the parser, the following is added:
- `x-parser-circular` property is added to the root of the AsyncAPI document to indicate that the document contains circular references. Tooling developer that doesn't want to support circular references can use the `hasCircular` method to check the document and provide a proper message to the user.
- `x-parser-circular` property is added to every schema where circular reference starts. You should use `isCircular` method on a Schema model like `document.components().schema('RecursiveSelf').properties()['selfChildren'].isCircular()`.

### Develop

1. Run tests with `npm test`
102 changes: 101 additions & 1 deletion lib/customValidators.js
Original file line number Diff line number Diff line change
@@ -135,8 +135,108 @@ function validateOperationId(parsedJSON, asyncapiYAMLorJSON, initialFormat, oper
return true;
}

/**
* Validates if server security is declared properly and the name has a corresponding security schema definition in components with the same name
*
* @param {Object} parsedJSON parsed AsyncAPI document
* @param {String} asyncapiYAMLorJSON AsyncAPI document in string
* @param {String} initialFormat information of the document was oryginally JSON or YAML
* @param {Array[String]} specialSecTypes list of security types that can have data in array
* @returns {Boolean} true in case the document is valid, otherwise throws ParserError
*/
function validateServerSecurity(parsedJSON, asyncapiYAMLorJSON, initialFormat, specialSecTypes) {
const srvs = parsedJSON.servers;
if (!srvs) return true;

const root = 'servers';
const srvsMap = new Map(Object.entries(srvs));

const missingSecSchema = new Map();
const invalidSecurityValues = new Map();

srvsMap.forEach((server, serverName) => {
const serverSecInfo = server.security;

if (!serverSecInfo) return true;

serverSecInfo.forEach(secObj => {
Object.keys(secObj).forEach(secName => {
const schema = findSecuritySchema(secName, parsedJSON.components);
const srvrSecurityPath = `${serverName}/security/${secName}`;

if (!schema.length) return missingSecSchema.set(srvrSecurityPath);

const schemaType = schema[1];
if (!isSrvrSecProperArray(schemaType, specialSecTypes, secObj, secName)) invalidSecurityValues.set(srvrSecurityPath, schemaType);
});
});
});

if (missingSecSchema.size) {
throw new ParserError({
type: validationError,
title: 'Server security name must correspond to a security scheme which is declared in the security schemes under the components object.',
parsedJSON,
validationErrors: groupValidationErrors(root, 'doesn\'t have a corresponding security schema under the components object', missingSecSchema, asyncapiYAMLorJSON, initialFormat)
});
}

if (invalidSecurityValues.size) {
throw new ParserError({
type: validationError,
title: 'Server security value must be an empty array if corresponding security schema type is not oauth2 or openIdConnect.',
parsedJSON,
validationErrors: groupValidationErrors(root, 'security info must have an empty array because its corresponding security schema type is', invalidSecurityValues, asyncapiYAMLorJSON, initialFormat)
});
}

return true;
}

/**
* Searches for server security corresponding object in security schema object
* @private
* @param {String} securityName name of the server security element that you want to localize in the security schema object
* @param {Object} components components object from the AsyncAPI document
* @returns {Array[String]} there are 2 elements in array, index 0 is the name of the security schema object and index 1 is it's type
*/
function findSecuritySchema(securityName, components) {
const secSchemes = components && components.securitySchemes;
const secSchemesMap = secSchemes ? new Map(Object.entries(secSchemes)) : new Map();
const schemaInfo = [];

//using for loop here as there is no point to iterate over all entries as it is enough to find first matching element
for (const [schemaName, schema] of secSchemesMap.entries()) {
if (schemaName === securityName) {
schemaInfo.push(schemaName, schema.type);
return schemaInfo;
}
}
return schemaInfo;
}

/**
* Validates if given server security is a proper empty array when security type requires it
* @private
* @param {String} schemaType security type, like httpApiKey or userPassword
* @param {Array[String]} specialSecTypes list of special types that do not have to be an empty array
* @param {Object} secObj server security object
* @param {String} secName name os server security object
* @returns {Array[String]} there are 2 elements in array, index 0 is the name of the security schema object and index 1 is it's type
*/
function isSrvrSecProperArray(schemaType, specialSecTypes, secObj, secName) {
if (!specialSecTypes.includes(schemaType)) {
const securityObjValue = secObj[String(secName)];

return !securityObjValue.length;
}

return true;
}

module.exports = {
validateChannelParams,
validateServerVariables,
validateOperationId
validateOperationId,
validateServerSecurity
};
Loading

0 comments on commit 4aaa5aa

Please sign in to comment.