Skip to content

Commit

Permalink
feat: --ruleset multiple
Browse files Browse the repository at this point in the history
* feat: --ruleset multiple

SL-2295 SL-2493

* docs: update ruleset example

SL-2295 SL-2493
  • Loading branch information
Chris Miaskowski authored Apr 25, 2019
1 parent 1aeff30 commit 3d305a0
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 17 deletions.
12 changes: 6 additions & 6 deletions docs/rulesets.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Spectral Rulesets

## Usage

```bash
spectral lint foo.yaml --ruleset=path/to/acme-company-ruleset.yaml --ruleset=http://example.com/acme-common-ruleset.yaml
```

## Example ruleset file

We currently support ruleset files in both `yaml` and `json` formats.
Expand Down Expand Up @@ -134,12 +140,6 @@ Rules are highly configurable. There are only few required parameters but the op
</tbody>
</table>

## Configuring rulesets via CLI

```bash
spectral lint foo.yaml --ruleset=path/to/acme-company-ruleset.yaml
```

### Ruleset validation

We use JSON Schema & AJV to validate your rulesets file and help you spot issues early.
Expand Down
24 changes: 24 additions & 0 deletions src/cli/commands/__tests__/lint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,30 @@ describe('lint', () => {
});
});

describe('when multiple ruleset options provided', () => {
test
.stdout()
.command(['lint', validSpecPath, '-r', invalidRulesetPath, '-r', validRulesetPath])
.exit(2)
.it('given one is valid other is not, outputs "invalid ruleset" error', ctx => {
expect(ctx.stdout).toContain(`/rules/rule-without-given-nor-them should have required property 'given'`);
expect(ctx.stdout).toContain(`/rules/rule-without-given-nor-them should have required property 'then'`);
expect(ctx.stdout).toContain(`/rules/rule-with-invalid-enum/severity should be number`);
expect(ctx.stdout).toContain(
`/rules/rule-with-invalid-enum/severity should be equal to one of the allowed values`
);
});

test
.stdout()
.command(['lint', validSpecPath, '-r', invalidRulesetPath, '-r', validRulesetPath])
.exit(2)
.it('given one is valid other is not, reads both', ctx => {
expect(ctx.stdout).toContain(`Reading ruleset ${invalidRulesetPath}`);
expect(ctx.stdout).toContain(`Reading ruleset ${validRulesetPath}`);
});
});

describe('when single ruleset option provided', () => {
test
.stdout()
Expand Down
7 changes: 4 additions & 3 deletions src/cli/commands/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { json, stylish } from '../../formatters';
import { readParsable } from '../../fs/reader';
import { oas2Functions, oas2Rules } from '../../rulesets/oas2';
import { oas3Functions, oas3Rules } from '../../rulesets/oas3';
import { readRuleset } from '../../rulesets/reader';
import { readRulesets } from '../../rulesets/reader';
import { Spectral } from '../../spectral';
import { IParsedResult, RuleCollection } from '../../types';

Expand Down Expand Up @@ -50,7 +50,8 @@ linting ./openapi.yaml
}),
ruleset: flagHelpers.string({
char: 'r',
description: 'path to a ruleset file (supports http)',
description: 'path to a ruleset file (supports remote files)',
multiple: true,
}),
};

Expand All @@ -63,7 +64,7 @@ linting ./openapi.yaml

if (ruleset) {
try {
rules = await readRuleset(ruleset, this);
rules = await readRulesets(this, ...ruleset);
} catch (ex) {
this.error(ex.message);
}
Expand Down
33 changes: 27 additions & 6 deletions src/rulesets/__tests__/reader.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { readParsable } from '../../fs/reader';
import { IRulesetFile } from '../../types/ruleset';
import { formatAjv } from '../ajv';
import { resolvePath } from '../path';
import { readRuleset } from '../reader';
import { readRulesets } from '../reader';
import { validateRuleset } from '../validation';

const readParsableMock: jest.Mock = readParsable as jest.Mock;
Expand Down Expand Up @@ -47,11 +47,32 @@ describe('reader', () => {
},
});

