Last updated

Decorators in plugins

Decorators transform API descriptions, by adding, removing, or changing elements of the document. Before you build your own decorators:

If you can't find an existing decorator that fits your needs, then you can add a decorator in a plugin.

Preprocessors

Decorators and preprocessors are the same in structure, but preprocessors are run before linting, and decorators are run after. We always recommend using decorators where possible, since the document might not be valid or structured as expected if the linting step hasn't run yet.

Plugin structure

To create a preprocessor or decorator, the object that is exported from your module has to conform to an interface such as the following example:

module.exports = {
  id: 'my-local-plugin',
  preprocessors: {
    oas3: {
      "processor-id": () => {
        // ...
      }
    }
  },
  decorators: {
    oas3: {
      "decorator-id": () => {
        // ...
      }
    }
  }
}

Each decorator or preprocessor is a function that returns an object. The object's keys are the node types in the document, and each of those can contain any or all of the enter(), leave() and skip() functions for that node type. Find more information and examples on the visitor pattern page.

Decorator example

To give a small (but fun) example, here is a decorator that adds a sparkle emoji ✨ at the start of every operation description.

To help keep the plugin code organized, this example uses one file per decorator. In this example, this is the file plugins/decorators/operation-sparkle.js:

module.exports = OperationSparkle;

function OperationSparkle() {
  console.log("adding sparkles ... ");
  return {
    Operation: {
      leave(target) {
        if(target.description) {
          target.description = "✨ " + String(target.description);
        }
      }
    },
  }
};

Decorators use the visitor pattern to run an operation on every node in the document. In this example, when the code executes the leave() function on the Operation node, it checks if the node (passed as target in this example) has a description, and updates it if it does.

To use this decorator, add it to a plugin. In this example the main decorator file is plugins/sparkle.js:

const OperationSparkle = require('./decorators/operation-sparkle.js');

module.exports = {
  id: 'sparkle',
  decorators: {
    oas3: {
      'operation-sparkle': OperationSparkle,
    }
  }
};

The plugin is good to go. For a user to include it in their Redocly configuration, edit the configuration file to look something like this:

plugins:
  - plugins/sparkle.js

decorators:
  sparkle/operation-sparkle: on

Decorator example with parameters

A common use case is a decorator that can accept input values to be used during processing. This example decorator adds a suffix to all OperationIds in the document. Since every use case is different, the user can configure what should be used for the suffix value.

Here's the decorator code, in a file named plugins/decorations/add-suffix.js and it expects a configuration option named suffix:

module.exports = OpIdSuffix;

function OpIdSuffix({suffix}) {
  console.log("updating OperationIds ... ");
  return {
    Operation: {
      leave(target) {
        if(target.operationId) {
          target.operationId = target.operationId + suffix;
        }
      }
    },
  }
};

The suffix configuration option is automatically passed in, and it can be used in the function.

Now extend the decorator from the previous example to add this to the existing plugin in plugins/sparkle.js:

const OperationSparkle = require('./decorators/operation-sparkle.js');
const OpIdSuffix = require('./decorators/add-suffix.js');

module.exports = {
  id: 'sparkle',
  decorators: {
    oas3: {
      'operation-sparkle': OperationSparkle,
      'add-opid-suffix': OpIdSuffix,
    }
  }
};

All that remains is for a user to configure this decorator in their redocly.yaml configuration file to take advantage of the new decorator functionality. Here's an example of the configuration file:

plugins:
  - plugins/sparkle.js

decorators:
  sparkle/operation-sparkle: on
  sparkle/add-opid-suffix:
    suffix: ButShinier

With this configuration, an operationId called GetAllItems would be rewritten as GetAllItemsButShinier. You can choose a more sensible suffix for your use case as appropriate.

Further examples of custom decorators

See some more examples of decorators:

Preprocessors

Preprocessors follow the same structure and operation as decorators, but they are run before the validation/linting step. Running before the validation/linting step makes them brittle because the document may not be valid, and the extra processing step can cause performance impacts. We recommend looking for alternative approaches to preprocessing.

Some advanced use cases do require preprocessing, which is why the functionality is provided for those users.