thim81 / asyncapi-format

Format an AsyncAPI document by ordering, formatting and filtering fields.
MIT License
6 stars 1 forks source link
asyncapi asyncapi-tooling asyncapi-tools cli filtering formatting sorting

asyncapi-format icon

npm npm

asyncapi-format

Format an AsyncAPI document by ordering, formatting and filtering fields.

The asyncapi-format CLI can load an AsyncAPI file, sorts the AsyncAPI fields by ordering them in a hierarchical order, format the casing of the fields and can output the file with clean indenting, to either JSON or YAML.

Next to the ordering & formatting, the CLI provides additional options to filter fields & parts of the AsyncAPI document based on flags, tags, operations and operationID's.

Table of content

Use-cases

When working on large AsyncAPI documents or with multiple team members, the file can be become messy and difficult to compare changes. By sorting & formatting from time to time, the fields are all ordered in a structured manner & properly cased, which will help you to maintain the file with greater ease.

The filtering is a handy add-on to remove specific elements from the AsyncAPI like internal endpoints, beta tags, ... This can be useful in CI/CD pipelines, where the AsyncAPI is used as the source for other documents like Web documentation or for generating event producers/consumers.

Features

Installation

Local Installation (recommended)

While possible to install globally, we recommend that you add the asyncapi-format CLI to the node_modules by using:

$ npm install --save asyncapi-format

or using yarn...

$ yarn add asyncapi-format

Note that this will require you to run the asyncapi-format CLI with npx asyncapi-format your-asyncapi-file.yaml or, if you are using an older versions of npm, ./node_modules/.bin/asyncapi-format your-asyncapi-file.yaml.

Global Installation

$ npm install -g asyncapi-format

NPX usage

To execute the CLI without installing it via npm, use the npx method

$ npx asyncapi-format your-asyncapi-file.yaml

Command Line Interface

asyncapi-format.js <input-file> -o [ouptut-file] [options]

Arguments:
  input-file   the AsyncAPI document, can be either a .json or .yaml file
  ouptut-file  the output file is optional and be either a .json or .yaml file.

Options:

  --output, -o          Save the formatted AsyncAPI file as JSON/YAML           [path]

  --sortFile            The file to specify custom AsyncAPI fields ordering     [path]
  --casingFile          The file to specify casing rules                        [path]
  --filterFile          The file to specify filter rules                        [path]

  --no-sort             Don't sort the AsyncAPI file                         [boolean]
  --sortComponentsFile  The file with components to sort alphabetically         [path]

  --rename              Rename the AsyncAPI title                             [string]

  --configFile          The file with the AsyncAPI-format CLI options           [path]

  --lineWidth           Max line width of YAML output                         [number]

  --json                Prints the file to stdout as JSON                    [boolean]
  --yaml                Prints the file to stdout as YAML                    [boolean]

  --help                Show help                                            [boolean]
  --verbose             Output more details of the filter process              [count]

AsyncAPI format CLI options

Parameter Alias Description Input type Default Info
file the original AsyncAPI file path to file required
--output -o save the formatted AsyncAPI file as JSON/YAML path to file optional
--sortFile -s the file to specify custom AsyncAPI fields ordering path to file defaultSort.json optional
--filterFile -f the file to specify filter setting path to file defaultFilter.json optional
--casingFile -c the file to specify casing setting path to file optional
--no-sort don't sort the AsyncAPI file boolean FALSE optional
--sortComponentsFile sort the items of the components (schemas, parameters, ...) by alphabet path to file defaultSortComponents.json optional
--rename rename the AsyncAPI title string optional
--configFile -c the file with all the format config options path to file optional
--lineWidth max line width of YAML output number -1 (Infinity) optional
--json prints the file to stdout as JSON FALSE optional
--yaml prints the file to stdout as YAML FALSE optional
--verbose -v, -vv, -vvv verbosity that can be increased, which will show more output of the process optional
--help h display help for command optional

AsyncAPI sort configuration options

The CLI will sort the AsyncAPI document in the defined order liked defined per AsyncAPI key/element. The fields that are not specified will keep their order like it is in the original AsyncAPI document, so only defined fields will be re-ordered.

