Skip to content

Commit

Permalink
feat: change 'run' function signature and simplify
Browse files Browse the repository at this point in the history
SO-23

BREAKING CHANGE

- run takes target as first param
- runOpts simplified
- no more in-run configs
  • Loading branch information
Chris Miaskowski authored and Marc MacLeod committed Dec 11, 2018
1 parent 8336bd2 commit 21b4a70
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 138 deletions.
95 changes: 1 addition & 94 deletions src/__tests__/spectral.new.test.ts
@@ -1,5 +1,4 @@
const merge = require('lodash/merge');
import { ValidationSeverity } from '@stoplight/types/validations';

import { defaultRuleset } from '../rulesets';
import { Spectral } from '../spectral.new';
Expand All @@ -13,7 +12,7 @@ describe('spectral', () => {
rulesets: [defaultRuleset()],
});

const results = s.run({ target: todosPartialDeref, spec: 'oas2' });
const results = s.run(todosPartialDeref, { format: 'oas2' });
expect(results.length).toBeGreaterThan(0);
});

Expand Down Expand Up @@ -249,98 +248,6 @@ Array [
`);
});

test('be able to toggle rules on apply', () => {
const spec = {
hello: 'world',
};

const rulesets: IRuleset[] = [
{
rules: {
oas2: {
'lint:test': {
type: RuleType.STYLE,
function: RuleFunction.TRUTHY,
path: '$',
enabled: false,
severity: ValidationSeverity.Error,
description: 'this should return an error if enabled',
summary: '',
input: {
properties: 'nonexistant-property',
},
},
},
},
},
];

const overrideRulesets: IRuleset[] = [
{
rules: {
oas2: {
'lint:test': true,
},
},
},
];

const s = new Spectral({ rulesets });

// run once with no override config
let results = s.run({ target: spec, spec: 'oas2' });
expect(results.length).toEqual(0);

// run again with an override config
results = s.run({ target: spec, spec: 'oas2', rulesets: overrideRulesets });
expect(results.length).toEqual(1);
});

// Assures: https://stoplightio.atlassian.net/browse/SL-788
test('run with rulesets overrides ruleset on run, not permenantly', () => {
const spec = {
hello: 'world',
};

const rulesets: IRuleset[] = [
{
rules: {
format: {
test: {
type: RuleType.STYLE,
function: RuleFunction.TRUTHY,
path: '$',
enabled: false,
severity: ValidationSeverity.Error,
summary: '',
input: {
properties: 'nonexistant-property',
},
},
},
},
},
];

const overrideRulesets: IRuleset[] = [
{
rules: {
format: {
test: true,
},
},
},
];

const s = new Spectral({ rulesets });

const originalRules = s.getRules('format');

s.run({ target: spec, spec: 'format', rulesets: overrideRulesets });

expect(s.getRules('format')).toEqual(originalRules);
});

test('getRules returns a flattened list of rules filtered by format', () => {
const rulesets: IRuleset[] = [
{
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/style.new.test.ts
Expand Up @@ -12,7 +12,7 @@ const applyRuleToObject = (r: Rule, o: object): IRuleResult[] => {
},
];
const s = new Spectral({ rulesets: cfg });
return s.run({ target: o, spec: 'testing' });
return s.run(o, { format: 'testing' });
};

describe('lint', () => {
Expand Down
12 changes: 6 additions & 6 deletions src/__tests__/validation.new.test.ts
Expand Up @@ -10,34 +10,34 @@ const invalidV2 = require('./fixtures/todos.invalid.oas2.json');
describe('validation', () => {
test('validate a correct OASv2 spec', () => {
const s = new Spectral({ rulesets: [oas2Ruleset()] });
const results = s.run({ target: petstoreV2, spec: 'oas2', type: RuleType.VALIDATION });
const results = s.run(petstoreV2, { format: 'oas2', type: RuleType.VALIDATION });
expect(results.length).toEqual(0);
});

test('return errors on invalid OASv2 spec', () => {
const s = new Spectral({ rulesets: [oas2Ruleset()] });
const results = s.run({ target: invalidV2, spec: 'oas2', type: RuleType.VALIDATION });
const results = s.run(invalidV2, { format: 'oas2', type: RuleType.VALIDATION });
expect(results.length).toEqual(1);
expect(results[0].path).toEqual(['$', 'info', 'license', 'name']);
expect(results[0].message).toEqual('should be string');
});

test('validate a correct OASv3 spec', () => {
const s = new Spectral({ rulesets: [oas3Ruleset()] });
const results = s.run({ target: petstoreV3, spec: 'oas3', type: RuleType.VALIDATION });
const results = s.run(petstoreV3, { format: 'oas3', type: RuleType.VALIDATION });
expect(results.length).toEqual(0);
});

test('validate multiple formats with same validator', () => {
const s = new Spectral({ rulesets: [oas2Ruleset(), oas3Ruleset()] });

let results = s.run({ target: petstoreV2, spec: 'oas2', type: RuleType.VALIDATION });
let results = s.run(petstoreV2, { format: 'oas2', type: RuleType.VALIDATION });
expect(results.length).toEqual(0);

results = s.run({ target: invalidV2, spec: 'oas2', type: RuleType.VALIDATION });
results = s.run(invalidV2, { format: 'oas2', type: RuleType.VALIDATION });
expect(results.length).toEqual(1);

results = s.run({ target: petstoreV3, spec: 'oas3', type: RuleType.VALIDATION });
results = s.run(petstoreV3, { format: 'oas3', type: RuleType.VALIDATION });
expect(results.length).toEqual(0);
});
});
27 changes: 9 additions & 18 deletions src/spectral.new.ts
Expand Up @@ -65,30 +65,24 @@ export class Spectral {
this._rulesByIndex = ruleStore;
}

public run(opts: IRunOpts): types.IRuleResult[] {
const { target, rulesets = [] } = opts;

const ruleStore = rulesets.length
? this._parseRuleSets(rulesets, { includeCurrent: true }).ruleStore
: this._rulesByIndex;

return target ? this.runAllLinters(ruleStore, opts) : [];
public run(target: object, opts: IRunOpts): types.IRuleResult[] {
return this.runAllLinters(target, this._rulesByIndex, opts);
}

private runAllLinters(ruleStore: IRuleStore, opts: IRunOpts): types.IRuleResult[] {
private runAllLinters(target: object, ruleStore: IRuleStore, opts: IRunOpts): types.IRuleResult[] {
return flatten(
compact(
values(ruleStore).map((ruleEntry: IRuleEntry) => {
if (
!ruleEntry.rule.enabled ||
(opts.type && ruleEntry.rule.type !== opts.type) ||
ruleEntry.format !== opts.spec
ruleEntry.format !== opts.format
) {
return null;
}

try {
return this.lintNodes(ruleEntry, opts);
return this.lintNodes(target, ruleEntry, opts);
} catch (e) {
console.error(`Unable to run rule '${ruleEntry.name}':\n${e}`);
return null;
Expand All @@ -98,8 +92,8 @@ export class Spectral {
);
}

private lintNodes(ruleEntry: IRuleEntry, opts: IRunOpts): types.IRuleResult[] {
const nodes = jp.nodes(opts.target, ruleEntry.rule.path);
private lintNodes(target: object, ruleEntry: IRuleEntry, opts: IRunOpts): types.IRuleResult[] {
const nodes = jp.nodes(target, ruleEntry.rule.path);
return flatten(
compact(
nodes.map(node => {
Expand Down Expand Up @@ -133,11 +127,8 @@ export class Spectral {
if (ruleEntry.rule.path === '$') {
// allow resolved and stringified targets to be passed to rules when operating on
// the root path
if (opts.resTarget) {
opt.resObj = opts.resTarget;
}
if (opts.strTarget) {
opt.strObj = opts.strTarget;
if (opts.resolvedTarget) {
opt.resObj = opts.resolvedTarget;
}
}

Expand Down
48 changes: 29 additions & 19 deletions src/types/spectral.ts
@@ -1,3 +1,4 @@
import { ObjPath } from '@stoplight/types/parsers';
import { IRuleFunction, IRuleset, Rule, RuleType } from '.';

export interface IFunctionStore {
Expand Down Expand Up @@ -27,33 +28,42 @@ export interface ISpectralOpts {

export interface IRunOpts {
/**
* The un-resolved object being parsed
* The fully-resolved version of the target object.
*
* Some functions require this in order to operate.
*/
target: object;
resolvedTarget?: object;

/**
* The fully-resolved object being parsed
*/
resTarget?: object;

/**
* A stringified version of the target
* Optional rule type, when supplied only rules of this type are run
*/
strTarget?: string;
type?: RuleType;

/**
* The specification to apply to the target
*/
spec: string;
format: string;
}

/**
* Optional ruleset to apply to the target. If not provided, the initialized ruleset will be used
* instead.
*/
rulesets?: IRuleset[];
export type IFunction<O = any> = (
targetValue: any,
options: O,
paths: IFunctionPaths,
otherValues: IFunctionValues
) => void | IFunctionResult[];

/**
* Optional rule type, when supplied only rules of this type are run
*/
type?: RuleType;
export interface IFunctionPaths {
given: ObjPath;
target: ObjPath;
}

export interface IFunctionValues {
original: any;
resolved?: any;
given: any;
}

export interface IFunctionResult {
message: string;
path?: ObjPath;
}

0 comments on commit 21b4a70

Please sign in to comment.