From 4d3ab6f549c4487ba0259d48374b5ab0e65b1ac8 Mon Sep 17 00:00:00 2001 From: Pasi Eronen Date: Thu, 2 Apr 2026 09:53:21 +0300 Subject: [PATCH 1/2] refactor: replace ajv-formats-draft2019 with custom implementations for idn-email format (leveraging ajv-formats) Signed-off-by: Pasi Eronen --- README.md | 1 - docs/install.rst | 1 - package.json | 5 ----- src/_optPlug.node/__jsonValidators/ajv.ts | 14 ++++++++++---- src/_optPlug.node/jsonValidator.ts | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6523bc1d5..84b9da90a 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,6 @@ Some features require optional peer dependencies — see `package.json` for vers * Validation of JSON on _Node.js_ requires all of: * [`ajv`](https://www.npmjs.com/package/ajv) * [`ajv-formats`](https://www.npmjs.com/package/ajv-formats) - * [`ajv-formats-draft2019`](https://www.npmjs.com/package/ajv-formats-draft2019) * Validation of XML on _Node.js_ requires any of: * [`libxmljs2`](https://www.npmjs.com/package/libxmljs2) * the system might need to meet the requirements for [`node-gyp`](https://github.com/TooTallNate/node-gyp#installation), in certain cases. diff --git a/docs/install.rst b/docs/install.rst index 411c357fe..223802b33 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -30,7 +30,6 @@ See the shipped ``package.json`` for version constraints. * Validation of JSON on _Node.js_ requires all of: * `ajv `_ * `ajv-formats `_ - * `ajv-formats-draft2019 `_ * Validation of XML on _Node.js_ requires all of: * `libxmljs2 `_ * the system must meet the requirements for `node-gyp `_ diff --git a/package.json b/package.json index 1f70cf661..ca8af858b 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,6 @@ "peerDependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", - "ajv-formats-draft2019": "^1.6.1", "libxmljs2": "^0.35||^0.37", "xmlbuilder2": "^3.0.2||^4.0.0", "packageurl-js": "*", @@ -96,9 +95,6 @@ "ajv-formats": { "optional": true }, - "ajv-formats-draft2019": { - "optional": true - }, "libxmljs2": { "optional": true }, @@ -127,7 +123,6 @@ "devDependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", - "ajv-formats-draft2019": "^1.6.1", "libxmljs2": "^0.35||^0.37", "xmlbuilder2": "^3.0.2||^4.0.0", "spdx-expression-parse": "^3.0.1||^4", diff --git a/src/_optPlug.node/__jsonValidators/ajv.ts b/src/_optPlug.node/__jsonValidators/ajv.ts index 76da789fd..910bc0cc8 100644 --- a/src/_optPlug.node/__jsonValidators/ajv.ts +++ b/src/_optPlug.node/__jsonValidators/ajv.ts @@ -21,8 +21,6 @@ import { readFile } from 'node:fs/promises' import Ajv, { type Options as AjvOptions } from 'ajv' import addFormats from 'ajv-formats' -/* @ts-expect-error TS7016 */ -import addFormats2019 from 'ajv-formats-draft2019' import type { ValidationError } from '../../validation/types' import type { Functionality, Validator } from '../jsonValidator' @@ -50,11 +48,19 @@ export default (async function (schemaPath: string, schemaMap: Record emailValidator(x.replace(/[\u0080-\uffff]+/g, 'x')) + }) + const validator = ajv.compile(schema) return function (data: string): null | ValidationError { diff --git a/src/_optPlug.node/jsonValidator.ts b/src/_optPlug.node/jsonValidator.ts index bc0827e53..1b35f56cd 100644 --- a/src/_optPlug.node/jsonValidator.ts +++ b/src/_optPlug.node/jsonValidator.ts @@ -27,7 +27,7 @@ export default opWrapper('JsonValidator', [ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-require-imports -- needed */ - ['( ajv && ajv-formats && ajv-formats-draft2019 )', () => require('./__jsonValidators/ajv').default] + ['( ajv && ajv-formats )', () => require('./__jsonValidators/ajv').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-require-imports */ From 6f127bd51e27108d6328af90e2c0647df6db23d3 Mon Sep 17 00:00:00 2001 From: Pasi Eronen Date: Tue, 7 Apr 2026 14:33:47 +0300 Subject: [PATCH 2/2] test: add test cases for idn-email validation Signed-off-by: Pasi Eronen --- .../OpPlug.node.jsonValidator.spec.js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js index c9c04ea42..c433fbcf0 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -67,4 +67,49 @@ suite('functional: internals: OpPlug.node.jsonValidator auto', () => { const validator = await makeValidator(schemaPath, schemaMap) assert.throws(() => { validator(brokenJson) }) }) + + // list of valid/invalid emails is from https://github.com/json-schema-org/JSON-Schema-Test-Suite + // (tests/draft2019-09/optional/format/email.json and idn-email.json) + // and https://github.com/luzlab/ajv-formats-draft2019 (index.test.js) + + suite('accepts valid emails', () => + [ + 'joe.bloggs@example.com', + 'te~st@example.com', + '~test@example.com', + 'test~@example.com', + 'te.s.t@example.com', + '실례@실례.테스트', + 'квіточка@пошта.укр', + 'Dörte@Sörensen.example.com' + ].forEach(validEmail => test(validEmail, async () => { + const validator = await makeValidator(schemaPath, schemaMap) + const validJson = JSON.stringify({ + bomFormat: 'CycloneDX', + specVersion: '1.7', + metadata: { authors: [{ email: validEmail }] } + }) + assert.strictEqual(validator(validJson), null) + })) + ) + + suite('rejects invalid emails', () => + [ + '2962', + '.test@example.com', + 'test.@example.com', + 'te..st@example.com', + '', + 'johndoe', + 'valid@example.com?asdf' + ].forEach(invalidEmail => test(invalidEmail, async () => { + const validator = await makeValidator(schemaPath, schemaMap) + const invalidJson = JSON.stringify({ + bomFormat: 'CycloneDX', + specVersion: '1.7', + metadata: { authors: [{ email: invalidEmail }] } + }) + assert.notEqual(validator(invalidJson), null) + })) + ) })