Docs
Custom Rules

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 the src/main.ts code into a single JS file and output it to build/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