The default sorting based on the defined order (listed in the table below), which is stored in the defaultSort.json file.

You can easily modify this by specifying your own ordering per key, which can be passed on to the CLI (see below for an example on how to do this).

Key Ordered by AsyncAPI reference
root - asyncapi
- info
- servers
- channels
- components
- tags
- externalDocs
AsyncAPI-object
channels - description
- parameters
- subscribe
- publish
- bindings
channels-item-object
parameters - name
- in
- description
- required
- schema
parameters-object
subscribe - operationId
- summary
- description
- message
- traits
- tags
operation-object
publish - operationId
- summary
- description
- message
- traits
- tags
operation-object
messages - name
- title
- summary
- description
- headers
- payload
- contentType
message-object
payload - description
- type
- items
- properties
- format
- example
- default
schema-object
components - parameters
- messages
- schemas
components-object
schema - description
- type
- items
- properties
- format
- example
- default
schema-object
schemas - description
- type
- items
- properties
- format
- example
- default
properties - description
- type
- items
- format
- example
- default
- enum

Have a look at the folder yaml-default and compare the "output.yaml" (sorted document) with the "input.yaml" (original document), to see how asyncapi-format have sorted the AsyncAPI document.

AsyncAPI filter options

By specifying the desired filter values for the available filter types, the asyncapi-format CLI will strip out any matching item from the AsyncAPI document. You can combine multiple types to filter out a range of AsyncAPI items.

For more complex use-cases, we can advise the excellent https://github.com/Mermade/openapi-filter package, which has extended options for filtering AsyncAPI documents.

Type Description Type Examples
operations AsyncAPI operations array ['subscribe','publish']
inverseOperations AsyncAPI operations that will be kept array ['subscribe','publish']
tags AsyncAPI tags array ['measure','command']
inverseTags AsyncAPI tags that will be kept array ['measure','command']
operationIds AsyncAPI operation ID's array ['turnOff','dimLight']
inverseOperationIds AsyncAPI operation ID's that will be kept array ['turnOff','dimLight']
flags Custom flags array ['x-exclude','x-internal']
flagValues Custom flags with a specific value array ['x-version: 1.0','x-version: 3.0']
unusedComponents Unused components array ['examples','schemas']
stripFlags Custom flags that will be stripped array ['x-exclude','x-internal']
textReplace Search & replace values to replace array [{'searchFor':'API','replaceWith':'Event'}]

Some more details on the available filter types:

Filter - operations

=> operations: Refers to the Channel Item Object

This will remove all fields and attached fields that match the verbs. In the example below, this would mean that all publish, subscribe items would be removed from the AsyncAPI document.

channels:
    smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured:
        publish:
            summary: Inform about environmental lighting conditions of a particular streetlight.
            operationId: receiveLightMeasurement
            traits:
                - $ref: '#/components/operationTraits/kafka'
            message:
                $ref: '#/components/messages/lightMeasured'
        subscribe:
            operationId: turnOn
            traits:
                - $ref: '#/components/operationTraits/kafka'
            message:
                $ref: '#/components/messages/turnOnOff'

=> inverseOperations: This option does the inverse filtering, by keeping only the operations defined and remove all other operations.

Filter - tags

=> tags: Refers to the "tags" field from the Operation Object

This will remove all fields and attached fields that match the tags. In the example below, this would mean that all items with the tags command or measure would be removed from the AsyncAPI document.

For example:

asyncapi: 2.0.0
info:
    title: Streetlights API
    version: 1.0.0
tags:
    - name: command
      description: Light commands
    - name: measure
      description: Measurement data
channels:
  smartylighting.streetlights.measured:
    description: The topic on which measured values may be produced and consumed.
    publish:
        summary: Inform about environmental lighting conditions of a particular streetlight.
        operationId: receiveLightMeasurement
        tags:
            - name: measure
            - name: command

=> inverseTags: This option does the inverse filtering, by keeping only the tags defined and remove all other tags, including the operations without a tags.

Filter - operationIds

=> operationIds: Refers to the "operationId" field from the Operation Object

This will remove specific fields and attached fields that match the operation ID's. In the example below, this would mean that the item with operationID turnOff would be removed from the AsyncAPI document.

