diff --git a/docs/options.md b/docs/options.md index b104eb1ee..b4d526560 100644 --- a/docs/options.md +++ b/docs/options.md @@ -36,6 +36,7 @@ const defaultOptions = { allErrors: false, verbose: false, discriminator: false, // * + unicodeRegExp: true // * $comment: false, // * formats: {}, keywords: {}, @@ -167,6 +168,15 @@ Include the reference to the part of the schema (`schema` and `parentSchema`) an Support [discriminator keyword](./json-schema.md#discriminator) from [OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md). +### unicodeRegExp + +By default Ajv uses unicode flag "u" with "pattern" and "patternProperties", as per JSON Schema spec. See [RegExp.prototype.unicode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode) . + +Option values: + +- `true` (default) - use unicode flag "u". +- `false` - do not use flag "u". + ### $comment Log or pass the value of `$comment` keyword to a function. diff --git a/lib/core.ts b/lib/core.ts index 40aa56d33..090f0f2ea 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -97,6 +97,7 @@ export interface CurrentOptions { allErrors?: boolean verbose?: boolean discriminator?: boolean + unicodeRegExp?: boolean $comment?: | true | ((comment: string, schemaPath?: string, rootSchema?: AnySchemaObject) => unknown) @@ -212,7 +213,8 @@ type RequiredInstanceOptions = { | "messages" | "addUsedSchema" | "validateSchema" - | "validateFormats"]: NonNullable + | "validateFormats" + | "unicodeRegExp"]: NonNullable } & {code: InstanceCodeOptions} export type InstanceOptions = Options & RequiredInstanceOptions @@ -239,6 +241,7 @@ function requiredOptions(o: Options): RequiredInstanceOptions { addUsedSchema: o.addUsedSchema ?? true, validateSchema: o.validateSchema ?? true, validateFormats: o.validateFormats ?? true, + unicodeRegExp: o.unicodeRegExp ?? true, } } diff --git a/lib/vocabularies/applicator/additionalProperties.ts b/lib/vocabularies/applicator/additionalProperties.ts index 03ca8080a..bfb511ce5 100644 --- a/lib/vocabularies/applicator/additionalProperties.ts +++ b/lib/vocabularies/applicator/additionalProperties.ts @@ -60,7 +60,7 @@ const def: CodeKeywordDefinition & AddedKeywordDefinition = { definedProp = nil } if (patProps.length) { - definedProp = or(definedProp, ...patProps.map((p) => _`${usePattern(gen, p)}.test(${key})`)) + definedProp = or(definedProp, ...patProps.map((p) => _`${usePattern(cxt, p)}.test(${key})`)) } return not(definedProp) } diff --git a/lib/vocabularies/applicator/patternProperties.ts b/lib/vocabularies/applicator/patternProperties.ts index 25c3e308f..525535438 100644 --- a/lib/vocabularies/applicator/patternProperties.ts +++ b/lib/vocabularies/applicator/patternProperties.ts @@ -50,7 +50,7 @@ const def: CodeKeywordDefinition = { function validateProperties(pat: string): void { gen.forIn("key", data, (key) => { - gen.if(_`${usePattern(gen, pat)}.test(${key})`, () => { + gen.if(_`${usePattern(cxt, pat)}.test(${key})`, () => { cxt.subschema( { keyword: "patternProperties", diff --git a/lib/vocabularies/code.ts b/lib/vocabularies/code.ts index 80387c81a..ca1cf5f0a 100644 --- a/lib/vocabularies/code.ts +++ b/lib/vocabularies/code.ts @@ -90,11 +90,12 @@ export function callValidateCode( return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})` } -export function usePattern(gen: CodeGen, pattern: string): Name { +export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name { + const u = opts.unicodeRegExp ? "u" : "" return gen.scopeValue("pattern", { key: pattern, - ref: new RegExp(pattern, "u"), - code: _`new RegExp(${pattern}, "u")`, + ref: new RegExp(pattern, u), + code: _`new RegExp(${pattern}, ${u})`, }) } diff --git a/lib/vocabularies/validation/pattern.ts b/lib/vocabularies/validation/pattern.ts index 880279f8f..7b27b7d3c 100644 --- a/lib/vocabularies/validation/pattern.ts +++ b/lib/vocabularies/validation/pattern.ts @@ -17,8 +17,10 @@ const def: CodeKeywordDefinition = { $data: true, error, code(cxt: KeywordCxt) { - const {gen, data, $data, schema, schemaCode} = cxt - const regExp = $data ? _`(new RegExp(${schemaCode}, "u"))` : usePattern(gen, schema) // TODO regexp should be wrapped in try/catch + 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) cxt.fail$data(_`!${regExp}.test(${data})`) }, } diff --git a/spec/options/unicodeRegExp.spec.ts b/spec/options/unicodeRegExp.spec.ts new file mode 100644 index 000000000..737fa0432 --- /dev/null +++ b/spec/options/unicodeRegExp.spec.ts @@ -0,0 +1,67 @@ +import _Ajv from "../ajv" +import chai from "../chai" +const should = chai.should() + +describe("unicodeRegExp option", () => { + const unicodeChar = "\uD83D\uDC4D" + const unicodeSchema = { + type: "string", + pattern: `^[${unicodeChar}]$`, + } + + const schemaWithEscape = { + type: "string", + pattern: "^[\\:]$", + } + + const patternPropertiesSchema = { + type: "object", + patternProperties: { + "^\\:.*$": {type: "number"}, + }, + additionalProperties: false, + } + + describe("= true (default)", () => { + const ajv = new _Ajv() + it("should fail schema compilation if used invalid (unnecessary) escape sequence for pattern", () => { + should.throw(() => { + ajv.compile(schemaWithEscape) + }, /Invalid escape/) + }) + + it("should fail schema compilation if used invalid (unnecessary) escape sequence for patternProperties", () => { + should.throw(() => { + ajv.compile(patternPropertiesSchema) + }, /Invalid escape/) + }) + + it("should validate unicode character", () => { + const validate = ajv.compile(unicodeSchema) + validate(unicodeChar).should.equal(true) + }) + }) + + describe("= false", () => { + const ajv = new _Ajv({unicodeRegExp: false}) + it("should pass schema compilation if used unnecessary escape sequence for pattern", () => { + should.not.throw(() => { + const validate = ajv.compile(schemaWithEscape) + validate(":").should.equal(true) + }) + }) + + it("should pass schema compilation if used unnecessary escape sequence for patternProperties", () => { + should.not.throw(() => { + const validate = ajv.compile(patternPropertiesSchema) + validate({":test": 1}).should.equal(true) + validate({test: 1}).should.equal(false) + }) + }) + + it("should not validate unicode character", () => { + const validate = ajv.compile(unicodeSchema) + validate(unicodeChar).should.equal(false) + }) + }) +})