Skip to content

Commit

Permalink
fix(schema): configure missingRefs
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip committed May 14, 2020
1 parent a02842a commit 258837f
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 46 deletions.
19 changes: 0 additions & 19 deletions src/__tests__/linter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { RuleCollection, Spectral } from '../spectral';

const invalidSchema = JSON.stringify(require('./__fixtures__/petstore.invalid-schema.oas3.json'));
const studioFixture = JSON.stringify(require('./__fixtures__/studio-default-fixture-oas3.json'), null, 2);
const todosInvalid = JSON.stringify(require('./__fixtures__/todos.invalid.oas2.json'));
const petstoreMergeKeys = JSON.stringify(require('./__fixtures__/petstore.merge.keys.oas3.json'));

const fnName = 'fake';
Expand Down Expand Up @@ -655,24 +654,6 @@ responses:: !!foo
]);
});

test('should report invalid schema $refs', async () => {
spectral.registerFormat('oas2', isOpenApiv2);
spectral.registerFormat('oas3', isOpenApiv3);
await spectral.loadRuleset('spectral:oas');

const result = await spectral.run(todosInvalid);

expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
code: 'oas2-valid-parameter-example',
message: "can't resolve reference #/parameters/missing from id #",
path: ['paths', '/todos/{todoId}', 'put', 'parameters', '1', 'schema', 'example'],
}),
]),
);
});

