feat: add support for a plugin interface common with Realm (#1661)

This commit is contained in:
volodymyr-rutskyi
2024-08-21 14:23:02 +03:00
committed by GitHub
parent 9ce88a33a5
commit 7a0e52f57e
46 changed files with 637 additions and 272 deletions

View File

@@ -9,22 +9,24 @@ configuration file.
The following is an example plugin, defining two configuration bundles:
```js
module.exports = {
id: 'my-local-plugin'
configs: {
all: {
rules: {
'operation-id-not-test': 'error',
'boolean-parameter-prefixes': 'error',
module.exports = function myLocalPlugin() {
return {
id: 'my-local-plugin',
configs: {
all: {
rules: {
'operation-id-not-test': 'error',
'boolean-parameter-prefixes': 'error',
},
},
minimal: {
rules: {
'operation-id-not-test': 'off',
'boolean-parameter-prefixes': 'error',
},
},
},
minimal: {
rules: {
'operation-id-not-test': 'off',
'boolean-parameter-prefixes': 'error',
},
}
}
};
};
```

View File

@@ -13,26 +13,29 @@ Decorators and preprocessors are the same in structure, but preprocessors are ru
## 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:
To create a preprocessor or decorator, the function that is exported from your module has to conform to an interface such as the following example:
```js
module.exports = {
id: 'my-local-plugin',
preprocessors: {
oas3: {
"processor-id": () => {
// ...
}
}
},
decorators: {
oas3: {
"decorator-id": () => {
// ...
}
}
}
}
module.exports = function myLocalPlugin() {
return {
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](./visitor.md).
@@ -67,14 +70,16 @@ To use this decorator, add it to a plugin. In this example the main decorator fi
```js
const OperationSparkle = require('./decorators/operation-sparkle.js');
module.exports = {
id: 'sparkle',
decorators: {
oas3: {
'operation-sparkle': OperationSparkle,
}
}
};
module.exports = function sparklePlugin() {
return {
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:
@@ -118,15 +123,17 @@ Now extend the decorator from the previous example to add this to the existing p
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,
}
}
};
module.exports = function sparklePlugin() {
return {
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:

View File

@@ -35,18 +35,21 @@ function OperationIdNotTest() {
The `ctx` object here holds all the context, which can be used to give more situation-aware functionality to the rules you build. This is one of the main use cases for custom rules. The `report()` method is used to give information to return to the user if the node being visited doesn't comply with the rule. You can read the [context](#the-context-object) and [location](#location-object) sections for more information.
Adding this as part of a plugin requires you to add it to the `rules` part of the plugin object, under the relevant document type. The example rule here is intended to be used with OpenAPI, so the plugin code in `plugins/my-rules.js` is as follows:
Adding this as part of a plugin requires you to add it to the `rules` part of the plugin object returned by the exported function, under the relevant document type.
The example rule here is intended to be used with OpenAPI, so the plugin code in `plugins/my-rules.js` is as follows:
```js
const OperationIdNotTest = require('./rules/opid-not-test.js');
module.exports = {
id: 'my-rules',
rules: {
oas3: {
'opid-not-test': OperationIdNotTest,
}
},
module.exports = function myRulesPlugin() {
return {
id: 'my-rules',
rules: {
oas3: {
'opid-not-test': OperationIdNotTest,
},
},
};
};
```

View File

@@ -38,23 +38,25 @@ Note the quotes around the `owner-team` key since it contains a hyphen `-`. Thes
To include the new type in the type tree, the plugin must add the type and modify the parent type, which in this example is `info`. This is done by returning a `typeExtension` structure, as shown in the example below (this example is in `plugins/example-type-extension.js`, this filename is used again in the configuration example later):
```js
module.exports = {
id: 'example-type-extension',
typeExtension: {
oas3(types) {
return {
...types,
XMetaData: XMetaData,
Info: {
...types.Info,
properties: {
...types.Info.properties,
'x-metadata': 'XMetaData',
}
}
};
}
}
module.exports = function typeExtensionsPlugin() {
return {
id: 'example-type-extension',
typeExtension: {
oas3(types) {
return {
...types,
XMetaData: XMetaData,
Info: {
...types.Info,
properties: {
...types.Info.properties,
'x-metadata': 'XMetaData',
},
},
};
},
},
};
};
```

View File

@@ -43,11 +43,13 @@ The paths are relative to the configuration file location. Where there are multi
### Plugin structure
The minimal plugin should export an `id` string that is used to refer to the contents of the plugin in the `redocly.yaml` configuration file:
The minimal plugin should export a function that returns an object with a single property `id` that is used to refer to the contents of the plugin in the `redocly.yaml` configuration file:
```js
module.exports = {
id: 'my-local-plugin',
module.exports = function myPlugin() {
return {
id: 'my-local-plugin',
};
};
```

View File

@@ -34,9 +34,11 @@ Estimated time: 15 minutes
},
};
module.exports = {
id,
decorators,
module.exports = function changeTokenPlugin() {
return {
id,
decorators,
};
};
```

View File

@@ -44,9 +44,11 @@ In this step, create a custom plugin and define the decorator dependency.
},
};
module.exports = {
id,
decorators,
module.exports = function hideExtensionsPlugin() {
return {
id,
decorators,
};
};
```

View File

@@ -84,9 +84,11 @@ const decorators = {
},
};
module.exports = {
id,
decorators,
module.exports = function replaceServersUrlPlugin() {
return {
id,
decorators,
};
};
```

View File

@@ -242,34 +242,36 @@ rule/operation-summary-check:
`plugin.js`
```js
module.exports = {
id: 'local',
assertions: {
checkWordsStarts: (value, options, ctx) => {
const regexp = new RegExp(`^${options.words.join('|')}`);
if (regexp.test(value)) {
return [];
}
return [
{
message: 'Operation summary should start with an active verb',
location: ctx.baseLocation,
},
];
module.exports = function localPlugin() {
return {
id: 'local',
assertions: {
checkWordsStarts: (value, options, ctx) => {
const regexp = new RegExp(`^${options.words.join('|')}`);
if (regexp.test(value)) {
return [];
}
return [
{
message: 'Operation summary should start with an active verb',
location: ctx.baseLocation,
},
];
},
checkWordsCount: (value, options, ctx) => {
const words = value.split(' ');
if (words.length >= options.min) {
return [];
}
return [
{
message: `Operation summary should contain at least ${options.min} words`,
location: ctx.baseLocation,
},
];
},
},
checkWordsCount: (value, options, ctx) => {
const words = value.split(' ');
if (words.length >= options.min) {
return [];
}
return [
{
message: `Operation summary should contain at least ${options.min} words`,
location: ctx.baseLocation,
},
];
},
},
};
};
```