Skip to content

Commit

Permalink
feat: improve and add some errors (#61)
Browse files Browse the repository at this point in the history
* feat: improve errors

* Unify errors and make errors aware of the original document

* Fix typo

* Update docs

* Change upgrade for update

* Add toJS method to the error class

* Add docs for toJS method

* Add info about all the errors in the README

* Change error message and fix tests
  • Loading branch information
fmvilas authored Apr 14, 2020
1 parent e5ffb4a commit 2af2417
Show file tree
Hide file tree
Showing 14 changed files with 956 additions and 188 deletions.
149 changes: 148 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
## Classes

<dl>
<dt><a href="#ParserError">ParserError</a></dt>
<dd><p>Represents an error while trying to parse an AsyncAPI document.</p>
</dd>
<dt><a href="#AsyncAPIDocument">AsyncAPIDocument</a> ⇐ <code><a href="#Base">Base</a></code></dt>
<dd><p>Implements functions to deal with the AsyncAPI document.</p>
</dd>
Expand Down Expand Up @@ -84,6 +87,29 @@
</dd>
</dl>

## Functions

<dl>
<dt><a href="#assignUidToParameterSchemas">assignUidToParameterSchemas(doc)</a></dt>
<dd><p>Assign parameter keys as uid for the parameter schema.</p>
</dd>
<dt><a href="#assignUidToComponentSchemas">assignUidToComponentSchemas(doc)</a></dt>
<dd><p>Assign uid to component schemas.</p>
</dd>
<dt><a href="#assignNameToAnonymousMessages">assignNameToAnonymousMessages(doc)</a></dt>
<dd><p>Assign anonymous names to nameless messages.</p>
</dd>
<dt><a href="#recursiveSchema">recursiveSchema(schema, callback(schema))</a></dt>
<dd><p>Recursively go through each schema and execute callback.</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>
</dl>

<a name="module_Parser"></a>

## Parser
Expand All @@ -105,7 +131,7 @@ Parses and validate an AsyncAPI document from YAML or JSON.
| --- | --- | --- | --- |
| asyncapiYAMLorJSON | <code>String</code> | | An AsyncAPI document in JSON or YAML format. |
| [options] | <code>Object</code> | | Configuration options. |
| [options.path] | <code>String</code> | | Path to the AsyncAPI document. It will be used to resolve relative references. |
| [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). |
Expand Down Expand Up @@ -137,6 +163,59 @@ Registers a new schema parser. Schema parsers are in charge of parsing and trans
| schemaFormats | <code>Array.&lt;string&gt;</code> | An array of schema formats the given schema parser is able to recognize and transform. |
| parserFunction | <code>function</code> | The schema parser function. |

<a name="ParserError"></a>

## ParserError
Represents an error while trying to parse an AsyncAPI document.

**Kind**: global class

* [ParserError](#ParserError)
* [new ParserError(definition)](#new_ParserError_new)
* [.toJS()](#ParserError+toJS)

<a name="new_ParserError_new"></a>

### new ParserError(definition)
Instantiates an error


| Param | Type | Description |
| --- | --- | --- |
| definition | <code>Object</code> | |
| definition.type | <code>String</code> | The type of the error. |
| definition.title | <code>String</code> | The message of the error. |
| [definition.detail] | <code>String</code> | A string containing more detailed information about the error. |
| [definition.parsedJSON] | <code>Object</code> | The resulting JSON after YAML transformation. Or the JSON object if the this was the initial format. |
| [definition.validationErrors] | <code>Array.&lt;Object&gt;</code> | The errors resulting from the validation. For more information, see https://www.npmjs.com/package/better-ajv-errors. |
| definition.validationErrors.title | <code>String</code> | A validation error message. |
| definition.validationErrors.jsonPointer | <code>String</code> | The path to the field that contains the error. Uses JSON Pointer format. |
| definition.validationErrors.startLine | <code>Number</code> | The line where the error starts in the AsyncAPI document. |
| definition.validationErrors.startColumn | <code>Number</code> | The column where the error starts in the AsyncAPI document. |
| definition.validationErrors.startOffset | <code>Number</code> | The offset (starting from the beginning of the document) where the error starts in the AsyncAPI document. |
| definition.validationErrors.endLine | <code>Number</code> | The line where the error ends in the AsyncAPI document. |
| definition.validationErrors.endColumn | <code>Number</code> | The column where the error ends in the AsyncAPI document. |
| definition.validationErrors.endOffset | <code>Number</code> | The offset (starting from the beginning of the document) where the error ends in the AsyncAPI document. |
| [definition.location] | <code>Object</code> | Error location details after trying to parse an invalid JSON or YAML document. |
| definition.location.startLine | <code>Number</code> | The line of the YAML/JSON document where the error starts. |
| definition.location.startColumn | <code>Number</code> | The column of the YAML/JSON document where the error starts. |
| definition.location.startOffset | <code>Number</code> | The offset (starting from the beginning of the document) where the error starts in the YAML/JSON AsyncAPI document. |
| [definition.refs] | <code>Array.&lt;Object&gt;</code> | Error details after trying to resolve $ref's. |
| definition.refs.title | <code>String</code> | A validation error message. |
| definition.refs.jsonPointer | <code>String</code> | The path to the field that contains the error. Uses JSON Pointer format. |
| definition.refs.startLine | <code>Number</code> | The line where the error starts in the AsyncAPI document. |
| definition.refs.startColumn | <code>Number</code> | The column where the error starts in the AsyncAPI document. |
| definition.refs.startOffset | <code>Number</code> | The offset (starting from the beginning of the document) where the error starts in the AsyncAPI document. |
| definition.refs.endLine | <code>Number</code> | The line where the error ends in the AsyncAPI document. |
| definition.refs.endColumn | <code>Number</code> | The column where the error ends in the AsyncAPI document. |
| definition.refs.endOffset | <code>Number</code> | The offset (starting from the beginning of the document) where the error ends in the AsyncAPI document. |

<a name="ParserError+toJS"></a>

### parserError.toJS()
Returns a JS object representation of the error.

**Kind**: instance method of [<code>ParserError</code>](#ParserError)
<a name="AsyncAPIDocument"></a>

## AsyncAPIDocument ⇐ [<code>Base</code>](#Base)
Expand Down Expand Up @@ -1694,3 +1773,71 @@ Implements functions to deal with a Tag object.

### tag.json() ⇒ <code>Any</code>
**Kind**: instance method of [<code>Tag</code>](#Tag)
<a name="assignUidToParameterSchemas"></a>

## assignUidToParameterSchemas(doc)
Assign parameter keys as uid for the parameter schema.

**Kind**: global function

| Param | Type |
| --- | --- |
| doc | [<code>AsyncAPIDocument</code>](#AsyncAPIDocument) |

<a name="assignUidToComponentSchemas"></a>

## assignUidToComponentSchemas(doc)
Assign uid to component schemas.

**Kind**: global function

| Param | Type |
| --- | --- |
| doc | [<code>AsyncAPIDocument</code>](#AsyncAPIDocument) |

<a name="assignNameToAnonymousMessages"></a>

## assignNameToAnonymousMessages(doc)
Assign anonymous names to nameless messages.

**Kind**: global function

| Param | Type |
| --- | --- |
| doc | [<code>AsyncAPIDocument</code>](#AsyncAPIDocument) |

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

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

**Kind**: global function

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

<a name="schemaDocument"></a>

## schemaDocument(doc, callback(schema))
Go through each channel and for each parameter, and message payload and headers recursively call the callback for each schema.

**Kind**: global function

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

<a name="assignIdToAnonymousSchemas"></a>

## assignIdToAnonymousSchemas(doc)
Gives schemas id to all anonymous schemas.

**Kind**: global function

| Param | Type |
| --- | --- |
| doc | [<code>AsyncAPIDocument</code>](#AsyncAPIDocument) |

21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ Head over to [asyncapi/openapi-schema-parser](https://www.github.com/asyncapi/op

Head over to [asyncapi/raml-dt-schema-parser](https://www.github.com/asyncapi/raml-dt-schema-parser) for more information.

### Error types

This package throws a bunch of different error types. All errors contain a `type` (prefixed by this repo URL) and a `title` field. The following table describes all the errors and the extra fields they include:

|Type|Extra Fields|Description|
|---|---|---|
|`null-or-falsey-document`| None | The AsyncAPI document is null or a JS "falsey" value.
|`invalid-document-type`| None | The AsyncAPI document is not a string nor a JS object.
|`invalid-json`| `detail`, `location` | The AsyncAPI document is not valid JSON.
|`invalid-yaml`| `detail`, `location` | The AsyncAPI document is not valid YAML.
|`impossible-to-convert-to-json`|`detail`|Internally, this parser only handles JSON so it tries to immediately convert the YAML to JSON. This error means this process failed.
|`missing-asyncapi-field`|`parsedJSON`|The AsyncAPI document doesn't have the mandatory `asyncapi` field.
|`unsupported-version`|`detail`, `parsedJSON`, `validationErrors`|The version of the `asyncapi` field is not supported. Typically, this means that you're using a version below 2.0.0.
|`dereference-error`|`parsedJSON`, `refs`|This means the parser tried to resolve and dereference $ref's and the process failed. Typically, this means the $ref it's pointing to doesn't exist.
|`unexpected-error`|`parsedJSON`|We have our code covered with try/catch blocks and you should never see this error. If you see it, please open an issue to let us know.
|`validation-errors`|`parsedJSON`, `validationErrors`|The AsyncAPI document contains errors. See `validationErrors` for more information.

For more information about the `ParserError` class, [check out the documentation](./API.md#new_ParserError_new).

### Develop

1. Run tests with `npm test`
Expand All @@ -89,4 +108,4 @@ Head over to [asyncapi/raml-dt-schema-parser](https://www.github.com/asyncapi/ra

## Contributing

Read [CONTRIBUTING](CONTRIBUTING.md) guide.
Read [CONTRIBUTING](CONTRIBUTING.md) guide.
6 changes: 0 additions & 6 deletions lib/errors/parser-error-no-js.js

This file was deleted.

9 changes: 0 additions & 9 deletions lib/errors/parser-error-unsupported-version.js

This file was deleted.

76 changes: 56 additions & 20 deletions lib/errors/parser-error.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,62 @@
class ParserError extends Error {
constructor(e, json, errors) {
super(e);

let msg;

if (typeof e === 'string') {
msg = e;
}
if (typeof e.message === 'string') {
msg = e.message;
}

if (json) {
this.parsedJSON = json;
}
const ERROR_URL_PREFIX = 'https://github.com/asyncapi/parser-js/';

if (errors) {
this.errors = errors;
}
/**
* Represents an error while trying to parse an AsyncAPI document.
*/
class ParserError extends Error {
/**
* Instantiates an error
* @param {Object} definition
* @param {String} definition.type The type of the error.
* @param {String} definition.title The message of the error.
* @param {String} [definition.detail] A string containing more detailed information about the error.
* @param {Object} [definition.parsedJSON] The resulting JSON after YAML transformation. Or the JSON object if the this was the initial format.
* @param {Object[]} [definition.validationErrors] The errors resulting from the validation. For more information, see https://www.npmjs.com/package/better-ajv-errors.
* @param {String} definition.validationErrors.title A validation error message.
* @param {String} definition.validationErrors.jsonPointer The path to the field that contains the error. Uses JSON Pointer format.
* @param {Number} definition.validationErrors.startLine The line where the error starts in the AsyncAPI document.
* @param {Number} definition.validationErrors.startColumn The column where the error starts in the AsyncAPI document.
* @param {Number} definition.validationErrors.startOffset The offset (starting from the beginning of the document) where the error starts in the AsyncAPI document.
* @param {Number} definition.validationErrors.endLine The line where the error ends in the AsyncAPI document.
* @param {Number} definition.validationErrors.endColumn The column where the error ends in the AsyncAPI document.
* @param {Number} definition.validationErrors.endOffset The offset (starting from the beginning of the document) where the error ends in the AsyncAPI document.
* @param {Object} [definition.location] Error location details after trying to parse an invalid JSON or YAML document.
* @param {Number} definition.location.startLine The line of the YAML/JSON document where the error starts.
* @param {Number} definition.location.startColumn The column of the YAML/JSON document where the error starts.
* @param {Number} definition.location.startOffset The offset (starting from the beginning of the document) where the error starts in the YAML/JSON AsyncAPI document.
* @param {Object[]} [definition.refs] Error details after trying to resolve $ref's.
* @param {String} definition.refs.title A validation error message.
* @param {String} definition.refs.jsonPointer The path to the field that contains the error. Uses JSON Pointer format.
* @param {Number} definition.refs.startLine The line where the error starts in the AsyncAPI document.
* @param {Number} definition.refs.startColumn The column where the error starts in the AsyncAPI document.
* @param {Number} definition.refs.startOffset The offset (starting from the beginning of the document) where the error starts in the AsyncAPI document.
* @param {Number} definition.refs.endLine The line where the error ends in the AsyncAPI document.
* @param {Number} definition.refs.endColumn The column where the error ends in the AsyncAPI document.
* @param {Number} definition.refs.endOffset The offset (starting from the beginning of the document) where the error ends in the AsyncAPI document.
*/
constructor(def) {
super();
buildError(def, this);
this.message = def.title;
}

this.message = msg;
/**
* Returns a JS object representation of the error.
*/
toJS() {
return buildError(this, {});
}
}

const buildError = (from, to) => {
to.type = from.type.startsWith(ERROR_URL_PREFIX) ? from.type : `${ERROR_URL_PREFIX}${from.type}`;
to.title = from.title;
if (from.detail) to.detail = from.detail;
if (from.validationErrors) to.validationErrors = from.validationErrors;
if (from.parsedJSON) to.parsedJSON = from.parsedJSON;
if (from.location) to.location = from.location;
if (from.refs) to.refs = from.refs;
return to;
};

module.exports = ParserError;
38 changes: 38 additions & 0 deletions lib/json-parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module.exports = (txt, reviver, context = 20) => {
try {
return JSON.parse(txt, reviver)
} catch (e) {
if (typeof txt !== 'string') {
const isEmptyArray = Array.isArray(txt) && txt.length === 0
const errorMessage = 'Cannot parse ' +
(isEmptyArray ? 'an empty array' : String(txt))
throw new TypeError(errorMessage)
}
const syntaxErr = e.message.match(/^Unexpected token.*position\s+(\d+)/i)
const errIdx = syntaxErr
? +syntaxErr[1]
: e.message.match(/^Unexpected end of JSON.*/i)
? txt.length - 1
: null
if (errIdx != null) {
const start = errIdx <= context
? 0
: errIdx - context
const end = errIdx + context >= txt.length
? txt.length
: errIdx + context
e.message += ` while parsing near '${
start === 0 ? '' : '...'
}${txt.slice(start, end)}${
end === txt.length ? '' : '...'
}'`
} else {
e.message += ` while parsing '${txt.slice(0, context * 2)}'`
}
e.offset = errIdx
const lines = txt.substr(0, errIdx).split('\n');
e.startLine = lines.length;
e.startColumn = lines[lines.length - 1].length;
throw e
}
}
Loading

0 comments on commit 2af2417

Please sign in to comment.