Skip to content

Commit

Permalink
build(assets): $refs under functionOptions cannot be relative to the …
Browse files Browse the repository at this point in the history
…document (#1305)
  • Loading branch information
P0lip authored Aug 19, 2020
1 parent c9d2714 commit 627a24d
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 5 deletions.
35 changes: 32 additions & 3 deletions scripts/generate-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import * as path from '@stoplight/path';
import * as fs from 'fs';
import { promisify } from 'util';
import * as $RefParser from '@apidevtools/json-schema-ref-parser';
import { KNOWN_RULESETS } from "../src/formats";
import { KNOWN_RULESETS } from '../src/formats';
import { Dictionary } from '@stoplight/types';
import { isLocalRef, pointerToPath } from '@stoplight/json';
import { get } from 'lodash';

const readFileAsync = promisify(fs.readFile);
const writeFileAsync = promisify(fs.writeFile);
Expand All @@ -27,7 +30,7 @@ if (!fs.existsSync(baseDir)) {

const generatedAssets = {};

(async () => {
(async (): Promise<void> => {
for (const kind of KNOWN_RULESETS.map(ruleset => ruleset.replace('spectral:', ''))) {
const assets = await processDirectory(path.join(__dirname, `../rulesets/${kind}`));
Object.assign(generatedAssets, assets);
Expand All @@ -48,7 +51,7 @@ async function _processDirectory(assets: Record<string, string>, dir: string): P
} else {
let content = await readFileAsync(target, 'utf8');
if (path.extname(name) === '.json') {
content = JSON.stringify(await $RefParser.bundle(target, JSON.parse(content), {}))
content = JSON.stringify(await resolveExternal$Refs(JSON.parse(content), target));
}

assets[path.join('@stoplight/spectral', path.relative(path.join(__dirname, '..'), target))] = content;
Expand All @@ -62,3 +65,29 @@ async function processDirectory(dir: string): Promise<Record<string, string>> {
await _processDirectory(assets, dir);
return assets;
}

async function resolveExternal$Refs(document: Dictionary<unknown>, source: string): Promise<unknown> {
for (const [key, value] of Object.entries(document)) {
if (key === '$ref') {
if (typeof value === 'string' && !isLocalRef(value)) {
const [filepath, pointer = '#'] = value.split('#');

const actualFilepath = path.join(path.dirname(source), filepath);
const referencedDocument = JSON.parse(await readFileAsync(actualFilepath, 'utf8'));
const jsonPath = pointerToPath(pointer);

return await $RefParser.bundle(
actualFilepath,
jsonPath.length > 0 ? get(referencedDocument, jsonPath) : referencedDocument,
{},
);
}
}

if (value !== null && typeof value === 'object') {
document[key] = await resolveExternal$Refs(value as Dictionary<unknown>, source);
}
}

return document;
}
82 changes: 81 additions & 1 deletion src/__tests__/generate-assets.jest.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { existsSync, readFileSync } from 'fs';
import { Spectral } from '../spectral';
import { setFunctionContext } from '../rulesets/evaluators';
import { functions } from '../functions';
import oasDocumentSchema from '../rulesets/oas/functions/oasDocumentSchema';
import { KNOWN_FORMATS } from '../formats';
import { DiagnosticSeverity } from '@stoplight/types/dist';

describe('generate-assets', () => {
let assets: Record<string, string>;
Expand Down Expand Up @@ -37,9 +43,83 @@ describe('generate-assets', () => {
);
});

it('Does not contain test files', () => {
it('does not contain test files', () => {
Object.keys(assets).forEach(key => {
expect(key).not.toMatch('__tests__');
});
});

it('dereferences OAS schemas in the way they can be resolved by Ajv', async () => {
const key = `@stoplight/spectral/rulesets/oas/index.json`;
const ruleset = JSON.parse(assets[key]);
const spectral = new Spectral();

for (const [name, fn] of KNOWN_FORMATS) {
spectral.registerFormat(name, fn);
}

spectral.setFunctions({ oasDocumentSchema: setFunctionContext({ functions }, oasDocumentSchema) });
spectral.setRules({
'oas2-schema': {
...ruleset.rules['oas2-schema'],
},
'oas3-schema': {
...ruleset.rules['oas3-schema'],
},
});

expect(
await spectral.run({
openapi: '3.0.0',
info: {},
paths: {
'/': {
'500': true,
},
},
}),
).toStrictEqual([
{
code: 'oas3-schema',
message: '`info` property should have required property `title`.',
path: ['info'],
range: expect.any(Object),
severity: DiagnosticSeverity.Error,
},
{
code: 'oas3-schema',
message: 'Property `500` is not expected to be here.',
path: ['paths', '/'],
range: expect.any(Object),
severity: DiagnosticSeverity.Error,
},
]);

expect(
await spectral.run({
swagger: '2.0',
info: {},
paths: {
'/': {
'500': true,
},
},
}),
).toStrictEqual([
{
code: 'oas2-schema',
message: '`info` property should have required property `title`.',
path: ['info'],
range: expect.any(Object),
severity: DiagnosticSeverity.Error,
},
{
code: 'oas2-schema',
message: 'Property `500` is not expected to be here.',
path: ['paths', '/'],
range: expect.any(Object),
severity: DiagnosticSeverity.Error,
},
]);
});
});
8 changes: 7 additions & 1 deletion src/functions/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ const validators = new (class extends WeakMap<JSONSchema, ValidateFunction> {
public get({ schema: schemaObj, oasVersion, allErrors }: ISchemaOptions): ValidateFunction {
const ajv = getAjv(oasVersion, allErrors);
const schemaId = getSchemaId(schemaObj);
let validator = schemaId !== void 0 ? ajv.getSchema(schemaId) : void 0;
let validator;
try {
validator = schemaId !== void 0 ? ajv.getSchema(schemaId) : void 0;
} catch {
validator = void 0;
}

if (validator !== void 0) {
return validator;
}
Expand Down

0 comments on commit 627a24d

Please sign in to comment.