For example:

asyncapi: 2.0.0
info:
    title: Streetlights API
    version: 1.0.0
channels:
    smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured:
        subscribe:
            operationId: turnOn

=> inverseOperationIds: This option does the inverse filtering, by keeping only the operationIds defined and remove all other operationIds, including the operations without an operationId.

Filter - flags

=> flags: Refers to a custom property that can be set on any field in the AsyncAPI document.

This will remove all fields and attached fields that match the flags. In the example below, this would mean that all items with the flag x-exclude would be removed from the AsyncAPI document.

For example:

asyncapi: 2.0.0
info:
    title: Streetlights API
    version: 1.0.0
channels:
    smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured:
        description: The topic on which measured values may be produced and consumed.
        x-exclude: true
        subscribe:
            operationId: turnOn

Filter - flagValues

=> flagValues: Refers to a flag, custom property which can be set on any field in the AsyncAPI document, and the combination with the value for that flag.

This will remove all fields and attached fields that match the flag with the specific value.

A flagValues example:

flagValues:
    - x-version: 1.0
    - x-version: 3.0

In the example below, this would mean that all items with the flag x-version that matches x-version: 1.0 OR x-version: 3.0 would be removed from the AsyncAPI document.

asyncapi: '2.2.0'
info:
    title: Streetlights Kafka API
channels:
    smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured:
        x-version: 1.0

The filter option flagValues also will remove flags that contain an array of values in the AsyncAPI document.

A flagValues example:

flagValues:
    - x-versions: 1.0
    - x-versions: 2.0

In the example below, this would mean that all items with the flag x-versions, which is an array, that match x-version: 1.0 OR x-version: 3.0 would be removed from the AsyncAPI document.

asyncapi: '2.2.0'
info:
    title: Streetlights Kafka API
channels:
    smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured:
        x-versions:
            - 1.0
            - 3.0
            - 5.0

Have a look at flagValues and flagValues for array values for a practical example.

Filter - unusedComponents

=> unusedComponents: Refers to a list of reusable component types, from which unused items will be removed.

This option allows you to strip the AsyncAPI document from any unused items of the targeted components types. Any item in the list of AsyncAPI components that is not referenced as $ref, will get marked and removed from the AsyncAPI document.

REMARK: We will recursively strip all unused components, with a maximum depth of 10 times. This means that "nested" components, that become unused, will also get removed

Supported component types that can be marked as "unused":

Filter - textReplace

=> textReplace: "search & replace" option to replace text in the AsyncAPI specification

The textReplace provides a "search & replace" method, that will search for a text/word/characters in the AsyncAPI description, summary, URL fields and replace it with another text/word/characters. This is very useful to replace data in the AsyncAPI specification.

A textReplace example:

textReplace:
    - searchFor: 'DummyLighting'
      replaceWith: 'Smartylighting'
    - searchFor: 'apiasync.com/'
      replaceWith: 'asyncapi.com/'

This will replace all "DummyLighting" with "Smartylighting" & "apiasync.com/" with "asyncapi.com/" in the AsyncAPI document.

Filter - stripFlags

=> stripFlags: Refers to a list of custom properties that can be set on any field in the AsyncAPI document.

The stripFlags will remove only the flags, the linked parent and properties will remain. In the example below, this would mean that all flags x-exclude itself would be stripped from the AsyncAPI document.

Example before:

asyncapi: 2.0.0
info:
    title: Streetlights API
    version: 1.0.0
channels:
    smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured:
        description: The topic on which measured values may be produced and consumed.
        x-exclude: true
        subscribe:
            operationId: turnOn

Example after:

asyncapi: 2.0.0
info:
    title: Streetlights API
    version: 1.0.0
channels:
    smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured:
        description: The topic on which measured values may be produced and consumed.
        subscribe:
            operationId: turnOn

AsyncAPI formatting configuration options

The asyncapi-format CLI formatting option can assist with keeping the field names consistent by automatically changing the casing of the properties/keys/names for the different elements in the AsyncAPI document. The desired casing can be defined per AsyncAPI key/element (see list below). The keys that are not specified will keep their casing like it is in the original AsyncAPI document, so only for defined fields, the casing will be changed.

