Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nulltoken committed Mar 13, 2020
1 parent 2541cce commit 276ecfd
Show file tree
Hide file tree
Showing 21 changed files with 2,523 additions and 47 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"scripts": {
"build.binary": "pkg . --output ./binaries/spectral",
"build.clean": "rimraf ./coverage && rimraf ./dist && rimraf ./rulesets && rimraf ./__karma__/__fixtures__",
"build.oas-functions": "rollup -c",
"build.functions": "rollup -c",
"build": "tsc -p ./tsconfig.build.json",
"cli": "node -r ts-node/register -r tsconfig-paths/register src/cli/index.ts",
"cli:debug": "node -r ts-node/register -r tsconfig-paths/register --inspect-brk src/cli/index.ts",
Expand All @@ -44,9 +44,9 @@
"lint.fix": "yarn lint --fix",
"lint": "tsc --noEmit && tslint 'src/**/*.ts'",
"copy.html-templates": "copyfiles -u 1 \"./src/formatters/html/*.html\" \"./dist/\"",
"postbuild.oas-functions": "copyfiles -u 1 \"dist/rulesets/oas*/functions/*.js\" ./",
"postbuild": "yarn build.oas-functions && yarn generate-assets",
"prebuild": "yarn build.clean && copyfiles -u 1 \"src/rulesets/oas*/**/*.json\" dist && copyfiles -u 1 \"src/rulesets/oas*/**/*.json\" ./ && yarn copy.html-templates",
"postbuild.functions": "copyfiles -u 1 \"dist/rulesets/{o,a}as/functions/*.js\" ./",
"postbuild": "yarn build.functions && yarn generate-assets",
"prebuild": "yarn build.clean && copyfiles -u 1 \"src/rulesets/{o,a}as/**/*.json\" dist && copyfiles -u 1 \"src/rulesets/{o,a}as/**/*.json\" ./ && yarn copy.html-templates",
"prebuild.binary": "yarn build",
"pretest.karma": "node ./scripts/generate-karma-fixtures.js && yarn pretest",
"pretest": "yarn generate-assets",
Expand Down
29 changes: 19 additions & 10 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@ import commonjs from 'rollup-plugin-commonjs';
import { terser } from 'rollup-plugin-terser';

const BASE_PATH = process.cwd();
const directory = 'dist/rulesets/oas/functions';
const targetDir = path.join(BASE_PATH, directory);

const functions = [];
for (const file of fs.readdirSync(targetDir)) {
const targetFile = path.join(targetDir, file);
const stat = fs.statSync(targetFile);
if (!stat.isFile()) continue;
const ext = path.extname(targetFile);
if (ext !== '.js') continue;

functions.push(targetFile);

const builtIns = ['oas', 'aas']

for (const rulesetName of builtIns) {
const targetDir = path.join(BASE_PATH, `dist/rulesets/${rulesetName}/functions/`);

if (!fs.existsSync(targetDir)) {
continue;
}

for (const file of fs.readdirSync(targetDir)) {
const targetFile = path.join(targetDir, file);
const stat = fs.statSync(targetFile);
if (!stat.isFile()) continue;
const ext = path.extname(targetFile);
if (ext !== '.js') continue;

functions.push(targetFile);
}
}

module.exports = functions.map(fn => ({
Expand Down
22 changes: 14 additions & 8 deletions scripts/generate-assets.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* This script generates a list of assets that are needed to load spectral:oas ruleset.
* It contains all OAS custom functions and *resolved* rulesets.
* The assets are stores in a single filed call assets.json in the following format:
* This script generates a list of assets that are needed to load spectral:oas and spectral:aas rulesets.
* It contains all custom functions and *resolved* rulesets.
* The assets are stored in a single file named `assets.json` in the following format:
* `<require-call-path>: <content>`
* where the `require-call-path` is the path you'd normally pass to require(), i.e. `@stoplight/spectral/rulesets/oas/index.js` and `content` is the text data.
* Assets can be loaded using Spectral#registerStaticAssets statc method, i.e. `Spectral.registerStaticAssets(require('@stoplight/spectral/rulesets/assets/assets.json'))`;
* Assets can be loaded using Spectral#registerStaticAssets static method, i.e. `Spectral.registerStaticAssets(require('@stoplight/spectral/rulesets/assets/assets.json'))`;
* If you execute the code above, ruleset will be loaded fully offline, without a need to make any request.
*/

Expand All @@ -30,14 +30,16 @@ const assetsPath = path.join(baseDir, `assets.json`);
const generatedAssets = {};

(async () => {
await processDirectory(generatedAssets, path.join(__dirname, '../rulesets/oas'));
await writeFileAsync(assetsPath, JSON.stringify(generatedAssets, null, 2));
for (const kind of ['oas', 'aas']) {
await processDirectory(generatedAssets, path.join(__dirname, `../rulesets/${kind}`));
await writeFileAsync(assetsPath, JSON.stringify(generatedAssets, null, 2));
}
})();

async function processDirectory(assets: Record<string, string>, dir: string) {
await Promise.all(
(await readdirAsync(dir)).map(async (name: string) => {
if (name === 'schemas') return;
if (['schemas', '__tests__'].includes(name)) return;
const target = path.join(dir, name);
const stats = await statAsync(target);
if (stats.isDirectory()) {
Expand All @@ -53,7 +55,11 @@ async function processDirectory(assets: Record<string, string>, dir: string) {
baseUri: target,
parseResolveResult(opts) {
return new Promise<IUriParserResult>(resolve => {
resolve({ result: parse(opts.result) });
try {
resolve({ result: parse(opts.result) });
} catch (e) {
resolve({ error: e });
}
});
},
})
Expand Down
6 changes: 3 additions & 3 deletions scripts/generate-karma-fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ if (!fs.existsSync(baseDir)) {
fs.mkdirSync(baseDir);
}

for (const spec of ['']) {
const target = path.join(baseDir, `oas${spec}-functions.json`);
const fnsPath = path.join(__dirname, `../rulesets/oas${spec}/functions`);
for (const rulesetName of ['oas', 'aas']) {
const target = path.join(baseDir, `${rulesetName}-functions.json`);
const fnsPath = path.join(__dirname, `../rulesets/${rulesetName}/functions`);
const bundledFns = {};

if (fs.existsSync(fnsPath)) {
Expand Down
18 changes: 12 additions & 6 deletions setupKarma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FetchMockSandbox } from 'fetch-mock';

const oasRuleset = JSON.parse(JSON.stringify(require('./rulesets/oas/index.json')));
const oasFunctions = JSON.parse(JSON.stringify(require('./__karma__/__fixtures__/oas-functions.json')));
const aasFunctions = JSON.parse(JSON.stringify(require('./__karma__/__fixtures__/aas-functions.json')));
const oas2Schema = JSON.parse(JSON.stringify(require('./rulesets/oas/schemas/schema.oas2.json')));
const oas3Schema = JSON.parse(JSON.stringify(require('./rulesets/oas/schemas/schema.oas3.json')));

Expand All @@ -28,12 +29,17 @@ beforeEach(() => {
body: JSON.parse(JSON.stringify(oas3Schema)),
});

for (const [name, fn] of Object.entries<string>(oasFunctions)) {
fetchMock.get(`https://unpkg.com/@stoplight/spectral/rulesets/oas/functions/${name}`, {
status: 200,
body: fn,
});
}
[
['oas', oasFunctions],
['aas', aasFunctions],
].forEach(([rulesetName, funcs]) => {
for (const [name, fn] of Object.entries<string>(funcs)) {
fetchMock.get(`https://unpkg.com/@stoplight/spectral/rulesets/${rulesetName}/functions/${name}`, {
status: 200,
body: fn,
});
}
});

fetchMock.get('http://json-schema.org/draft-04/schema', {
status: 200,
Expand Down
7 changes: 7 additions & 0 deletions src/__tests__/generate-assets.jest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('generate-assets', () => {
const testCases = [
['oas', 'oas2-schema', 'title', 'A JSON Schema for Swagger 2.0 API.'],
['oas', 'oas3-schema', 'description', 'Validation schema for OpenAPI Specification 3.0.X.'],
['aas', 'asyncapi-schema', 'title', 'AsyncAPI 2.0.0 schema.'],
];

it.each(testCases)(
Expand All @@ -35,4 +36,10 @@ describe('generate-assets', () => {
},
);
});

it('Does not contain test files', () => {
Object.keys(assets).forEach(key => {
expect(key).not.toMatch('__tests__');
});
});
});
20 changes: 14 additions & 6 deletions src/__tests__/spectral.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@ import { RulesetExceptionCollection } from '../types/ruleset';
import { buildRulesetExceptionCollectionFrom } from '../../setupTests';

const oasRuleset = JSON.parse(JSON.stringify(require('../rulesets/oas/index.json')));
const aasRuleset = JSON.parse(JSON.stringify(require('../rulesets/aas/index.json')));
const oasRulesetRules: Dictionary<IRunRule, string> = oasRuleset.rules;
const aasRulesetRules: Dictionary<IRunRule, string> = aasRuleset.rules;

describe('spectral', () => {
describe('loadRuleset', () => {
test('should support loading built-in rulesets', async () => {
test.each([
['spectral:oas', oasRulesetRules],
['spectral:aas', aasRulesetRules],
])('should support loading "%s" built-in ruleset', async (rulesetName, rules) => {
const s = new Spectral();
await s.loadRuleset('spectral:oas');
await s.loadRuleset(rulesetName);

expect(s.rules).toEqual(
expect.objectContaining(
Object.entries(oasRulesetRules).reduce<Dictionary<IRunRule, string>>((oasRules, [name, rule]) => {
Object.entries(rules).reduce<Dictionary<IRunRule, string>>((oasRules, [name, rule]) => {
oasRules[name] = {
name,
...rule,
Expand All @@ -37,12 +42,15 @@ describe('spectral', () => {
);
});

test('should support loading multiple times the built-in ruleset', async () => {
test.each([
['spectral:oas', oasRulesetRules],
['spectral:aas', aasRulesetRules],
])('should support loading multiple times the built-in ruleset "%s"', async (rulesetName, expectedRules) => {
const s = new Spectral();
await s.loadRuleset(['spectral:oas', 'spectral:oas']);
await s.loadRuleset([rulesetName, rulesetName]);

expect(s.rules).toEqual(
Object.entries(oasRulesetRules).reduce<Dictionary<IRunRule, string>>((oasRules, [name, rule]) => {
Object.entries(expectedRules).reduce<Dictionary<IRunRule, string>>((oasRules, [name, rule]) => {
oasRules[name] = {
name,
...rule,
Expand Down
1 change: 1 addition & 0 deletions src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function resolveSpectralRuleset(ruleset: string) {

export const RESOLVE_ALIASES: Dictionary<string, string> = {
'spectral:oas': resolveSpectralRuleset('oas'),
'spectral:aas': resolveSpectralRuleset('aas'),
};

export const STATIC_ASSETS: Dictionary<string> = {};
2 changes: 2 additions & 0 deletions src/cli/services/linter/linter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Document, STDIN } from '../../../document';
import {
isAsyncApiv2,
isJSONSchema,
isJSONSchemaDraft2019_09,
isJSONSchemaDraft4,
Expand All @@ -21,6 +22,7 @@ import { getResolver } from './utils/getResolver';
const KNOWN_FORMATS: Array<[string, FormatLookup, string]> = [
['oas2', isOpenApiv2, 'OpenAPI 2.0 (Swagger) detected'],
['oas3', isOpenApiv3, 'OpenAPI 3.x detected'],
['aas2', isAsyncApiv2, 'AsyncAPI 2.x detected'],
['json-schema', isJSONSchema, 'JSON Schema detected'],
['json-schema-loose', isJSONSchemaLoose, 'JSON Schema (loose) detected'],
['json-schema-draft4', isJSONSchemaDraft4, 'JSON Schema Draft 4 detected'],
Expand Down
2 changes: 1 addition & 1 deletion src/cli/services/linter/utils/getRuleset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ export async function getRuleset(rulesetFile: Optional<string[]>) {

return await (rulesetFiles
? loadRulesets(process.cwd(), Array.isArray(rulesetFiles) ? rulesetFiles : [rulesetFiles])
: readRuleset('spectral:oas'));
: readRuleset(['spectral:oas', 'spectral:aas'])); // TODO: Rule name clashes
}
28 changes: 28 additions & 0 deletions src/formats/__tests__/asyncapi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { isAsyncApiv2 } from '../asyncapi';

describe('AsyncApi format', () => {
describe('AsyncApi 2.0', () => {
it.each(['2.0.0'])('recognizes %s version correctly', version => {
expect(isAsyncApiv2({ asyncapi: version })).toBe(true);
});

const testCases = [
{ asyncapi: '3.0' },
{ asyncapi: '2' },
{ asyncapi: '2.0' },
{ asyncapi: '1.0' },
{ asyncapi: 2 },
{ openapi: '4.0' },
{ openapi: '2.0' },
{ openapi: null },
{ swagger: null },
{ swagger: '3.0' },
{},
null,
];

it.each(testCases)('does not recognize invalid document %o', document => {
expect(isAsyncApiv2(document)).toBe(false);
});
});
});
10 changes: 10 additions & 0 deletions src/formats/asyncapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { isObject } from 'lodash';

type MaybeAsyncApi2 = Partial<{ asyncapi: unknown }>;

export const bearsAStringPropertyNamed = (document: unknown, propertyName: string) => {
return isObject(document) && propertyName in document && typeof document[propertyName] === 'string';
};

export const isAsyncApiv2 = (document: unknown) =>
bearsAStringPropertyNamed(document, 'asyncapi') && String((document as MaybeAsyncApi2).asyncapi) === '2.0.0';
1 change: 1 addition & 0 deletions src/formats/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './openapi';
export * from './asyncapi';
export * from './json-schema';
Loading

0 comments on commit 276ecfd

Please sign in to comment.