Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(schema): swallow ajv missing reference #1147

Merged
merged 5 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 () => {
P0lip marked this conversation as resolved.
Show resolved Hide resolved
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';
P0lip marked this conversation as resolved.
Show resolved Hide resolved
}

export interface IFunctionResult {
Expand Down
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 #
P0lip marked this conversation as resolved.
Show resolved Hide resolved
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)