Key Description AsyncAPI reference
channels Changes key/name of the channels channels-object
operationId Changes operation ID's that are part of the Operations Object operation-object
properties Changes property keys of the schemas of the inline messages, payload & components schemaObject
componentsSchemas Changes the key of the schema models in the components sections & "$ref" links components-object
componentsMessages Changes the key of the messages models in the components sections & "$ref" links components-object
componentsParameters Changes the key of the parameters models in the components sections & "$ref" links components-object
componentsMessageTraits Changes the key of the message traits models in the components sections & "$ref" links components-object
componentsOperationTraits Changes the key of the operation traits models in the components sections & "$ref" links components-object
componentsSecuritySchemes Changes the key of the security schemes in the components sections & "$ref" links components-object

Casing options

Casing type Casing alias Description Example
πŸͺ camelCase camelCase converts a strings to camelCase asyncapiFormat
πŸ‘¨β€πŸ« PascalCase PascalCase converts a strings to PascalCase AsyncapiFormat
πŸ₯™ kebab-case kebabCase converts a strings to kebab-case asyncapi-format
πŸš‚ Train-Case TrainCase converts a strings to Train-Case Asyncapi-Format
🐍 snake_case snakeCase converts a strings to snake_case asyncapi_format
πŸ•Š Ada_Case AdaCase converts a strings to Ada_Case Asyncapi_Format
πŸ“£ CONSTANT_CASE constantCase converts a strings to CONSTANT_CASE ASYNCAPI_FORMAT
πŸ‘” COBOL-CASE cobolCase converts a strings to COBOL-CASE ASYNCAPI-FORMAT
πŸ“ Dot.notation dotNotation converts a strings to Dot.notation asyncapi.format
πŸ›° Space case spaceCase converts a strings to Space case (with spaces) asyncapi format
πŸ› Capital Case capitalCase converts a strings to Capital Case (with spaces) Asyncapi Format
πŸ”‘ lower case lowerCase converts a strings to lower case (with spaces) asyncapi format
πŸ”  UPPER CASE upperCase converts a strings to UPPER CASE (with spaces) ASYNCAPI FORMAT

REMARK: All special characters are stripped during conversion, except for the @ and $.

The casing options are provided by the nano NPM case-anything package.

Format casing - channels

=> channels: Refers to the channels elements in the AsyncAPI document.

Formatting casing example:

channels: snake_case

Example before:

channels:
  smartylighting.streetlights.lighting.measured:
    description: The topic on which measured values may be produced and consumed.
    subscribe:
      operationId: measuredStreetlight

asyncapi-format will format the "measuredStreetlight" from the original dot.notation to snake_case.

Example after:

channels:
  smartylighting_streetlights_lighting_measured:
    description: The topic on which measured values may be produced and consumed.
    subscribe:
        operationId: measuredStreetlight

Format casing - operationId

=> operationId: Refers to the operationId properties in the AsyncAPI document.

Formatting casing example:

operationId: kebab-case

Example before:

channels:
  smartylighting.streetlights.lighting.measured:
    description: The topic on which measured values may be produced and consumed.
    subscribe:
      operationId: measuredStreetlight

asyncapi-format will format the "measuredStreetlight" from the original camelcase to kebab-case.

Example after:

channels:
  smartylighting.streetlights.lighting.measured:
    description: The topic on which measured values may be produced and consumed.
    subscribe:
      operationId: measured-streetlight

Format casing - model & schema properties

=> properties: Refers to all the schema properties, that are defined inline in the channels and the models in the components section of the AsyncAPI document.

Formatting casing example:

properties: snake_case

Example before:

components:
  schemas:
    lightMeasuredPayload:
      type: object
      properties:
        lumensIntensity:
          type: integer
          minimum: 0
          description: Light intensity measured in lumens.
        sentAt:
          $ref: '#/components/schemas/sentAt'

The CLI will format all the properties like: "lumens", "sentAt" from the original camelcase to snake_case.

Example after:

