diff --git a/lib/vocabularies/code.ts b/lib/vocabularies/code.ts index 92cdd5b04e..498d9159fb 100644 --- a/lib/vocabularies/code.ts +++ b/lib/vocabularies/code.ts @@ -1,4 +1,4 @@ -import type {AnySchema, SchemaMap} from "../types" +import type {AnySchema, RegExpLike, SchemaMap} from "../types" import type {SchemaCxt} from "../compile" import type {KeywordCxt} from "../compile/validate" import {CodeGen, _, and, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen" @@ -92,10 +92,16 @@ export function callValidateCode( const newRegExp = _`new RegExp` -export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name { +export function usePattern({gen, it: {opts, errSchemaPath}}: KeywordCxt, pattern: string): Name { const u = opts.unicodeRegExp ? "u" : "" const {regExp} = opts.code - const rx = regExp(pattern, u) + + let rx: RegExpLike + try { + rx = regExp(pattern, u) + } catch (e) { + throw new Error(`${(e as Error).message} | pattern ${pattern} at ${errSchemaPath}`) + } return gen.scopeValue("pattern", { key: rx.toString(), diff --git a/lib/vocabularies/validation/pattern.ts b/lib/vocabularies/validation/pattern.ts index 7b27b7d3c0..e42997727a 100644 --- a/lib/vocabularies/validation/pattern.ts +++ b/lib/vocabularies/validation/pattern.ts @@ -18,9 +18,16 @@ const def: CodeKeywordDefinition = { error, code(cxt: KeywordCxt) { const {data, $data, schema, schemaCode, it} = cxt - // TODO regexp should be wrapped in try/catchs const u = it.opts.unicodeRegExp ? "u" : "" - const regExp = $data ? _`(new RegExp(${schemaCode}, ${u}))` : usePattern(cxt, schema) + const regExp = $data + ? _`(function() { + try { + return new RegExp(${schemaCode}, ${u}) + } catch (e) { + throw new Error(e.message + ' | pattern ' + ${schemaCode} + ' at ' + ${it.errSchemaPath}) + } + })()` + : usePattern(cxt, schema) cxt.fail$data(_`!${regExp}.test(${data})`) }, } diff --git a/spec/issues/2477_informative_pattern_errors.spec.ts b/spec/issues/2477_informative_pattern_errors.spec.ts new file mode 100644 index 0000000000..94077134da --- /dev/null +++ b/spec/issues/2477_informative_pattern_errors.spec.ts @@ -0,0 +1,46 @@ +import _Ajv from "../ajv2020" +import * as assert from "assert" + +describe("Invalid regexp patterns should throw more informative errors (issue #2477)", () => { + it("throws with pattern and schema path", () => { + const ajv = new _Ajv() + + const rootSchema = { + type: "string", + pattern: "^[0-9]{2-4}", + } + + assert.throws( + () => ajv.compile(rootSchema), + (thrown: Error) => thrown.message.includes("pattern ^[0-9]{2-4} at #") + ) + + const pathSchema = { + type: "object", + properties: { + foo: rootSchema, + }, + } + + assert.throws( + () => ajv.compile(pathSchema), + (thrown: Error) => thrown.message.includes("pattern ^[0-9]{2-4} at #/properties/foo") + ) + }) + it("throws with pattern and schema path with $data", () => { + const ajv = new _Ajv({$data: true}) + + const schema = { + properties: { + shouldMatch: {}, + string: {pattern: {$data: "1/shouldMatch"}}, + }, + } + const validate = ajv.compile(schema) + + assert.throws( + () => ajv.compile(validate({shouldMatch: "^[0-9]{2-4}", string: "123"})), + (thrown: Error) => thrown.message.includes("pattern ^[0-9]{2-4} at #/properties/string") + ) + }) +})