Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feat/core/validati…
Browse files Browse the repository at this point in the history
…on-rework
  • Loading branch information
P0lip committed Aug 3, 2022
2 parents f53ee36 + bb732ae commit 1329113
Show file tree
Hide file tree
Showing 72 changed files with 3,549 additions and 228 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ If you have a bug or feature request, please [create an issue](https://github.co

### How is this different to Ajv

[Ajv](https://www.npmjs.com/package/ajv) is a JSON Schema validator, and Spectral is a JSON/YAML linter. Instead of just validating against JSON Schema, it can be used to write rules for any sort of JSON/YAML object, which could be JSON Schema, or OpenAPI, or anything similar. Spectral does expose a [`schema` function](https://meta.stoplight.io/docs/spectral/docs/reference/functions.md) that you can use in your rules to validate all or part of the target object with JSON Schema (we even use Ajv used under the hood for this), but that's just one of many functions.
[Ajv](https://www.npmjs.com/package/ajv) is a JSON Schema validator, and Spectral is a JSON/YAML linter. Instead of just validating against JSON Schema, it can be used to write rules for any sort of JSON/YAML object, which could be JSON Schema, or OpenAPI, or anything similar. Spectral does expose a [`schema` function](https://meta.stoplight.io/docs/spectral/docs/reference/functions.md) that you can use in your rules to validate all or part of the target object with JSON Schema (we even use Ajv under the hood for this), but that's just one of many functions.

### I want to lint my OpenAPI documents but don't want to implement Spectral right now.

Expand All @@ -91,9 +91,10 @@ No problem! A hosted version of Spectral comes **free** with the Stoplight platf

## ⚙️ Integrations

- [Stoplight Studio](https://stoplight.io/studio?utm_source=github&utm_medium=spectral&utm_campaign=readme) uses Spectral to validate and lint OpenAPI documents.
- [Spectral GitHub Action](https://github.com/stoplightio/spectral-action), lints documents in your repo, built by [Vincenzo Chianese](https://github.com/XVincentX/).
- [VS Code Spectral](https://github.com/stoplightio/vscode-spectral), all the power of Spectral without leaving VS Code.
- [GitHub Action](https://github.com/stoplightio/spectral-action) - lints documents in your repo, built by [Vincenzo Chianese](https://github.com/XVincentX/).
- [Jetbrains Plugin](https://plugins.jetbrains.com/plugin/18520-spectral), Automatic linting of your OpenAPI specifications and highlighting in your editor.
- [Stoplight Studio](https://stoplight.io/studio?utm_source=github&utm_medium=spectral&utm_campaign=readme) - Uses Spectral to validate and lint OpenAPI documents.
- [VS Code Spectral](https://marketplace.visualstudio.com/items?itemName=stoplight.spectral), all the power of Spectral without leaving VS Code.

## 🏁 Help Others Utilize Spectral

Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started/1-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ The power of integrating linting into the design-first workflow, or any workflow

To achieve this, Spectral has three key concepts:

- **Rulesets** act as a container for rules and functions.
- **Rules** filter your object down to a set of target values and specify the function that is used to evaluate those values.
- **Functions** accept a value and return issues if the value is incorrect.
- **Rulesets** act as a container for rules and functions.

Rules can be comprised of one of more functions. For example:
Rules can be comprised of one of more functions, to standardize structured content, like making sure your OpenAPI descriptions match your [API style guides](https://stoplight.io/api-style-guides-guidelines-and-best-practices):

- HTTP Basic is not allowed at this company
- All operations are secured with a security schema
Expand Down
5 changes: 3 additions & 2 deletions docs/getting-started/3-rulesets.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ Formats are an optional way to specify which API description formats a rule, or
- `aas2_1` (AsyncAPI v2.1.0)
- `aas2_2` (AsyncAPI v2.2.0)
- `aas2_3` (AsyncAPI v2.3.0)
- `aas2_4` (AsyncAPI v2.4.0)
- `oas2` (OpenAPI v2.0)
- `oas3` (OpenAPI v3.x)
- `oas3.0` (OpenAPI v3.0.x)
- `oas3.1` (OpenAPI v3.1.x)
- `oas3_0` (OpenAPI v3.0.x)
- `oas3_1` (OpenAPI v3.1.x)
- `json-schema` (`$schema` says this is some JSON Schema draft)
- `json-schema-loose` (looks like JSON Schema, but no `$schema` found)
- `json-schema-draft4` (`$schema` says this is JSON Schema Draft 04)
Expand Down
158 changes: 104 additions & 54 deletions docs/guides/3-javascript.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
# Spectral in JavaScript

The Spectral CLI is a thin wrapper around a JavaScript (TypeScript) API, which can be used independently to do all the same things outside of the CLI.
The Spectral CLI is a thin wrapper around a JavaScript (TypeScript) API, which can be used independently to do all the same things outside of the CLI, such as linting YAML and JSON documents from a string or an object.

Assuming it has been installed as a Node module via NPM/Yarn, it can be used to lint YAML and JSON documents from a string, or from an object.
## Prerequisites

First of all, in order to consume the JS API you need to install the appropriate package(s).
To use the Spectral JS API, you need to install the appropriate package.

For npm users:

```bash
npm install -g @stoplight/spectral-core
```

If you are a Yarn user:
For Yarn users:

```bash
yarn global add @stoplight/spectral-core
```

## Linting a YAML String
## Get Started

Similar to using Spectral in the CLI, there are two things you'll need to run Spectral in JS:

- A string or a file containing your structured data (OpenAPI, AsyncAPI, Kubernetes, etc).
- An object or a file representing a ruleset

As an example, here's a script of Spectral in action:

```js
const { Spectral, Document } = require("@stoplight/spectral-core");
const Parsers = require("@stoplight/spectral-parsers"); // make sure to install the package if you intend to use default parsers!
const { truthy } = require("@stoplight/spectral-functions"); // this has to be installed as well
```js title="example-1.mjs" lineNumbers
import spectralCore from "@stoplight/spectral-core";
const { Spectral, Document } = spectralCore;
import Parsers from "@stoplight/spectral-parsers"; // make sure to install the package if you intend to use default parsers!
import { truthy } from "@stoplight/spectral-functions"; // this has to be installed as well

// this will be our API specification document
const myDocument = new Document(
`---
responses:
Expand All @@ -34,7 +45,7 @@ responses:

const spectral = new Spectral();
spectral.setRuleset({
// a ruleset has to be provided
// this will be our ruleset
rules: {
"no-empty-description": {
given: "$..description",
Expand All @@ -45,55 +56,87 @@ spectral.setRuleset({
},
},
});

// we lint our document using the ruleset we passed to the Spectral object
spectral.run(myDocument).then(console.log);
```

This will run Spectral with no formats, rules or functions, so it's not going to do anything besides \$ref resolving.
Find out how to add formats, rules and functions below.
## Load Rulesets and API Specification Files

## Loading Rulesets
Let's look at some other examples and how to work with external files.

Spectral comes with some rulesets that are very specific to OpenAPI v2/v3, and they can be set using `Spectral.setRuleset()`.
### Load a JSON/YAML Ruleset

```js
const { Spectral } = require("@stoplight/spectral-core");
const ruleset = require("./my-ruleset"); // this works only for JS ruleset, look at the section below to learn how to load a YAML/JSON ruleset
If you would like to run this example, make sure that you have:

const spectral = new Spectral();
spectral.setRuleset(ruleset);
// lint
- An OpenAPI description document in the same directory as your script named `openapi.yaml`. You can use the one found [here](https://github.com/stoplightio/Public-APIs/blob/master/reference/plaid/openapi.yaml).
- A ruleset file named `.spectral.yaml`. It can have the following contents:

```yaml
extends:
- spectral:oas
```
### Loading YAML/JSON rulesets
Here's a script that shows how to load an external API specification file, and an external YAML ruleset:
#### Node.js
```js title="example-2.mjs" lineNumbers
import * as fs from "node:fs";
import { fileURLToPath } from "node:url";
import * as path from "node:path";
import { join } from "path";
import { bundleAndLoadRuleset } from "@stoplight/spectral-ruleset-bundler/with-loader";
import Parsers from "@stoplight/spectral-parsers"; // make sure to install the package if you intend to use default parsers!
import spectralCore from "@stoplight/spectral-core";
const { Spectral, Document } = spectralCore;
import spectralRuntime from "@stoplight/spectral-runtime";
const { fetch } = spectralRuntime;

```js
const path = require("path");
const fs = require("fs");
const __dirname = path.dirname(fileURLToPath(import.meta.url));

const { Spectral } = require("@stoplight/spectral-core");
const { fetch } = require("@stoplight/spectral-runtime"); // can also use isomorphic-fetch, etc.. If you ruleset does not reference any external assets, you can provide some stub instead.
const { bundleAndLoadRuleset } = require("@stoplight/spectral-ruleset-bundler/with-loader");
// const { commonjs } = require("@stoplight/spectral-ruleset-bundler/plugins/commonjs"); needed if you want to use CommonJS
const myDocument = new Document(
// load an API specification file from your project's root directory. You can use the openapi.yaml example from here: https://github.com/stoplightio/Public-APIs/blob/master/reference/plaid/openapi.yaml
fs.readFileSync(join(__dirname, "openapi.yaml"), "utf-8").trim(),
Parsers.Yaml,
"openapi.yaml",
);

const spectral = new Spectral();
// load a ruleset file from your project's root directory.
const rulesetFilepath = path.join(__dirname, ".spectral.yaml");
spectral.setRuleset(await bundleAndLoadRuleset(rulesetFilepath, { fs, fetch }));

spectral.run(myDocument).then(console.log);
```

### Load a JavaScript Ruleset

Starting in Spectral v6.0, support was added for rulesets to be written using JavaScript.

You can find more information about it [here](./4-custom-rulesets.md#alternative-js-ruleset-format).

To load a JavaScript ruleset, you have to import it similar to how you would import a module:

```js lineNumbers
import { Spectral } from "@stoplight/spectral-core";
import ruleset from "./my-javascript-ruleset";

const spectral = new Spectral();
s.setRuleset(await bundleAndLoadRuleset(rulesetFilepath, { fs, fetch }));
// or, if you use module.exports (CommonJS) s.setRuleset(await bundleAndLoadRuleset(rulesetFilepath, { fs, fetch }), [commonjs()]);
spectral.setRuleset(ruleset);
```

#### Browser
### Browser

Here's an example script of how you could run Spectral in the browser:

```js
const { Spectral } = require("@stoplight/spectral-core");
const { bundleAndLoadRuleset } = require("@stoplight/spectral-ruleset-bundler/with-loader");
// const { commonjs } = require("@stoplight/spectral-ruleset-bundler/plugins/commonjs"); needed if you want to use CommonJS
```js title="example-3.mjs" lineNumbers
import { Spectral } from "@stoplight/spectral-core";
import { bundleAndLoadRuleset } from "@stoplight/spectral-ruleset-bundler/with-loader";

// create a ruleset that extends the spectral:oas ruleset
const myRuleset = `extends: spectral:oas
rules: {}`;

// try to load an external ruleset
const fs = {
promises: {
async readFile(filepath) {
Expand All @@ -108,37 +151,41 @@ const fs = {

const spectral = new Spectral();
s.setRuleset(await bundleAndLoadRuleset("/.spectral.yaml", { fs, fetch }));
// or, if you use module.exports (CommonJS) s.setRuleset(await bundleAndLoadRuleset(rulesetFilepath, { fs, fetch }), [commonjs()]);
```

### Load Multiple Rulesets

If you'd like to use the `bundleAndLoadRuleset` method to load multiple rulesets, you'll have to create a new Spectral ruleset file, and use the [`extends`](../getting-started/3-rulesets.md#extending-rulesets) functionality to extend the rulesets you'd like to use.

## Advanced

### Using a Proxy
### How to Use a Proxy

Spectral supports HTTP(S) proxies when fetching remote assets.
Spectral supports HTTP(S) proxies when fetching remote assets:

```js
const { Spectral } = require("@stoplight/spectral-core");
const ProxyAgent = require("proxy-agent");
const { createHttpAndFileResolver } = require("@stoplight/spectral-ref-resolver");
```js title="example-4.mjs" lineNumbers
import { Spectral } from "@stoplight/spectral-core";
import ProxyAgent from "proxy-agent";
import { createHttpAndFileResolver } from "@stoplight/spectral-ref-resolver";

// start Spectral using a proxy
const spectral = new Spectral({
resolver: createHttpAndFileResolver({ agent: new ProxyAgent(process.env.PROXY) }),
});

// lint as usual - $refs and rules will be requested using the proxy
// ... load document

// ... lint document - $refs and rules will be requested using the proxy
```

### Using a Custom Resolver
### How to Use a Custom Resolver

Spectral lets you provide any custom \$ref resolver. By default, http(s) and file protocols are resolved, relatively to
the document Spectral lints against. If you'd like support any additional protocol or adjust the resolution, you are
absolutely fine to do it. In order to achieve that, you need to create a custom json-ref-resolver instance.
Spectral lets you provide any custom \$ref resolver. By default, HTTP(S) and file protocols are resolved, relatively to
the document Spectral lints against. You can also add support for additional protocols, or adjust the resolution. To achieve that, you need to create a custom json-ref-resolver instance.

You can find more information about how to create custom resolvers in
the [@stoplight/json-ref-resolver](https://github.com/stoplightio/json-ref-resolver) repository.
For example:

```js
```js title="example-5.cjs" lineNumbers
const path = require("path");
const fs = require("fs");
const { Spectral } = require("@stoplight/spectral-cli");
Expand Down Expand Up @@ -166,9 +213,12 @@ const customFileResolver = new Resolver({

const spectral = new Spectral({ resolver: customFileResolver });

// lint document as usual
// ... load document

// ... lint document - $refs and rules will be requested using the proxy
```

The custom resolver we've just created will resolve all remote file refs relatively to the current working directory.
This custom resolver will resolve all remote file refs relatively to the current working directory.

More on that can be found in the [json-ref-resolver repo](https://github.com/stoplightio/json-ref-resolver).
You can find more information about how to create custom resolvers in
the [@stoplight/json-ref-resolver](https://github.com/stoplightio/json-ref-resolver) repository.
20 changes: 18 additions & 2 deletions docs/guides/4-custom-rulesets.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ where you may need to run a rule on the "raw" un-resolved document.

For example, if you want to enforce conventions on the folder structure used for
[splitting up
documents](https://stoplight.io/blog/keeping-openapi-dry-and-portable/?utm_source=github&utm_medium=spectral&utm_campaign=docs).
documents](https://blog.stoplight.io/keeping-openapi-dry-and-portable?utm_medium=spectral&utm_source=github&utm_campaign=docs).

If your rule needs to access the raw `$ref` reference values, you can set
`resolved: false` to allow the rule to receive the raw un-resolved version of
Expand Down Expand Up @@ -86,7 +86,7 @@ rules:

The `then` part of the rule explains which function to apply to the `given` JSONPath. The function you apply [may be one of the core functions](../reference/functions.md) or it may be [a custom function](./5-custom-functions.md).

`then` has two required keywords:
`then` has two main keywords:

```yaml
then:
Expand Down Expand Up @@ -115,6 +115,22 @@ responses:
foo: bar
```

You can also have multiple `then`s to target different properties in the same object, or to use different functions. For example, you can have one rule that will check if an object has multiple properties:

```yaml
contact-properties:
description: Contact object must have "name", "url", and "email".
given: $.info.contact
severity: warn
then:
- field: name
function: truthy
- field: url
function: truthy
- field: email
function: truthy
```

### Message

To help you create meaningful messages for results, Spectral comes with a couple of placeholders that are evaluated at runtime.
Expand Down
Loading

0 comments on commit 1329113

Please sign in to comment.