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

feat(schema): improve enum validation #579

Merged
merged 4 commits into from
Sep 23, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"abort-controller": "^3.0.0",
"ajv": "^6.7",
"ajv-oai": "^1.1.1",
"better-ajv-errors": "0.6.6",
"better-ajv-errors": "^0.6.7",
"chalk": "^2.4.2",
"deprecated-decorator": "^0.1.6",
"jsonpath-plus": "~1.0",
Expand Down
30 changes: 30 additions & 0 deletions src/functions/__tests__/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,34 @@ describe('schema', () => {

expect(runSchema.bind(null, 'd', testSchema)).not.toThrow();
});

test('reports pretty enum errors for primitive values', () => {
const testSchema: JSONSchema6 = {
$schema: `http://json-schema.org/draft-06/schema#`,
type: 'string',
enum: ['foo', 'bar'],
};

expect(runSchema('baz', testSchema)).toEqual([
{
message: 'should be equal to one of the allowed values: foo, bar. Did you mean bar?',
path: [],
},
]);
});

test('reports slightly less pretty enum errors for primitive values that are not similar to any values in enum', () => {
const testSchema: JSONSchema6 = {
$schema: `http://json-schema.org/draft-06/schema#`,
type: 'string',
P0lip marked this conversation as resolved.
Show resolved Hide resolved
enum: ['foo', 'bar'],
};

expect(runSchema('three', testSchema)).toEqual([
{
message: 'should be equal to one of the allowed values: foo, bar',
path: [],
},
]);
});
});
14 changes: 10 additions & 4 deletions src/functions/schema.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { decodePointerFragment } from '@stoplight/json';
import { Optional } from '@stoplight/types';
import * as AJV from 'ajv';
import { ValidateFunction } from 'ajv';
import * as jsonSpecv4 from 'ajv/lib/refs/json-schema-draft-04.json';
import * as jsonSpecv6 from 'ajv/lib/refs/json-schema-draft-06.json';
import * as jsonSpecv7 from 'ajv/lib/refs/json-schema-draft-07.json';
import { IOutputError } from 'better-ajv-errors';
import { JSONSchema4, JSONSchema6 } from 'json-schema';
import { escapeRegExp } from 'tslint/lib/utils';
import { IFunction, IFunctionResult, ISchemaOptions } from '../types';
const oasFormatValidator = require('ajv-oai/lib/format-validator');
const betterAjvErrors = require('better-ajv-errors/lib/modern');
Expand Down Expand Up @@ -76,7 +78,11 @@ const validators = new class extends WeakMap<JSONSchema4 | JSONSchema6, Validate
}
}();

const cleanAJVErrorMessage = (message: string) => message.trim().replace(/^[^:]*:\s*/, '');
const cleanAJVErrorMessage = (message: string, path: Optional<string>, suggestion: Optional<string>) => {
const cleanMessage =
typeof path === 'string' ? message.trim().replace(new RegExp(`^${escapeRegExp(path)}:\\s*`), '') : message.trim();
return `${cleanMessage}${typeof suggestion === 'string' && suggestion.length > 0 ? `. ${suggestion}` : ''}`;
};

export const schema: IFunction<ISchemaOptions> = (targetVal, opts, paths) => {
const results: IFunctionResult[] = [];
Expand All @@ -101,16 +107,16 @@ export const schema: IFunction<ISchemaOptions> = (targetVal, opts, paths) => {
try {
results.push(
...(betterAjvErrors(schemaObj, targetVal, validator.errors, { format: 'js' }) as IAJVOutputError[]).map(
({ error, path: errorPath }) => ({
message: cleanAJVErrorMessage(error),
({ suggestion, error, path: errorPath }) => ({
message: cleanAJVErrorMessage(error, errorPath, suggestion),
path: [...path, ...(errorPath ? errorPath.replace(/^\//, '').split('/') : [])],
}),
),
);
} catch {
results.push(
...validator.errors.map(({ message, dataPath }) => ({
message: message ? cleanAJVErrorMessage(message) : '',
message: message ? cleanAJVErrorMessage(message, dataPath, void 0) : '',
path: [
...path,
...dataPath
Expand Down
4 changes: 2 additions & 2 deletions test-harness/scenarios/external-schemas-ruleset.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ lint {document} --ruleset ./test-harness/scenarios/rulesets/external-schemas-rul
OpenAPI 3.x detected

{document}
3:10 error info-title should be equal to one of the allowed values
4:16 error info-description should be equal to one of the allowed values
3:10 error info-title should be equal to one of the allowed values: Stoplight, Stoplight.io, StoplightIO. Did you mean Stoplight?
4:16 error info-description should be equal to one of the allowed values: foo, foo-bar, bar-foo

✖ 2 problems (2 errors, 0 warnings, 0 infos, 0 hints)
17 changes: 12 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"

"@babel/runtime@^7.0.0", "@babel/runtime@^7.4.5":
"@babel/runtime@^7.0.0":
version "7.6.0"
P0lip marked this conversation as resolved.
Show resolved Hide resolved
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205"
integrity sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ==
dependencies:
regenerator-runtime "^0.13.2"

"@babel/runtime@^7.4.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
Expand Down Expand Up @@ -1096,10 +1103,10 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"

better-ajv-errors@0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/better-ajv-errors/-/better-ajv-errors-0.6.6.tgz#967f3075ca43455021c4802c92761dfbf843188e"
integrity sha512-CD5Xb75GtFpwcPnGH60MFlqwhMt0uUhKEjCTaWNXa3btSEQ0dbokn4WbyuMy/ykpfa0miJ2fEU5iQWIVSXIXgw==
better-ajv-errors@^0.6.7:
version "0.6.7"
resolved "https://registry.yarnpkg.com/better-ajv-errors/-/better-ajv-errors-0.6.7.tgz#b5344af1ce10f434fe02fc4390a5a9c811e470d1"
integrity sha512-PYgt/sCzR4aGpyNy5+ViSQ77ognMnWq7745zM+/flYO4/Yisdtp9wDQW2IKCyVYPUxQt3E/b5GBSwfhd1LPdlg==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/runtime" "^7.0.0"
Expand Down