From 58e41dc271afb3478a96aa670872773ea956d3ff Mon Sep 17 00:00:00 2001 From: Utkarsh patwa Date: Sun, 8 Nov 2026 11:43:57 +0530 Subject: [PATCH] feat: avoid redundant keyword repetition for array constraints (#171) Signed-off-by: Utkarsh patwa --- src/__tests__/index.ts | 40 ++++++++++++++++++++++++++++++++ src/validation-errors/default.ts | 13 +++++++---- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/__tests__/index.ts b/src/__tests__/index.ts index 71a6aafa..e40e6b9c 100644 --- a/src/__tests__/index.ts +++ b/src/__tests__/index.ts @@ -321,5 +321,45 @@ describe('Main', () => { }, ]); }); + it('formats array keyword errors (minItems, maxItems, uniqueItems) without awkward keyword repetition (#171)', () => { + const schema = { + type: 'object', + properties: { + operators: { + type: 'array', + minItems: 1, + maxItems: 3, + uniqueItems: true, + }, + }, + }; + + expect(getErrors(schema, { operators: [] })).toEqual([ + { + start: { line: 1, column: 14, offset: 13 }, + end: { line: 1, column: 16, offset: 15 }, + error: '/operators: must NOT have fewer than 1 items', + path: '/operators', + }, + ]); + + expect(getErrors(schema, { operators: [1, 2, 3, 4] })).toEqual([ + { + start: { line: 1, column: 14, offset: 13 }, + end: { line: 1, column: 23, offset: 22 }, + error: '/operators: must NOT have more than 3 items', + path: '/operators', + }, + ]); + + expect(getErrors(schema, { operators: [1, 1] })).toEqual([ + { + start: { line: 1, column: 14, offset: 13 }, + end: { line: 1, column: 19, offset: 18 }, + error: '/operators: must NOT have duplicate items (items ## 0 and 1 are identical)', + path: '/operators', + }, + ]); + }); }); }); diff --git a/src/validation-errors/default.ts b/src/validation-errors/default.ts index 6d2801fb..9ff445fb 100644 --- a/src/validation-errors/default.ts +++ b/src/validation-errors/default.ts @@ -2,6 +2,8 @@ import { styleText } from 'node:util'; import type { IOutputError } from '../index'; import BaseValidationError from './base'; +const ARRAY_KEYWORDS = new Set(['minItems', 'maxItems', 'uniqueItems']); + export default class DefaultValidationError extends BaseValidationError { print(): string[] { const { keyword, message } = this.options; @@ -9,17 +11,20 @@ export default class DefaultValidationError extends BaseValidationError { styleText('red', styleText('bold', keyword.toUpperCase()) + ' ' + message) + '\n', ]; - return output.concat( - this.getCodeFrame('👈🏽 ' + styleText('magentaBright', keyword) + ' ' + message) - ); + const label = ARRAY_KEYWORDS.has(keyword) + ? message + : `${styleText('magentaBright', keyword)} ${message}`; + + return output.concat(this.getCodeFrame(`👈🏽 ${label}`)); } getError(): IOutputError { const { keyword, message } = this.options; + const errorMsg = ARRAY_KEYWORDS.has(keyword) ? (message ?? '') : `${keyword} ${message}`; return { ...this.getLocation(), - error: this.withDecoratedPath(`${keyword} ${message}`, ': '), + error: this.withDecoratedPath(errorMsg, ': '), path: this.instancePath, }; }