test('should report invalid $refs', async () => {
const result = await spectral.run(invalidSchema);

Expand Down
2 changes: 2 additions & 0 deletions src/functions/__tests__/alphabetical.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function runAlphabetical(target: any, keyedBy?: string) {
given: null,
original: null,
documentInventory: new DocumentInventory(new Document(safeStringify(target), Parsers.Json), {} as any),
context: 'resolved',
},
);
}
Expand Down Expand Up @@ -118,6 +119,7 @@ describe('alphabetical', () => {
given: null,
original: null,
documentInventory: new DocumentInventory(document, {} as any),
context: 'resolved',
},
),
).toEqual([
Expand Down
2 changes: 1 addition & 1 deletion src/functions/__tests__/casing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function runCasing(target: unknown, type: CasingType, disallowDigits?: boolean,
target,
{ type, disallowDigits, separator },
{ given: ['$'] },
{ given: null, original: null, documentInventory: {} as any },
{ given: null, original: null, documentInventory: {} as any, context: 'resolved' },
);
}

Expand Down
33 changes: 29 additions & 4 deletions src/functions/__tests__/schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { Optional } from '@stoplight/types';
import { JSONSchema4, JSONSchema6 } from 'json-schema';
import { IFunctionValues } from '../../types';
import { schema } from '../schema';

function runSchema(target: any, schemaObj: object, oasVersion?: Optional<2 | 3 | 3.1>) {
return schema(target, { schema: schemaObj, oasVersion }, { given: [] }, { given: null, original: null } as any);
function runSchema(
target: any,
schemaObj: object,
oasVersion?: Optional<2 | 3 | 3.1>,
context?: Partial<IFunctionValues>,
) {
return schema(target, { schema: schemaObj, oasVersion }, { given: [] }, {
given: null,
original: null,
...context,
} as IFunctionValues);
}

describe('schema', () => {
Expand Down Expand Up @@ -321,10 +331,25 @@ describe('schema', () => {
test('pretty-prints path-less property', () => {
const input = { foo: true };
expect(runSchema(input, { additionalProperties: false })).toEqual([
expect.objectContaining({
{
message: 'Property `foo` is not expected to be here',
path: [],
}),
},
]);
});

describe('when schema has a $ref left', () => {
test('given unresolved context, reports an error', () => {
expect(runSchema({}, { $ref: '#/foo' }, void 0, { context: 'unresolved' })).toEqual([
{
message: "can't resolve reference #/foo from id #",
path: [],
},
]);
});

test('given resolved context, ignores', () => {
expect(runSchema({}, { $ref: '#/bar' }, void 0, { context: 'resolved' })).toEqual([]);
});
});
});
11 changes: 7 additions & 4 deletions src/functions/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const cleanAJVErrorMessage = (message: string, path: Optional<string>, suggestio
}`;
};

export const schema: ISchemaFunction = (targetVal, opts, paths) => {
export const schema: ISchemaFunction = (targetVal, opts, paths, { context }) => {
const results: IFunctionResult[] = [];

const path = paths.target || paths.given;
Expand Down Expand Up @@ -191,13 +191,16 @@ export const schema: ISchemaFunction = (targetVal, opts, paths) => {
}
}
} catch (ex) {
if (ex instanceof AJV.MissingRefError) {
if (!(ex instanceof AJV.MissingRefError)) {
throw ex;
} else if (context === 'unresolved') {
// let's ignore any $ref errors if schema fn is provided with already resolved content,
// if our resolver fails to resolve them,
// ajv is unlikely to do it either, since it won't have access to the whole document, but a small portion of it
results.push({
message: ex.message,
path,
});
} else {
throw ex;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function runPayloadValidation(targetVal: any) {
targetVal,
null,
{ given: ['$', 'components', 'messages', 'aMessage'] },
{ given: null, original: null, documentInventory: {} as any },
{ given: null, original: null, documentInventory: {} as any, context: 'resolved' },
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/rulesets/oas/functions/__tests__/typedEnum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function runTypedEnum(targetVal: any) {
targetVal,
null,
{ given: ['$'] },
{ given: null, original: null, documentInventory: {} as any },
{ given: null, original: null, documentInventory: {} as any, context: 'resolved' },
);
}

Expand Down
5 changes: 3 additions & 2 deletions src/runner/lintNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Document } from '../document';
import { Rule } from '../rule';
import { IMessageVars, message } from '../rulesets/message';
import { getDiagnosticSeverity } from '../rulesets/severity';
import { IFunctionResult, IGivenNode } from '../types';
import { IFunctionResult, IFunctionValues, IGivenNode } from '../types';
import { decodeSegmentFragment, getClosestJsonPath, printPath, PrintStyle } from '../utils';
import { IRunnerInternalContext } from './types';
import { getLintTargets, IExceptionLocation, isAKnownException } from './utils';
Expand All @@ -16,10 +16,11 @@ export const lintNode = (
rule: Rule,
exceptionLocations: Optional<IExceptionLocation[]>,
): void => {
const fnContext = {
const fnContext: IFunctionValues = {
original: node.value,
given: node.value,
documentInventory: context.documentInventory,
context: rule.resolved ? 'resolved' : 'unresolved',
};

const givenPath = node.path.length > 0 && node.path[0] === '$' ? node.path.slice(1) : node.path;
Expand Down
1 change: 1 addition & 0 deletions src/types/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface IFunctionValues {
original: any;
given: any;
documentInventory: DocumentInventory;
context: 'resolved' | 'unresolved';
}

export interface IFunctionResult {
Expand Down
23 changes: 9 additions & 14 deletions test-harness/scenarios/exceptions.circular.reference.oas3.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,13 @@ extends: spectral:oas
OpenAPI 3.x detected

{document}
1:1 warning oas3-api-servers OpenAPI `servers` must be present and non-empty array.
1:1 warning openapi-tags OpenAPI object should have non-empty `tags` array.
10:9 warning operation-operationId Operation should have an `operationId`.
10:9 warning operation-tags Operation should have non-empty `tags` array.
20:17 error oas3-valid-oas-content-example can't resolve reference #/components/schemas/links from id #
22:9 warning operation-operationId Operation should have an `operationId`.
22:9 warning operation-tags Operation should have non-empty `tags` array.
32:17 error oas3-valid-oas-content-example can't resolve reference #/components/schemas/links from id #
34:9 warning operation-operationId Operation should have an `operationId`.
34:9 warning operation-tags Operation should have non-empty `tags` array.
44:17 error oas3-valid-oas-content-example can't resolve reference #/components/schemas/links from id #
63:15 error oas3-valid-content-schema-example can't resolve reference #/components/schemas/links from id #
63:15 error oas3-valid-schema-example can't resolve reference #/components/schemas/links from id #
1:1 warning oas3-api-servers OpenAPI `servers` must be present and non-empty array.
1:1 warning openapi-tags OpenAPI object should have non-empty `tags` array.
10:9 warning operation-operationId Operation should have an `operationId`.
10:9 warning operation-tags Operation should have non-empty `tags` array.
22:9 warning operation-operationId Operation should have an `operationId`.
22:9 warning operation-tags Operation should have non-empty `tags` array.
34:9 warning operation-operationId Operation should have an `operationId`.
34:9 warning operation-tags Operation should have non-empty `tags` array.

13 problems (5 errors, 8 warnings, 0 infos, 0 hints)
8 problems (0 errors, 8 warnings, 0 infos, 0 hints)

0 comments on commit 258837f

Please sign in to comment.