Create an Optic Ruleset
Optic Rulesets are written in Typescript using Optic's SDK. If you have written Spectral rules, the first thing you will notice is that Optic does not rely on JSON Path. Instead, rules are attached to OpenAPI entities i.e. property
, or operation
. Specific patterns in the spec can be matched using a predicate matches: (...) => true | false
, and rules can be triggered when parts of the spec are added
, removed
, changed
or requirement
(i.e. always run).
First let's set up a project:
Get Started
Use the ruleset init command to Bootstrap a Typescript project with the correct test, build and publish scripts already configured:
optic ruleset init
You will need to have the optic
CLI installed to run this command. Install Instructions
Optic will write the following files:
├── README.md
├── jest.config.js
├── package.json
├── src
│ ├── __tests__
│ │ └── main.test.ts
│ └── main.ts
└── tsconfig.json
If you use yarn
, run yarn install
. If you use npm
run npm install
Scripts
-
yarn run build
bundles all the rules and dependencies into a single.js
file for simple distribution. -
yarn run test
runs any tests you have written to ensure your rules work as expected -
yarn run upload
deploys your ruleset to Optic Cloud. In the dashboard you will have the option to run this ruleset whenever a tracked API changes.
Writing your first rule
Open up src/main.ts
, and inside you will see the following example ruleset:
import { Matchers, Ruleset, OperationRule } from "@useoptic/rulesets-base";
export const MustHaveOperationDescription = new OperationRule({
name: "Must have description version",
rule: (operationAssertions) => {
operationAssertions.requirement.matches({
description: Matchers.string,
});
},
});
const name = "my-ruleset";
export default {
name,
description: "An example ruleset that validates things in OpenAPI",
// A JSON schema object
configSchema: {},
rulesetConstructor: () => {
return new Ruleset({
name,
rules: [MustHaveOperationDescription],
});
},
};
To get started, import a rule from @useoptic/rulesets-base
and start writing. For example, to write a rule that all added GET 200 responses
must include an id
, we would use the ResponseBodyRule
.
import { Matchers, Ruleset, ResponseBodyRule, ResponseBody, RuleContext } from "@useoptic/rulesets-base";
const Get200ResponsesMustIncludeId = new ResponseBodyRule({
name: 'GET 200 responses must include an id',
matches: (response: ResponseBody, context: RuleContext) => {
return context.operation.method === 'get' && response.statusCode === '200'
},
rule: (responseBodyAssertions) => {
responseBodyAssertions.body.added.matches({
schema: {
type: 'object',
properties: {
id: {
type: 'string',
},
},
},
})
}
});
Finally, you'll need to include this ruleset in the default export.
...
export default {
name,
description: "An example ruleset that validates things in OpenAPI",
// A JSON schema object
configSchema: {},
rulesetConstructor: () => {
return new Ruleset({
name,
rules: [Get200ResponsesMustIncludeId], // Add your ruleset here
});
},
};
Writing tests
A sample test is included in the bootstrapped project under src/__tests__/main.test.ts
. @useoptic/rulesets-base
includes TestHelpers which provides a couple of utilities to write and run tests.
const spec = TestHelpers.createEmptySpec() // returns a base OpenAPI spec
const results = await TestHelpers.runRulesWithInputs(rules, beforeSpec, afterSpec) // runs rules against a before and after spec
Building and uploading your rulesets
Uploaded rulesets must be bundled into a single file that exports the name of the ruleset and the rules.
This starter template comes pre-configured with a build and upload step that matches the expected output (see the scripts field in the package.json). To upload your ruleset, follow the steps below:
- run
npm run build
- this will bundle thesrc/main.ts
code into a single JS file and output it tobuild/main.js
- this starter template uses esbuild, but any other bundler could be used (e.g. webpack, parcel, etc)
- run
npm run upload
- this will upload the bundled rulesets (build/main.js
) to optic cloud- this package will be then available to be used locally and in optic cloud