components:
  schemas:
    lightMeasuredPayload:
      type: object
      properties:
          lumens_intensity:
          type: integer
          minimum: 0
          description: Light intensity measured in lumens.
        sent_at:
          $ref: '#/components/schemas/sentAt'

Format casing - component keys

=> componentsSchemas / componentsMessages / componentsParameters / componentsMessageTraits / componentsOperationTraits / componentsSecuritySchemes: Refers to all the model objects that are defined in the components section of the AsyncAPI document.

Formatting casing example:

componentsSchemas: PascalCase

Example before:

channels:
  smartylighting.streetlights.lighting.measured:
    description: The topic on which measured values may be produced and consumed.
    subscribe:
      message:
        $ref: '#/components/messages/turnOnOff'
components:
  messages:
    lightMeasured:
      name: lightMeasured
      title: Light measured
    turnOnOff:
      name: turnOnOff
      title: Turn on/off
    dimLight:
        name: dimLight
        title: Dim light

asyncapi-format will format all the component keys like: "lightMeasured", "turnOnOff", "dimLight" to PascalCase, including formatting all the "$ref" used in the AsyncAPI document.

Example after:

channels:
  smartylighting.streetlights.lighting.measured:
    description: The topic on which measured values may be produced and consumed.
    subscribe:
      message:
        $ref: '#/components/messages/TurnOnOff'
components:
  messages:
    LightMeasured:
      name: lightMeasured
      title: Light measured
    TurnOnOff:
      name: turnOnOff
      title: Turn on/off
    DimLight:
        name: dimLight
        title: Dim light

CLI sort usage

$ asyncapi-format asyncapi.json -o asyncapi-formatted.json
$ asyncapi-format asyncapi.yaml -o asyncapi-formatted.yaml
$ asyncapi-format asyncapi.json --json
$ asyncapi-format asyncapi.json --yaml
$ asyncapi-format asyncapi.json -o asyncapi.yaml
$ asyncapi-format asyncapi.json -o asyncapi-formatted.json --no-sort

This should keep the AsyncAPI fields in the same order. This can be needed, when you only want to do a filtering or rename action.

$ asyncapi-format asyncapi.json -o asyncapi-formatted.json --sortComponentsFile ./test/json-sort-components/customSortComponents.json

This will sort all elements in the components ( components/schemas, components/messages, components/parameters, components/securitySchemes, ...) section by alphabet.

CLI filter usage

When you want to strip certain flags, tags, operations, operationID's, you can pass a filterFile which contains the specific values for the flags, tags, operations, operationID's.

This can be useful to combine with the sorting, to end-up with an order and filtered AsyncAPI document.

example:

$ asyncapi-format asyncapi.json -o asyncapi-formatted.json --filterFile customFilter.yaml

where the customFilter.yaml would contain a combination of all the elements you want to filter out.

flags:
    - x-visibility
flagValues: [ ]
tags: [ ]
operationIds:
    - dimLight
    - turnOff

CLI rename usage

During CI/CD pipelines, you might want to create different results of the AsyncAPI document. Having the option to rename them might make it easier to work with the results, so that is why we provide this command option.

$ asyncapi-format asyncapi.json -o asyncapi.json --rename "Streetlights API - AsyncAPI 2.0"

which results in

{
    "asyncapi": "2.0.0",
    "info": {
        "title": "Streetlights API",
{
    "asyncapi": "2.0.0",
    "info": {
        "title": "Streetlights API - AsyncAPI 2.0",

CLI configuration usage

All the CLI options can be managed in a separate configuration file and passed along the asyncapi-format command. This will make configuration easier, especially in CI/CD implementations where the configuration can be stored in version control systems.

example:

$ asyncapi-format asyncapi.json --configFile asyncapi-format-options.json

The formatting will happen based on all the options set in the asyncapi-format-options.json file. All the available AsyncAPI format options can be used in the config file.

OpenAPI documents

For handling OpenAPI documents, we have created a separate package openapi-format to allow customisation specific for OpenAPI use-cases.

Credits

The filter capabilities from asyncapi-format are a light version grounded by the work from @MikeRalphson on the openapi-filter package.

The casing options available in asyncapi-format are powered by the excellent case-anything nano package from Luca Ban (@mesqueeb).