docs: add more dev docs

This commit is contained in:
Roman Hotsiy
2020-07-04 16:09:34 +03:00
parent 69959baba3
commit 3dba1d54c1
6 changed files with 313 additions and 20 deletions

View File

@@ -230,10 +230,10 @@ Ignore file can be autogenerated to add all messages to it by running `openapi l
### Advanced
[Plugins](docs/plugins.md)
[Type extensions](docs/type-extensions.md)
[Custom rules](docs/custom-rules.md)
[Decorators](docs/decorators.md)
[Preprocessors](docs/preprocessors.md)
[Type extensions](docs/type-extensions.md)
## Credits

View File

@@ -1,11 +1,178 @@
# Custom rules
Each rule is a function that accepts rule config and returns an object with methods that openapi-cli calls to "visit" nodes while traversing the defintion document.
#### Rules (visitors)
Here is the basic example of a rule:
TBD
```js
function OperationIdNotTest() {
return {
Operation(operation, ctx) {
if (operation.operationId === 'test') {
ctx.report({
message: `operationId must be not "test"`,
location: ctx.location.child('operationId'),
});
}
},
};
}
```
### Adding custom rules
## Format of visitor
Keys of the object can be any of the following:
- node type - visitor will be called on specific node type. List of available node types for speicific OAS3 version:
TODO: update the link below once merged
- OAS3: https://github.com/Redocly/openapi-cli/blob/full-rewrite/src/types/oas3.ts#L518-L560
- OAS2: not supported yet
- `any` - visitor will be called on every node
- `ref` - visitor will be called on $ref nodes
The value of each node can be either **visitor function** (runs while going down the tree) or **visitor object** (see below).
## Visitor Object
Visitor object can contain `enter` and/or `leave` visitor functions and `skip` predicate method.
openapi-cli calls `enter` **visitor function** while going down the tree and `leave` going up the tree.
`skip` predidate is called and if it returns `true` the node is is ignored from this visitor.
TBD
```js
function ExampleRule() {
const seen = {};
return {
DefinitionRoot: {
leave() {
// check something and report
}
}
Operation: {
enter(operation, ctx) {
seen[operation.operationId] = true;
},
}
};
}
```
Also, visitor object (if it is not `any` or `ref`) can define [nested visitors](#nested-visitors).
## Visitors execution and $ref
Top level **visitor functions** run only once for each node. If the same node is referenced via $ref multiple times top-level **visitor functions** will be executed only once for this node.
This works fine for most context-free rules which check basic things. If you need contextual info you should use [nested visitors](#nested-visitors).
## Nested Visitors
Here is basic example of nested visitor:
```js
function ExampleRule() {
const seen = {};
return {
Operation: {
// skip: (value, key) => ... // if needed
// enter(operation) {} // if needed
Schema(schema, ctx, parents) {
console.log(`type ${schema.type} from ${parents.Operation.operationId}`)
}
}
};
}
```
The `Schema` **visit function** will be called by openapi-cli if only Schema Object is encountered while traversing a tree ahile Operation Object is **entered**.
As the third argument the **visitor function** accpets the `parents` object with corresponding parent nodes as defined in the **visitor object**.
> Note: it will be executed only for the first level of Schema Object.
For the example document below:
```yaml
get:
operationId: get
parameters:
- name: a
in: path
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
properties:
a:
type: boolean
put:
operationId: put
parameters:
- name: a
in: path
schema:
type: number
```
The visitor above will log the following:
```
type string from get
tpye object from get
type number from put
```
## The Context Object
The context object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the context object contains information that is relevant to the context of the rule. The context object has the following properties:
- `location` - current location in the source document. See [Location Object](#location-object)
- `parentLocations` - mapping of parent node to its location (only for nested visitors)
- `type` - information about current type from type tree
- `parent` - parent object or array
- `key` - key in parent object or array
- `oasVersion` specific OAS minor version of current document (can be `oas2`, `oas3` or `oas3_1`).
Additionally, the context object has the following methods:
- `report(descriptor)` - reports a problem in the definition (see the dedicated section).
- `resolve(node)` - synchornosly dereferences $ref node to its value. Works only with $refs from the original document.
## Location Object
Location is the class with the folowing fields:
`source` - current document source
`pointer` - pointer within the document to the node
`abolutePointer` - absolute pointer to the node (including source document absolute ref)
and the following methods:
`key()` - returns new Location pointing to the current node key instead of value (used to highlight the key in codeframes)
`child(propName)` - returns new Location pointing to the `propName` of the current node. `propName` can be array of strings to point deep.
## context.report()
The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties:
`message` - {string} the problem message.
`location` - {Location} (optional) an object specifying the location of the problem. Can be constructed using location object methods.
`suggest` - {string[]} (optional) - "did you mean" suggestion
`from` - {Location} (optional) - referenced by location
The simplest example is to use just message:
```js
context.report({
message: "Unexpected identifier"
});
```
By default, the message is reported at the current node location.

View File

@@ -0,0 +1,125 @@
# Working with Plugins
Plugins can be used to extend behaviour of `@redocly/openapi-cli`. Each plugin is an javascript module which can export custom rules, preprocessors, decorators or type tree extenstions.
## Plugin structure
The minimal plugin should export `id` string:
```js
module.exports = {
id: 'my-local-plugin'
}
```
## OAS Major versions
Everything that is exported from plugin can be related to one of supported OAS3 major versions. It is done by exporting object containing key-value mapping from major OAS version (`oas2` or `oas3` are supported) to the extension object (rules, prerpocessors, etc.).
Before processing the definition document openapi-cli detects the OAS version and applies corresponding set of extensions.
## Rules in Plugins
Plugins can expose additional rules for use in openapi-cli. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention (so it can just be `tag-name`, for instance). The rules defined in
```js
module.exports = {
id: 'my-local-plugin',
rules: {
oas3: {
'tag-name': () => {
//...
},
}
oas2: {}
}
}
```
To use the rule in openapi-cli, you would use the plugin name, followed by a slash, followed by the rule name. So if this plugin id is `my-local-plugin`, then in your configuration you'd refer to the rule by the name `my-local-plugin/tag-name`. Example: `"rules": {"my-local-plugin/tag-name": "error"}`.
See [rules documentation](./custom-rules.md)
## Preprocessors and Decorators in Plugins
In order to create a preprocessor or decorators, the object that is exported from your module has to conform to the following interface:
```js
module.exports = {
id: 'my-local-plugin`,
preprocessors: {
oas3: {
"processor-id": () => {
// ...
}
}
},
decorators: {
oas3: {
"decorator-id": () => {
// ...
}
}
}
}
```
See [preprocessors and decorators documentation](./preprocessors-and-decorators.md)
## Configs in Plugins
You can bundle configurations inside a plugin by specifying them under the `configs` key. Multiple configurations are supported per plugin. Note that it is not possible to specify a default configuration for a given plugin and that users must specify in their configuration file when they want to use one.
```js
module.exports = {
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',
},
}
}
};
```
If the example plugin above id was `my-local-plugin`, the `all` and `minimal` configurations would then be usable by extending off of `"my-local-plugin/all"` and `"my-local-plugin/minimal"`, respectively.
```yaml
extends:
- my-local-plugin/all
```
## Type Extensions in plugins
See [type extensions](./type-extensions.md)
Plugin can define certain type extension. To do so it must export `typeExtension` property:
```js
module.exports = {
id: 'my-local-plugin',
typeExtension: {
oas3(types) {
// modify types here
return {
...types,
// add new or modify existing
};
},
}
};
```
## Share Plugins
Community plugins are not supported yet.

View File

@@ -0,0 +1,14 @@
# Preprocessors and Decorators
## Rationale
There might appear such situation, when the input data should be mutated before or after validation and/or bundling. For this case, you might want to run custom preprocessors before or decorators after bundling.
## Preprocessors
> NOTE: this feature is experimental. Mutating OpenAPI definition may break codeframes in errors.
## Decorators
TBD

View File

@@ -1,13 +0,0 @@
# Preprocessors
> NOTE: this feature is experimental. Modifying your data may break codeframes in errors.
## Rationale
There might appear such situation, when the input data should be mutated before applying default rules and/or bundling. For this case, you might want to run certain custom preprocessors before rules.
You can do it with `preprocessors`.
## Usage
TBD

View File

@@ -8,11 +8,11 @@ Type tree is built from top level `Types` which can link to child types.
TBD
This tree can be modified.
This tree can be extended or even modified.
## Extending type definitions
Type tree can be extended using by [plugin](./plugin.md) It should follow the following pattern (similar to reducers):
Type tree can be extended by exporting `typeExtension` function from [plugin](./plugins.md). It should follow the following pattern (similar to reducers):
```js
exports.typeExtension = {