asyncapi / parser-js

AsyncAPI parser for Javascript (browser-compatible too).
115 stars 91 forks source link
get-global-docs-autoupdate get-global-node-release-workflows get-global-releaserc hacktoberfest javascript json-schema nodejs parser

AsyncAPI JavaScript Parser

This is a Monorepo managed using Turborepo and contains the following package:

  1. Parser-js: Use this package to validate and parse AsyncAPI documents —either YAML or JSON— in your Node.js or browser application.

Validation is powered by Spectral.
Updated bundle for the browser is always attached to the GitHub Release.

npm npm

Warning This package doesn't support AsyncAPI 1.x anymore. We recommend to upgrade to the latest AsyncAPI version using the AsyncAPI converter. If you need to convert documents on the fly, you may use the Node.js or Go converters.

Warning This package has rewrote the Model API (old one) to Intent API. If you still need to use the old API, read the Convert to the old API section.

Note Read the migration guide from v2 to v3.

Installation

npm install @asyncapi/parser
yarn add @asyncapi/parser

The parser by default supports AsyncAPI Schema Format and JSON Schema Format for schemas. For additional formats, check Custom schema parsers section.

Usage

The package exposes the main class Parser, which has two main functions:

Natively Parser class does not contain methods that operate on the source (AsyncAPI document) from a file or URL. However, the package exposes utils that make this possible:

import { fromURL, fromFile } from "@asyncapi/parser";

Check out the examples of using the above mentioned functionalities.

Examples

Example with parsing

import { Parser } from "@asyncapi/parser";
const parser = new Parser();
const { document } = await parser.parse(`
  asyncapi: '2.4.0'
  info:
    title: Example AsyncAPI specification
    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
`);

if (document) {
  // => Example AsyncAPI specification
  console.log(document.info().title());
}

Example with validation

import { Parser } from "@asyncapi/parser";

const parser = new Parser();

// One of the diagnostics will contain an error regarding an unsupported version of AsyncAPI (2.1.37)
const diagnostics = await parser.validate(`
  asyncapi: '2.1.37'
  info:
    title: Example AsyncAPI specification
    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
`);

Example using Avro schemas

Head over to asyncapi/avro-schema-parser for more information.

Example using OpenAPI schemas

Head over to asyncapi/openapi-schema-parser for more information.

Example using RAML data types

Head over to asyncapi/raml-dt-schema-parser for more information.

Example with performing actions on HTTP source

import { Parser, fromURL } from "@asyncapi/parser";

const parser = new Parser();

const { document, diagnostics } = await fromURL(
  parser,
  "https://example.com/",
).parse();

Example with performing actions on file source

import { Parser, fromFile } from "@asyncapi/parser";

const parser = new Parser();

const { document, diagnostics } = await fromFile(
  parser,
  "./asyncapi.yaml",
).parse();

Example with stringify and unstringify parsed document

import { Parser, stringify, unstringify } from "@asyncapi/parser";

const parser = new Parser();

const { document } = await parser.parse(`
  asyncapi: '2.4.0'
  info:
    title: Example AsyncAPI specification
    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
`);

if (document) {
  // stringify function returns string type
  const stringifiedDocument = stringify(document);
  // unstringify function returns new AsyncAPIDocument instance
  const unstringifiedDocument = unstringify(stringifiedDocument);
}

API documentation

Parser-JS API implements a global API definition for all AsyncAPI parser implementations known as the Parser-API. This API is designed having in mind developer experience and resiliency to breaking changes.

The following table shows a compatibility matrix between this parser, and the Parser-API, as well as the AsyncAPI spec version supported by each release of this parser.

Parser-JS Parser-API Spec 2.x Spec 3.x
2.x 1.x
3.x 3.x

Additionally to all the methods declared in the Parser-API, this parser might introduce some helper functions like:

Spectral rulesets

Spectral powers the validation of AsyncAPI documents within ParserJS. The default built-in rulesets are:

It is possible to use your rulesets/rules or overwrite existing ones, passing the ruleset option to the Parser instance:

import { Parser, stringify, unstringify } from "@asyncapi/parser";
const parser = new Parser({
  ruleset: {
    extends: [],
    rules: {
      "asyncapi-defaultContentType": "off",
      "asyncapi-termsOfService": {
        description:
          'Info "termsOfService" should be present and non-empty string.',
        recommended: true,
        given: "$",
        then: {
          field: "info.termsOfService",
          function: "truthy",
        },
      },
    },
  },
});
// The returned diagnostics object will include `asyncapi-termsOfService` diagnostic with `warning` (`recommended: true`) severity because `$.info.termsOfService` is not defined in the following AsyncAPI document.
// On the other hand, since we turned it off, we won't see the diagnostics related to the `defaultContentType` field.
const diagnostics = await parser.validate(`
  asyncapi: '2.0.0'
  info:
    title: Example AsyncAPI specification
    version: '0.1.0'
  channels: {}
`);

ParserJS has some built-in Spectral rulesets that validate AsyncAPI documents and inform on good practices.

Using in the browser/SPA applications

The package contains a built-in version of the parser. To use it, you need to import the parser into the HTML file as below:

<script src="https://unpkg.com/@asyncapi/parser@latest/browser/index.js"></script>

<script>
  const parser = new window.AsyncAPIParser();
  const { document, diagnostics } = parser.parse(...);
</script>

Or, if you want to use the parser in a JS SPA-type application where you have a predefined bundler configuration that is difficult to change (e.g. you use create-react-app) then you can import the parser as below:

import Parser from '@asyncapi/parser/browser';

const parser = new Parser();
const { document, diagnostics } = parser.parse(...);

Note Using the above code, we import the entire bundled parser into application. This may result in a duplicate code in the final application bundle, only if the application uses the same dependencies what the parser. If, on the other hand, you want to have the smallest bundle as possible, we recommend using the following import and properly configure bundler.

Otherwise, if your application is bundled via bundlers like webpack and you can configure it, you can import the parser like a regular package:

import { Parser } from '@asyncapi/parser';

const parser = new Parser();
const { document, diagnostics } = parser.parse(...);

Note The package uses some native NodeJS modules underneath. If you are building a front-end application you can find more information about the correct configuration for Webpack here.

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.

Custom schema parsers

AsyncAPI doesn't enforce one schema format. The payload of the messages can be described with OpenAPI (3.0.0), Avro, etc. This parser by default parses only AsyncAPI Schema Format (superset of JSON Schema Format). We can extend it by creating a custom parser and registering it within the parser:

  1. Create custom parser module that exports three functions:

    • validate - function that validates (its syntax) used schema.
    • parse - function that parses the given schema to the AsyncAPI Schema Format.
    • getMimeTypes - function that returns the list of mime types that will be used as the schemaFormat property to determine the mime type of a given schema.

    Example:

    export default {
     validate(input) { ... },
     parse(input) { ... },
     getMimeTypes() {
       return [
         'application/vnd.custom.type;version=1.0.0',
         'application/vnd.custom.type+json;version=1.0.0',
       ]
     }
    }
  2. Before parsing/validating an AsyncAPI document with a parser, register the additional custom schema parser:

    import { Parser } from "@asyncapi/parser";
    import myCustomSchemaParser from "./my-custom-schema-parser";
    
    const parser = new Parser();
    parser.registerSchemaParser(myCustomSchemaParser);

Official supported custom schema parsers

In AsyncAPI Initiative we support below custom schema parsers. To install them, run below comamnds:

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:

In addition, the convertToOldAPI() function which converts new API to an old one adds additional extensions:

Warning 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:

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 optimisation cases.

For that, the Parser supports the ability to stringify a parsed AsyncAPI document through the stringify function exposed by package. 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 path with a $ref: prefix:

{
  "foo": "$ref:$.some.path.to.the.bar"
}