expect(await readRuleset('flat-ruleset.yaml', command)).toEqual({
expect(await readRulesets(command, 'flat-ruleset.yaml')).toEqual({
'rule-1': { given: 'abc', then: { function: 'f' }, enabled: false },
});
});

it('given two flat, valid ruleset files should return rules', async () => {
validateRulesetMock.mockReturnValue([]);
givenRulesets({
'flat-ruleset-a.yaml': {
rules: {
'rule-1': simpleRule,
},
},
'flat-ruleset-b.yaml': {
rules: {
'rule-2': simpleRule,
},
},
});

expect(await readRulesets(command, 'flat-ruleset-a.yaml', 'flat-ruleset-b.yaml')).toEqual({
'rule-1': { given: 'abc', then: { function: 'f' }, enabled: false },
'rule-2': { given: 'abc', then: { function: 'f' }, enabled: false },
});
});

it('should override properties of extended rulesets', async () => {
validateRulesetMock.mockReturnValue([]);
givenRulesets({
Expand All @@ -72,7 +93,7 @@ describe('reader', () => {
},
});

expect(await readRuleset('oneParentRuleset', command)).toEqual({
expect(await readRulesets(command, 'oneParentRuleset')).toEqual({
'rule-1': { given: 'abc', then: { function: 'f' }, enabled: true },
});
});
Expand All @@ -98,7 +119,7 @@ describe('reader', () => {
},
});

expect(await readRuleset('oneParentRuleset', command)).toEqual({
expect(await readRulesets(command, 'oneParentRuleset')).toEqual({
'rule-1': { given: 'abc', then: { function: 'f' }, enabled: false },
'rule-2': { given: 'another given', then: { function: 'b' } },
});
Expand Down Expand Up @@ -147,7 +168,7 @@ describe('reader', () => {
},
});

expect(await readRuleset('oneParentRuleset', command)).toEqual({
expect(await readRulesets(command, 'oneParentRuleset')).toEqual({
'rule-1': { given: 'abc', then: { function: 'f' }, enabled: false },
'rule-a': { given: 'given-a', then: { function: 'a' } },
'rule-b': { given: 'given-b', then: { function: 'b' } },
Expand All @@ -169,7 +190,7 @@ describe('reader', () => {
.calledWith(['fake errors'])
.mockReturnValue('fake formatted message');

await readRuleset('flat-ruleset.yaml', command);
await readRulesets(command, 'flat-ruleset.yaml');

expect(command.log).toHaveBeenCalledTimes(2);
expect(command.log).toHaveBeenCalledWith('fake formatted message');
Expand Down
9 changes: 7 additions & 2 deletions src/rulesets/reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import { formatAjv } from './ajv';
import { resolvePath } from './path';
import { validateRuleset } from './validation';

export async function readRuleset(file: string, command: Lint): Promise<RuleCollection> {
export async function readRulesets(command: Lint, ...files: string[]): Promise<RuleCollection> {
const rulesets = await Promise.all(files.map(file => readRuleset(command, file)));
return merge({}, ...rulesets);
}

async function readRuleset(command: Lint, file: string): Promise<RuleCollection> {
command.log(`Reading ruleset ${file}`);
const parsed = await readParsable(file, 'utf8');
const { data: ruleset } = parsed;
Expand All @@ -23,7 +28,7 @@ export async function readRuleset(file: string, command: Lint): Promise<RuleColl
if (extendz && extendz.length) {
extendedRules = await blendRuleCollections(
extendz.map(extend => {
return readRuleset(resolvePath(file, extend), command);
return readRuleset(command, resolvePath(file, extend));
})
);
}
Expand Down

0 comments on commit 3d305a0

Please sign in to comment.