Giter Club home page Giter Club logo

parser-js's Introduction

AsyncAPI JavaScript Parser

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:

  • validate() - function that validates the passed AsyncAPI document. Returns array of all possible errors against the validation conditions.
  • parse() - function that validates the passed AsyncAPI document, and then if it's valid, parses the input. It returns an object that contains:
    • document object, which is an parsed AsyncAPI document with AsyncAPIDocumentInterface API. If the schema is invalid against the validation conditions, the field has undefined value.
    • diagnostics array that contains all possible errors against the validation conditions.
  • registerSchemaParser() - function that registers custom schema parsers. For more info, please check Custom schema parsers section.

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
`);

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

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

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();
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
  • Fully supported version.
  • - The AsyncAPI Spec version has features the Parser-JS can't use but the rest are fully supported.
  • Empty means not supported version.

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

  • json() which returns the JSON object of the given object. It is possible to pass as an argument the name of a field in an object and retrieve corresponding value.
  • jsonPath() which returns the JSON Path of the given object.
  • meta() which returns the metadata of a given object, like a parsed AsyncAPI Document.

Spectral rulesets

Spectral powers the validation of AsyncAPI documents within ParserJS. For this reason, 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:

  • Avro schema:

    npm install @asyncapi/avro-schema-parser
    yarn add @asyncapi/avro-schema-parser
  • OpenAPI (3.0.0) Schema Object:

    npm install @asyncapi/openapi-schema-parser
    yarn add @asyncapi/openapi-schema-parser
  • RAML data type:

    npm install @asyncapi/raml-dt-schema-parser
    yarn add @asyncapi/raml-dt-schema-parser

    Note That custom parser works only in the NodeJS environment. Do not use it in browser applications!

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-api-version is used to specify which version of the Parser-API the parsed AsyncAPI document uses. Property x-parser-api-version is added to the root of the document with the 1 value if the parsed document uses Parser-API in the v1 version or 0 if document uses old parser-js API.
  • 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.id() (message.uid() in the old API) when regular name property is not present.
  • 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.

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

  • 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 message object with the true value.
  • 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.

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:

  • x-parser-circular property is added to the root of the AsyncAPI document to indicate that the document contains circular references. In old API the Parser exposes hasCircular() function to check if given AsyncAPI document has circular references.
  • isCircular() function is added to the Schema Model to determine if a given schema is circular with respect to previously occurring schemas in the JSON 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 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:

  • The string remains as small as possible due to the use of JSON Pointers.
  • All references (also circular) are preserved.

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!

parser-js's People

Contributors

aayushmau5 avatar acethecreator avatar aeworxet avatar allcontributors[bot] avatar apreshagarwal avatar asyncapi-bot avatar bolt04 avatar boyney123 avatar chrispatmore avatar danielchudc avatar dependabot[bot] avatar derberg avatar dschwank avatar eltociear avatar fmvilas avatar fr3shw3b avatar iamdevelopergirl avatar jamescrowley avatar jcmellado avatar jonaslagoni avatar juergenbr avatar khudadad414 avatar magicmatatjahu avatar pchol avatar rmelian avatar ruchip16 avatar smoya avatar tenischev avatar vishesh13byte avatar woylie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

parser-js's Issues

Add validation of message examples

Reason/Context

Have a mechanism that validates examples of payload. Examples are used as part of rendered docs and by mocking tools and should be 100% accurate.

Description

Ajv doesn't want to be responsible for examples validation, but it could be done with Ajv plugin, all details here. I did not find any available plugin at the moment.
Knowing that Ajv is now under development to support the latest JSON Schema, and it affects Ajv architecture as the plan is to rewrite with TypeScript, I would suggest waiting until they are done with it.

Header other than object doesn't cause validation fail

Describe the bug

Header other than object doesn't cause validation fail. It is probably the same not only in message but also messageTrait

How to Reproduce

Try below file in playground and you'll see no errors even though the spec clearly says that headers must be object of type

{
      "asyncapi": "2.0.0",
      "info": {
          "title": "Valid AsyncApi document",
          "version": "1.0"
      },
      "channels": {},
      "components": {
          "messages": {
              "userSignUp": {
                  "summary": "Action to sign a user up.",
                  "description": "Multiline description of what this action does.\nHere you have another line.\n",
                  "tags": [{
                          "name": "user"
                      },
                      {
                          "name": "signup"
                      }
                  ],
                  "headers": {
                      "type": "integer",
                      "enum": [1, 2]
                  },
                  "payload": {
                      "type": "integer"
                  }
              }
          }
      }
  }

Expected behavior

Parser should throw error that header must be of object type

Review differences in the compatibility suite tests between older and new parser version

Passing empty string to parse raises fatal error

What?

If you pass an empty string to the parse method, it breaks.

Example:

parser.parse('');

It looks like it's related to the Go parser though as I see Go stuff:

fatal error: out of memory

runtime stack:
runtime.throw(0x1054503bc, 0xd)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/panic.go:617 +0x72
runtime.largeAlloc(0xffffffffffffffff, 0xc000020100, 0xc000000600)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/malloc.go:1043 +0x186
runtime.mallocgc.func1()
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/malloc.go:950 +0x46
runtime.systemstack(0x7fff5fbf1d70)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/asm_amd64.s:351 +0x66
runtime.mstart()
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/proc.go:1153

goroutine 17 [running, locked to thread]:
runtime.systemstack_switch()
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/asm_amd64.s:311 fp=0xc000497cb0 sp=0xc000497ca8 pc=0x1050571c0
runtime.mallocgc(0xffffffffffffffff, 0x0, 0xc0000baa00, 0x1)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/malloc.go:949 +0x872 fp=0xc000497d50 sp=0xc000497cb0 pc=0x10500d8d2
runtime.rawbyteslice(0xffffffffffffffff, 0x0, 0x0, 0x0)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/string.go:272 +0xa1 fp=0xc000497d90 sp=0xc000497d50 pc=0x1050471f1
runtime.stringtoslicebyte(0x0, 0x104014720, 0xffffffffffffffff, 0x1054549e9, 0x19, 0xc000497e48)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/string.go:161 +0xb5 fp=0xc000497dd8 sp=0xc000497d90 pc=0x105046bc5
main.Parse(0x104014720, 0xffffffffffffffff, 0x0, 0x0, 0x0)
	/Users/fmvilas/www/parser/cparser/cparser.go:20 +0x5f fp=0xc000497e58 sp=0xc000497dd8 pc=0x10537a11f
main._cgoexpwrap_6ca36088b704_Parse(0x104014720, 0xffffffffffffffff, 0x0, 0x0, 0x0)
	_cgo_gotypes.go:86 +0x70 fp=0xc000497e90 sp=0xc000497e58 pc=0x10537a000
runtime.call64(0x0, 0x7fff5fbf1dc0, 0x7fff5fbf1e58, 0x28)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/asm_amd64.s:520 +0x3b fp=0xc000497ee0 sp=0xc000497e90 pc=0x1050575fb
runtime.cgocallbackg1(0x0)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/cgocall.go:314 +0x177 fp=0xc000497f58 sp=0xc000497ee0 pc=0x105004e87
runtime.cgocallbackg(0x0)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/cgocall.go:191 +0xc5 fp=0xc000497fc0 sp=0xc000497f58 pc=0x105004c75
runtime.cgocallback_gofunc(0x0, 0x0, 0x0, 0x0)
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/asm_amd64.s:773 +0x9b fp=0xc000497fe0 sp=0xc000497fc0 pc=0x105058b4b
runtime.goexit()
	/usr/local/Cellar/go/1.12.1/libexec/src/runtime/asm_amd64.s:1337 +0x1 fp=0xc000497fe8 sp=0xc000497fe0 pc=0x105059271

Native JS Parser: Encapsulate access to AsyncAPI

it’d be great to think about the API to retrieve useful information without having to know the structure of the document

something like .getMessages, getServers, getChannels, etc…

The idea is that, if at some point there’s a breaking change in the spec, the .getMessages call (for example) remains the same.

Extracted from a conversation with @fmvilas

Add validation of duplicated tags

Reason/Context

We need to validate if users did not provide 2 tags with the same name on the same level. Tags can be provided in many objects, we should avoid duplicates only in a given object not across the whole document.

Description

https://json-schema.org/understanding-json-schema/reference/combining.html
Unfortunately, now the schema will reject everything. This is because the Properties refers to the entire schema. And that entire schema includes no properties, and knows nothing about the properties in the subschemas inside of the allOf array.
This shortcoming is perhaps one of the biggest surprises of the combining operations in JSON schema: it does not behave like inheritance in an object-oriented language. There are some proposals to address this in the next version of the JSON schema specification.

Tests should be run on PR

Reason/Context

It would be nice to see the tests running on the PR before the review process.

Description

Make a test workflow on PR's which runs the tests upon a new PR and not just on release.

Extract checkErrorWrapper and use it in other tests

Reason/Context

Tests that do try/catch to evaluate thrown error must be wrapped in checkErrorWrapper to make sure tests always work, and they fail even if the test doesn't jump into the catch.

Description

  • move checkErrorWrapper from parse_test.js to separate file, utils.js ?
  • refactor asyncapiSchemaFormatParser_test.js and customValidators_test.js to use wrapper in test cases where try/catch is used

Message traits are not applied when using oneOf on channels

Describe the bug

Message traits are not applied when using oneOf on channels.

How to Reproduce

Using the following code for testing:

const asyncapi = await parser.parse(<FILE>);
  
const result = asyncapi.channels()['test'].subscribe().messages()[0].contentType();

I get the expected result (contentType: application/json) with the following YAML file:

asyncapi: 2.0.0

info:
  title: Test
  version: 1.0.0

channels:
  test:
    subscribe:
      message:
        $ref: "#/components/messages/test"

components:
  messages:
    test:
      payload:
        type: string
      traits:
        - $ref: "#/components/messageTraits/commons"

  messageTraits:
    commons:
      contentType: application/json

But I get the wrong result (contentType: undefined) with the following YAML file:

asyncapi: 2.0.0

info:
  title: Test
  version: 1.0.0

channels:
  test:
    subscribe:
      message:
        oneOf:
          - $ref: "#/components/messages/test"

components:
  messages:
    test:
      payload:
        type: string
      traits:
        - $ref: "#/components/messageTraits/commons"

  messageTraits:
    commons:
      contentType: application/json

I have tried with more than one message, and other traits, like headers, but they are missing from the message too.

Expected behavior

Message traits should be applied.

Error: Document has to be either JSON or YAML.

Apparently there is an error in my document, as the generator spews out:

Something went wrong:
Error: Document has to be either JSON or YAML.
    at parse (/usr/lib/node_modules/asyncapi-generator/node_modules/asyncapi-parser/lib/parser.js:75:11)

Please consider changing the message to something a bit more helpful. E.g. like the playground does it:

yaml: line 28: mapping values are not allowed in this context

Provided with this message, it was really easy to fix the issue.

Running ag v0.12.0

MIssing validation of channel parameters

It would be nice if the parser could validate channel parameters
Having e.g.

channels
  playground/testParams/{param1}

without the parameters being specified like e.g.

    parameters:
      - name: param1
        schema:
          type: string

should result in the parser bailing out - with a useful error message.
Please consider this.
Thanks.

Types for typescript?

When using this tool programmatically within a typescript app, I could not see any type definitions anywhere.. are there any in the pipelines or an @types package i might have missed?

AsyncAPI reference to Avro [BUG]

Describe the bug
I try to make a reference to an Avro object in AsyncAPI. For development, I used playground.asyncapi.io.
When defining a reference to our Kafka Schema Registry, which returns Avro like in your documentation

"Train": {
  "name": "Train",
  "title": "Train Information",
  "summary": "Inform about Trains.",
  "schemaFormat": "application/vnd.apache.avro+json;version=1.9.0",
  "payload": {
    "$ref": "https://schema-registry-dev.app.01d.io.sbb.ch/subjects/kafka.my-schema-value/versions/1#Train"
  }
}

the reference gets correctly resolved to

"Train": {
  "name": "Train",
  "title": "Train Information",
  "summary": "Information about trains.",
  "payload": {
    "subject": "kafka.my-schema-value",
    "version": 1,
    "id": 1,
    "schema":   
        {
          "type": "record",
          "name": "Train",
          "namespace": "ch.sbb.kafka.platform.model",
          "fields": [
            {
              "name": "model",
              "type": "string"
            },
            {
              "name": "manufacturer",
              "type": "string"
            }
          ]
        }
    }
}

this means that the reference itself and the link is working correctly. But when you then check the HTML panel in playground, the payload object is displayed as "", instead of the object. Do I miss something here or is the playground component broken?

To Reproduce
Steps to reproduce the behavior:

  1. Open playground.asyncapi.io
  2. Enter an AsyncAPI document with a reference to Avro as described above.
  3. The reference gets resolved automatically.
  4. Check right HTML panel for the operation that included the reference. Payload is "" instead of the object.

Expected behavior
Object is being displayed in JSON.

Screenshots
I have a screenshot. How do I add it to this bug report?

allSchemas does not return "all" schemas as expected

Reason

Given the definition of the function, one would expect ALL the schemas to be returned, however this is not the case. As seen in the implementation it only shallowly look at schemas from messages and components.

Description

  • Think about how it can be done recursive and handle circle references.
  • Remember to update tests so they reflect this new behavior.

Hints:

  • Discussed in #32

Make sure new parser releases are not introducing any regression

Reason/Context

Parser has many tests but not all cases are covered. https://github.com/asyncapi/tck/ was created to hold all the possible valid and invalid cases. We should make sure that with new releases the percentage of the parser compatibility doesn't decrease.

Description

Maybe we can add an action that runs tck on a PR and validates the numbers and block merge? tck outputs JSON with results so this should be relatively easy

Parser inconsistently applying additionalProperties schema validation

Describe the bug

Depending on how the schema is constructed, additionalProperties is sometimes allowed to deviate from the schema without a parse error.

How to Reproduce

The following is parsed successfully:

asyncapi: 2.0.0
info:
  title: Demo API
  version: '2-0-0'

channels:
  /someChannel:
    publish:
      operationId: someOp
      message:
        payload:
          type: object
          additionalProperties: [invalid_array]

However, the following is rejected:

asyncapi: 2.0.0
info:
  title: Demo API
  version: '2-0-0'

channels:
  /someChannel:
    publish:
      operationId: someOp
      message:
        payload:
          $ref: #/components/schemas/someOp
components:
  schemas:
    someOp:
      type: object
      additionalProperties: [invalid_array]

Expected behavior

I would expect the parser to reject both formats, given it does not comply with the schema

allSchemas and allMessages does not return expected keys

Given the following input:

 schemas:
    lightMeasuredPayload:
      type: object
      properties:
        lumens:
          type: integer
          minimum: 0
          description: Light intensity measured in lumens.
          x-pi: false
        sentAt:
          $ref: "#/components/schemas/sentAt"
    turnOnOffPayload:
      type: object
      properties:
        command:
          type: string
          enum:
            - on
            - off
          description: Whether to turn on or off the light.
          x-pi: false
        sentAt:
          $ref: "#/components/schemas/sentAt"

I would expect them to have the key lightMeasuredPayload and turnOnOffPayload however, they get these anonymous-schema-x names.

The unit test for the method should also be changed.

describe('#allSchemas()', function () {
it('should return an array with all the schemas used in the document', () => {
const doc = { channels: { test: { parameters: { test: { schema: { $id: 'test', test: true, k: 0 } } }, publish: { message: { headers: { test: true, k: 1 }, payload: { test: true, k: 2 } } } }, test2: { subscribe: { message: { payload: { $id: 'test', test: true, k: 2 } } } } }, components: { schemas: { test: { test: true, k: 3 } } } };
const d = new AsyncAPIDocument(doc);
expect(d.allSchemas().size).to.be.equal(4);
d.allSchemas().forEach(t => {
expect(t.constructor.name).to.be.equal('Schema');
expect(t.json().test).to.be.equal(true);
});
});

This problem is also present when calling allMessages function.

Recursive messages cause the parser to stack overflow

Describe the bug

I need to define messages that are recursive tree structures. When I do this and attempt to use the @asyncapi/generator, to generate documentation (or code) for my spec, the parser fails with a Error: Maximum call stack size exceeded message.

How to Reproduce

Here's a minimal asyncapi spec that reproduces the issue:

asyncapi: '2.0.0'
info:
  title: Example
  version: 0.1.1
channels:
  recursive:
    subscribe:
      message:
        payload:
          $ref: '#/components/schemas/Recursive'
components:
  schemas:
    Recursive:
      type: object
      properties:
        children:
          type: array
          items:
            $ref: '#/components/schemas/Recursive'
        # This also causes a stack overflow
        # child:
        #   $ref: '#/components/schemas/Recursive'
        something:
          type: string
$ npx -p @asyncapi/generator ag ./recursive.yaml @asyncapi/html-template --force-write -o docs
npx: installed 213 in 8.774s
Something went wrong:
Error: Maximum call stack size exceeded
    at parse (~/.npm/_npx/32509/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/parser.js:121:11)
    at async Generator.generateFromString (~/.npm/_npx/32509/lib/node_modules/@asyncapi/generator/lib/generator.js:233:21)
    at async /~/.npm/_npx/32509/lib/node_modules/@asyncapi/generator/cli.js:150:9

Expected behavior

I would expect the parser to be able to handle recursive messages.

Map/Dictionary Parsing Failing

Describe the bug

I am not 100% sure this is a bug but I've been able the whittle this down to a simple document to duplicate it. I am only able to duplicate it using the CLI for generator, but I believe the issue is in the parser, hence I'm posting the issue here. The issue does not show up when using the playground online.

When I try to generate either an html or MD template from the YML file below, I get the following error:

Something went wrong:
TypeError: Cannot convert undefined or null to object
    at Function.entries (<anonymous>)
    at recursiveSchema (/Users/ryan/.nvm/versions/node/v13.13.0/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/models/asyncapi.js:272:49)
    at /Users/ryan/.nvm/versions/node/v13.13.0/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/models/asyncapi.js:327:11
    at Array.forEach (<anonymous>)
    at /Users/ryan/.nvm/versions/node/v13.13.0/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/models/asyncapi.js:325:40
    at Array.forEach (<anonymous>)
    at schemaDocument (/Users/ryan/.nvm/versions/node/v13.13.0/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/models/asyncapi.js:311:24)
    at assignIdToAnonymousSchemas (/Users/ryan/.nvm/versions/node/v13.13.0/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/models/asyncapi.js:346:3)
    at new AsyncAPIDocument (/Users/ryan/.nvm/versions/node/v13.13.0/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/models/asyncapi.js:25:5)
    at parse (/Users/ryan/.nvm/versions/node/v13.13.0/lib/node_modules/@asyncapi/generator/node_modules/@asyncapi/parser/lib/parser.js:128:10)

The generator I'm using is 0.39.1. I was able to hit the same issue with version 0.37.0.

I understand it very well could be that I'm not using the standard correctly. The documentation on how to do a map/dictionary, while present, is hard to find. And I believe there is not much regarding doing list/arrays. In the end I need to do a Map<String,List<Object>> but this example is just Map<String, String> to keep it simple.

How to Reproduce

asyncapi: '2.0.0'
defaultContentType: application/json
info:
  title: Map Object Test
  version: '1'

channels:
  some_channel:
    subscribe:
      message:
        name: some_map
        payload:
          type: object
          additionalProperties:
            type: string
          #properties:
          #  name:
          #    type: string

If I comment the additionalProperties branch and uncomment the properties branch, it works.

ramldt2jsonschema package has link to fs module

The 'ramldt2jsonschema' module you are using has a link to the 'fs' module in the code. As a result, it turned out to be impossible to use parser version 0.14 with the asyncapi-react project when it is built only for the browser.

Is there anything you can do about this?

Make parser auto-discover schema parsers mime-types

While I was working on extracting RAML and OpenAPI schema parsers (#44), I realized that it's a bit painful to remember all the mime types that a given schema parser supports. It would be great to make the parser auto-discover these mime types.

For instance, instead of writing this:

parser.registerSchemaParser([
  'application/vnd.oai.openapi;version=3.0.0',
  'application/vnd.oai.openapi+json;version=3.0.0',
  'application/vnd.oai.openapi+yaml;version=3.0.0',
], openapiSchemaParser);

You write this:

parser.registerSchemaParser(openapiSchemaParser);

This will improve a few things:

  1. You will no longer have to remember the list of mime types.
  2. It will help with evolution. E.g., if a parser supports a new mime type, you'll only have to update to the latest version.
  3. It's less error-prone.

How?

By enforcing a common interface on schema parsers, e.g.:

openapiSchemaParser.parse = function ({ message, defaultSchemaFormat }) { ... }
openapiSchemaParser.getMimeTypes = function () { ... }

Add validation of operationId in OperationTraits

Reason/Context

We have validation of duplicated operationId in Operation objects but it doesn't take into account a possibility that operationId can be provided in the OperationTrait

Description

Add validation of server variable example

Reason/Context

You can provide in the AsyncAPI document a variable used in the server URL, later you describe such a variable with https://www.asyncapi.com/docs/specifications/2.0.0/#a-name-servervariableobject-a-server-variable-object and one of the supported fields is example that we should validate

Description

validate variable example against the enum value. In case there is no enum we should not fail validation but if the enum is there and the example is not one of the values from the enum, validation should fail

Documentation of generator posibilities

In docs/authoring.md documented usage of "schemaName" variable for $$schema$$ file definition which is actually is schema.uid().
Perhaps it will be good to document this, because in case of schema like:

components:
  schemas: 
   peoplePayload:
      type: object
      properties:
        event:
          $ref: "#/components/schemas/people"
    people:
      type: object
      properties:
        id:
          type: integer
          format: int64

The "peoplePayload.properties()" will return [string="event", Schema=people]
So, during simple generation of files $$schema$$ like

Schema name is '{{schemaName}}' and properties are:
{% for propName, prop in schema.properties() %}
- {{propName}}
{% endfor %}

I've got:

Schema name is 'peoplePayload' and properties are:
- event

and

Schema name is 'people' and properties are:
- id

Which is broke chain.

It might also be useful for other "* Name" variables to explicitly define a path in parser API.

Add hasParameters to Channel object

It would be great to have a convenient method for the Channel object to check if it has parameters defined. Something like:

channel.hasParameters()

Automate release of parser and other packages

deprecated attribute not supported

Describe the bug

The deprecated JSON schema attribute appears to be supported by AsyncAPI, but is not supported by the parser.

How to Reproduce

There is no exposed 'deprecated' field, and therefore cannot be used by generator templates, for instance.

Expected behavior

Support the fields that AsyncApi schema defines.

Improve error handling in parser

Reason/Context

On the Slack, there was a discussion in which several proposals were proposed to improve the handling of errors in parser.

Description

Proposals:

  1. Add custom-validation-error new error type for custom validation like: validate variables of server name etc...

  2. Create the predefinied classes for error types like in models:

class ValidationError extends ParserError {
  super(...args) {
     args.type = "validation-errors";
     super(args);
  }

  // additional logic
}
  1. Think about subtypes for custom-validation-errors, for channels, server-variables etc. It would be great if it will be implemented with HTTP Problems RFC.

NOTE: this is improvement not for 1.0.0 but for the next version.

Schemas are not assigned correct uid's

Nested schemas without manually specifying the $id attribute never get assigned correct id's.

Example

{
	"channels": {
		"test": {
			"parameters": {
				"testParam1": {
					"schema": {
						"type": "object",
						"properties": {
							"testParamNestedSchemaProp": {
								"type": "object",
								"properties": {
									"testParamNestedNestedSchemaProp2": {
										"type": "string"
									}
								}
							}
						}
					}
				}
			},
                        ...
		}
	}
}

given the above JSON I would expect the $uid of the schema to be <anonymous-schema-1>, testParamNestedSchemaProp, testParamNestedNestedSchemaProp2, however it is <anonymous-schema-1>, undefined, undefined.

One question, should the parameter schema not get assigned the uid testParam1?

Custom schema and its parsing

This might be daft, but I'm a bit puzzled.
How does one uses custom schema that is based on AsyncApi one. I assume I need a custom parser, but what's the role of the parser?

After registering custom parser, how does one call parse? Parser.parse() has no clue about custom parsers there were registered.
Is it expected for iterateDocument() to be used instead of parse with custom parser?

Also I'm bit uncertain about how exactly one points to a schema when registering it.
parser.registerSchemaParser(
[
"application/vnd.aai.asyncapi;version=2.0.0",
"application/vnd.aai.asyncapi+json;version=2.0.0",
"application/vnd.aai.asyncapi+yaml;version=2.0.0",
"application/schema;version=draft-07",
"application/schema+json;version=draft-07",
"application/schema+yaml;version=draft-07"
],

this is clearly not a path to a file holding a schema. I've looked through all schemas, and apart from version, they do not contain any vnd.aa.xxx fields, thus I am clueless on how it matches to a file schema.

Would really appreciate a little of guidance on how to register a custom schema (from a local file path) and its parser and how parse documents with it.

@asyncapi/parser doesn't work in browser

Describe the bug

I created a new react app and add in dependencies @asyncapi/parser. Making example as in Readme.md I see the error:

Module not found: Can't resolve './build/Release/re2'

How to Reproduce

Steps to reproduce the issue. Attach all resources that can help us understand the issue:

Create any browser app, but I give react example:

  • npx create-react-app my-app
  • cd my-app && npm i @asyncapi/parser
  • change main App.js component to:
import { parse } from "@asyncapi/parser";

function App() {
  useEffect(() => {
    const lol = async () => {
      const doc = await parse(`
        asyncapi: '2.0.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
      `);
      const channels  = doc.channels();
      console.log(channels);
    }
  });

  return null;
}
  • you should see error, which is mentioned in description of bug.

Expected behavior

Parser should works in node and in browser without errors.

Possible solution

Use native Regex, not Re2, because it has a compiled binary, and also investigate if node-fetch could problems running on browser.

allSchemas does not return array items.

Given the following AsyncAPI files:
Visualizer.json

{
	"asyncapi": "2.0.0",
	"info": {
		"title": "Visualizer application",
		"version": "1.0.0",
		"license": {
			"name": "Apache 2.0",
			"url": "https://www.apache.org/licenses/LICENSE-2.0"
		}
	},
	"defaultContentType": "application/json",
	"channels": {
		"processed/spotPrices": {
			"description": "subscribes to event for when new data is being processed",
			"subscribe": {
				"message": {
					"$ref": "./components/messages/processedSpotPrices.json",
					"description": "Dataset"
				}
			}
		},
	}
}

processedSpotPrices.json

{
	"title": "Energinet data in raw format",
	"name": "processedSpotPrices",
	"payload": {
		"$ref": "../schemas/processedSpotPricesSchema.json"
	}
}

processedSpotPricesSchema.json

{
	"type": "array",
	"$id": "allProcessedSpotPricesSchema",
	"items": {
		"type": "object",
		"$id": "processedSpotPricesSchema",
		"properties": {
			"MONTH_DATE_DK": {
				"type": "string",
				"description": "Timestamp for the year and month of the recording - \"yyyy-MM\""
			},
			"PRICE_AREA": {
				"type": "string",
				"description": "DK1 or DK2."
			},
			"AVERAGE_SPOT_PRICE_EUR": {
				"type": "string",
				"description": "Euro spot price over the month."
			}
		}
	}
}

I would expect allSchemas function to return an array of allProcessedSpotPricesSchema and processedSpotPricesSchema however, only allProcessedSpotPricesSchema is returned.

If you see anything wrong with my json files let me know!

Simplify the definition of models using mixins.

Reason/Context

Proposal presented in #88 (comment) about mixins could reduce the amount of code and help in project's maintenance.

Description

Add mixins for common functions related to tags, externalDocs, extensions etc. and use them wherever possible.

asyncapi.components().messages() is undefined when message ref'ed is in other file

I have in a template as follows:

{%- for messageName, message in asyncapi.components().messages() -%}
{{-messageName|log-}}
{%- endfor -%}

When executed on the following localref.yaml file:

asyncapi: '2.0.0-rc2'
id: 'urn:hello_world_publisher'
info:
  title: Hello world publisher application
  version: '0.1.0'
channels:
  hello:
    publish:
      message:
        $ref: '#/components/messages/hello-msg'
    x-protocolInfo:
      nats:
        pubSub:
components:
  messages:
    hello-msg:
      name: helloMsg
      payload:
        type: string
        pattern: '^hello .+$'

the output is:

hello-msg

But when executed on a globalref.yaml file, that references a message defined in another file, I get an error.
Content of the globalref.yaml file is:

asyncapi: '2.0.0-rc2'
id: 'urn:hello_world_publisher'
info:
  title: Hello world publisher application
  version: '0.1.0'
channels:
  hello:
    publish:
      message:
        $ref: './common.yaml#/components/messages/hello-msg'

Content of the common.yaml file is:

components:
  messages:
    hello-msg:
      name: helloMsg
      payload:
        type: string
        pattern: '^hello .+$'

The error shown is:

Something went wrong:
Template render error: (unknown path) [Line 3, Column 35]
  Error: Unable to call `the return value of (asyncapi["components"])["messages"]`, which is undefined or falsey
    at Object._prettifyError (/usr/lib/node_modules/asyncapi-generator/node_modules/nunjucks/src/lib.js:36:11)

I would expect the asyncapi.components().messages() to contain all messages, whether they are defined in the same file or in a different file.

There is a similar issue with asyncapi.components().schemas().

Using ag v0.16.0

List down manual validations

The JSON Schema version of the spec is capable of validating many things, however, many others are impossible or very complicated to express. We should carefully read the spec and create a list of all the restrictions/validations that should be applied aside from the JSON Schema ones.

List

parser fails when using oneOf

The following example fails:

asyncapi: '2.0.0-rc1'
id: 'urn:hello-world-oneOf'
info:
  title: Hello world oneOf application
  version: '0.1.0'
channels:
  hello:
    publish:
      message:
        oneOf:
          - $ref: '#/components/messages/hello-msg1'
          - $ref: '#/components/messages/hello-msg2'          
        
components:
  messages:
    hello-msg1:
      payload:
        type: string
        pattern: '^hello .+$' 
    hello-msg2:
      payload:
        type: string        

with error

Error: Unexpected token u in JSON at position 0
      at Object.parse (lib/parser.js:72:11)

Improve testing for typescript types and bundle.js

Reason/Context

  • Parser is not only for node.js but should work in the browser too. I recently broke bundle.js by introducing a library that doesn't work in the browser. I don't want to break it again, but I also know that when you choose a library, it is not a common habit to check if it is isomorphic (working both on server and browser). We need a way to test if the PR is not causing bundle.js to fail
  • We will now generate typescript types from our JSDocs which is, in my opinion, a very good thing but also a very error-prone. We need a way to test that given PR is not breaking those types

I think testing of above can be solved with one sample project, like under one umbrella.

Description

we need some sample project in TS that will also have a ui where we can run parser and test result with
puppeteer. I was thinking that we might use asyncapi-react as it is already a TS project with UI but, but based on my experience, I know that it might look like best and easiest solution but it can backfire over time and writing a simple test project might be much more efficient.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.