To parse a stringified document into an AsyncAPIDocument instance, you must use the unstringify function (also exposed by package). It isn't compatible with the native JSON.parse() method. It replaces the given references pointed by the JSON Pointer path, with an $ref: prefix to the original objects.

A few advantages of this solution:

Check example.

Convert to the old API

Version 2.0.0 of package introduced a lot of breaking changes, including changing the API of the returned parsed document (parser uses New API). Due to the fact that a large part of the AsyncAPI tooling ecosystem uses a Parser with the old API and rewriting the tool for the new one can be time-consuming and difficult, the package exposes the convertToOldAPI() function to convert new API to old one:

import { Parser, convertToOldAPI } from '@asyncapi/parser';

const parser = new Parser();
const { document } = parser.parse(...);
const oldAsyncAPIDocument = convertToOldAPI(document);

Warning The old api will be supported only for a certain period of time. The target date for turning off support of the old API is around the end of January 2023.

Notes

Using with Webpack

Versions <5 of Webpack should handle bundling without problems. Due to the fact that Webpack 5 no longer does fallbacks to native NodeJS modules by default we need to install buffer package and add fallbacks:

{
  resolve: {
    fallback: {
      "fs": false,
      "path": false,
      "util": false,
      "buffer": require.resolve("buffer/"),
    }
  }
}

Testing with Jest

Using a Parser in an application that is tested using Jest, there will probably an error like:

Cannot find module 'nimma/legacy' from 'node_modules/@stoplight/spectral-core/dist/runner/runner.js

It's a problem with Jest, which cannot understand NodeJS's package exports. To fix that, should be enabled ESM support in Jest or set an appropriate Jest's moduleNameMapper config:

moduleNameMapper: {
  '^nimma/legacy$': '<rootDir>/node_modules/nimma/dist/legacy/cjs/index.js',
  '^nimma/(.*)': '<rootDir>/node_modules/nimma/dist/cjs/$1',
},

Develop

  1. Make sure you are using Node.js 16 or higher and npm 8 or higher
  2. Write code and tests.
  3. Make sure all tests pass npm test

For Windows environments, some tests might still fail randomly during local development even when you made no changes to the tests. The reason for this from file endings are different than expected and this comes from Git defaulting to an unexpected file ending. If you encounter this issue you can run the following commands to set Git to use the expected one:

git config --global core.autocrlf false
git config --global core.eol lf
  1. Make sure code is well formatted and secure npm run lint

Contributing

Read CONTRIBUTING guide.

Contributors

Thanks goes to these wonderful people (emoji key):

Fran Méndez
Fran Méndez

💬 🐛 💻 📖 🤔 🚧 🔌 👀 ⚠️
Lukasz Gornicki
Lukasz Gornicki

💬 🐛 💻 📖 🤔 🚧 👀 ⚠️
Jonas Lagoni
Jonas Lagoni

💬 🐛 💻 🤔 👀
Maciej Urbańczyk
Maciej Urbańczyk

🐛 💻 👀
Juan Mellado
Juan Mellado

💻
James Crowley
James Crowley

💻
raisel melian
raisel melian

💻
danielchu
danielchu

🚇 💻
Jürgen B.
Jürgen B.

💻
Viacheslav Turovskyi
Viacheslav Turovskyi

⚠️ 💻
Khuda Dad Nomani
Khuda Dad Nomani

💻 🐛 ⚠️
Aayush Kumar Sahu
Aayush Kumar Sahu

⚠️
Jordan Tucker
Jordan Tucker

⚠️ 💻
vishesh13byte
vishesh13byte

⚠️
Elakya
Elakya

💻
Dominik Schwank
Dominik Schwank

🐛 💻
Ruchi Pakhle
Ruchi Pakhle

📖
Đỗ Trọng Hải
Đỗ Trọng Hải

🛡️

This project follows the all-contributors specification. Contributions of any kind welcome!