Befor generating
This commit is contained in:
+4068
File diff suppressed because it is too large
Load Diff
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright The Linux Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
[](https://www.asyncapi.com)
|
||||
|
||||
Use this package to parse and validate AsyncAPI documents —either YAML or JSON— in your Node.js or browser application. Updated bundle for the browser is always attached to the GitHub Release.
|
||||
|
||||
 
|
||||
|
||||
> :warning: This package doesn't support AsyncAPI 1.x anymore. We recommend to upgrade to the latest AsyncAPI version using the [AsyncAPI converter](https://github.com/asyncapi/converter-js). If you need to convert documents on the fly, you may use the [Node.js](https://github.com/asyncapi/converter-js) or [Go](https://github.com/asyncapi/converter-go) converters.
|
||||
|
||||
<!-- toc is generated with GitHub Actions do not remove toc markers -->
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Install](#install)
|
||||
- [Examples](#examples)
|
||||
* [Example passing inline AsyncAPI](#example-passing-inline-asyncapi)
|
||||
* [Example passing a URL](#example-passing-a-url)
|
||||
* [Example using Avro schemas](#example-using-avro-schemas)
|
||||
* [Example using OpenAPI schemas](#example-using-openapi-schemas)
|
||||
* [Example using RAML data types](#example-using-raml-data-types)
|
||||
- [API documentation](#api-documentation)
|
||||
- [Using in the browser](#using-in-the-browser)
|
||||
- [Custom message parsers](#custom-message-parsers)
|
||||
- [Error types](#error-types)
|
||||
- [Custom extensions](#custom-extensions)
|
||||
- [Circular references](#circular-references)
|
||||
- [Stringify](#stringify)
|
||||
- [Develop](#develop)
|
||||
- [Contributing](#contributing)
|
||||
- [Contributors](#contributors)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
npm install @asyncapi/parser
|
||||
```
|
||||
The parser by default supports AsyncAPI Schema Format and JSON Schema Format. For additional formats, you need to install additional plugins. For example:
|
||||
- Avro schema
|
||||
```
|
||||
npm install @asyncapi/avro-schema-parser
|
||||
```
|
||||
- OpenAPI Schema Object
|
||||
```
|
||||
npm install @asyncapi/openapi-schema-parser
|
||||
```
|
||||
- RAML data type
|
||||
```
|
||||
npm install @asyncapi/raml-dt-schema-parser
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Example passing inline AsyncAPI
|
||||
|
||||
```js
|
||||
const parser = require('@asyncapi/parser');
|
||||
|
||||
const doc = await parser.parse(`
|
||||
asyncapi: '2.1.0'
|
||||
info:
|
||||
title: Example
|
||||
version: '0.1.0'
|
||||
channels:
|
||||
example-channel:
|
||||
subscribe:
|
||||
message:
|
||||
payload:
|
||||
type: object
|
||||
properties:
|
||||
exampleField:
|
||||
type: string
|
||||
exampleNumber:
|
||||
type: number
|
||||
exampleDate:
|
||||
type: string
|
||||
format: date-time
|
||||
`);
|
||||
|
||||
console.log(doc.info().title());
|
||||
// => Example
|
||||
```
|
||||
|
||||
### Example passing a URL
|
||||
|
||||
```js
|
||||
const parser = require('@asyncapi/parser');
|
||||
|
||||
const doc = await parser.parseFromUrl('https://my.server.com/example-asyncapi.yaml');
|
||||
|
||||
console.log(doc.info().title());
|
||||
// => Example
|
||||
```
|
||||
|
||||
### Example using Avro schemas
|
||||
|
||||
Head over to [asyncapi/avro-schema-parser](https://www.github.com/asyncapi/avro-schema-parser) for more information.
|
||||
|
||||
### Example using OpenAPI schemas
|
||||
|
||||
Head over to [asyncapi/openapi-schema-parser](https://www.github.com/asyncapi/openapi-schema-parser) for more information.
|
||||
|
||||
### Example using RAML data types
|
||||
|
||||
Head over to [asyncapi/raml-dt-schema-parser](https://www.github.com/asyncapi/raml-dt-schema-parser) for more information.
|
||||
|
||||
## API documentation
|
||||
|
||||
The parser API is generally structured the same way as the AsyncAPI specification, with additional support functions such as `has<Something>()`. The parser uses wrapped functions to get the properties stored in JSON. This means in order to get the info object you would use the function `doc.info()` and to get the title inside the info object you call `doc.info().title()`.
|
||||
|
||||
See [API documentation](./API.md) for more example and full API reference information.
|
||||
|
||||
## Using in the browser
|
||||
|
||||
The package contains a built-in version of the parser, which is created via [`browserify`](https://github.com/browserify/browserify). To use it, you need to import the parser into the HTML file as below:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/@asyncapi/parser@latest/dist/bundle.js"></script>
|
||||
|
||||
<script>
|
||||
const parser = window['AsyncAPIParser'];
|
||||
...
|
||||
</script>
|
||||
```
|
||||
|
||||
Or, if you want to use a parser in a JS application of the SPA kind, import the parser as shown below:
|
||||
|
||||
```js
|
||||
import '@asyncapi/parser/dist/bundle';
|
||||
|
||||
const parser = window['AsyncAPIParser'];
|
||||
...
|
||||
```
|
||||
|
||||
Otherwise, if your application is bundled via bundlers like `webpack`, you can import the parser like a regular package:
|
||||
|
||||
```js
|
||||
import parser from '@asyncapi/parser';
|
||||
```
|
||||
|
||||
In case you just want to check out the latest `bundle.js` without installing the package, we publish one on each GitHub release. You can find it under [this link to the latest release](https://github.com/asyncapi/parser-js/releases/latest/download/bundle.js).
|
||||
|
||||
## Custom message parsers
|
||||
|
||||
AsyncAPI doesn't enforce one schema format for messages. You can have payload of your messages described with OpenAPI, Avro, etc. This parser by default parses only AsyncAPI schema format. You can extend it by creating a custom parser and registering it within the parser:
|
||||
|
||||
1. Create custom parser module that exports two functions:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
/*
|
||||
* message {Object} is the object containing AsyncAPI Message property
|
||||
* defaultSchemaFormat {String} information about the default schema format mime type
|
||||
* schemaFormat {String} information about custom schemaFormat mime type provided in AsyncAPI Document
|
||||
* fileFormat {String} information if provided AsyncAPI Document was JSON or YAML
|
||||
* parsedAsyncAPIDocument {Object} Full AsyncAPI Document parsed into Object
|
||||
* pathToPayload {String} path of the message passed to the parser, relative to the root of AsyncAPI Document
|
||||
*/
|
||||
parse: ({ message, defaultSchemaFormat, originalAsyncAPIDocument, schemaFormat, fileFormat, parsedAsyncAPIDocument, pathToPayload }) => { /* custom parsing logic */ },
|
||||
getMimeTypes: () => [
|
||||
'//mime types that will be used as the `schemaFormat` property of the message to specify its mime type',
|
||||
'application/vnd.custom.type;version=1.0.0',
|
||||
'application/vnd.custom.type+json;version=1.0.0',
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. Before parsing an AsyncAPI document with a parser, register the additional custom schema parser:
|
||||
|
||||
```js
|
||||
const myCustomParser = require('mycustomParser');
|
||||
|
||||
parser.registerSchemaParser(myCustomParser);
|
||||
```
|
||||
|
||||
## 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.
|
||||
|`impossible-to-register-parser`| None | Registration of custom message parser failed.
|
||||
|`schema-validation-errors`| `parsedJSON`, `validationErrors` | Schema of the payload provided in the AsyncAPI document is not valid with AsyncAPI schema format.
|
||||
|`fetch-url-error`| None | The URL provided for fetching AsynAPI document is invalid.
|
||||
|
||||
For more information about the `ParserError` class, [check out the documentation](./API.md#new_ParserError_new).
|
||||
|
||||
## Custom extensions
|
||||
|
||||
The parser uses custom extensions to define additional information about the spec. Each has a different purpose but all of them are there to make it much easier to work with the AsyncAPI document. These extensions are prefixed with `x-parser-`. The following extensions are used :
|
||||
- `x-parser-spec-parsed` is used to specify if the AsyncAPI document is already parsed by the parser. Property `x-parser-spec-parsed` is added to the root of the document with the `true` value.
|
||||
- `x-parser-message-parsed` is used to specify if the message is already parsed by the message parser. Property `x-parser-message-parsed` is added to the root of the document with the `true` value.
|
||||
- `x-parser-message-name` is used to specify the name of the message if it is not provided. For messages without names, the parser generates anonymous names. Property `x-parser-message-name` is added to a message object with a value that follows this pattern: `<anonymous-message-${number}>`. This value is returned by `message.uid()` when regular `name` property is not present.
|
||||
- `x-parser-schema-id` is used to specify the ID of the schema if it is not provided. For schemas without IDs, the parser generates anonymous names. Property `x-parser-schema-id` is added to every object of a schema with a value that follows this pattern: `<anonymous-schema-${number}>`. This value is returned by `schema.uid()` when regular `$id` property is not present.
|
||||
- `x-parser-original-traits` is where traits are stored after they are applied on the AsyncAPI document. The reason is because the original `traits` property is removed.
|
||||
- `x-parser-original-schema-format` holds information about the original schema format of the payload. You can use different schema formats with the AsyncAPI documents and the parser converts them to AsyncAPI schema. This is why different schema format is set, and the original one is preserved in the extension.
|
||||
- `x-parser-original-payload` holds the original payload of the message. You can use different formats for payloads with the AsyncAPI documents and the parser converts them to. For example, it converts payload described with Avro schema to AsyncAPI schema. The original payload is preserved in the extension.
|
||||
- [`x-parser-circular`](#circular-references)
|
||||
|
||||
> **NOTE**: All extensions added by the parser (including all properties) should be retrieved using special functions. Names of extensions and their location may change, and their eventual changes will not be announced.
|
||||
|
||||
## 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()` function to check the document and provide a proper message to the user.
|
||||
- `isCircular()` function is added to the [Schema Model](./lib/models/schema.js) to determine if a given schema is circular with respect to previously occurring schemas in the tree.
|
||||
|
||||
## Stringify
|
||||
|
||||
Converting a parsed document to a string may be necessary when saving the parsed document to a database, or similar situations where you need to parse the document just once and then reuse it.
|
||||
|
||||
For that, the Parser supports the ability to stringify a parsed AsyncAPI document through the static `AsyncAPIDocument.stringify(...parsedDoc)` method. This method differs from the native `JSON.stringify(...json)` implementation, in that every reference that occurs (at least twice throughout the document) is converted into a [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) path with a `$ref:` prefix:
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": "$ref:$.some.path.to.the.bar"
|
||||
}
|
||||
```
|
||||
|
||||
To parse a stringified document into an AsyncAPIDocument instance, you must use the static `AsyncAPIDocument.parse(...stringifiedDoc)` method. It isn't compatible with the native `JSON.parse()` method. It replaces the given references pointed by the [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) path, with an `$ref:` prefix to the original objects.
|
||||
|
||||
A few advantages of this solution:
|
||||
- The string remains as small as possible due to the use of [JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901).
|
||||
- All circular references are preserved.
|
||||
|
||||
## Develop
|
||||
|
||||
1. Make sure you are using Node.js 14 or higher and npm 7 or higher
|
||||
1. Write code and tests.
|
||||
1. Make sure all tests pass `npm test`
|
||||
1. Make sure code is well formatted and secure `npm run lint`
|
||||
|
||||
Release regenerates API documentation and browser bundle, so you do not have to regenerate it manually with `npm run docs` and `npm run prepublishOnly`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Read [CONTRIBUTING](https://github.com/asyncapi/.github/blob/master/CONTRIBUTING.md) guide.
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.fmvilas.com/"><img src="https://avatars3.githubusercontent.com/u/242119?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fran Méndez</b></sub></a><br /><a href="#question-fmvilas" title="Answering Questions">💬</a> <a href="https://github.com/asyncapi/parser-js/issues?q=author%3Afmvilas" title="Bug reports">🐛</a> <a href="https://github.com/asyncapi/parser-js/commits?author=fmvilas" title="Code">💻</a> <a href="https://github.com/asyncapi/parser-js/commits?author=fmvilas" title="Documentation">📖</a> <a href="#ideas-fmvilas" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-fmvilas" title="Maintenance">🚧</a> <a href="#plugin-fmvilas" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/asyncapi/parser-js/pulls?q=is%3Apr+reviewed-by%3Afmvilas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/asyncapi/parser-js/commits?author=fmvilas" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://resume.github.io/?derberg"><img src="https://avatars1.githubusercontent.com/u/6995927?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lukasz Gornicki</b></sub></a><br /><a href="#question-derberg" title="Answering Questions">💬</a> <a href="https://github.com/asyncapi/parser-js/issues?q=author%3Aderberg" title="Bug reports">🐛</a> <a href="https://github.com/asyncapi/parser-js/commits?author=derberg" title="Code">💻</a> <a href="https://github.com/asyncapi/parser-js/commits?author=derberg" title="Documentation">📖</a> <a href="#ideas-derberg" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-derberg" title="Maintenance">🚧</a> <a href="https://github.com/asyncapi/parser-js/pulls?q=is%3Apr+reviewed-by%3Aderberg" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/asyncapi/parser-js/commits?author=derberg" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/jonaslagoni"><img src="https://avatars1.githubusercontent.com/u/13396189?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonas Lagoni</b></sub></a><br /><a href="#question-jonaslagoni" title="Answering Questions">💬</a> <a href="https://github.com/asyncapi/parser-js/issues?q=author%3Ajonaslagoni" title="Bug reports">🐛</a> <a href="https://github.com/asyncapi/parser-js/commits?author=jonaslagoni" title="Code">💻</a> <a href="#ideas-jonaslagoni" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/asyncapi/parser-js/pulls?q=is%3Apr+reviewed-by%3Ajonaslagoni" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center"><a href="https://github.com/magicmatatjahu"><img src="https://avatars2.githubusercontent.com/u/20404945?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maciej Urbańczyk</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/issues?q=author%3Amagicmatatjahu" title="Bug reports">🐛</a> <a href="https://github.com/asyncapi/parser-js/commits?author=magicmatatjahu" title="Code">💻</a> <a href="https://github.com/asyncapi/parser-js/pulls?q=is%3Apr+reviewed-by%3Amagicmatatjahu" title="Reviewed Pull Requests">👀</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.inmensia.com/"><img src="https://avatars2.githubusercontent.com/u/6494060?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Juan Mellado</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=jcmellado" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.jamescrowley.net/"><img src="https://avatars1.githubusercontent.com/u/509533?v=4?s=100" width="100px;" alt=""/><br /><sub><b>James Crowley</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=jamescrowley" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rmelian"><img src="https://avatars3.githubusercontent.com/u/4565267?v=4?s=100" width="100px;" alt=""/><br /><sub><b>raisel melian</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=rmelian" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/DanielChuDC"><img src="https://avatars3.githubusercontent.com/u/52316624?v=4?s=100" width="100px;" alt=""/><br /><sub><b>danielchu</b></sub></a><br /><a href="#infra-DanielChuDC" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/asyncapi/parser-js/commits?author=DanielChuDC" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.linkedin.com/in/jbreitenbaumer/"><img src="https://avatars3.githubusercontent.com/u/683438?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jürgen B.</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=juergenbr" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/aeworxet"><img src="https://avatars.githubusercontent.com/u/16149591?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Viacheslav Turovskyi</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=aeworxet" title="Tests">⚠️</a> <a href="https://github.com/asyncapi/parser-js/commits?author=aeworxet" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/KhudaDad414"><img src="https://avatars.githubusercontent.com/u/32505158?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Khuda Dad Nomani</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=KhudaDad414" title="Code">💻</a> <a href="https://github.com/asyncapi/parser-js/issues?q=author%3AKhudaDad414" title="Bug reports">🐛</a> <a href="https://github.com/asyncapi/parser-js/commits?author=KhudaDad414" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/aayushmau5"><img src="https://avatars.githubusercontent.com/u/54525741?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Aayush Kumar Sahu</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=aayushmau5" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/JQrdan"><img src="https://avatars.githubusercontent.com/u/25624685?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jordan Tucker</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=JQrdan" title="Tests">⚠️</a> <a href="https://github.com/asyncapi/parser-js/commits?author=JQrdan" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/vishesh13byte"><img src="https://avatars.githubusercontent.com/u/66796715?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vishesh13byte</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=vishesh13byte" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://iamdevelopergirl.github.io/Website-With-Animations/"><img src="https://avatars.githubusercontent.com/u/16351809?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Elakya</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/commits?author=iamdevelopergirl" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://schwank.cc"><img src="https://avatars.githubusercontent.com/u/8232196?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dominik Schwank</b></sub></a><br /><a href="https://github.com/asyncapi/parser-js/issues?q=author%3Adschwank" title="Bug reports">🐛</a> <a href="https://github.com/asyncapi/parser-js/commits?author=dschwank" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
+1
File diff suppressed because one or more lines are too long
+128
@@ -0,0 +1,128 @@
|
||||
const {xParserMessageName, xParserSchemaId} = require('./constants');
|
||||
const {traverseAsyncApiDocument} = require('./iterators');
|
||||
|
||||
/**
|
||||
* Assign message keys as message name to all the component messages.
|
||||
*
|
||||
* @private
|
||||
* @param {AsyncAPIDocument} doc
|
||||
*/
|
||||
function assignNameToComponentMessages(doc) {
|
||||
if (doc.hasComponents()) {
|
||||
for (const [key, m] of Object.entries(doc.components().messages())) {
|
||||
if (m.name() === undefined) {
|
||||
m.json()[String(xParserMessageName)] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign ids based on parameter keys.
|
||||
*
|
||||
* @private
|
||||
* @param {Record<string,Schema>} parameterObject
|
||||
*/
|
||||
function assignIdToParameters(parameterObject) {
|
||||
for (const [parameterKey, parameter] of Object.entries(parameterObject)) {
|
||||
if (parameter.schema()) {
|
||||
parameter.schema().json()[String(xParserSchemaId)] = parameterKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign parameter keys as uid for the parameter schema.
|
||||
*
|
||||
* @private
|
||||
* @param {AsyncAPIDocument} doc
|
||||
*/
|
||||
function assignUidToParameterSchemas(doc) {
|
||||
doc.channelNames().forEach(channelName => {
|
||||
const channel = doc.channel(channelName);
|
||||
assignIdToParameters(channel.parameters());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign uid to component schemas.
|
||||
*
|
||||
* @private
|
||||
* @param {AsyncAPIDocument} doc
|
||||
*/
|
||||
function assignUidToComponentSchemas(doc) {
|
||||
if (doc.hasComponents()) {
|
||||
for (const [key, s] of Object.entries(doc.components().schemas())) {
|
||||
s.json()[String(xParserSchemaId)] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign uid to component parameters schemas
|
||||
*
|
||||
* @private
|
||||
* @param {AsyncAPIDocument} doc
|
||||
*/
|
||||
function assignUidToComponentParameterSchemas(doc) {
|
||||
if (doc.hasComponents()) {
|
||||
assignIdToParameters(doc.components().parameters());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign anonymous names to nameless messages.
|
||||
*
|
||||
* @private
|
||||
* @param {AsyncAPIDocument} doc
|
||||
*/
|
||||
function assignNameToAnonymousMessages(doc) {
|
||||
let anonymousMessageCounter = 0;
|
||||
|
||||
if (doc.hasChannels()) {
|
||||
doc.channelNames().forEach(channelName => {
|
||||
const channel = doc.channel(channelName);
|
||||
if (channel.hasPublish()) addNameToKey(channel.publish().messages(), ++anonymousMessageCounter);
|
||||
if (channel.hasSubscribe()) addNameToKey(channel.subscribe().messages(), ++anonymousMessageCounter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add anonymous name to key if no name provided.
|
||||
*
|
||||
* @private
|
||||
* @param {Message} map of messages
|
||||
*/
|
||||
function addNameToKey(messages, number) {
|
||||
messages.forEach(m => {
|
||||
if (m.name() === undefined && m.ext(xParserMessageName) === undefined) {
|
||||
m.json()[String(xParserMessageName)] = `<anonymous-message-${number}>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives schemas id to all anonymous schemas.
|
||||
*
|
||||
* @private
|
||||
* @param {AsyncAPIDocument} doc
|
||||
*/
|
||||
function assignIdToAnonymousSchemas(doc) {
|
||||
let anonymousSchemaCounter = 0;
|
||||
const callback = (schema) => {
|
||||
if (!schema.uid()) {
|
||||
schema.json()[String(xParserSchemaId)] = `<anonymous-schema-${++anonymousSchemaCounter}>`;
|
||||
}
|
||||
};
|
||||
traverseAsyncApiDocument(doc, callback);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assignNameToComponentMessages,
|
||||
assignUidToParameterSchemas,
|
||||
assignUidToComponentSchemas,
|
||||
assignUidToComponentParameterSchemas,
|
||||
assignNameToAnonymousMessages,
|
||||
assignIdToAnonymousSchemas
|
||||
};
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
const Ajv = require('ajv');
|
||||
const ParserError = require('./errors/parser-error');
|
||||
const asyncapi = require('@asyncapi/specs');
|
||||
const { improveAjvErrors } = require('./utils');
|
||||
const cloneDeep = require('lodash.clonedeep');
|
||||
|
||||
const ajv = new Ajv({
|
||||
jsonPointers: true,
|
||||
allErrors: true,
|
||||
schemaId: 'auto',
|
||||
logger: false,
|
||||
});
|
||||
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
|
||||
|
||||
module.exports = {
|
||||
parse,
|
||||
getMimeTypes
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
async function parse({ message, originalAsyncAPIDocument, fileFormat, parsedAsyncAPIDocument, pathToPayload, defaultSchemaFormat }) {
|
||||
const payload = message.payload;
|
||||
if (!payload) return;
|
||||
|
||||
message['x-parser-original-schema-format'] = message.schemaFormat || defaultSchemaFormat;
|
||||
message['x-parser-original-payload'] = cloneDeep(message.payload);
|
||||
|
||||
const validate = getValidator(parsedAsyncAPIDocument.asyncapi);
|
||||
const valid = validate(payload);
|
||||
const errors = validate.errors && [...validate.errors];
|
||||
|
||||
if (!valid) throw new ParserError({
|
||||
type: 'schema-validation-errors',
|
||||
title: 'This is not a valid AsyncAPI Schema Object.',
|
||||
parsedJSON: parsedAsyncAPIDocument,
|
||||
validationErrors: improveAjvErrors(addFullPathToDataPath(errors, pathToPayload), originalAsyncAPIDocument, fileFormat),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getMimeTypes() {
|
||||
const mimeTypes = [
|
||||
'application/schema;version=draft-07',
|
||||
'application/schema+json;version=draft-07',
|
||||
'application/schema+yaml;version=draft-07',
|
||||
];
|
||||
['2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0'].forEach(version => {
|
||||
mimeTypes.push(
|
||||
`application/vnd.aai.asyncapi;version=${version}`,
|
||||
`application/vnd.aai.asyncapi+json;version=${version}`,
|
||||
`application/vnd.aai.asyncapi+yaml;version=${version}`,
|
||||
);
|
||||
});
|
||||
return mimeTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (or reuses) a function that validates an AsyncAPI Schema Object based on the passed AsyncAPI version.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} version AsyncAPI version.
|
||||
* @returns {Function} Function that validates an AsyncAPI Schema Object based on the passed AsyncAPI version.
|
||||
*/
|
||||
function getValidator(version) {
|
||||
let validate = ajv.getSchema(version);
|
||||
if (!validate) {
|
||||
const payloadSchema = preparePayloadSchema(asyncapi[String(version)], version);
|
||||
ajv.addSchema(payloadSchema, version);
|
||||
validate = ajv.getSchema(version);
|
||||
}
|
||||
return validate;
|
||||
}
|
||||
|
||||
/**
|
||||
* To validate schema of the payload we just need a small portion of official AsyncAPI spec JSON Schema, the definition of the schema must be
|
||||
* a main part of the JSON Schema
|
||||
*
|
||||
* @private
|
||||
* @param {Object} asyncapiSchema AsyncAPI specification JSON Schema
|
||||
* @param {Object} version AsyncAPI version.
|
||||
* @returns {Object} valid JSON Schema document describing format of AsyncAPI-valid schema for message payload
|
||||
*/
|
||||
function preparePayloadSchema(asyncapiSchema, version) {
|
||||
const payloadSchema = `http://asyncapi.com/definitions/${version}/schema.json`;
|
||||
const definitions = asyncapiSchema.definitions;
|
||||
// Remove the meta schemas because it is already present within Ajv, and it's not possible to add duplicate schemas.
|
||||
delete definitions['http://json-schema.org/draft-07/schema'];
|
||||
delete definitions['http://json-schema.org/draft-04/schema'];
|
||||
return {
|
||||
$ref: payloadSchema,
|
||||
definitions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors from Ajv contain dataPath information about parameter relative to parsed payload message.
|
||||
* This function enriches dataPath with additional information on where is the parameter located in AsyncAPI document
|
||||
*
|
||||
* @private
|
||||
* @param {Array<Object>} errors Ajv errors
|
||||
* @param {String} path Path to location of the payload schema in AsyncAPI Document
|
||||
* @returns {Array<Object>} same object as received in input but with modified datePath property so it contain full path relative to AsyncAPI document
|
||||
*/
|
||||
function addFullPathToDataPath(errors, path) {
|
||||
return errors.map((err) => ({
|
||||
...err,
|
||||
...{
|
||||
dataPath: `${path}${err.dataPath}`
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
window.AsyncAPIParser = require('./index');
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
const xParserSpecParsed = 'x-parser-spec-parsed';
|
||||
const xParserSpecStringified = 'x-parser-spec-stringified';
|
||||
const xParserMessageName = 'x-parser-message-name';
|
||||
const xParserSchemaId = 'x-parser-schema-id';
|
||||
const xParserCircle = 'x-parser-circular';
|
||||
const xParserCircleProps = 'x-parser-circular-props';
|
||||
|
||||
module.exports = {
|
||||
xParserSpecParsed,
|
||||
xParserSpecStringified,
|
||||
xParserMessageName,
|
||||
xParserSchemaId,
|
||||
xParserCircle,
|
||||
xParserCircleProps
|
||||
};
|
||||
+682
@@ -0,0 +1,682 @@
|
||||
const ParserError = require('./errors/parser-error');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const Operation = require('./models/operation');
|
||||
const {
|
||||
parseUrlVariables,
|
||||
getMissingProps,
|
||||
groupValidationErrors,
|
||||
tilde,
|
||||
parseUrlQueryParameters,
|
||||
setNotProvidedParams,
|
||||
getUnknownServers
|
||||
} = require('./utils');
|
||||
const validationError = 'validation-errors';
|
||||
|
||||
/**
|
||||
* Validates if variables provided in the url have corresponding variable object defined and if example is correct
|
||||
* @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 validateServerVariables(
|
||||
parsedJSON,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
) {
|
||||
const srvs = parsedJSON.servers;
|
||||
if (!srvs) return true;
|
||||
|
||||
const srvsMap = new Map(Object.entries(srvs));
|
||||
const notProvidedVariables = new Map();
|
||||
const notProvidedExamplesInEnum = new Map();
|
||||
|
||||
srvsMap.forEach((srvr, srvrName) => {
|
||||
const variables = parseUrlVariables(srvr.url);
|
||||
const variablesObj = srvr.variables;
|
||||
const notProvidedServerVars = notProvidedVariables.get(tilde(srvrName));
|
||||
if (!variables) return;
|
||||
|
||||
const missingServerVariables = getMissingProps(variables, variablesObj);
|
||||
if (missingServerVariables.length) {
|
||||
notProvidedVariables.set(
|
||||
tilde(srvrName),
|
||||
notProvidedServerVars
|
||||
? notProvidedServerVars.concat(missingServerVariables)
|
||||
: missingServerVariables
|
||||
);
|
||||
}
|
||||
|
||||
if (variablesObj) {
|
||||
setNotValidExamples(variablesObj, srvrName, notProvidedExamplesInEnum);
|
||||
}
|
||||
});
|
||||
|
||||
if (notProvidedVariables.size) {
|
||||
throw new ParserError({
|
||||
type: validationError,
|
||||
title: 'Not all server variables are described with variable object',
|
||||
parsedJSON,
|
||||
validationErrors: groupValidationErrors(
|
||||
'servers',
|
||||
'server does not have a corresponding variable object for',
|
||||
notProvidedVariables,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (notProvidedExamplesInEnum.size) {
|
||||
throw new ParserError({
|
||||
type: validationError,
|
||||
title:
|
||||
'Check your server variables. The example does not match the enum list',
|
||||
parsedJSON,
|
||||
validationErrors: groupValidationErrors(
|
||||
'servers',
|
||||
'server variable provides an example that does not match the enum list',
|
||||
notProvidedExamplesInEnum,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* extend map with info about examples that are not part of the enum
|
||||
*
|
||||
* @function setNotValidExamples
|
||||
* @private
|
||||
* @param {Array<Object>} variables server variables object
|
||||
* @param {String} srvrName name of the server where variables object is located
|
||||
* @param {Map} notProvidedExamplesInEnum result map of all wrong examples and what variable they belong to
|
||||
*/
|
||||
function setNotValidExamples(variables, srvrName, notProvidedExamplesInEnum) {
|
||||
const variablesMap = new Map(Object.entries(variables));
|
||||
variablesMap.forEach((variable, variableName) => {
|
||||
if (variable.enum && variable.examples) {
|
||||
const wrongExamples = variable.examples.filter(r => !variable.enum.includes(r));
|
||||
if (wrongExamples.length) {
|
||||
notProvidedExamplesInEnum.set(
|
||||
`${tilde(srvrName)}/variables/${tilde(variableName)}`,
|
||||
wrongExamples
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if operationIds 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 validateOperationId(
|
||||
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 duplicatedOperations = new Map();
|
||||
//is is a 2-dimensional array that holds information with operationId value and its path
|
||||
const allOperations = [];
|
||||
|
||||
const addDuplicateToMap = (op, channelName, opName) => {
|
||||
const operationId = op.operationId;
|
||||
if (!operationId) return;
|
||||
|
||||
const operationPath = `${tilde(channelName)}/${opName}/operationId`;
|
||||
const isOperationIdDuplicated = allOperations.filter(
|
||||
(v) => v[0] === operationId
|
||||
);
|
||||
if (!isOperationIdDuplicated.length)
|
||||
return allOperations.push([operationId, operationPath]);
|
||||
|
||||
//isOperationIdDuplicated always holds one record and it is an array of paths, the one that is a duplicate and the one that is duplicated
|
||||
duplicatedOperations.set(operationPath, isOperationIdDuplicated[0][1]);
|
||||
};
|
||||
|
||||
chnlsMap.forEach((chnlObj, chnlName) => {
|
||||
operations.forEach((opName) => {
|
||||
const op = chnlObj[String(opName)];
|
||||
if (op) addDuplicateToMap(op, chnlName, opName);
|
||||
});
|
||||
});
|
||||
|
||||
if (duplicatedOperations.size) {
|
||||
throw new ParserError({
|
||||
type: validationError,
|
||||
title: 'operationId must be unique across all the operations.',
|
||||
parsedJSON,
|
||||
validationErrors: groupValidationErrors(
|
||||
'channels',
|
||||
'is a duplicate of',
|
||||
duplicatedOperations,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @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
|
||||
* @param {String[]} specialSecTypes list of security types that can have data in array
|
||||
* @returns {Boolean} true in case the document is valid, otherwise throws {@link 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(),
|
||||
invalidSecurityValues = new Map();
|
||||
|
||||
//we need to validate every server specified in the document
|
||||
srvsMap.forEach((server, serverName) => {
|
||||
const serverSecInfo = server.security;
|
||||
|
||||
if (!serverSecInfo) return true;
|
||||
|
||||
//server security info is an array of many possible values
|
||||
serverSecInfo.forEach((secObj) => {
|
||||
Object.keys(secObj).forEach((secName) => {
|
||||
//security schema is located in components object, we need to find if there is security schema with the same name as the server security info object
|
||||
const schema = findSecuritySchema(secName, parsedJSON.components);
|
||||
const srvrSecurityPath = `${serverName}/security/${secName}`;
|
||||
|
||||
if (!schema.length) return missingSecSchema.set(srvrSecurityPath);
|
||||
|
||||
//findSecuritySchema returns type always on index 1. Type is needed further to validate if server security info can be or not an empty array
|
||||
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 {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 {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 {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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if parameters specified in the channel have corresponding parameters object defined and if name does not contain url parameters.
|
||||
* Also validates that all servers listed for this channel are declared in the top-level servers object.
|
||||
*
|
||||
* @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 validateChannels(parsedJSON, asyncapiYAMLorJSON, initialFormat) {
|
||||
const chnls = parsedJSON.channels;
|
||||
if (!chnls) return true;
|
||||
|
||||
const chnlsMap = new Map(Object.entries(chnls));
|
||||
const notProvidedParams = new Map(); //return object for missing parameters
|
||||
const invalidChannelName = new Map(); //return object for invalid channel names with query parameters
|
||||
const unknownServers = new Map(); //return object for server names not declared in top-level servers object
|
||||
|
||||
chnlsMap.forEach((val, key) => {
|
||||
const variables = parseUrlVariables(key);
|
||||
const notProvidedChannelParams = notProvidedParams.get(tilde(key));
|
||||
const queryParameters = parseUrlQueryParameters(key);
|
||||
const unknownServerNames = getUnknownServers(parsedJSON, val);
|
||||
|
||||
//channel variable validation: fill return object with missing parameters
|
||||
if (variables) {
|
||||
setNotProvidedParams(
|
||||
variables,
|
||||
val,
|
||||
key,
|
||||
notProvidedChannelParams,
|
||||
notProvidedParams
|
||||
);
|
||||
}
|
||||
|
||||
//channel name validation: fill return object with channels containing query parameters
|
||||
if (queryParameters) {
|
||||
invalidChannelName.set(tilde(key), queryParameters);
|
||||
}
|
||||
|
||||
//server validation: fill return object with unknown server names
|
||||
if (unknownServerNames.length > 0) {
|
||||
unknownServers.set(tilde(key), unknownServerNames);
|
||||
}
|
||||
});
|
||||
|
||||
//combine validation errors of both checks and output them as one array
|
||||
const parameterValidationErrors = groupValidationErrors(
|
||||
'channels',
|
||||
'channel does not have a corresponding parameter object for',
|
||||
notProvidedParams,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
const nameValidationErrors = groupValidationErrors(
|
||||
'channels',
|
||||
'channel contains invalid name with url query parameters',
|
||||
invalidChannelName,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
const serverValidationErrors = groupValidationErrors(
|
||||
'channels',
|
||||
'channel contains servers that are not on the servers list in the root of the document',
|
||||
unknownServers,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
const allValidationErrors = parameterValidationErrors.concat(nameValidationErrors).concat(serverValidationErrors);
|
||||
|
||||
//channel variable validation: throw exception if channel validation fails
|
||||
if (notProvidedParams.size || invalidChannelName.size || unknownServers.size) {
|
||||
throw new ParserError({
|
||||
type: validationError,
|
||||
title: 'Channel validation failed',
|
||||
parsedJSON,
|
||||
validationErrors: allValidationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if tags specified in the following objects have no duplicates: root, operations, operation traits, channels,
|
||||
* messages and message traits.
|
||||
*
|
||||
* @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 validateTags(parsedJSON, asyncapiYAMLorJSON, initialFormat) {
|
||||
const invalidRoot = validateRootTags(parsedJSON);
|
||||
const invalidChannels = validateAllChannelsTags(parsedJSON);
|
||||
const invalidOperationTraits = validateOperationTraitTags(parsedJSON);
|
||||
const invalidMessages = validateMessageTags(parsedJSON);
|
||||
const invalidMessageTraits = validateMessageTraitsTags(parsedJSON);
|
||||
const errorMessage = 'contains duplicate tag names';
|
||||
|
||||
let invalidRootValidationErrors = [];
|
||||
let invalidChannelsValidationErrors = [];
|
||||
let invalidOperationTraitsValidationErrors = [];
|
||||
let invalidMessagesValidationErrors = [];
|
||||
let invalidMessageTraitsValidationErrors = [];
|
||||
|
||||
if (invalidRoot.size) {
|
||||
invalidRootValidationErrors = groupValidationErrors(
|
||||
null,
|
||||
errorMessage,
|
||||
invalidRoot,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
}
|
||||
|
||||
if (invalidChannels.size) {
|
||||
invalidChannelsValidationErrors = groupValidationErrors(
|
||||
'channels',
|
||||
errorMessage,
|
||||
invalidChannels,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
}
|
||||
|
||||
if (invalidOperationTraits.size) {
|
||||
invalidOperationTraitsValidationErrors = groupValidationErrors(
|
||||
'components',
|
||||
errorMessage,
|
||||
invalidOperationTraits,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
}
|
||||
|
||||
if (invalidMessages.size) {
|
||||
invalidMessagesValidationErrors = groupValidationErrors(
|
||||
'components',
|
||||
errorMessage,
|
||||
invalidMessages,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
}
|
||||
|
||||
if (invalidMessageTraits.size) {
|
||||
invalidMessageTraitsValidationErrors = groupValidationErrors(
|
||||
'components',
|
||||
errorMessage,
|
||||
invalidMessageTraits,
|
||||
asyncapiYAMLorJSON,
|
||||
initialFormat
|
||||
);
|
||||
}
|
||||
|
||||
const allValidationErrors = invalidRootValidationErrors
|
||||
.concat(invalidChannelsValidationErrors)
|
||||
.concat(invalidOperationTraitsValidationErrors)
|
||||
.concat(invalidMessagesValidationErrors)
|
||||
.concat(invalidMessageTraitsValidationErrors);
|
||||
|
||||
if (allValidationErrors.length) {
|
||||
throw new ParserError({
|
||||
type: validationError,
|
||||
title: 'Tags validation failed',
|
||||
parsedJSON,
|
||||
validationErrors: allValidationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateRootTags(parsedJSON) {
|
||||
const invalidRoot = new Map();
|
||||
const duplicateNames = parsedJSON.tags && getDuplicateTagNames(parsedJSON.tags);
|
||||
|
||||
if (duplicateNames && duplicateNames.length) {
|
||||
invalidRoot.set('tags', duplicateNames.toString());
|
||||
}
|
||||
|
||||
return invalidRoot;
|
||||
}
|
||||
|
||||
function validateOperationTraitTags(parsedJSON) {
|
||||
const invalidOperationTraits = new Map();
|
||||
|
||||
if (parsedJSON && parsedJSON.components && parsedJSON.components.operationTraits) {
|
||||
Object.keys(parsedJSON.components.operationTraits).forEach((operationTrait) => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const duplicateNames = getDuplicateTagNames(parsedJSON.components.operationTraits[operationTrait].tags);
|
||||
|
||||
if (duplicateNames && duplicateNames.length) {
|
||||
const operationTraitsPath = `operationTraits/${operationTrait}/tags`;
|
||||
invalidOperationTraits.set(
|
||||
operationTraitsPath,
|
||||
duplicateNames.toString()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return invalidOperationTraits;
|
||||
}
|
||||
|
||||
function validateAllChannelsTags(parsedJSON) {
|
||||
const chnls = parsedJSON.channels;
|
||||
if (!chnls) return true;
|
||||
|
||||
const chnlsMap = new Map(Object.entries(chnls));
|
||||
const invalidChannels = new Map();
|
||||
chnlsMap.forEach((channel, channelName) => validateChannelTags(invalidChannels, channel, channelName));
|
||||
|
||||
return invalidChannels;
|
||||
}
|
||||
|
||||
function validateChannelTags(invalidChannels, channel, channelName) {
|
||||
if (channel.publish) {
|
||||
validateOperationTags(invalidChannels, channel.publish, `${tilde(channelName)}/publish`);
|
||||
}
|
||||
|
||||
if (channel.subscribe) {
|
||||
validateOperationTags(invalidChannels, channel.subscribe, `${tilde(channelName)}/subscribe`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check tags in operation and in message.
|
||||
*
|
||||
* @private
|
||||
* @param {Map} invalidChannels map with invalid channel entries
|
||||
* @param {Operation} operation operation object
|
||||
* @param {String} operationPath operation path
|
||||
*/
|
||||
function validateOperationTags(invalidChannels, operation, operationPath) {
|
||||
if (!operation) return;
|
||||
|
||||
tryAddInvalidEntries(invalidChannels, `${operationPath}/tags`, operation.tags);
|
||||
|
||||
if (operation.message) {
|
||||
if (operation.message.oneOf) {
|
||||
operation.message.oneOf.forEach((message, idx) => {
|
||||
tryAddInvalidEntries(invalidChannels, `${operationPath}/message/oneOf/${idx}/tags`, message.tags);
|
||||
});
|
||||
} else {
|
||||
tryAddInvalidEntries(invalidChannels, `${operationPath}/message/tags`, operation.message.tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tryAddInvalidEntries(invalidChannels, key, tags) {
|
||||
const duplicateNames = tags && getDuplicateTagNames(tags);
|
||||
if (duplicateNames && duplicateNames.length) {
|
||||
invalidChannels.set(key, duplicateNames.toString());
|
||||
}
|
||||
}
|
||||
|
||||
function validateMessageTraitsTags(parsedJSON) {
|
||||
const invalidMessageTraits = new Map();
|
||||
|
||||
if (parsedJSON && parsedJSON.components && parsedJSON.components.messageTraits) {
|
||||
Object.keys(parsedJSON.components.messageTraits).forEach((messageTrait) => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const duplicateNames = getDuplicateTagNames(parsedJSON.components.messageTraits[messageTrait].tags);
|
||||
|
||||
if (duplicateNames && duplicateNames.length) {
|
||||
const messageTraitsPath = `messageTraits/${messageTrait}/tags`;
|
||||
invalidMessageTraits.set(messageTraitsPath, duplicateNames.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return invalidMessageTraits;
|
||||
}
|
||||
|
||||
function validateMessageTags(parsedJSON) {
|
||||
const invalidMessages = new Map();
|
||||
|
||||
if (parsedJSON && parsedJSON.components && parsedJSON.components.messages) {
|
||||
Object.keys(parsedJSON.components.messages).forEach((message) => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const duplicateNames = getDuplicateTagNames(parsedJSON.components.messages[message].tags);
|
||||
|
||||
if (duplicateNames && duplicateNames.length) {
|
||||
const messagePath = `messages/${message}/tags`;
|
||||
invalidMessages.set(messagePath, duplicateNames.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return invalidMessages;
|
||||
}
|
||||
|
||||
function getDuplicateTagNames(tags) {
|
||||
if (!tags) return null;
|
||||
|
||||
const tagNames = tags.map((item) => item.name);
|
||||
return tagNames.reduce((acc, item, idx, arr) => {
|
||||
if (arr.indexOf(item) !== idx && acc.indexOf(item) < 0) {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateServerVariables,
|
||||
validateOperationId,
|
||||
validateMessageId,
|
||||
validateServerSecurity,
|
||||
validateChannels,
|
||||
validateTags,
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
const ERROR_URL_PREFIX = 'https://github.com/asyncapi/parser-js/';
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an error while trying to parse an AsyncAPI document.
|
||||
* @alias module:@asyncapi/parser#ParserError
|
||||
* @extends Error
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JS object representation of the error.
|
||||
*/
|
||||
toJS() {
|
||||
return buildError(this, {});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ParserError;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
const parser = require('./parser');
|
||||
const defaultAsyncAPISchemaParser = require('./asyncapiSchemaFormatParser');
|
||||
|
||||
parser.registerSchemaParser(defaultAsyncAPISchemaParser);
|
||||
|
||||
module.exports = parser;
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* @readonly
|
||||
* @enum {SchemaIteratorCallbackType}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The different kind of stages when crawling a schema.
|
||||
*
|
||||
* @typedef SchemaIteratorCallbackType
|
||||
* @property {string} NEW_SCHEMA The crawler just started crawling a schema.
|
||||
* @property {string} END_SCHEMA The crawler just finished crawling a schema.
|
||||
*/
|
||||
const SchemaIteratorCallbackType = Object.freeze({
|
||||
NEW_SCHEMA: 'NEW_SCHEMA',
|
||||
END_SCHEMA: 'END_SCHEMA'
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @readonly
|
||||
* @enum {SchemaTypesToIterate}
|
||||
*/
|
||||
|
||||
/**
|
||||
* The different types of schemas you can iterate
|
||||
*
|
||||
* @typedef SchemaTypesToIterate
|
||||
* @property {string} parameters Crawl all schemas in parameters
|
||||
* @property {string} payloads Crawl all schemas in payloads
|
||||
* @property {string} headers Crawl all schemas in headers
|
||||
* @property {string} components Crawl all schemas in components
|
||||
* @property {string} objects Crawl all schemas of type object
|
||||
* @property {string} arrays Crawl all schemas of type array
|
||||
* @property {string} oneOfs Crawl all schemas in oneOf's
|
||||
* @property {string} allOfs Crawl all schemas in allOf's
|
||||
* @property {string} anyOfs Crawl all schemas in anyOf's
|
||||
* @property {string} nots Crawl all schemas in not field
|
||||
* @property {string} propertyNames Crawl all schemas in propertyNames field
|
||||
* @property {string} patternProperties Crawl all schemas in patternProperties field
|
||||
* @property {string} contains Crawl all schemas in contains field
|
||||
* @property {string} ifs Crawl all schemas in if field
|
||||
* @property {string} thenes Crawl all schemas in then field
|
||||
* @property {string} elses Crawl all schemas in else field
|
||||
* @property {string} dependencies Crawl all schemas in dependencies field
|
||||
* @property {string} definitions Crawl all schemas in definitions field
|
||||
*/
|
||||
const SchemaTypesToIterate = Object.freeze({
|
||||
parameters: 'parameters',
|
||||
payloads: 'payloads',
|
||||
headers: 'headers',
|
||||
components: 'components',
|
||||
objects: 'objects',
|
||||
arrays: 'arrays',
|
||||
oneOfs: 'oneOfs',
|
||||
allOfs: 'allOfs',
|
||||
anyOfs: 'anyOfs',
|
||||
nots: 'nots',
|
||||
propertyNames: 'propertyNames',
|
||||
patternProperties: 'patternProperties',
|
||||
contains: 'contains',
|
||||
ifs: 'ifs',
|
||||
thenes: 'thenes',
|
||||
elses: 'elses',
|
||||
dependencies: 'dependencies',
|
||||
definitions: 'definitions',
|
||||
});
|
||||
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
/**
|
||||
* Traverse current schema and all nested schemas.
|
||||
*
|
||||
* @private
|
||||
* @param {Schema} schema which is being crawled.
|
||||
* @param {(String | Number)} propOrIndex if the schema is from a property/index get the name/number of such.
|
||||
* @param {Object} options
|
||||
* @param {SchemaIteratorCallbackType} [options.callback] callback used when crawling a schema.
|
||||
* @param {SchemaTypesToIterate[]} [options.schemaTypesToIterate] list of schema types to crawl.
|
||||
* @param {Set<Object>} [options.seenSchemas] Set which holds all defined schemas in the tree - it is mainly used to check circular references
|
||||
*/
|
||||
function traverseSchema(schema, propOrIndex, options) { // NOSONAR
|
||||
if (!schema) return;
|
||||
const { callback, schemaTypesToIterate, seenSchemas } = options;
|
||||
|
||||
// handle circular references
|
||||
const jsonSchema = schema.json();
|
||||
if (seenSchemas.has(jsonSchema)) return;
|
||||
seenSchemas.add(jsonSchema);
|
||||
|
||||
// `type` isn't required so save type as array in the fallback
|
||||
let types = schema.type() || [];
|
||||
// change primitive type to array of types for easier handling
|
||||
if (!Array.isArray(types)) {
|
||||
types = [types];
|
||||
}
|
||||
if (!schemaTypesToIterate.includes(SchemaTypesToIterate.objects) && types.includes('object')) return;
|
||||
if (!schemaTypesToIterate.includes(SchemaTypesToIterate.arrays) && types.includes('array')) return;
|
||||
|
||||
// check callback `NEW_SCHEMA` case
|
||||
if (callback(schema, propOrIndex, SchemaIteratorCallbackType.NEW_SCHEMA) === false) return;
|
||||
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.objects) && types.includes('object')) {
|
||||
recursiveSchemaObject(schema, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.arrays) && types.includes('array')) {
|
||||
recursiveSchemaArray(schema, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.oneOfs)) {
|
||||
(schema.oneOf() || []).forEach((combineSchema, idx) => {
|
||||
traverseSchema(combineSchema, idx, options);
|
||||
});
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.anyOfs)) {
|
||||
(schema.anyOf() || []).forEach((combineSchema, idx) => {
|
||||
traverseSchema(combineSchema, idx, options);
|
||||
});
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.allOfs)) {
|
||||
(schema.allOf() || []).forEach((combineSchema, idx) => {
|
||||
traverseSchema(combineSchema, idx, options);
|
||||
});
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.nots) && schema.not()) {
|
||||
traverseSchema(schema.not(), null, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.ifs) && schema.if()) {
|
||||
traverseSchema(schema.if(), null, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.thenes) && schema.then()) {
|
||||
traverseSchema(schema.then(), null, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.elses) && schema.else()) {
|
||||
traverseSchema(schema.else(), null, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.dependencies)) {
|
||||
Object.entries(schema.dependencies() || {}).forEach(([depName, dep]) => {
|
||||
// do not iterate dependent required
|
||||
if (dep && !Array.isArray(dep)) {
|
||||
traverseSchema(dep, depName, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.definitions)) {
|
||||
Object.entries(schema.definitions() || {}).forEach(([defName, def]) => {
|
||||
traverseSchema(def, defName, options);
|
||||
});
|
||||
}
|
||||
|
||||
callback(schema, propOrIndex, SchemaIteratorCallbackType.END_SCHEMA);
|
||||
seenSchemas.delete(jsonSchema);
|
||||
}
|
||||
/* eslint-enable sonarjs/cognitive-complexity */
|
||||
|
||||
/**
|
||||
* Recursively go through schema of object type and execute callback.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @param {SchemaIteratorCallbackType} [options.callback] callback used when crawling a schema.
|
||||
* @param {SchemaTypesToIterate[]} [options.schemaTypesToIterate] list of schema types to crawl.
|
||||
* @param {Set<Object>} [options.seenSchemas] Set which holds all defined schemas in the tree - it is mainly used to check circular references
|
||||
*/
|
||||
function recursiveSchemaObject(schema, options) {
|
||||
Object.entries(schema.properties() || {}).forEach(([propertyName, property]) => {
|
||||
traverseSchema(property, propertyName, options);
|
||||
});
|
||||
|
||||
const additionalProperties = schema.additionalProperties();
|
||||
if (typeof additionalProperties === 'object') {
|
||||
traverseSchema(additionalProperties, null, options);
|
||||
}
|
||||
|
||||
const schemaTypesToIterate = options.schemaTypesToIterate;
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.propertyNames) && schema.propertyNames()) {
|
||||
traverseSchema(schema.propertyNames(), null, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.patternProperties)) {
|
||||
Object.entries(schema.patternProperties() || {}).forEach(([propertyName, property]) => {
|
||||
traverseSchema(property, propertyName, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively go through schema of array type and execute callback.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @param {SchemaIteratorCallbackType} [options.callback] callback used when crawling a schema.
|
||||
* @param {SchemaTypesToIterate[]} [options.schemaTypesToIterate] list of schema types to crawl.
|
||||
* @param {Set<Object>} [options.seenSchemas] Set which holds all defined schemas in the tree - it is mainly used to check circular references
|
||||
*/
|
||||
function recursiveSchemaArray(schema, options) {
|
||||
const items = schema.items();
|
||||
if (items) {
|
||||
if (Array.isArray(items)) {
|
||||
items.forEach((item, idx) => {
|
||||
traverseSchema(item, idx, options);
|
||||
});
|
||||
} else {
|
||||
traverseSchema(items, null, options);
|
||||
}
|
||||
}
|
||||
|
||||
const additionalItems = schema.additionalItems();
|
||||
if (typeof additionalItems === 'object') {
|
||||
traverseSchema(additionalItems, null, options);
|
||||
}
|
||||
|
||||
if (options.schemaTypesToIterate.includes(SchemaTypesToIterate.contains) && schema.contains()) {
|
||||
traverseSchema(schema.contains(), null, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through each channel and for each parameter, and message payload and headers recursively call the callback for each schema.
|
||||
*
|
||||
* @private
|
||||
* @param {AsyncAPIDocument} doc parsed AsyncAPI Document
|
||||
* @param {FoundSchemaCallback} callback callback used when crawling a schema.
|
||||
* @param {SchemaTypesToIterate[]} schemaTypesToIterate list of schema types to crawl.
|
||||
*/
|
||||
function traverseAsyncApiDocument(doc, callback, schemaTypesToIterate) {
|
||||
if (!schemaTypesToIterate) {
|
||||
schemaTypesToIterate = Object.values(SchemaTypesToIterate);
|
||||
}
|
||||
const options = { callback, schemaTypesToIterate, seenSchemas: new Set() };
|
||||
|
||||
if (doc.hasChannels()) {
|
||||
Object.values(doc.channels()).forEach(channel => {
|
||||
traverseChannel(channel, options);
|
||||
});
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.components) && doc.hasComponents()) {
|
||||
const components = doc.components();
|
||||
Object.values(components.messages() || {}).forEach(message => {
|
||||
traverseMessage(message, options);
|
||||
});
|
||||
Object.values(components.schemas() || {}).forEach(schema => {
|
||||
traverseSchema(schema, null, options);
|
||||
});
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.parameters)) {
|
||||
Object.values(components.parameters() || {}).forEach(parameter => {
|
||||
traverseSchema(parameter.schema(), null, options);
|
||||
});
|
||||
}
|
||||
Object.values(components.messageTraits() || {}).forEach(messageTrait => {
|
||||
traverseMessageTrait(messageTrait, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through each schema in channel
|
||||
*
|
||||
* @private
|
||||
* @param {Channel} channel
|
||||
* @param {Object} options
|
||||
* @param {SchemaIteratorCallbackType} [options.callback] callback used when crawling a schema.
|
||||
* @param {SchemaTypesToIterate[]} [options.schemaTypesToIterate] list of schema types to crawl.
|
||||
* @param {Set<Object>} [options.seenSchemas] Set which holds all defined schemas in the tree - it is mainly used to check circular references
|
||||
*/
|
||||
function traverseChannel(channel, options) {
|
||||
if (!channel) return;
|
||||
const { schemaTypesToIterate } = options;
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.parameters)) {
|
||||
Object.values(channel.parameters() || {}).forEach(parameter => {
|
||||
traverseSchema(parameter.schema(), null, options);
|
||||
});
|
||||
}
|
||||
if (channel.hasPublish()) {
|
||||
channel.publish().messages().forEach(message => {
|
||||
traverseMessage(message, options);
|
||||
});
|
||||
}
|
||||
if (channel.hasSubscribe()) {
|
||||
channel.subscribe().messages().forEach(message => {
|
||||
traverseMessage(message, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through each schema in a message
|
||||
*
|
||||
* @private
|
||||
* @param {Message} message
|
||||
* @param {Object} options
|
||||
* @param {SchemaIteratorCallbackType} [options.callback] callback used when crawling a schema.
|
||||
* @param {SchemaTypesToIterate[]} [options.schemaTypesToIterate] list of schema types to crawl.
|
||||
* @param {Set<Object>} [options.seenSchemas] Set which holds all defined schemas in the tree - it is mainly used to check circular references
|
||||
*/
|
||||
function traverseMessage(message, options) {
|
||||
if (!message) return;
|
||||
const { schemaTypesToIterate } = options;
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.headers)) {
|
||||
traverseSchema(message.headers(), null, options);
|
||||
}
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.payloads)) {
|
||||
traverseSchema(message.payload(), null, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through each schema in a messageTrait
|
||||
*
|
||||
* @private
|
||||
* @param {MessageTrait} messageTrait
|
||||
* @param {Object} options
|
||||
* @param {SchemaIteratorCallbackType} [options.callback] callback used when crawling a schema.
|
||||
* @param {SchemaTypesToIterate[]} [options.schemaTypesToIterate] list of schema types to crawl.
|
||||
* @param {Set<Object>} [options.seenSchemas] Set which holds all defined schemas in the tree - it is mainly used to check circular references
|
||||
*/
|
||||
function traverseMessageTrait(messageTrait, options) {
|
||||
if (!messageTrait) return;
|
||||
const { schemaTypesToIterate } = options;
|
||||
if (schemaTypesToIterate.includes(SchemaTypesToIterate.headers)) {
|
||||
traverseSchema(messageTrait.headers(), null, options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SchemaIteratorCallbackType,
|
||||
SchemaTypesToIterate,
|
||||
traverseAsyncApiDocument,
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
module.exports = (txt, reviver, context = 20) => {
|
||||
try {
|
||||
return JSON.parse(txt, reviver);
|
||||
} catch (e) {
|
||||
handleJsonNotString(txt);
|
||||
const syntaxErr = e.message.match(/^Unexpected token.*position\s+(\d+)/i);
|
||||
const errIdxBrokenJson = e.message.match(/^Unexpected end of JSON.*/i) ? txt.length - 1 : null;
|
||||
const errIdx = syntaxErr ? +syntaxErr[1] : errIdxBrokenJson;
|
||||
handleErrIdxNotNull(e, txt, errIdx, context);
|
||||
e.offset = errIdx;
|
||||
const lines = txt.substr(0, errIdx).split('\n');
|
||||
e.startLine = lines.length;
|
||||
e.startColumn = lines[lines.length - 1].length;
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
function handleJsonNotString(txt) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function handleErrIdxNotNull(e, txt, errIdx, context) {
|
||||
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)}'`;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
const { getMapValueByKey } = require('../models/utils');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the common Bindings object.
|
||||
* @mixin
|
||||
*/
|
||||
const MixinBindings = {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasBindings() {
|
||||
return !!(this._json.bindings && Object.keys(this._json.bindings).length);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Object}
|
||||
*/
|
||||
bindings() {
|
||||
return this.hasBindings() ? this._json.bindings : {};
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
bindingProtocols() {
|
||||
return Object.keys(this.bindings());
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the binding.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasBinding(name) {
|
||||
return this.hasBindings() && !!this._json.bindings[String(name)];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the binding.
|
||||
* @returns {(Object | null)}
|
||||
*/
|
||||
binding(name) {
|
||||
return getMapValueByKey(this._json.bindings, name);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = MixinBindings;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
const { getMapValueByKey } = require('../models/utils');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the description field.
|
||||
* @mixin
|
||||
*/
|
||||
const MixinDescription = {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasDescription() {
|
||||
return !!this._json.description;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {(string | null)}
|
||||
*/
|
||||
description() {
|
||||
return getMapValueByKey(this._json, 'description');
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = MixinDescription;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
const { getMapValueOfType } = require('../models/utils');
|
||||
|
||||
const ExternalDocs = require('../models/external-docs');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the ExternalDocs object.
|
||||
* @mixin
|
||||
*/
|
||||
const MixinExternalDocs = {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasExternalDocs() {
|
||||
return !!(this._json.externalDocs && Object.keys(this._json.externalDocs).length);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {(ExternalDocs | null)}
|
||||
*/
|
||||
externalDocs() {
|
||||
return getMapValueOfType(this._json, 'externalDocs', ExternalDocs);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = MixinExternalDocs;
|
||||
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Implements functions to deal with the SpecificationExtensions object.
|
||||
* @mixin
|
||||
*/
|
||||
const MixinSpecificationExtensions = {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasExtensions() {
|
||||
return !!this.extensionKeys().length;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Object<string, any>}
|
||||
*/
|
||||
extensions() {
|
||||
const result = {};
|
||||
Object.entries(this._json).forEach(([key, value]) => {
|
||||
if ((/^x-[\w\d\.\-\_]+$/).test(key)) {
|
||||
result[String(key)] = value;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
extensionKeys() {
|
||||
return Object.keys(this.extensions());
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
extKeys() {
|
||||
return this.extensionKeys();
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} key - Extension key.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasExtension(key) {
|
||||
if (!key.startsWith('x-')) {
|
||||
return false;
|
||||
}
|
||||
return !!this._json[String(key)];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} key - Extension key.
|
||||
* @returns {any}
|
||||
*/
|
||||
extension(key) {
|
||||
if (!key.startsWith('x-')) {
|
||||
return null;
|
||||
}
|
||||
return this._json[String(key)];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} key - Extension key.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasExt(key) {
|
||||
return this.hasExtension(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} key - Extension key.
|
||||
* @returns {any}
|
||||
*/
|
||||
ext(key) {
|
||||
return this.extension(key);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = MixinSpecificationExtensions;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
const Tag = require('../models/tag');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the Tags object.
|
||||
* @mixin
|
||||
*/
|
||||
const MixinTags = {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasTags() {
|
||||
return !!(Array.isArray(this._json.tags) && this._json.tags.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Tag[]}
|
||||
*/
|
||||
tags() {
|
||||
return this.hasTags() ? this._json.tags.map(t => new Tag(t)) : [];
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
tagNames() {
|
||||
return this.hasTags() ? this._json.tags.map(t => t.name) : [];
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the tag.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasTag(name) {
|
||||
return this.hasTags() && this._json.tags.some(t => t.name === name);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the tag.
|
||||
* @returns {(Tag | null)}
|
||||
*/
|
||||
tag(name) {
|
||||
const tg = this.hasTags() && this._json.tags.find(t => t.name === name);
|
||||
return tg ? new Tag(tg) : null;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = MixinTags;
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
const { createMapOfType, getMapValueOfType, mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const Info = require('./info');
|
||||
const Server = require('./server');
|
||||
const Channel = require('./channel');
|
||||
const Components = require('./components');
|
||||
const MixinExternalDocs = require('../mixins/external-docs');
|
||||
const MixinTags = require('../mixins/tags');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
const {xParserSpecParsed, xParserSpecStringified, xParserCircle} = require('../constants');
|
||||
const {assignNameToAnonymousMessages, assignNameToComponentMessages, assignUidToComponentSchemas, assignUidToParameterSchemas, assignIdToAnonymousSchemas, assignUidToComponentParameterSchemas} = require('../anonymousNaming');
|
||||
const {traverseAsyncApiDocument} = require('../iterators');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the AsyncAPI document.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#AsyncAPIDocument
|
||||
* @extends Base
|
||||
* @mixes MixinTags
|
||||
* @mixes MixinExternalDocs
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {AsyncAPIDocument}
|
||||
*/
|
||||
class AsyncAPIDocument extends Base {
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
if (this.ext(xParserSpecParsed) === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
assignNameToComponentMessages(this);
|
||||
assignNameToAnonymousMessages(this);
|
||||
|
||||
assignUidToComponentSchemas(this);
|
||||
assignUidToComponentParameterSchemas(this);
|
||||
assignUidToParameterSchemas(this);
|
||||
assignIdToAnonymousSchemas(this);
|
||||
|
||||
// We add `x-parser-spec-parsed=true` extension to determine that the specification is parsed and validated
|
||||
// and when the specification is re-passed to the AsyncAPIDocument constructor,
|
||||
// there is no need to perform the same operations.
|
||||
this.json()[String(xParserSpecParsed)] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
version() {
|
||||
return this._json.asyncapi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Info}
|
||||
*/
|
||||
info() {
|
||||
return new Info(this._json.info);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
id() {
|
||||
return this._json.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasServers() {
|
||||
return !!this._json.servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Server>}
|
||||
*/
|
||||
servers() {
|
||||
return createMapOfType(this._json.servers, Server);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
serverNames() {
|
||||
if (!this._json.servers) return [];
|
||||
return Object.keys(this._json.servers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the server.
|
||||
* @returns {Server}
|
||||
*/
|
||||
server(name) {
|
||||
return getMapValueOfType(this._json.servers, name, Server);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasDefaultContentType() {
|
||||
return !!this._json.defaultContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string|null}
|
||||
*/
|
||||
defaultContentType() {
|
||||
return this._json.defaultContentType || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasChannels() {
|
||||
return !!this._json.channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Channel>}
|
||||
*/
|
||||
channels() {
|
||||
return createMapOfType(this._json.channels, Channel, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
channelNames() {
|
||||
if (!this._json.channels) return [];
|
||||
return Object.keys(this._json.channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the channel.
|
||||
* @returns {Channel}
|
||||
*/
|
||||
channel(name) {
|
||||
return getMapValueOfType(this._json.channels, name, Channel, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasComponents() {
|
||||
return !!this._json.components;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Components}
|
||||
*/
|
||||
components() {
|
||||
if (!this._json.components) return null;
|
||||
return new Components(this._json.components);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasMessages() {
|
||||
return !!this.allMessages().size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Map<string, Message>}
|
||||
*/
|
||||
allMessages() {
|
||||
const messages = new Map();
|
||||
|
||||
if (this.hasChannels()) {
|
||||
this.channelNames().forEach(channelName => {
|
||||
const channel = this.channel(channelName);
|
||||
if (channel.hasPublish()) {
|
||||
channel.publish().messages().forEach(m => {
|
||||
messages.set(m.uid(), m);
|
||||
});
|
||||
}
|
||||
if (channel.hasSubscribe()) {
|
||||
channel.subscribe().messages().forEach(m => {
|
||||
messages.set(m.uid(), m);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.hasComponents()) {
|
||||
Object.values(this.components().messages()).forEach(m => {
|
||||
messages.set(m.uid(), m);
|
||||
});
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Map<string, Schema>}
|
||||
*/
|
||||
allSchemas() {
|
||||
const schemas = new Map();
|
||||
const allSchemasCallback = (schema) => {
|
||||
if (schema.uid()) {
|
||||
schemas.set(schema.uid(), schema);
|
||||
}
|
||||
};
|
||||
traverseAsyncApiDocument(this, allSchemasCallback);
|
||||
return schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasCircular() {
|
||||
return !!this._json[String(xParserCircle)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used when crawling a schema.
|
||||
* @callback module:@asyncapi/parser.TraverseSchemas
|
||||
* @param {Schema} schema which is being crawled
|
||||
* @param {String} propName if the schema is from a property get the name of such
|
||||
* @param {SchemaIteratorCallbackType} callbackType is the schema a new one or is the crawler finishing one.
|
||||
* @returns {boolean} should the crawler continue crawling the schema?
|
||||
*/
|
||||
|
||||
/**
|
||||
* Traverse schemas in the document and select which types of schemas to include.
|
||||
* By default all schemas are iterated
|
||||
* @param {TraverseSchemas} callback
|
||||
* @param {SchemaTypesToIterate[]} schemaTypesToIterate
|
||||
*/
|
||||
traverseSchemas(callback, schemaTypesToIterate) {
|
||||
traverseAsyncApiDocument(this, callback, schemaTypesToIterate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a valid AsyncAPI document to a JavaScript Object Notation (JSON) string.
|
||||
* A stringified AsyncAPI document using this function should be parsed via the AsyncAPIDocument.parse() function - the JSON.parse() function is not compatible.
|
||||
*
|
||||
* @param {AsyncAPIDocument} doc A valid AsyncAPIDocument instance.
|
||||
* @param {(number | string)=} space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.
|
||||
* @returns {string}
|
||||
*/
|
||||
static stringify(doc, space) {
|
||||
const rawDoc = doc.json();
|
||||
const copiedDoc = { ...rawDoc };
|
||||
copiedDoc[String(xParserSpecStringified)] = true;
|
||||
return JSON.stringify(copiedDoc, refReplacer(), space);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a valid stringified AsyncAPIDocument instance into an AsyncAPIDocument instance.
|
||||
*
|
||||
* @param {string} doc A valid stringified AsyncAPIDocument instance.
|
||||
* @returns {AsyncAPIDocument}
|
||||
*/
|
||||
static parse(doc) {
|
||||
let parsedJSON = doc;
|
||||
if (typeof doc === 'string') {
|
||||
parsedJSON = JSON.parse(doc);
|
||||
} else if (typeof doc === 'object') {
|
||||
// shall copy
|
||||
parsedJSON = { ...parsedJSON };
|
||||
}
|
||||
|
||||
// the `doc` must be an AsyncAPI parsed document
|
||||
if (typeof parsedJSON !== 'object' || !parsedJSON[String(xParserSpecParsed)]) {
|
||||
throw new Error('Cannot parse invalid AsyncAPI document');
|
||||
}
|
||||
// if the `doc` is not stringified via the `stringify` static method then immediately return a model.
|
||||
if (!parsedJSON[String(xParserSpecStringified)]) {
|
||||
return new AsyncAPIDocument(parsedJSON);
|
||||
}
|
||||
// remove `x-parser-spec-stringified` extension
|
||||
delete parsedJSON[String(xParserSpecStringified)];
|
||||
|
||||
const objToPath = new Map();
|
||||
const pathToObj = new Map();
|
||||
traverseStringifiedDoc(parsedJSON, undefined, parsedJSON, objToPath, pathToObj);
|
||||
|
||||
return new AsyncAPIDocument(parsedJSON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacer function (that transforms the result) for AsyncAPI.stringify() function.
|
||||
* Handles circular references by replacing it by JSONPath notation.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function refReplacer() {
|
||||
const modelPaths = new Map();
|
||||
const paths = new Map();
|
||||
let init = null;
|
||||
|
||||
return function(field, value) {
|
||||
// `this` points to parent object of given value - some object or array
|
||||
const pathPart = modelPaths.get(this) + (Array.isArray(this) ? `[${field}]` : `.${ field}`);
|
||||
|
||||
// check if `objOrPath` has "reference"
|
||||
const isComplex = value === Object(value);
|
||||
if (isComplex) {
|
||||
modelPaths.set(value, pathPart);
|
||||
}
|
||||
|
||||
const savedPath = paths.get(value) || '';
|
||||
if (!savedPath && isComplex) {
|
||||
const valuePath = pathPart.replace(/undefined\.\.?/,'');
|
||||
paths.set(value, valuePath);
|
||||
}
|
||||
|
||||
const prefixPath = savedPath[0] === '[' ? '$' : '$.';
|
||||
let val = savedPath ? `$ref:${prefixPath}${savedPath}` : value;
|
||||
if (init === null) {
|
||||
init = value;
|
||||
} else if (val === init) {
|
||||
val = '$ref:$';
|
||||
}
|
||||
return val;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses stringified AsyncAPIDocument and replaces all JSON Pointer instance with real object reference.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} parent object
|
||||
* @param {string} field of parent object
|
||||
* @param {Object} root reference to the original object
|
||||
* @param {Map} objToPath
|
||||
* @param {Map} pathToObj
|
||||
*/
|
||||
function traverseStringifiedDoc(parent, field, root, objToPath, pathToObj) {
|
||||
let objOrPath = parent;
|
||||
let path = '$ref:$';
|
||||
|
||||
if (field !== undefined) {
|
||||
// here can be string with `$ref` prefix or normal value
|
||||
objOrPath = parent[String(field)];
|
||||
const concatenatedPath = field ? `.${field}` : '';
|
||||
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : concatenatedPath);
|
||||
}
|
||||
|
||||
objToPath.set(objOrPath, path);
|
||||
pathToObj.set(path, objOrPath);
|
||||
|
||||
const ref = pathToObj.get(objOrPath);
|
||||
if (ref) {
|
||||
parent[String(field)] = ref;
|
||||
}
|
||||
if (objOrPath === '$ref:$' || ref === '$ref:$') { // NOSONAR
|
||||
parent[String(field)] = root;
|
||||
}
|
||||
|
||||
// traverse all keys, only if object is array/object
|
||||
if (objOrPath === Object(objOrPath)) {
|
||||
for (const f in objOrPath) {
|
||||
traverseStringifiedDoc(objOrPath, f, root, objToPath, pathToObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(AsyncAPIDocument, MixinTags, MixinExternalDocs, MixinSpecificationExtensions);
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
const ParserError = require('../errors/parser-error');
|
||||
|
||||
/**
|
||||
* Implements common functionality for all the models.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Base
|
||||
* @returns {Base}
|
||||
*/
|
||||
class Base {
|
||||
constructor(json) {
|
||||
if (json === undefined || json === null) throw new ParserError(`Invalid JSON to instantiate the ${this.constructor.name} object.`);
|
||||
this._json = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [key] A key to retrieve from the JSON object.
|
||||
* @returns {any}
|
||||
*/
|
||||
json(key) {
|
||||
if (key === undefined) return this._json;
|
||||
if (!this._json) return;
|
||||
return this._json[String(key)];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Base;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const Schema = require('./schema');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a ChannelParameter object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#ChannelParameter
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {ChannelParameter}
|
||||
*/
|
||||
class ChannelParameter extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
location() {
|
||||
return this._json.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
schema() {
|
||||
if (!this._json.schema) return null;
|
||||
return new Schema(this._json.schema);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(ChannelParameter, MixinDescription, MixinSpecificationExtensions);
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
const { createMapOfType, getMapValueOfType, mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const ChannelParameter = require('./channel-parameter');
|
||||
const PublishOperation = require('./publish-operation');
|
||||
const SubscribeOperation = require('./subscribe-operation');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinBindings = require('../mixins/bindings');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a Channel object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Channel
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinBindings
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {Channel}
|
||||
*/
|
||||
class Channel extends Base {
|
||||
/**
|
||||
* @returns {Object<string, ChannelParameter>}
|
||||
*/
|
||||
parameters() {
|
||||
return createMapOfType(this._json.parameters, ChannelParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the parameter.
|
||||
* @returns {ChannelParameter}
|
||||
*/
|
||||
parameter(name) {
|
||||
return getMapValueOfType(this._json.parameters, name, ChannelParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasParameters() {
|
||||
return !!this._json.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasServers() {
|
||||
return !!this._json.servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String[]}
|
||||
*/
|
||||
servers() {
|
||||
if (!this._json.servers) return [];
|
||||
return this._json.servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index - Index of the server.
|
||||
* @returns {String}
|
||||
*/
|
||||
server(index) {
|
||||
if (!this._json.servers) return null;
|
||||
if (typeof index !== 'number') return null;
|
||||
if (index > this._json.servers.length - 1) return null;
|
||||
return this._json.servers[+index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {PublishOperation}
|
||||
*/
|
||||
publish() {
|
||||
if (!this._json.publish) return null;
|
||||
return new PublishOperation(this._json.publish);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {SubscribeOperation}
|
||||
*/
|
||||
subscribe() {
|
||||
if (!this._json.subscribe) return null;
|
||||
return new SubscribeOperation(this._json.subscribe);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasPublish() {
|
||||
return !!this._json.publish;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasSubscribe() {
|
||||
return !!this._json.subscribe;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(Channel, MixinDescription, MixinBindings, MixinSpecificationExtensions);
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
const { createMapOfType, getMapValueOfType, mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const Channel = require('./channel');
|
||||
const Message = require('./message');
|
||||
const Schema = require('./schema');
|
||||
const SecurityScheme = require('./security-scheme');
|
||||
const Server = require('./server');
|
||||
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');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a Components object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Components
|
||||
* @extends Base
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {Components}
|
||||
*/
|
||||
class Components extends Base {
|
||||
/**
|
||||
* @returns {Object<string, Channel>}
|
||||
*/
|
||||
channels() {
|
||||
return createMapOfType(this._json.channels, Channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasChannels() {
|
||||
return !!this._json.channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the channel.
|
||||
* @returns {Channel}
|
||||
*/
|
||||
channel(name) {
|
||||
return getMapValueOfType(this._json.channels, name, Channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Message>}
|
||||
*/
|
||||
messages() {
|
||||
return createMapOfType(this._json.messages, Message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasMessages() {
|
||||
return !!this._json.messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the message.
|
||||
* @returns {Message}
|
||||
*/
|
||||
message(name) {
|
||||
return getMapValueOfType(this._json.messages, name, Message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Schema>}
|
||||
*/
|
||||
schemas() {
|
||||
return createMapOfType(this._json.schemas, Schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasSchemas() {
|
||||
return !!this._json.schemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the schema.
|
||||
* @returns {Schema}
|
||||
*/
|
||||
schema(name) {
|
||||
return getMapValueOfType(this._json.schemas, name, Schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, SecurityScheme>}
|
||||
*/
|
||||
securitySchemes() {
|
||||
return createMapOfType(this._json.securitySchemes, SecurityScheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasSecuritySchemes() {
|
||||
return !!this._json.securitySchemes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the security schema.
|
||||
* @returns {SecurityScheme}
|
||||
*/
|
||||
securityScheme(name) {
|
||||
return getMapValueOfType(this._json.securitySchemes, name, SecurityScheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Server>}
|
||||
*/
|
||||
servers() {
|
||||
return createMapOfType(this._json.servers, Server);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasServers() {
|
||||
return !!this._json.servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the server.
|
||||
* @returns {Server}
|
||||
*/
|
||||
server(name) {
|
||||
return getMapValueOfType(this._json.servers, name, Server);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, ChannelParameter>}
|
||||
*/
|
||||
parameters() {
|
||||
return createMapOfType(this._json.parameters, ChannelParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasParameters() {
|
||||
return !!this._json.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the channel parameter.
|
||||
* @returns {ChannelParameter}
|
||||
*/
|
||||
parameter(name) {
|
||||
return getMapValueOfType(this._json.parameters, name, ChannelParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, CorrelationId>}
|
||||
*/
|
||||
correlationIds() {
|
||||
return createMapOfType(this._json.correlationIds, CorrelationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasCorrelationIds() {
|
||||
return !!this._json.correlationIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the correlationId.
|
||||
* @returns {CorrelationId}
|
||||
*/
|
||||
correlationId(name) {
|
||||
return getMapValueOfType(this._json.correlationIds, name, CorrelationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, OperationTrait>}
|
||||
*/
|
||||
operationTraits() {
|
||||
return createMapOfType(this._json.operationTraits, OperationTrait);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasOperationTraits() {
|
||||
return !!this._json.operationTraits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the operation trait.
|
||||
* @returns {OperationTrait}
|
||||
*/
|
||||
operationTrait(name) {
|
||||
return getMapValueOfType(this._json.operationTraits, name, OperationTrait);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, MessageTrait>}
|
||||
*/
|
||||
messageTraits() {
|
||||
return createMapOfType(this._json.messageTraits, MessageTrait);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasMessageTraits() {
|
||||
return !!this._json.messageTraits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the message trait.
|
||||
* @returns {MessageTrait}
|
||||
*/
|
||||
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);
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the Contact object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Contact
|
||||
* @extends Base
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {Contact}
|
||||
*/
|
||||
class Contact extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
name() {
|
||||
return this._json.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
url() {
|
||||
return this._json.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
email() {
|
||||
return this._json.email;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(Contact, MixinSpecificationExtensions);
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a CorrelationId object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#CorrelationId
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {CorrelationId}
|
||||
*/
|
||||
class CorrelationId extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
location() {
|
||||
return this._json.location;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(CorrelationId, MixinSpecificationExtensions, MixinDescription);
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with an ExternalDocs object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#ExternalDocs
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {ExternalDocs}
|
||||
*/
|
||||
class ExternalDocs extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
url() {
|
||||
return this._json.url;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(ExternalDocs, MixinDescription, MixinSpecificationExtensions);
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const License = require('./license');
|
||||
const Contact = require('./contact');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the Info object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Info
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {Info}
|
||||
*/
|
||||
class Info extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
title() {
|
||||
return this._json.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
version() {
|
||||
return this._json.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {(string | undefined)}
|
||||
*/
|
||||
termsOfService() {
|
||||
return this._json.termsOfService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {License}
|
||||
*/
|
||||
license() {
|
||||
if (!this._json.license) return null;
|
||||
return new License(this._json.license);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Contact}
|
||||
*/
|
||||
contact() {
|
||||
if (!this._json.contact) return null;
|
||||
return new Contact(this._json.contact);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(Info, MixinDescription, MixinSpecificationExtensions);
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the License object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#License
|
||||
* @extends Base
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {License}
|
||||
*/
|
||||
class License extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
name() {
|
||||
return this._json.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
url() {
|
||||
return this._json.url;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(License, MixinSpecificationExtensions);
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const MessageTraitable = require('./message-traitable');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a MessageTrait object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#MessageTrait
|
||||
* @extends MessageTraitable
|
||||
* @returns {MessageTrait}
|
||||
*/
|
||||
class MessageTrait extends MessageTraitable {
|
||||
}
|
||||
|
||||
module.exports = MessageTrait;
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
const { getMapValueOfType, mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const Schema = require('./schema');
|
||||
const CorrelationId = require('./correlation-id');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinExternalDocs = require('../mixins/external-docs');
|
||||
const MixinTags = require('../mixins/tags');
|
||||
const MixinBindings = require('../mixins/bindings');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a the common properties that Message and MessageTrait objects have.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#MessageTraitable
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinTags
|
||||
* @mixes MixinExternalDocs
|
||||
* @mixes MixinBindings
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {MessageTraitable}
|
||||
*/
|
||||
class MessageTraitable extends Base {
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
headers() {
|
||||
if (!this._json.headers) return null;
|
||||
return new Schema(this._json.headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the header.
|
||||
* @returns {Schema}
|
||||
*/
|
||||
header(name) {
|
||||
if (!this._json.headers) return null;
|
||||
return getMapValueOfType(this._json.headers.properties, name, Schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
id() {
|
||||
return this._json.messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {CorrelationId}
|
||||
*/
|
||||
correlationId() {
|
||||
if (!this._json.correlationId) return null;
|
||||
return new CorrelationId(this._json.correlationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
schemaFormat() {
|
||||
return this._json.schemaFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
contentType() {
|
||||
return this._json.contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
name() {
|
||||
return this._json.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
title() {
|
||||
return this._json.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
summary() {
|
||||
return this._json.summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {any[]}
|
||||
*/
|
||||
examples() {
|
||||
return this._json.examples;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(MessageTraitable, MixinDescription, MixinTags, MixinExternalDocs, MixinBindings, MixinSpecificationExtensions);
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
const MessageTrait = require('./message-trait');
|
||||
const MessageTraitable = require('./message-traitable');
|
||||
const Schema = require('./schema');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a Message object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Message
|
||||
* @extends MessageTraitable
|
||||
* @returns {Message}
|
||||
*/
|
||||
class Message extends MessageTraitable {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
uid() {
|
||||
return this.id() || this.name() || this.ext('x-parser-message-name') || Buffer.from(JSON.stringify(this._json)).toString('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
payload() {
|
||||
if (!this._json.payload) return null;
|
||||
return new Schema(this._json.payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {MessageTrait[]}
|
||||
*/
|
||||
traits() {
|
||||
const traits = this._json['x-parser-original-traits'] || this._json.traits;
|
||||
if (!traits) return [];
|
||||
return traits.map(t => new MessageTrait(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasTraits() {
|
||||
return !!this._json['x-parser-original-traits'] || !!this._json.traits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {any}
|
||||
*/
|
||||
originalPayload() {
|
||||
return this._json['x-parser-original-payload'] || this.payload();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
originalSchemaFormat() {
|
||||
return this._json['x-parser-original-schema-format'] || this.schemaFormat();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Message;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a OAuthFlow object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#OAuthFlow
|
||||
* @extends Base
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {OAuthFlow}
|
||||
*/
|
||||
class OAuthFlow extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
authorizationUrl() {
|
||||
return this._json.authorizationUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
tokenUrl() {
|
||||
return this._json.tokenUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
refreshUrl() {
|
||||
return this._json.refreshUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, string>}
|
||||
*/
|
||||
scopes() {
|
||||
return this._json.scopes;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(OAuthFlow, MixinSpecificationExtensions);
|
||||
Generated
Vendored
+13
@@ -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;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const OperationTraitable = require('./operation-traitable');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a OperationTrait object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#OperationTrait
|
||||
* @extends OperationTraitable
|
||||
* @returns {OperationTrait}
|
||||
*/
|
||||
class OperationTrait extends OperationTraitable {
|
||||
}
|
||||
|
||||
module.exports = OperationTrait;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinTags = require('../mixins/tags');
|
||||
const MixinExternalDocs = require('../mixins/external-docs');
|
||||
const MixinBindings = require('../mixins/bindings');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with the common properties Operation and OperationTrait object have.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#OperationTraitable
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinTags
|
||||
* @mixes MixinExternalDocs
|
||||
* @mixes MixinBindings
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {OperationTraitable}
|
||||
*/
|
||||
class OperationTraitable extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
id() {
|
||||
return this._json.operationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
summary() {
|
||||
return this._json.summary;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(OperationTraitable, MixinDescription, MixinTags, MixinExternalDocs, MixinBindings, MixinSpecificationExtensions);
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
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.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Operation
|
||||
* @extends OperationTraitable
|
||||
* @returns {Operation}
|
||||
*/
|
||||
class Operation extends OperationTraitable {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasMultipleMessages() {
|
||||
if (this._json.message && this._json.message.oneOf && this._json.message.oneOf.length > 1) return true;
|
||||
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
||||
if (!this._json.message) return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {OperationTrait[]}
|
||||
*/
|
||||
traits() {
|
||||
const traits = this._json['x-parser-original-traits'] || this._json.traits;
|
||||
if (!traits) return [];
|
||||
return traits.map(t => new OperationTrait(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasTraits() {
|
||||
return !!this._json['x-parser-original-traits'] || !!this._json.traits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Message[]}
|
||||
*/
|
||||
messages() {
|
||||
if (!this._json.message) return [];
|
||||
if (this._json.message.oneOf) return this._json.message.oneOf.map(m => new Message(m));
|
||||
return [new Message(this._json.message)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Message}
|
||||
*/
|
||||
message(index) {
|
||||
if (!this._json.message) return null;
|
||||
if (this._json.message.oneOf && this._json.message.oneOf.length === 1) return new Message(this._json.message.oneOf[0]);
|
||||
if (!this._json.message.oneOf) return new Message(this._json.message);
|
||||
if (typeof index !== 'number') return null;
|
||||
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;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
const Operation = require('./operation');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a PublishOperation object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#PublishOperation
|
||||
* @extends Operation
|
||||
* @returns {PublishOperation}
|
||||
*/
|
||||
class PublishOperation extends Operation {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPublish() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSubscribe() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
kind() {
|
||||
return 'publish';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PublishOperation;
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
const { createMapOfType, getMapValueOfType, mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const {xParserCircle, xParserCircleProps} = require('../constants');
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinExternalDocs = require('../mixins/external-docs');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a Schema object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Schema
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinExternalDocs
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {Schema}
|
||||
*/
|
||||
class Schema extends Base {
|
||||
/**
|
||||
* Instantiates a schema object
|
||||
*
|
||||
* @constructor
|
||||
* @param {any} json Schema definition
|
||||
* @param {Object=} options
|
||||
* @param {Schema=} options.parent Parent schema definition
|
||||
*/
|
||||
constructor(json, options) {
|
||||
super(json);
|
||||
this.options = options || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
uid() {
|
||||
return this.$id() || this.ext('x-parser-schema-id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
$id() {
|
||||
return this._json.$id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
multipleOf() {
|
||||
return this._json.multipleOf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
maximum() {
|
||||
return this._json.maximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
exclusiveMaximum() {
|
||||
return this._json.exclusiveMaximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
minimum() {
|
||||
return this._json.minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
exclusiveMinimum() {
|
||||
return this._json.exclusiveMinimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
maxLength() {
|
||||
return this._json.maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
minLength() {
|
||||
return this._json.minLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
pattern() {
|
||||
return this._json.pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
maxItems() {
|
||||
return this._json.maxItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
minItems() {
|
||||
return this._json.minItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
uniqueItems() {
|
||||
return !!this._json.uniqueItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
maxProperties() {
|
||||
return this._json.maxProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
minProperties() {
|
||||
return this._json.minProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
required() {
|
||||
return this._json.required;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {any[]}
|
||||
*/
|
||||
enum() {
|
||||
return this._json.enum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string|string[]}
|
||||
*/
|
||||
type() {
|
||||
return this._json.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema[]}
|
||||
*/
|
||||
allOf() {
|
||||
if (!this._json.allOf) return null;
|
||||
return this._json.allOf.map(s => new Schema(s, { parent: this }));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema[]}
|
||||
*/
|
||||
oneOf() {
|
||||
if (!this._json.oneOf) return null;
|
||||
return this._json.oneOf.map(s => new Schema(s, { parent: this }));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema[]}
|
||||
*/
|
||||
anyOf() {
|
||||
if (!this._json.anyOf) return null;
|
||||
return this._json.anyOf.map(s => new Schema(s, { parent: this }));
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
not() {
|
||||
if (!this._json.not) return null;
|
||||
return new Schema(this._json.not, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema|Schema[]}
|
||||
*/
|
||||
items() {
|
||||
if (!this._json.items) return null;
|
||||
if (Array.isArray(this._json.items)) {
|
||||
return this._json.items.map(s => new Schema(s, { parent: this }));
|
||||
}
|
||||
return new Schema(this._json.items, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Schema>}
|
||||
*/
|
||||
properties() {
|
||||
return createMapOfType(this._json.properties, Schema, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the property.
|
||||
* @returns {Schema}
|
||||
*/
|
||||
property(name) {
|
||||
return getMapValueOfType(this._json.properties, name, Schema, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean|Schema}
|
||||
*/
|
||||
additionalProperties() {
|
||||
const ap = this._json.additionalProperties;
|
||||
if (ap === undefined || ap === null) return;
|
||||
if (typeof ap === 'boolean') return ap;
|
||||
return new Schema(ap, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
additionalItems() {
|
||||
const ai = this._json.additionalItems;
|
||||
if (ai === undefined || ai === null) return;
|
||||
return new Schema(ai, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Schema>}
|
||||
*/
|
||||
patternProperties() {
|
||||
return createMapOfType(this._json.patternProperties, Schema, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {any}
|
||||
*/
|
||||
const() {
|
||||
return this._json.const;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
contains() {
|
||||
if (!this._json.contains) return null;
|
||||
return new Schema(this._json.contains, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Schema|string[]>}
|
||||
*/
|
||||
dependencies() {
|
||||
if (!this._json.dependencies) return null;
|
||||
const result = {};
|
||||
Object.entries(this._json.dependencies).forEach(([key, value]) => {
|
||||
result[String(key)] = !Array.isArray(value) ? new Schema(value, { parent: this }) : value;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
propertyNames() {
|
||||
if (!this._json.propertyNames) return null;
|
||||
return new Schema(this._json.propertyNames, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
if() {
|
||||
if (!this._json.if) return null;
|
||||
return new Schema(this._json.if, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
then() {
|
||||
if (!this._json.then) return null;
|
||||
return new Schema(this._json.then, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
else() {
|
||||
if (!this._json.else) return null;
|
||||
return new Schema(this._json.else, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
format() {
|
||||
return this._json.format;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
contentEncoding() {
|
||||
return this._json.contentEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
contentMediaType() {
|
||||
return this._json.contentMediaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, Schema>}
|
||||
*/
|
||||
definitions() {
|
||||
return createMapOfType(this._json.definitions, Schema, { parent: this });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
title() {
|
||||
return this._json.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {any}
|
||||
*/
|
||||
default() {
|
||||
return this._json.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
deprecated() {
|
||||
return this._json.deprecated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
discriminator() {
|
||||
return this._json.discriminator;
|
||||
}
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
readOnly() {
|
||||
return !!this._json.readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
writeOnly() {
|
||||
return !!this._json.writeOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {any[]}
|
||||
*/
|
||||
examples() {
|
||||
return this._json.examples;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isBooleanSchema() {
|
||||
return typeof this._json === 'boolean';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isCircular() {
|
||||
if (!!this.ext(xParserCircle)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let parent = this.options.parent;
|
||||
while (parent) {
|
||||
if (parent._json === this._json) return true;
|
||||
parent = parent.options && parent.options.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Schema}
|
||||
*/
|
||||
circularSchema() {
|
||||
let parent = this.options.parent;
|
||||
while (parent) {
|
||||
if (parent._json === this._json) return parent;
|
||||
parent = parent.options && parent.options.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasCircularProps() {
|
||||
if (Array.isArray(this.ext(xParserCircleProps))) {
|
||||
return this.ext(xParserCircleProps).length > 0;
|
||||
}
|
||||
return Object.entries(this.properties() || {})
|
||||
.map(([propertyName, property]) => {
|
||||
if (property.isCircular()) return propertyName;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @returns {string[]}
|
||||
*/
|
||||
circularProps() {
|
||||
if (Array.isArray(this.ext(xParserCircleProps))) {
|
||||
return this.ext(xParserCircleProps);
|
||||
}
|
||||
return Object.entries(this.properties() || {})
|
||||
.map(([propertyName, property]) => {
|
||||
if (property.isCircular()) return propertyName;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(Schema, MixinDescription, MixinExternalDocs, MixinSpecificationExtensions);
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
const { createMapOfType, mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const OAuthFlow = require('./oauth-flow');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a SecurityScheme object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#SecurityScheme
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {SecurityScheme}
|
||||
*/
|
||||
class SecurityScheme extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
type() {
|
||||
return this._json.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
name() {
|
||||
return this._json.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
in() {
|
||||
return this._json.in;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
scheme() {
|
||||
return this._json.scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
bearerFormat() {
|
||||
return this._json.bearerFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
openIdConnectUrl() {
|
||||
return this._json.openIdConnectUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, OAuthFlow>}
|
||||
*/
|
||||
flows() {
|
||||
return createMapOfType(this._json.flows, OAuthFlow);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(SecurityScheme, MixinDescription, MixinSpecificationExtensions);
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
const Base = require('./base');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a ServerSecurityRequirement object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#ServerSecurityRequirement
|
||||
* @extends Base
|
||||
* @returns {ServerSecurityRequirement}
|
||||
*/
|
||||
class ServerSecurityRequirement extends Base {
|
||||
}
|
||||
|
||||
module.exports = ServerSecurityRequirement;
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a ServerVariable object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#ServerVariable
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {ServerVariable}
|
||||
*/
|
||||
class ServerVariable extends Base {
|
||||
/**
|
||||
* @returns {any[]}
|
||||
*/
|
||||
allowedValues() {
|
||||
return this._json.enum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the variable.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
allows(name) {
|
||||
if (this._json.enum === undefined) return true;
|
||||
return this._json.enum.includes(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAllowedValues() {
|
||||
return this._json.enum !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
defaultValue() {
|
||||
return this._json.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasDefaultValue() {
|
||||
return this._json.default !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
examples() {
|
||||
return this._json.examples;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(ServerVariable, MixinDescription, MixinSpecificationExtensions);
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
const { createMapOfType, getMapValueOfType, mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
const ServerVariable = require('./server-variable');
|
||||
const ServerSecurityRequirement = require('./server-security-requirement');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinBindings = require('../mixins/bindings');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
const MixinTags = require('../mixins/tags');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a Server object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Server
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinBindings
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @mixes MixinTags
|
||||
* @returns {Server}
|
||||
*/
|
||||
class Server extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
url() {
|
||||
return this._json.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
protocol() {
|
||||
return this._json.protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
protocolVersion() {
|
||||
return this._json.protocolVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object<string, ServerVariable>}
|
||||
*/
|
||||
variables() {
|
||||
return createMapOfType(this._json.variables, ServerVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name - Name of the server variable.
|
||||
* @returns {ServerVariable}
|
||||
*/
|
||||
variable(name) {
|
||||
return getMapValueOfType(this._json.variables, name, ServerVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasVariables() {
|
||||
return !!this._json.variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ServerSecurityRequirement[]}
|
||||
*/
|
||||
security() {
|
||||
if (!this._json.security) return null;
|
||||
return this._json.security.map(sec => new ServerSecurityRequirement(sec));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(Server, MixinDescription, MixinBindings, MixinSpecificationExtensions, MixinTags);
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
const Operation = require('./operation');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a SubscribeOperation object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#SubscribeOperation
|
||||
* @extends Operation
|
||||
* @returns {SubscribeOperation}
|
||||
*/
|
||||
class SubscribeOperation extends Operation {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPublish() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSubscribe() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
kind() {
|
||||
return 'subscribe';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SubscribeOperation;
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
const { mix } = require('./utils');
|
||||
|
||||
const Base = require('./base');
|
||||
|
||||
const MixinDescription = require('../mixins/description');
|
||||
const MixinExternalDocs = require('../mixins/external-docs');
|
||||
const MixinSpecificationExtensions = require('../mixins/specification-extensions');
|
||||
|
||||
/**
|
||||
* Implements functions to deal with a Tag object.
|
||||
* @class
|
||||
* @alias module:@asyncapi/parser#Tag
|
||||
* @extends Base
|
||||
* @mixes MixinDescription
|
||||
* @mixes MixinExternalDocs
|
||||
* @mixes MixinSpecificationExtensions
|
||||
* @returns {Tag}
|
||||
*/
|
||||
class Tag extends Base {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
name() {
|
||||
return this._json.name;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mix(Tag, MixinDescription, MixinExternalDocs, MixinSpecificationExtensions);
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
const utils = module.exports;
|
||||
|
||||
const getMapValue = (obj, key, Type, options) => {
|
||||
if (typeof key !== 'string' || !obj) return null;
|
||||
const v = obj[String(key)];
|
||||
if (v === undefined) return null;
|
||||
return Type ? new Type(v, options) : v;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates map of given type from object.
|
||||
* @private
|
||||
* @param {Object} obj
|
||||
* @param {Any} Type
|
||||
* @param {Object} options
|
||||
*/
|
||||
utils.createMapOfType = (obj, Type, options) => {
|
||||
const result = {};
|
||||
if (!obj) return result;
|
||||
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
result[String(key)] = new Type(value, options);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates given type from value retrieved from object by key.
|
||||
* @private
|
||||
* @param {Object} obj
|
||||
* @param {string} key
|
||||
* @param {Any} Type
|
||||
* @param {Object} options
|
||||
*/
|
||||
utils.getMapValueOfType = (obj, key, Type, options) => {
|
||||
return getMapValue(obj, key, Type, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves value from object by key.
|
||||
* @private
|
||||
* @param {Object} obj
|
||||
* @param {string} key
|
||||
*/
|
||||
utils.getMapValueByKey = (obj, key) => {
|
||||
return getMapValue(obj, key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extends a given model with additional methods related to frequently recurring models.
|
||||
* @function mix
|
||||
* @private
|
||||
* @param {Object} model model to extend
|
||||
* @param {Array<Object>} mixins array with mixins to extend the model with
|
||||
*/
|
||||
utils.mix = (model, ...mixins) => {
|
||||
let duplicatedMethods = false;
|
||||
function checkDuplication(mixin) {
|
||||
// check duplication of model in mixins array
|
||||
if (model === mixin) return true;
|
||||
// check duplication of model's methods
|
||||
duplicatedMethods = Object.keys(mixin).some(mixinMethod => model.prototype.hasOwnProperty(mixinMethod));
|
||||
return duplicatedMethods;
|
||||
}
|
||||
|
||||
if (mixins.some(checkDuplication)) {
|
||||
if (duplicatedMethods) {
|
||||
throw new Error(`invalid mix function: model ${model.name} has at least one method that it is trying to replace by mixin`);
|
||||
} else {
|
||||
throw new Error(`invalid mix function: cannot use the model ${model.name} as a mixin`);
|
||||
}
|
||||
}
|
||||
mixins.forEach(mixin => Object.assign(model.prototype, mixin));
|
||||
return model;
|
||||
};
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
const path = require('path');
|
||||
const fetch = require('node-fetch');
|
||||
const Ajv = require('ajv');
|
||||
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, validateMessageId } = require('./customValidators.js');
|
||||
const {
|
||||
toJS,
|
||||
findRefs,
|
||||
getLocationOf,
|
||||
improveAjvErrors,
|
||||
getDefaultSchemaFormat,
|
||||
getBaseUrl,
|
||||
} = require('./utils');
|
||||
const AsyncAPIDocument = require('./models/asyncapi');
|
||||
|
||||
const OPERATIONS = ['publish', 'subscribe'];
|
||||
//the only security types that can have a non empty array in the server security item
|
||||
const SPECIAL_SECURITY_TYPES = ['oauth2', 'openIdConnect'];
|
||||
const PARSERS = {};
|
||||
const xParserCircle = 'x-parser-circular';
|
||||
const xParserMessageParsed = 'x-parser-message-parsed';
|
||||
|
||||
const ajv = new Ajv({
|
||||
jsonPointers: true,
|
||||
allErrors: true,
|
||||
schemaId: 'auto',
|
||||
logger: false,
|
||||
validateSchema: true,
|
||||
});
|
||||
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
|
||||
|
||||
/**
|
||||
* @module @asyncapi/parser
|
||||
*/
|
||||
module.exports = {
|
||||
parse,
|
||||
parseFromUrl,
|
||||
registerSchemaParser,
|
||||
ParserError,
|
||||
AsyncAPIDocument,
|
||||
};
|
||||
|
||||
/**
|
||||
* The complete list of parse configuration options used to parse the given data.
|
||||
* @typedef {Object} ParserOptions
|
||||
* @property {String=} path - Path to the AsyncAPI document. It will be used to resolve relative references. Defaults to current working dir.
|
||||
* @property {Object=} parse - Options object to pass to {@link https://apitools.dev/json-schema-ref-parser/docs/options.html|json-schema-ref-parser}.
|
||||
* @property {Object=} resolve - Options object to pass to {@link https://apitools.dev/json-schema-ref-parser/docs/options.html|json-schema-ref-parser}.
|
||||
* @property {Boolean=} applyTraits - Whether to resolve and apply traits or not. Defaults to true.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses and validate an AsyncAPI document from YAML or JSON.
|
||||
*
|
||||
* @param {(String | Object)} asyncapiYAMLorJSON An AsyncAPI document in JSON or YAML format.
|
||||
* @param {ParserOptions=} options Configuration options object {@link #asyncapiparserparseroptions--object|ParserOptions}
|
||||
* @returns {Promise<AsyncAPIDocument>} The parsed AsyncAPI document.
|
||||
*/
|
||||
async function parse(asyncapiYAMLorJSON, options = {}) {
|
||||
let parsedJSON;
|
||||
let initialFormat;
|
||||
|
||||
if (typeof window !== 'undefined' && !options.hasOwnProperty('path')) {
|
||||
options.path = getBaseUrl(window.location.href);
|
||||
} else {
|
||||
options.path = options.path || `${process.cwd()}${path.sep}`;
|
||||
}
|
||||
|
||||
try {
|
||||
({ initialFormat, parsedJSON } = toJS(asyncapiYAMLorJSON));
|
||||
|
||||
if (typeof parsedJSON !== 'object') {
|
||||
throw new ParserError({
|
||||
type: 'impossible-to-convert-to-json',
|
||||
title: 'Could not convert AsyncAPI to JSON.',
|
||||
detail: 'Most probably the AsyncAPI document contains invalid YAML or YAML features not supported in JSON.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!parsedJSON.asyncapi) {
|
||||
throw new ParserError({
|
||||
type: 'missing-asyncapi-field',
|
||||
title: 'The `asyncapi` field is missing.',
|
||||
parsedJSON,
|
||||
});
|
||||
}
|
||||
|
||||
if (parsedJSON.asyncapi.startsWith('1.') || !asyncapi[parsedJSON.asyncapi]) {
|
||||
throw new ParserError({
|
||||
type: 'unsupported-version',
|
||||
title: `Version ${parsedJSON.asyncapi} is not supported.`,
|
||||
detail: 'Please use latest version of the specification.',
|
||||
parsedJSON,
|
||||
validationErrors: [getLocationOf('/asyncapi', asyncapiYAMLorJSON, initialFormat)],
|
||||
});
|
||||
}
|
||||
|
||||
if (options.applyTraits === undefined) options.applyTraits = true;
|
||||
|
||||
const refParser = new $RefParser;
|
||||
//because of Ajv lacks support for circular refs, parser should not resolve them before Ajv validation and first needs to ignore them and leave circular $refs to successfully validate the document
|
||||
//this is done pair to advice from Ajv creator https://github.com/ajv-validator/ajv/issues/1122#issuecomment-559378449
|
||||
//later we perform full dereference of circular refs if they occure
|
||||
await dereference(refParser, parsedJSON, initialFormat, asyncapiYAMLorJSON, { ...options, dereference: { circular: 'ignore' } });
|
||||
|
||||
const validate = getValidator(parsedJSON.asyncapi);
|
||||
const valid = validate(parsedJSON);
|
||||
const errors = validate.errors && [...validate.errors];
|
||||
|
||||
if (!valid) throw new ParserError({
|
||||
type: 'validation-errors',
|
||||
title: 'There were errors validating the AsyncAPI document.',
|
||||
parsedJSON,
|
||||
validationErrors: improveAjvErrors(errors, asyncapiYAMLorJSON, initialFormat),
|
||||
});
|
||||
|
||||
await customDocumentOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options);
|
||||
if (refParser.$refs.circular) await handleCircularRefs(refParser, parsedJSON, initialFormat, asyncapiYAMLorJSON, options);
|
||||
} catch (e) {
|
||||
if (e instanceof ParserError) throw e;
|
||||
throw new ParserError({
|
||||
type: 'unexpected-error',
|
||||
title: e.message,
|
||||
parsedJSON,
|
||||
});
|
||||
}
|
||||
|
||||
return new AsyncAPIDocument(parsedJSON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an AsyncAPI document from the given URL and passes its content to the `parse` method.
|
||||
*
|
||||
* @param {String} url URL where the AsyncAPI document is located.
|
||||
* @param {Object=} [fetchOptions] Configuration to pass to the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request|fetch} call.
|
||||
* @param {ParserOptions=} [options] Configuration to pass to the {@link #asyncapiparserparseroptions--object|ParserOptions} method.
|
||||
* @returns {Promise<AsyncAPIDocument>} The parsed AsyncAPI document.
|
||||
*/
|
||||
function parseFromUrl(url, fetchOptions, options = {}) {
|
||||
//Why not just addinga default to the arguments list?
|
||||
//All function parameters with default values should be declared after the function parameters without default values. Otherwise, it makes it impossible for callers to take advantage of defaults; they must re-specify the defaulted values or pass undefined in order to "get to" the non-default parameters.
|
||||
//To not break the API by changing argument position and to silet the linter it is just better to move adding
|
||||
if (!fetchOptions) fetchOptions = {};
|
||||
|
||||
if (!options.hasOwnProperty('path')) {
|
||||
options = { ...options, path: getBaseUrl(url) };
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, fetchOptions)
|
||||
.then(res => res.text())
|
||||
.then(doc => parse(doc, options))
|
||||
.then(result => resolve(result))
|
||||
.catch(e => {
|
||||
if (e instanceof ParserError) return reject(e);
|
||||
return reject(new ParserError({
|
||||
type: 'fetch-url-error',
|
||||
title: e.message,
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function dereference(refParser, parsedJSON, initialFormat, asyncapiYAMLorJSON, options) {
|
||||
try {
|
||||
return await refParser.dereference(options.path, parsedJSON, {
|
||||
continueOnError: true,
|
||||
parse: options.parse,
|
||||
resolve: options.resolve,
|
||||
dereference: options.dereference,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new ParserError({
|
||||
type: 'dereference-error',
|
||||
title: err.errors[0].message,
|
||||
parsedJSON,
|
||||
refs: findRefs(err.errors, initialFormat, asyncapiYAMLorJSON),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In case of circular refs, this function dereferences the spec again to dereference circular dependencies
|
||||
* Special property is added to the document that indicates it contains circular refs
|
||||
*/
|
||||
async function handleCircularRefs(refParser, parsedJSON, initialFormat, asyncapiYAMLorJSON, options) {
|
||||
await dereference(refParser, parsedJSON, initialFormat, asyncapiYAMLorJSON, { ...options, dereference: { circular: true } });
|
||||
//mark entire document as containing circular references
|
||||
parsedJSON[String(xParserCircle)] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (or reuses) a function that validates an AsyncAPI document based on the passed AsyncAPI version.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} version AsyncAPI version.
|
||||
* @returns {Function} Function that validates an AsyncAPI document based on the passed AsyncAPI version.
|
||||
*/
|
||||
function getValidator(version) {
|
||||
let validate = ajv.getSchema(version);
|
||||
if (!validate) {
|
||||
const asyncapiSchema = asyncapi[String(version)];
|
||||
// Remove the meta schemas because it is already present within Ajv, and it's not possible to add duplicate schemas.
|
||||
delete asyncapiSchema.definitions['http://json-schema.org/draft-07/schema'];
|
||||
delete asyncapiSchema.definitions['http://json-schema.org/draft-04/schema'];
|
||||
ajv.addSchema(asyncapiSchema, version);
|
||||
validate = ajv.getSchema(version);
|
||||
}
|
||||
return validate;
|
||||
}
|
||||
|
||||
async function customDocumentOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options) {
|
||||
validateServerVariables(parsedJSON, asyncapiYAMLorJSON, initialFormat);
|
||||
validateServerSecurity(parsedJSON, asyncapiYAMLorJSON, initialFormat, SPECIAL_SECURITY_TYPES);
|
||||
|
||||
if (!parsedJSON.channels) return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
async function validateAndConvertMessage(msg, originalAsyncAPIDocument, fileFormat, parsedAsyncAPIDocument, pathToPayload) {
|
||||
//check if the message has been parsed before
|
||||
if (xParserMessageParsed in msg && msg[String(xParserMessageParsed)] === true) return;
|
||||
const defaultSchemaFormat = getDefaultSchemaFormat(parsedAsyncAPIDocument.asyncapi);
|
||||
const schemaFormat = msg.schemaFormat || defaultSchemaFormat;
|
||||
|
||||
await PARSERS[String(schemaFormat)]({
|
||||
schemaFormat,
|
||||
message: msg,
|
||||
defaultSchemaFormat,
|
||||
originalAsyncAPIDocument,
|
||||
parsedAsyncAPIDocument,
|
||||
fileFormat,
|
||||
pathToPayload
|
||||
});
|
||||
|
||||
msg.schemaFormat = defaultSchemaFormat;
|
||||
msg[String(xParserMessageParsed)] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new schema parser. Schema parsers are in charge of parsing and transforming payloads to AsyncAPI Schema format.
|
||||
*
|
||||
* @param {Object} parserModule The schema parser module containing parse() and getMimeTypes() functions.
|
||||
*/
|
||||
function registerSchemaParser(parserModule) {
|
||||
if (typeof parserModule !== 'object'
|
||||
|| typeof parserModule.parse !== 'function'
|
||||
|| typeof parserModule.getMimeTypes !== 'function')
|
||||
throw new ParserError({
|
||||
type: 'impossible-to-register-parser',
|
||||
title: 'parserModule must have parse() and getMimeTypes() functions.'
|
||||
});
|
||||
|
||||
parserModule.getMimeTypes().forEach((schemaFormat) => {
|
||||
PARSERS[String(schemaFormat)] = parserModule.parse;
|
||||
});
|
||||
}
|
||||
|
||||
function applyTraits(js) {
|
||||
if (Array.isArray(js.traits)) {
|
||||
for (const trait of js.traits) {
|
||||
for (const key in trait) {
|
||||
js[String(key)] = mergePatch(js[String(key)], trait[String(key)]);
|
||||
}
|
||||
}
|
||||
|
||||
js['x-parser-original-traits'] = js.traits;
|
||||
delete js.traits;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers additional operations on the AsyncAPI channels like traits application or message validation and conversion
|
||||
*
|
||||
* @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
|
||||
* @param {ParserOptions} options Configuration options. {@link ParserOptions}
|
||||
*/
|
||||
async function customChannelsOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options) {
|
||||
const promisesArray = [];
|
||||
Object.entries(parsedJSON.channels).forEach(([channelName, channel]) => {
|
||||
promisesArray.push(...OPERATIONS.map(async (opName) => {
|
||||
const op = channel[String(opName)];
|
||||
if (!op) return;
|
||||
|
||||
const messages = op.message ? (op.message.oneOf || [op.message]) : [];
|
||||
if (options.applyTraits) {
|
||||
applyTraits(op);
|
||||
messages.forEach(m => applyTraits(m));
|
||||
}
|
||||
const pathToPayload = `/channels/${channelName}/${opName}/message/payload`;
|
||||
for (const m of messages) {
|
||||
await validateAndConvertMessage(m, asyncapiYAMLorJSON, initialFormat, parsedJSON, pathToPayload);
|
||||
}
|
||||
}));
|
||||
});
|
||||
await Promise.all(promisesArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers additional operations on the AsyncAPI messages located in the components section of the document. It triggers operations like traits application, validation and conversion
|
||||
*
|
||||
* @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
|
||||
* @param {ParserOptions} options Configuration options. {@link ParserOptions}
|
||||
*/
|
||||
async function customComponentsMsgOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options) {
|
||||
if (!parsedJSON.components || !parsedJSON.components.messages) return;
|
||||
|
||||
const promisesArray = [];
|
||||
|
||||
Object.entries(parsedJSON.components.messages).forEach(([messageName, message]) => {
|
||||
if (options.applyTraits) {
|
||||
applyTraits(message);
|
||||
}
|
||||
const pathToPayload = `/components/messages/${messageName}/payload`;
|
||||
promisesArray.push(validateAndConvertMessage(message, asyncapiYAMLorJSON, initialFormat, parsedJSON, pathToPayload));
|
||||
});
|
||||
|
||||
await Promise.all(promisesArray);
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
const YAML = require('js-yaml');
|
||||
const { yamlAST, loc } = require('@fmvilas/pseudo-yaml-ast');
|
||||
const jsonAST = require('json-to-ast');
|
||||
const jsonParseBetterErrors = require('../lib/json-parse');
|
||||
const ParserError = require('./errors/parser-error');
|
||||
|
||||
const jsonPointerToArray = jsonPointer => (jsonPointer || '/').split('/').splice(1);
|
||||
|
||||
const utils = module.exports;
|
||||
|
||||
const getAST = (asyncapiYAMLorJSON, initialFormat) => {
|
||||
if (initialFormat === 'yaml') {
|
||||
return yamlAST(asyncapiYAMLorJSON);
|
||||
} else if (initialFormat === 'json') {
|
||||
return jsonAST(asyncapiYAMLorJSON);
|
||||
}
|
||||
};
|
||||
|
||||
const findNode = (obj, location) => {
|
||||
for (const key of location) {
|
||||
obj = obj ? obj[utils.untilde(key)] : null;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const findNodeInAST = (ast, location) => {
|
||||
let obj = ast;
|
||||
for (const key of location) {
|
||||
if (!Array.isArray(obj.children)) return;
|
||||
let childArray;
|
||||
|
||||
const child = obj.children.find(c => {
|
||||
if (!c) return;
|
||||
|
||||
if (c.type === 'Object') return childArray = c.children.find(a => a.key.value === utils.untilde(key));
|
||||
return c.type === 'Property' && c.key && c.key.value === utils.untilde(key);
|
||||
});
|
||||
|
||||
if (!child) return;
|
||||
obj = childArray ? childArray.value : child.value;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const findLocationOf = (keys, ast, initialFormat) => {
|
||||
if (initialFormat === 'js') return { jsonPointer: `/${keys.join('/')}` };
|
||||
|
||||
let node;
|
||||
if (initialFormat === 'yaml') {
|
||||
node = findNode(ast, keys);
|
||||
} else if (initialFormat === 'json') {
|
||||
node = findNodeInAST(ast, keys);
|
||||
}
|
||||
|
||||
if (!node) return { jsonPointer: `/${keys.join('/')}` };
|
||||
|
||||
let info;
|
||||
if (initialFormat === 'yaml') {
|
||||
// disable eslint because loc is a Symbol
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
info = node[loc];
|
||||
} else if (initialFormat === 'json') {
|
||||
info = node.loc;
|
||||
}
|
||||
|
||||
if (!info) return { jsonPointer: `/${keys.join('/')}` };
|
||||
|
||||
return {
|
||||
jsonPointer: `/${keys.join('/')}`,
|
||||
startLine: info.start.line,
|
||||
startColumn: info.start.column + 1,
|
||||
startOffset: info.start.offset,
|
||||
endLine: info.end ? info.end.line : undefined,
|
||||
endColumn: info.end ? info.end.column + 1 : undefined,
|
||||
endOffset: info.end ? info.end.offset : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
utils.tilde = (str) => {
|
||||
return str.replace(/[~\/]{1}/g, (m) => {
|
||||
switch (m) {
|
||||
case '/': return '~1';
|
||||
case '~': return '~0';
|
||||
}
|
||||
return m;
|
||||
});
|
||||
};
|
||||
|
||||
utils.untilde = (str) => {
|
||||
if (!str.includes('~')) return str;
|
||||
return str.replace(/~[01]/g, (m) => {
|
||||
switch (m) {
|
||||
case '~1': return '/';
|
||||
case '~0': return '~';
|
||||
}
|
||||
return m;
|
||||
});
|
||||
};
|
||||
|
||||
utils.toJS = (asyncapiYAMLorJSON) => {
|
||||
if (!asyncapiYAMLorJSON) {
|
||||
throw new ParserError({
|
||||
type: 'null-or-falsey-document',
|
||||
title: 'Document can\'t be null or falsey.',
|
||||
});
|
||||
}
|
||||
|
||||
if (asyncapiYAMLorJSON.constructor && asyncapiYAMLorJSON.constructor.name === 'Object') {
|
||||
return {
|
||||
initialFormat: 'js',
|
||||
parsedJSON: asyncapiYAMLorJSON,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof asyncapiYAMLorJSON !== 'string') {
|
||||
throw new ParserError({
|
||||
type: 'invalid-document-type',
|
||||
title: 'The AsyncAPI document has to be either a string or a JS object.',
|
||||
});
|
||||
}
|
||||
if (asyncapiYAMLorJSON.trimLeft().startsWith('{')) {
|
||||
try {
|
||||
return {
|
||||
initialFormat: 'json',
|
||||
parsedJSON: jsonParseBetterErrors(asyncapiYAMLorJSON),
|
||||
};
|
||||
} catch (e) {
|
||||
throw new ParserError({
|
||||
type: 'invalid-json',
|
||||
title: 'The provided JSON is not valid.',
|
||||
detail: e.message,
|
||||
location: {
|
||||
startOffset: e.offset,
|
||||
startLine: e.startLine,
|
||||
startColumn: e.startColumn,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return {
|
||||
initialFormat: 'yaml',
|
||||
parsedJSON: YAML.safeLoad(asyncapiYAMLorJSON),
|
||||
};
|
||||
} catch (err) {
|
||||
throw new ParserError({
|
||||
type: 'invalid-yaml',
|
||||
title: 'The provided YAML is not valid.',
|
||||
detail: err.message,
|
||||
location: {
|
||||
startOffset: err.mark.position,
|
||||
startLine: err.mark.line + 1,
|
||||
startColumn: err.mark.column + 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
utils.findRefs = (errors, initialFormat, asyncapiYAMLorJSON) => {
|
||||
let refs = [];
|
||||
|
||||
errors.map(({ path }) => refs.push({ location: [...path.map(utils.tilde), '$ref'] }));
|
||||
|
||||
if (initialFormat === 'js') {
|
||||
return refs.map(ref => ({ jsonPointer: `/${ref.location.join('/')}` }));
|
||||
}
|
||||
|
||||
if (initialFormat === 'yaml') {
|
||||
const pseudoAST = yamlAST(asyncapiYAMLorJSON);
|
||||
refs = refs.map(ref => findLocationOf(ref.location, pseudoAST, initialFormat));
|
||||
} else if (initialFormat === 'json') {
|
||||
const ast = jsonAST(asyncapiYAMLorJSON);
|
||||
refs = refs.map(ref => findLocationOf(ref.location, ast, initialFormat));
|
||||
}
|
||||
|
||||
return refs;
|
||||
};
|
||||
|
||||
utils.getLocationOf = (jsonPointer, asyncapiYAMLorJSON, initialFormat) => {
|
||||
const ast = getAST(asyncapiYAMLorJSON, initialFormat);
|
||||
if (!ast) return { jsonPointer };
|
||||
|
||||
return findLocationOf(jsonPointerToArray(jsonPointer), ast, initialFormat);
|
||||
};
|
||||
|
||||
utils.improveAjvErrors = (errors, asyncapiYAMLorJSON, initialFormat) => {
|
||||
const ast = getAST(asyncapiYAMLorJSON, initialFormat);
|
||||
return errors.map(error => {
|
||||
const defaultLocation = { jsonPointer: error.dataPath || '/' };
|
||||
const additionalProperty = error.params.additionalProperty;
|
||||
const jsonPointer = additionalProperty ? `${error.dataPath}/${additionalProperty}`: error.dataPath;
|
||||
return {
|
||||
title: `${error.dataPath || '/'} ${error.message}`,
|
||||
location: ast ? findLocationOf(jsonPointerToArray(jsonPointer), ast, initialFormat) : defaultLocation,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* It parses the string and returns an array with all values that are between curly braces, including braces
|
||||
* @function parseUrlVariables
|
||||
* @private
|
||||
*/
|
||||
utils.parseUrlVariables = str => {
|
||||
if (typeof str !== 'string') return;
|
||||
|
||||
return str.match(/{(.+?)}/g);
|
||||
};
|
||||
|
||||
/**
|
||||
* It parses the string and returns url parameters as string
|
||||
* @function parseUrlQueryParameters
|
||||
* @private
|
||||
*/
|
||||
utils.parseUrlQueryParameters = str => {
|
||||
if (typeof str !== 'string') return;
|
||||
|
||||
return str.match(/\?((.*=.*)(&?))/g);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns base URL parsed from location of AsyncAPI document
|
||||
*
|
||||
* @function getBaseUrl
|
||||
* @private
|
||||
* @param {String} url URL of AsyncAPI document
|
||||
*/
|
||||
utils.getBaseUrl = url => {
|
||||
url = typeof url !== 'string' ? String(url) : url;
|
||||
//URL validation is not performed because 'node-fetch' performs its own
|
||||
//validation at fetch time, so no repetition of this task is made.
|
||||
//Only ensuring that 'url' has type of 'string' and letting 'node-fetch' deal
|
||||
//with the rest.
|
||||
|
||||
return url.substring(0, url.lastIndexOf('/') + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of not existing properties in provided object with names specified in provided array
|
||||
* @function getMissingProps
|
||||
* @private
|
||||
*/
|
||||
utils.getMissingProps = (arr, obj) => {
|
||||
arr = arr.map(val => val.replace(/[{}]/g, ''));
|
||||
|
||||
if (!obj) return arr;
|
||||
|
||||
return arr.filter(val => {
|
||||
return !obj.hasOwnProperty(val);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns array of errors messages compatible with validationErrors parameter from ParserError
|
||||
*
|
||||
* @function groupValidationErrors
|
||||
* @private
|
||||
* @param {String} root name of the root element in the AsyncAPI document, for example channels
|
||||
* @param {String} errorMessage the text of the custom error message that will follow the path that points the error
|
||||
* @param {Map} errorElements map of error elements cause the validation error might happen in many places in the document.
|
||||
* The key should have a path information where the error was found, the value holds information about error element but it is not mandatory
|
||||
* @param {String} asyncapiYAMLorJSON AsyncAPI document in string
|
||||
* @param {String} initialFormat information of the document was oryginally JSON or YAML
|
||||
* @returns {Array<Object>} Object has always 2 keys, title and location. Title is a combination of errorElement key + errorMessage + errorElement value.
|
||||
* Location is the object with information about location of the issue in the file and json Pointer
|
||||
*/
|
||||
utils.groupValidationErrors = (root, errorMessage, errorElements, asyncapiYAMLorJSON, initialFormat) => {
|
||||
const errors = [];
|
||||
|
||||
errorElements.forEach((val, key) => {
|
||||
if (typeof val === 'string') val = utils.untilde(val);
|
||||
|
||||
const jsonPointer = root ? `/${root}/${key}` : `/${key}`;
|
||||
errors.push({
|
||||
title: val ? `${ utils.untilde(key) } ${errorMessage}: ${val}` : `${ utils.untilde(key) } ${errorMessage}`,
|
||||
location: utils.getLocationOf(jsonPointer, asyncapiYAMLorJSON, initialFormat)
|
||||
});
|
||||
});
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
/**
|
||||
* extend map with channel params missing corresponding param object
|
||||
*
|
||||
* @function setNotProvidedParams
|
||||
* @private
|
||||
* @param {Array<String>} variables array of all identified URL variables in a channel name
|
||||
* @param {Object} val the channel object for which to identify the missing parameters
|
||||
* @param {String} key the channel name.
|
||||
* @param {Array<Object>} notProvidedChannelParams concatinated list of missing parameters for all channels
|
||||
* @param {Map} notProvidedParams result map of all missing parameters extended by this function
|
||||
*/
|
||||
utils.setNotProvidedParams = (variables, val, key, notProvidedChannelParams, notProvidedParams) => {
|
||||
const missingChannelParams = utils.getMissingProps(variables, val.parameters);
|
||||
|
||||
if (missingChannelParams.length) {
|
||||
notProvidedParams.set(utils.tilde(key),
|
||||
notProvidedChannelParams
|
||||
? notProvidedChannelParams.concat(missingChannelParams)
|
||||
: missingChannelParams);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of server names listed in a channel's servers list that are not declared in the top-level servers object.
|
||||
*
|
||||
* @param {Map} parsedJSON the parsed AsyncAPI document, with potentially a top-level map of servers (keys are server names)
|
||||
* @param {Object} channel the channel object for which to validate the servers list (array elements are server names)
|
||||
* @private
|
||||
*/
|
||||
utils.getUnknownServers = (parsedJSON, channel) => {
|
||||
// servers list on channel
|
||||
if (!channel) return []; // no channel: no unknown servers
|
||||
const channelServers = channel.servers;
|
||||
if (!channelServers || channelServers.length === 0) return []; // no servers listed on channel: no unknown servers
|
||||
|
||||
// top-level servers map
|
||||
const servers = parsedJSON.servers;
|
||||
if (!servers) return channelServers; // servers list on channel but no top-level servers: all servers are unknown
|
||||
const serversMap = new Map(Object.entries(servers));
|
||||
|
||||
// retain only servers listed on channel that are not defined in the top-level servers map
|
||||
return channelServers.filter(serverName => {
|
||||
return !serversMap.has(serverName);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* returns default schema format for a given asyncapi version
|
||||
*
|
||||
* @function getDefaultSchemaFormat
|
||||
* @private
|
||||
* @param {String} asyncapiVersion AsyncAPI spec version.
|
||||
*/
|
||||
utils.getDefaultSchemaFormat = (asyncapiVersion) => {
|
||||
return `application/vnd.aai.asyncapi;version=${asyncapiVersion}`;
|
||||
};
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"name": "@asyncapi/parser",
|
||||
"version": "1.18.1",
|
||||
"description": "JavaScript AsyncAPI parser.",
|
||||
"main": "lib/index.js",
|
||||
"types": "types.d.ts",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"files": [
|
||||
"dist/bundle.js",
|
||||
"API.md",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"lib/",
|
||||
"types.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:browser:cleanup && npm run test:lib && npm run test:parseFromUrl && npm run cover:report && npm run test:browser",
|
||||
"bundle": "mkdirp \"dist\" && browserify \"lib/browser.js\" | uglifyjs > \"dist/bundle.js\"",
|
||||
"docs": "jsdoc2md \"lib/parser.js\" -f \"lib/**/*.js\" > API.md",
|
||||
"types": "jsdoc -t \"node_modules/tsd-jsdoc/dist\" -r lib -d \"./\" && node \"./scripts/fix-ts-types.js\"",
|
||||
"prepublishOnly": "npm run bundle && npm run docs && npm run types",
|
||||
"release": "semantic-release",
|
||||
"lint": "eslint --max-warnings 0 --config \".eslintrc\" \".\"",
|
||||
"lint:fix": "eslint --max-warnings 0 --config \".eslintrc\" \".\" --fix",
|
||||
"test:lib": "npm run test:browser:cleanup && nyc --silent --no-clean mocha --exclude \"test/browser_test.js\" --exclude \"test/parseFromUrl_test.js\" --recursive",
|
||||
"test:parseFromUrl": "nyc --silent --no-clean start-server-and-test \"http-server test/sample_browser --cors -s\" 8080 \"mocha test/parseFromUrl_test.js\"",
|
||||
"cover:report": "nyc report --reporter=text --reporter=html",
|
||||
"test:browser": "npm run test:browser:cleanup && npm run bundle && shx cp \"dist/bundle.js\" \"test/sample_browser/\" && start-server-and-test \"http-server test/sample_browser --cors -s\" 8080 \"mocha --timeout 20000 test/browser_test.js\" && npm run test:browser:cleanup",
|
||||
"test:browser:cleanup": "rimraf \"test/sample_browser/bundle.js\"",
|
||||
"generate:readme:toc": "markdown-toc -i \"README.md\"",
|
||||
"generate:assets": "npm run docs && npm run generate:readme:toc && npm run types && npm run bundle",
|
||||
"bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/asyncapi/parser-js/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/asyncapi/parser-js.git"
|
||||
},
|
||||
"author": "Fran Mendez <fmvilas@gmail.com> (fmvilas.com)",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://github.com/asyncapi/parser-js",
|
||||
"devDependencies": {
|
||||
"@semantic-release/commit-analyzer": "^9.0.2",
|
||||
"@semantic-release/github": "^8.0.6",
|
||||
"@semantic-release/npm": "^9.0.1",
|
||||
"@semantic-release/release-notes-generator": "^10.0.3",
|
||||
"browserify": "^16.3.0",
|
||||
"browserify-shim": "^3.8.14",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"conventional-changelog-conventionalcommits": "^4.2.3",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-plugin-mocha": "^7.0.1",
|
||||
"eslint-plugin-security": "^1.4.0",
|
||||
"eslint-plugin-sonarjs": "^0.15.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jsdoc-to-markdown": "^7.1.1",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mocha": "^10.1.0",
|
||||
"nyc": "^15.1.0",
|
||||
"puppeteer": "^17.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"semantic-release": "19.0.3",
|
||||
"shx": "^0.3.3",
|
||||
"start-server-and-test": "^1.11.3",
|
||||
"tsd-jsdoc": "^2.5.0",
|
||||
"uglify-es": "^3.3.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "^9.0.6",
|
||||
"@asyncapi/specs": "^4.1.1",
|
||||
"@fmvilas/pseudo-yaml-ast": "^0.3.1",
|
||||
"ajv": "^6.10.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"json-to-ast": "^2.1.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"tiny-merge-patch": "^0.1.2"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"browserify-shim"
|
||||
]
|
||||
},
|
||||
"browserify-shim": {
|
||||
"node-fetch": "global:fetch"
|
||||
},
|
||||
"release": {
|
||||
"branches": [
|
||||
"master",
|
||||
{
|
||||
"name": "next-spec",
|
||||
"prerelease": true
|
||||
},
|
||||
{
|
||||
"name": "next-major-spec",
|
||||
"prerelease": true
|
||||
},
|
||||
{
|
||||
"name": "next-major",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits"
|
||||
}
|
||||
],
|
||||
"@semantic-release/npm",
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "dist/bundle.js",
|
||||
"label": "Browser Bundle"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"dist/bundle.js",
|
||||
"test/**/*.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
+1221
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user