From d33a36dab516bb65a6af408fc883f3ab935883d5 Mon Sep 17 00:00:00 2001 From: Jason Green Date: Sun, 4 Aug 2024 22:14:58 +0100 Subject: [PATCH 1/5] feat: add pattern and path to regexp create errors #2477 --- lib/vocabularies/code.ts | 13 +++- lib/vocabularies/validation/pattern.ts | 11 +++- .../2477_informative_pattern_errors.spec.ts | 61 +++++++++++++++++++ 3 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 spec/issues/2477_informative_pattern_errors.spec.ts diff --git a/lib/vocabularies/code.ts b/lib/vocabularies/code.ts index 92cdd5b04e..1710b4aa31 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,17 @@ 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 = new RegExp(pattern, u) + } catch (e) { + throw new Error(`Invalid regular expression: ${pattern} at ${errSchemaPath}`) + } + rx = regExp(pattern, u) return gen.scopeValue("pattern", { key: rx.toString(), diff --git a/lib/vocabularies/validation/pattern.ts b/lib/vocabularies/validation/pattern.ts index 7b27b7d3c0..947cd72ec6 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('Invalid regular expression: ' + ${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..aa19ec4019 --- /dev/null +++ b/spec/issues/2477_informative_pattern_errors.spec.ts @@ -0,0 +1,61 @@ +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: unknown) => { + assert.equal((thrown as Error).message, "Invalid regular expression: ^[0-9]{2-4} at #") + return true + } + ) + + const pathSchema = { + type: "object", + properties: { + foo: rootSchema, + }, + } + + assert.throws( + () => ajv.compile(pathSchema), + (thrown: unknown) => { + assert.equal( + (thrown as Error).message, + "Invalid regular expression: ^[0-9]{2-4} at #/properties/foo" + ) + return true + } + ) + }) + 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: unknown) => { + assert.equal( + (thrown as Error).message, + "Invalid regular expression: ^[0-9]{2-4} at #/properties/string" + ) + return true + } + ) + }) +}) From 4e3463d28d49397a246169a16246349cce518554 Mon Sep 17 00:00:00 2001 From: Jason Green Date: Wed, 7 Aug 2024 21:32:01 +0100 Subject: [PATCH 2/5] fix: include original error and add extra info --- lib/vocabularies/code.ts | 2 +- lib/vocabularies/validation/pattern.ts | 2 +- spec/issues/2477_informative_pattern_errors.spec.ts | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/vocabularies/code.ts b/lib/vocabularies/code.ts index 1710b4aa31..63ad9022d8 100644 --- a/lib/vocabularies/code.ts +++ b/lib/vocabularies/code.ts @@ -100,7 +100,7 @@ export function usePattern({gen, it: {opts, errSchemaPath}}: KeywordCxt, pattern try { rx = new RegExp(pattern, u) } catch (e) { - throw new Error(`Invalid regular expression: ${pattern} at ${errSchemaPath}`) + throw new Error(`${(e as Error).message} | pattern ${pattern} at ${errSchemaPath}`) } rx = regExp(pattern, u) diff --git a/lib/vocabularies/validation/pattern.ts b/lib/vocabularies/validation/pattern.ts index 947cd72ec6..e42997727a 100644 --- a/lib/vocabularies/validation/pattern.ts +++ b/lib/vocabularies/validation/pattern.ts @@ -24,7 +24,7 @@ const def: CodeKeywordDefinition = { try { return new RegExp(${schemaCode}, ${u}) } catch (e) { - throw new Error('Invalid regular expression: ' + ${schemaCode} + ' at ' + ${it.errSchemaPath}) + throw new Error(e.message + ' | pattern ' + ${schemaCode} + ' at ' + ${it.errSchemaPath}) } })()` : usePattern(cxt, schema) diff --git a/spec/issues/2477_informative_pattern_errors.spec.ts b/spec/issues/2477_informative_pattern_errors.spec.ts index aa19ec4019..facc6e450d 100644 --- a/spec/issues/2477_informative_pattern_errors.spec.ts +++ b/spec/issues/2477_informative_pattern_errors.spec.ts @@ -13,7 +13,10 @@ describe("Invalid regexp patterns should throw more informative errors (issue #2 assert.throws( () => ajv.compile(rootSchema), (thrown: unknown) => { - assert.equal((thrown as Error).message, "Invalid regular expression: ^[0-9]{2-4} at #") + assert.equal( + (thrown as Error).message, + "Invalid regular expression: /^[0-9]{2-4}/: Incomplete quantifier | pattern ^[0-9]{2-4} at #" + ) return true } ) @@ -30,7 +33,7 @@ describe("Invalid regexp patterns should throw more informative errors (issue #2 (thrown: unknown) => { assert.equal( (thrown as Error).message, - "Invalid regular expression: ^[0-9]{2-4} at #/properties/foo" + "Invalid regular expression: /^[0-9]{2-4}/: Incomplete quantifier | pattern ^[0-9]{2-4} at #/properties/foo" ) return true } @@ -52,7 +55,7 @@ describe("Invalid regexp patterns should throw more informative errors (issue #2 (thrown: unknown) => { assert.equal( (thrown as Error).message, - "Invalid regular expression: ^[0-9]{2-4} at #/properties/string" + "Invalid regular expression: /^[0-9]{2-4}/: Incomplete quantifier | pattern ^[0-9]{2-4} at #/properties/string" ) return true } From 1110b700147718bf2b51a9c9dba859eab166a107 Mon Sep 17 00:00:00 2001 From: Jason Green Date: Wed, 7 Aug 2024 22:20:13 +0100 Subject: [PATCH 3/5] simplify tests --- .../2477_informative_pattern_errors.spec.ts | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/spec/issues/2477_informative_pattern_errors.spec.ts b/spec/issues/2477_informative_pattern_errors.spec.ts index facc6e450d..78a108c394 100644 --- a/spec/issues/2477_informative_pattern_errors.spec.ts +++ b/spec/issues/2477_informative_pattern_errors.spec.ts @@ -1,7 +1,7 @@ import _Ajv from "../ajv2020" import * as assert from "assert" -describe("Invalid regexp patterns should throw more informative errors (issue #2477)", () => { +describe.only("Invalid regexp patterns should throw more informative errors (issue #2477)", () => { it("throws with pattern and schema path", () => { const ajv = new _Ajv() @@ -12,13 +12,7 @@ describe("Invalid regexp patterns should throw more informative errors (issue #2 assert.throws( () => ajv.compile(rootSchema), - (thrown: unknown) => { - assert.equal( - (thrown as Error).message, - "Invalid regular expression: /^[0-9]{2-4}/: Incomplete quantifier | pattern ^[0-9]{2-4} at #" - ) - return true - } + (thrown: Error) => thrown.message.includes("pattern ^[0-9]{2-4} at #") ) const pathSchema = { @@ -30,13 +24,7 @@ describe("Invalid regexp patterns should throw more informative errors (issue #2 assert.throws( () => ajv.compile(pathSchema), - (thrown: unknown) => { - assert.equal( - (thrown as Error).message, - "Invalid regular expression: /^[0-9]{2-4}/: Incomplete quantifier | pattern ^[0-9]{2-4} at #/properties/foo" - ) - return true - } + (thrown: Error) => thrown.message.includes("pattern ^[0-9]{2-4} at #/properties/foo") ) }) it("throws with pattern and schema path with $data", () => { @@ -52,13 +40,7 @@ describe("Invalid regexp patterns should throw more informative errors (issue #2 assert.throws( () => ajv.compile(validate({shouldMatch: "^[0-9]{2-4}", string: "123"})), - (thrown: unknown) => { - assert.equal( - (thrown as Error).message, - "Invalid regular expression: /^[0-9]{2-4}/: Incomplete quantifier | pattern ^[0-9]{2-4} at #/properties/string" - ) - return true - } + (thrown: Error) => thrown.message.includes("pattern ^[0-9]{2-4} at #/properties/string") ) }) }) From e99d84f06fffb0dbfe6f969bc80a155540744f0b Mon Sep 17 00:00:00 2001 From: Jason Green Date: Wed, 7 Aug 2024 22:24:31 +0100 Subject: [PATCH 4/5] remove only from test --- spec/issues/2477_informative_pattern_errors.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/issues/2477_informative_pattern_errors.spec.ts b/spec/issues/2477_informative_pattern_errors.spec.ts index 78a108c394..94077134da 100644 --- a/spec/issues/2477_informative_pattern_errors.spec.ts +++ b/spec/issues/2477_informative_pattern_errors.spec.ts @@ -1,7 +1,7 @@ import _Ajv from "../ajv2020" import * as assert from "assert" -describe.only("Invalid regexp patterns should throw more informative errors (issue #2477)", () => { +describe("Invalid regexp patterns should throw more informative errors (issue #2477)", () => { it("throws with pattern and schema path", () => { const ajv = new _Ajv() From ae457fb631d59c8b2f08505fa51a4490405212f8 Mon Sep 17 00:00:00 2001 From: Jason Green Date: Wed, 7 Aug 2024 22:45:56 +0100 Subject: [PATCH 5/5] fix --- lib/vocabularies/code.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/vocabularies/code.ts b/lib/vocabularies/code.ts index 63ad9022d8..498d9159fb 100644 --- a/lib/vocabularies/code.ts +++ b/lib/vocabularies/code.ts @@ -98,11 +98,10 @@ export function usePattern({gen, it: {opts, errSchemaPath}}: KeywordCxt, pattern let rx: RegExpLike try { - rx = new RegExp(pattern, u) + rx = regExp(pattern, u) } catch (e) { throw new Error(`${(e as Error).message} | pattern ${pattern} at ${errSchemaPath}`) } - rx = regExp(pattern, u) return gen.scopeValue("pattern", { key: rx.toString(),