#197 into release@5.0.0 💎 update zod from v3 to v4#252
#197 into release@5.0.0 💎 update zod from v3 to v4#252MiaInturi wants to merge 9 commits intorelease@5.0.0from
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis PR refactors the validation system by consolidating error-handling utilities, replacing custom schema helpers with a new Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Pre-merge checks and finishing touches✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| "skipLibCheck": true | ||
| }, | ||
| "exclude": ["examples", "bin/templates", "dist", "node_modules", "vi.config.js"] | ||
| "exclude": ["examples", "bin/templates", "dist", "node_modules"] |
There was a problem hiding this comment.
Файла не существует, поэтому удалил.
| interceptors: plainObjectSchema(interceptorsSchema).optional(), | ||
| interceptors: interceptorsSchema.optional(), |
There was a problem hiding this comment.
В бета-версии zod v4 regexp не воспринимался как объект, сейчас же снова стал.
По факту plainObjectSchema нужна была для случаев, когда можно прокинуть пустой объект (типо interceptors — там же можно просто {} написать и не прокидывать никакой из interceptor-ов).
Мне очень не нравилось, что из-за такого узкого и очень редкого кейса приходится использовать нашу самописную обертку, так что я пошел, чекнул как у других популярных либ (valibot, arktype) и у них все так же.
Так что я просто предлагаю убрать эту обертку.
| const validationMessage = getValidationMessageFromPath(path); | ||
| const validationMessage = getValidationMessage(validationResult.error.issues); |
There was a problem hiding this comment.
теперь вместо двух функций getMostSpecificPathFromError + getValidationMessageFromPath вызывается одна getValidationMessage, т.к. эти функции всегда шли в паре
| const isConfigsContainAtLeastOneElement = | ||
| Array.isArray(mockServerConfig.configs) && !!mockServerConfig.configs.length; |
There was a problem hiding this comment.
особо это условие смысла не имело — даже если configs пустой массив, то валидация для него не сломается
| export const oneKeyDispatchSchema = <S extends Record<string, z.ZodTypeAny>>(schemasByKey: S) => { | ||
| const keys = Object.keys(schemasByKey); | ||
|
|
||
| return z | ||
| .custom((value) => isPlainObject(value)) | ||
| .superRefine((value, ctx) => { | ||
| const existingKeys = keys.filter((key) => Object.prototype.hasOwnProperty.call(value, key)); | ||
|
|
||
| if (existingKeys.length !== 1) { | ||
| ctx.addIssue({ | ||
| code: 'custom', | ||
| path: [] | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const [key] = existingKeys; | ||
| const schema = schemasByKey[key]; | ||
|
|
||
| const result = schema.safeParse(value); | ||
| if (!result.success) { | ||
| const issuePath = getMostSpecificPathFromError(result.error.issues); | ||
| ctx.addIssue({ | ||
| code: 'custom', | ||
| path: issuePath | ||
| }); | ||
| } | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Функция создает Zod-схему валидации, которая требует, чтобы объект содержал ровно один ключ из заданного набора, и валидирует значение по соответствующей схеме:
- Принимает объект схем (schemasByKey), где ключи — строки, значения — Zod-схемы.
- Проверяет наличие ровно одного ключа из набора.
- Если ключей нет или их больше одного — ошибка.
- Берет схему по найденному ключу и валидирует весь объект. При ошибке добавляет её с указанием пути.
Это нужно для случаев, когда у нас в зависимости от ключа надо выбрать "ветку" валидации. Типо есть data, queue, file — одновременно эти ключи не должны существовать и в зависимости от того, какой ключ сейчас используется, должна применяться соотв. валидация.
Сам zod не имеет механизма, чтобы остановиться "заранее", просто проверив, что те или иные ключи есть/нет и выдает валидационные ошибки для всех веток сразу. А нам же, как я уже сказал выше, нужны ошибки для ветки, соответствующей ключу.
|
|
||
| const mappedEntityDescriptorSchema = extendedDiscriminatedUnion('checkMode', [ | ||
| const mappedEntityDescriptorSchema = z.discriminatedUnion('checkMode', [ | ||
| entityDescriptorSchema(checkActualValueCheckModeSchema), | ||
| entityDescriptorSchema(z.literal('function'), z.function()), | ||
| entityDescriptorSchema(z.literal('regExp'), z.instanceof(RegExp)), | ||
| entityDescriptorSchema(compareWithDescriptorValueCheckModeSchema, mappedEntityValueSchema) | ||
| ]); |
There was a problem hiding this comment.
раньше zod не поддерживал вложенные discriminatedUnion, а сейчас поддерживает.
Тут это важно, т.к. entityDescriptorSchema тоже discriminatedUnion (по параметру oneOf).
| export const mappedEntitySchema = z.record( | ||
| z.string(), | ||
| z.union([mappedEntityValueSchema, mappedEntityDescriptorSchema]) | ||
| ); |
There was a problem hiding this comment.
теперь z.record требует явного указания типа ключа
| "build:types": "tsc -p tsconfig.production.json && tsc-alias -p tsconfig.production.json", | ||
| "build:swc": "swc bin src index.ts -d dist --extensions .ts --copy-files --ignore **/*.test.ts --ignore bin/templates", | ||
| "build:copy-templates": "shx mkdir -p dist/bin/templates/ts && shx cp -r bin/templates/ts dist/bin/templates && shx mkdir -p dist/bin/templates/js && shx cp -r bin/templates/js dist/bin/templates", | ||
| "build:copy-templates": "tsc --noEmit -p bin/templates/tsconfig.json && shx mkdir -p dist/bin/templates/ts && shx cp -r bin/templates/ts dist/bin/templates && shx mkdir -p dist/bin/templates/js && shx cp -r bin/templates/js dist/bin/templates", |
There was a problem hiding this comment.
Раньше можно было выпустить билд c TS ошибкой в example-ах, теперь же нельзя
| validateFlatMockServerConfig(flatMockServerConfig); | ||
| const [option, ...flatMockServerComponents] = flatMockServerConfig; | ||
| const flatMockServerSettings = !('configs' in option) ? option : undefined; |
There was a problem hiding this comment.
Я перенес вызовы валидирующих функций в run-функции (run, runFlatConfig) по причине того, что раньше валидация применялась для merged конфигов и соотв при наличии какой-то ошибки путь был неправильным.
Например, если конфиг выглядит так:
const mockServerConfig: FlatMockServerConfig = [
{
name: 'rest',
configs: [
{
method: 'get',
path: '/users',
routes: [
{
// Для этого объекта должна быть ошибка
non_expected_key: 'value'
}
]
}
]
}
];то при валидации смердженного конфига (где настройки поставлены в качестве нулевого элемента) путь будет не configuration[0].configs ...., а configuration[1].configs .....
НО у этого есть минус: если в конфиге неправильные настройки (типо невозможного порта), но через CLI передано верное значение, то валидация ругнется, т.к. мы проверяем чисто конфиг, а не смердженную его версию.
Мб как-то по умному валидировать, типо если первый объект — это настройки, то смерджить и валидировать, а если НЕ-настройки, то валидировать до мерджа
There was a problem hiding this comment.
UPD: я понял, что теперь нет валидации, если люди напрямую вызывают create server функции. Мб стоит мерджить конфиги и CLI в этой функции тогда, хз)
Ну либо вообще отказаться от идеи того, что она является доступной извне. Типо если люди хотят подключить свой сервер к моку, то могут просто на своем сервере создать прокси
| if ( | ||
| Array.isArray(mergedApiMockServerConfig.configs) && | ||
| isPlainObject(mergedApiMockServerConfig.configs[0]) && | ||
| 'path' in mergedApiMockServerConfig.configs[0] | ||
| ) { | ||
| const { configs } = mergedApiMockServerConfig; | ||
| const [firstConfig] = Array.isArray(configs) ? configs : []; | ||
|
|
||
| if (isPlainObject(firstConfig) && 'path' in firstConfig) { | ||
| validateApiMockServerConfig(mockConfig, 'rest'); |
There was a problem hiding this comment.
логика та же самая, просто переписал чуток, чтобы не нужно было два раза писать Array.isArray(mergedApiMockServerConfig.configs) и доставать первый элемент через индекс
|
@coderabbitai please review this pr. You can consider my comments for this pr as explanation for code snippets |
|
@MiaInturi: I'll review this PR for the zod v3 to v4 update. I'll take your comments into consideration as explanations for the code changes. ✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/utils/validate/queueSchema/queueSchema.ts (1)
15-15: Fixz.function()usage for Zod v4 compatibility.In Zod v4,
z.function()is no longer a schema type but a function factory requiring explicitinputandoutputschemas. Usingz.function()without arguments is invalid and will fail validation.Since
z.any()already accepts functions,z.function()is redundant in this union. Remove it:data: z.any()If stricter function validation is needed, define the function schema with input/output types:
data: z.union([z.function({ input: [...], output: z.unknown() }), z.any()])Note: This pattern appears in multiple files (interceptorsSchema.ts, corsSchema.ts, routeConfigSchema.ts).
src/utils/validate/utils/entitiesSchema/entitiesSchema.ts (2)
48-57: NestedwithoutCheckModeSchemamay cause validation issues.Line 53 applies
withoutCheckModeSchematoplainEntityObjectiveValueSchema, which then pipes into a union. SincewithoutCheckModeSchemausesz.looseObject({}).refine(...), it will reject arrays (fromz.array(z.json())in the union), aslooseObjectonly accepts objects.This means the
plainEntityObjectiveValueSchemabranch for arrays would never match within this nested context.🔎 Suggested fix
If arrays should also be excluded from having
checkMode, consider adjusting the logic:z.union([ propertyLevelPlainEntityDescriptorSchema, - withoutCheckModeSchema(plainEntityObjectiveValueSchema), + withoutCheckModeSchema(z.record(z.string(), z.json())), + z.array(z.json()), plainEntityPrimitiveValueSchema ])Or if arrays don't need the
checkModeexclusion check, handle them separately in the union.
19-26: Fixz.function()calls to match Zod v4 API.In Zod v4,
z.function()must be called with explicit input and output schemas. The current usage ofz.function()without arguments does not validate function signatures properly. Usez.function(z.tuple([]), z.void())or provide appropriate input/output schemas instead. This pattern appears in lines 21 and 30 of this file and across multiple validation schemas in the codebase.
♻️ Duplicate comments (2)
bin/runFlatConfig.ts (1)
11-13: Consider post-merge validation to avoid false negatives.Based on your comment, the current approach validates the raw config before merging CLI arguments. This can reject configs that would be valid after CLI overrides are applied (e.g., invalid port in config but valid
--portfrom CLI).Options to address this:
- Preferred: Validate the merged config (after line 23) instead of the raw config (line 11). This ensures validation reflects the actual runtime config.
- Alternative: Implement conditional validation—if the first element contains settings, merge CLI args first; otherwise validate before merging.
The current placement trades off early error detection for potential false positives.
🔎 Proposed fix: validate after merge
export const runFlatConfig = ( flatMockServerConfig: FlatMockServerConfig, { baseUrl, port, staticPath }: MockServerConfigArgv ) => { try { - validateFlatMockServerConfig(flatMockServerConfig); const [option, ...flatMockServerComponents] = flatMockServerConfig; const flatMockServerSettings = !('configs' in option) ? option : undefined; const mergedFlatMockServerConfig = [ { ...flatMockServerSettings, ...(baseUrl && { baseUrl }), ...(port && { port }), ...(staticPath && { staticPath }) }, ...(flatMockServerSettings ? flatMockServerComponents : flatMockServerConfig) ] as FlatMockServerConfig; + validateFlatMockServerConfig(mergedFlatMockServerConfig); return startFlatMockServer(mergedFlatMockServerConfig); } catch (error: any) { console.error(error.message); } };src/utils/validate/utils/entitiesSchema/entitiesSchema.ts (1)
78-88: Correct Zod v4 migration forz.recordwith explicit key type.The explicit
z.string()key schema is required in Zod v4. The nestedz.discriminatedUnionusage is now properly supported. As noted in past comments, this leverages Zod v4's improved support for nested discriminated unions.
📜 Review details
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (42)
.gitignore.husky/commit-msgbin/cli.tsbin/init.tsbin/run.tsbin/runFlatConfig.tspackage.jsonsrc/server/createFlatMockServer/createFlatMockServer.tssrc/server/createGraphQLMockServer/createGraphQLMockServer.tssrc/server/createMockServer/createMockServer.tssrc/server/createRestMockServer/createRestMockServer.tssrc/utils/types/entities.tssrc/utils/types/server.tssrc/utils/validate/databaseConfigSchema/databaseConfigSchema.tssrc/utils/validate/getMostSpecificPathFromError.tssrc/utils/validate/getValidationMessage.tssrc/utils/validate/getValidationMessageFromPath.tssrc/utils/validate/graphqlConfigSchema/graphqlConfigSchema.tssrc/utils/validate/graphqlConfigSchema/routeConfigSchema/routeConfigSchema.test.tssrc/utils/validate/graphqlConfigSchema/routeConfigSchema/routeConfigSchema.tssrc/utils/validate/index.tssrc/utils/validate/isOnlyRequestedDataResolvingPropertyExists.tssrc/utils/validate/queueSchema/queueSchema.tssrc/utils/validate/restConfigSchema/restConfigSchema.tssrc/utils/validate/restConfigSchema/routeConfigSchema/routeConfigSchema.test.tssrc/utils/validate/restConfigSchema/routeConfigSchema/routeConfigSchema.tssrc/utils/validate/utils/checkModeSchema/checkModeSchema.tssrc/utils/validate/utils/entitiesSchema/entitiesSchema.test.tssrc/utils/validate/utils/entitiesSchema/entitiesSchema.tssrc/utils/validate/utils/extendedDiscriminatedUnion/extendedDiscriminatedUnion.test.tssrc/utils/validate/utils/extendedDiscriminatedUnion/extendedDiscriminatedUnion.tssrc/utils/validate/utils/index.tssrc/utils/validate/utils/jsonSchema/jsonSchema.tssrc/utils/validate/utils/nestedObjectOrArraySchema/nestedObjectOrArraySchema.tssrc/utils/validate/utils/oneKeyDispatchSchema/oneKeyDispatchSchema.tssrc/utils/validate/utils/plainObjectSchema/plainObjectSchema.tssrc/utils/validate/utils/requiredPropertiesSchema/requiredPropertiesSchema.tssrc/utils/validate/utils/sharedSchema/sharedSchema.tssrc/utils/validate/validateApiMockServerConfig.tssrc/utils/validate/validateFlatMockServerConfig.tssrc/utils/validate/validateMockServerConfig.tstsconfig.dev.json
💤 Files with no reviewable changes (13)
- src/utils/validate/utils/nestedObjectOrArraySchema/nestedObjectOrArraySchema.ts
- src/server/createMockServer/createMockServer.ts
- src/utils/validate/utils/requiredPropertiesSchema/requiredPropertiesSchema.ts
- src/utils/validate/isOnlyRequestedDataResolvingPropertyExists.ts
- src/utils/validate/utils/plainObjectSchema/plainObjectSchema.ts
- src/utils/validate/utils/extendedDiscriminatedUnion/extendedDiscriminatedUnion.test.ts
- src/utils/validate/getMostSpecificPathFromError.ts
- src/utils/validate/utils/extendedDiscriminatedUnion/extendedDiscriminatedUnion.ts
- src/server/createFlatMockServer/createFlatMockServer.ts
- src/utils/validate/getValidationMessageFromPath.ts
- src/server/createRestMockServer/createRestMockServer.ts
- src/utils/validate/utils/jsonSchema/jsonSchema.ts
- src/server/createGraphQLMockServer/createGraphQLMockServer.ts
🧰 Additional context used
🧬 Code graph analysis (16)
bin/runFlatConfig.ts (2)
src/utils/validate/validateFlatMockServerConfig.ts (1)
validateFlatMockServerConfig(15-55)examples/server/index.ts (1)
flatMockServerConfig(5-31)
src/utils/validate/validateFlatMockServerConfig.ts (3)
src/utils/validate/interceptorsSchema/interceptorsSchema.ts (1)
interceptorsSchema(3-6)src/utils/validate/baseUrlSchema/baseUrlSchema.ts (1)
baseUrlSchema(3-3)src/utils/validate/getValidationMessage.ts (1)
getValidationMessage(44-47)
src/utils/validate/utils/oneKeyDispatchSchema/oneKeyDispatchSchema.ts (2)
src/utils/helpers/isPlainObject/isPlainObject.ts (1)
isPlainObject(1-5)src/utils/validate/getValidationMessage.ts (1)
getMostSpecificPathFromError(12-42)
src/utils/validate/queueSchema/queueSchema.ts (1)
src/utils/helpers/isPlainObject/isPlainObject.ts (1)
isPlainObject(1-5)
bin/init.ts (1)
bin/helpers/createTemplate.ts (1)
createTemplate(14-40)
src/utils/validate/restConfigSchema/restConfigSchema.ts (3)
src/utils/types/rest.ts (1)
RestMethod(7-7)src/utils/validate/utils/sharedSchema/sharedSchema.ts (1)
stringForwardSlashSchema(3-3)src/utils/validate/interceptorsSchema/interceptorsSchema.ts (1)
interceptorsSchema(3-6)
src/utils/validate/graphqlConfigSchema/routeConfigSchema/routeConfigSchema.ts (5)
src/utils/validate/utils/entitiesSchema/entitiesSchema.ts (2)
mappedEntitySchema(85-88)variablesPlainEntitySchema(69-72)src/utils/validate/interceptorsSchema/interceptorsSchema.ts (1)
interceptorsSchema(3-6)src/utils/validate/settingsSchema/settingsSchema.ts (1)
settingsSchema(3-7)src/utils/validate/queueSchema/queueSchema.ts (1)
queueSchema(8-23)src/utils/validate/utils/oneKeyDispatchSchema/oneKeyDispatchSchema.ts (1)
oneKeyDispatchSchema(9-37)
src/utils/validate/restConfigSchema/routeConfigSchema/routeConfigSchema.test.ts (1)
src/utils/validate/getValidationMessage.ts (1)
getValidationMessage(44-47)
src/utils/validate/graphqlConfigSchema/routeConfigSchema/routeConfigSchema.test.ts (1)
src/utils/validate/getValidationMessage.ts (1)
getValidationMessage(44-47)
src/utils/validate/utils/entitiesSchema/entitiesSchema.test.ts (1)
src/utils/validate/getValidationMessage.ts (1)
getValidationMessage(44-47)
src/utils/validate/validateApiMockServerConfig.ts (6)
src/utils/validate/interceptorsSchema/interceptorsSchema.ts (1)
interceptorsSchema(3-6)src/utils/validate/corsSchema/corsSchema.ts (1)
corsSchema(6-13)src/utils/validate/databaseConfigSchema/databaseConfigSchema.ts (1)
databaseConfigSchema(5-10)src/utils/validate/graphqlConfigSchema/graphqlConfigSchema.ts (1)
graphqlConfigSchema(32-36)src/utils/validate/restConfigSchema/restConfigSchema.ts (1)
restConfigSchema(27-31)src/utils/validate/getValidationMessage.ts (1)
getValidationMessage(44-47)
src/utils/validate/utils/entitiesSchema/entitiesSchema.ts (1)
src/utils/validate/utils/checkModeSchema/checkModeSchema.ts (3)
entityDescriptorSchema(61-90)checkActualValueCheckModeSchema(10-10)compareWithDescriptorValueCheckModeSchema(20-22)
src/utils/validate/databaseConfigSchema/databaseConfigSchema.ts (1)
src/utils/validate/utils/sharedSchema/sharedSchema.ts (2)
stringJsonFilenameSchema(5-5)stringForwardSlashSchema(3-3)
src/utils/validate/validateMockServerConfig.ts (2)
src/utils/validate/interceptorsSchema/interceptorsSchema.ts (1)
interceptorsSchema(3-6)src/utils/validate/getValidationMessage.ts (1)
getValidationMessage(44-47)
src/utils/validate/graphqlConfigSchema/graphqlConfigSchema.ts (4)
src/utils/validate/graphqlConfigSchema/routeConfigSchema/routeConfigSchema.ts (1)
routeConfigSchema(34-37)src/utils/validate/interceptorsSchema/interceptorsSchema.ts (1)
interceptorsSchema(3-6)src/utils/validate/utils/oneKeyDispatchSchema/oneKeyDispatchSchema.ts (1)
oneKeyDispatchSchema(9-37)src/utils/validate/baseUrlSchema/baseUrlSchema.ts (1)
baseUrlSchema(3-3)
bin/cli.ts (4)
src/utils/types/server.ts (1)
MockServerConfigArgv(75-81)src/utils/validate/baseUrlSchema/baseUrlSchema.ts (1)
baseUrlSchema(3-3)src/utils/validate/portSchema/portSchema.ts (1)
portSchema(4-4)src/utils/validate/staticPathSchema/staticPathSchema.ts (1)
staticPathSchema(12-15)
🔇 Additional comments (33)
tsconfig.dev.json (1)
34-34: LGTM! Appropriate cleanup of the exclude list.Removing the non-existent
vi.config.jsfrom the TypeScript exclusions is correct and keeps the configuration accurate..gitignore (1)
101-101: No issue found with.cursorentry.The
.cursorentry is not a duplicate—it appears only once in the file at line 101. Adding this editor state file to the "IDE - VSCode" section is a reasonable housekeeping change, even if unrelated to the main PR objective.bin/init.ts (1)
83-83: LGTM - Correctly removed unnecessary await.The
createTemplatefunction is synchronous (usesfs.cpSync,fs.readFileSync,fs.writeFileSync), so awaiting it was unnecessary. The try-catch block will still catch any synchronous errors thrown bycreateTemplate, so error handling is preserved.package.json (2)
66-66: LGTM! Type-checking templates before build improves quality.Adding TypeScript type-checking to the build process prevents shipping examples with type errors, which aligns well with the validation improvements in this PR.
94-94: Zod v4.1.13 is valid and has no security vulnerabilities.Version 4.1.13 exists on npm and carries no known CVEs (the only Zod CVE, CVE-2023-4316, affected v3.21.0-3.22.3). Note that 4.2.1 is currently the latest version if you wish to adopt bug fixes, though 4.1.13 is stable.
The breaking changes introduced in Zod v4 (unified error API, z.function() schema overhaul, ZodError shape changes, new metadata and JSON Schema support) are well-documented with migration guides and community codemods available.
src/utils/validate/queueSchema/queueSchema.ts (2)
3-4: LGTM: Defensive plain object check added.The import of
isPlainObjectsupports the stricter validation logic, ensuring that only plain objects (not arrays, null, RegExp instances, etc.) are processed before checking for the 'data' property.
11-11: LGTM: Stricter validation prevents edge cases.The addition of
isPlainObject(value)before checking'data' in valueis a good defensive measure that prevents edge cases where the 'in' operator might be used on non-objects or where non-plain-objects with a 'data' property might pass through.src/utils/validate/utils/checkModeSchema/checkModeSchema.ts (1)
28-28: LGTM: Correct Zod v4 type signature updates.The change from the string literal
'strict'toz.core.$strictcorrectly reflects Zod v4's internal type system for strict object schemas.Also applies to: 46-46, 54-54
bin/cli.ts (5)
1-1: LGTM!The
node:fsimport is appropriately added to support the config file existence validation.
5-6: LGTM!The validation schema imports are correctly added to support the new coercion logic.
12-15: LGTM!The explicit type definition improves type safety and makes the relationship between CLI options and the config type more explicit.
52-52: LGTM!The change from a type assertion to a plain object closure is cleaner, especially with the explicit type definition on lines 12-15.
71-77: LGTM!The config file existence validation provides helpful early feedback to users. Using
fs.existsSyncis appropriate for this CLI use case.src/utils/types/entities.ts (1)
47-55: LGTM: Improved type naming clarity.The rename from
NonCheckModetoWithoutCheckModeis more explicit and self-documenting. The type behavior remains unchanged—excludingcheckModefrom the type union.src/utils/validate/utils/entitiesSchema/entitiesSchema.test.ts (1)
1-31: LGTM: Consistent migration to unified error handling.The test correctly migrates from the two-step path extraction to the single
getValidationMessageutility. The expected values (.value,.property.value) confirm thatgetValidationMessagereturns path strings indicating error locations.src/utils/validate/restConfigSchema/routeConfigSchema/routeConfigSchema.test.ts (1)
19-22: Verify empty string expectation for validation errors.Similar to the GraphQL route config test, this test expects an empty string for validation messages when parsing fails. Validation errors should typically provide meaningful feedback about what's wrong.
Please confirm this is the intended behavior for incorrect data-resolving property combinations.
src/utils/validate/index.ts (1)
4-4: LGTM: Consolidated validation error API.The replacement of
getMostSpecificPathFromErrorandgetValidationMessageFromPathwith a singlegetValidationMessageexport simplifies the public API and aligns with the broader error-handling consolidation across the codebase.src/utils/validate/utils/index.ts (1)
1-4: LGTM: Validation utility consolidation.The introduction of
oneKeyDispatchSchemaand removal ofextendedDiscriminatedUnion,nestedObjectOrArraySchema, andplainObjectSchemastreamlines the validation utilities. This aligns with the broader migration to Zod v4 and centralized dispatch-based validation patterns.Note: This is a breaking change if any external consumers depend on the removed exports.
src/utils/validate/graphqlConfigSchema/routeConfigSchema/routeConfigSchema.test.ts (1)
19-22: The test expectation is correct;getValidationMessagereturns the property path to the error, not a descriptive message.The function extracts the most specific path from zod validation issues and formats it (e.g.,
.property.valueor[0]). For root-level validation errors—such as the oneKeyDispatchSchema violations in this test (empty object{}or conflictingdataandqueuekeys)—the path is empty, sogetValidationMessagecorrectly returns an empty string. This is consistent with other tests in the codebase (e.g.,restConfigSchema.test.tsand similar patterns). The production error messages concatenate this into messages like"Validation Error: configuration.property does not match the API schema"for nested errors, or"Validation Error: configuration does not match the API schema"for root-level errors.src/utils/validate/validateMockServerConfig.ts (1)
8-8: LGTM! Validation refactor aligns with Zod v4 migration.The changes consolidate error handling by using the new
getValidationMessageutility and remove theplainObjectSchemawrapper for interceptors. This is consistent with the PR's broader refactoring strategy and Zod v4's improved handling of RegExp as objects.Also applies to: 31-31, 40-40
src/utils/validate/utils/sharedSchema/sharedSchema.ts (1)
3-3: LGTM! Modern Zod v4 template literal schemas.The migration from
.startsWith()and.endsWith()string methods toz.templateLiteral()aligns with Zod v4's new string validation patterns. This provides better type inference and runtime validation.Also applies to: 5-5
src/utils/validate/validateApiMockServerConfig.ts (1)
8-8: LGTM! Validation consolidation and schema extraction.The refactor correctly:
- Uses the new
getValidationMessageutility for consistent error reporting- Removes the
plainObjectSchemawrapper for interceptors- Extracts nested
configsschemas using.shape.configs.optional(), which is a valid Zod pattern for conditional schema compositionAlso applies to: 29-29, 32-33, 38-38
src/utils/validate/validateFlatMockServerConfig.ts (1)
8-8: LGTM! Flat config validation refactored for Zod v4.The changes correctly:
- Use the unified
getValidationMessageutility- Remove
plainObjectSchemawrappers for interceptors- Update the tuple schema to use Zod v4's
z.tuple([...], restSchema)API for variable-length tuples, where the first element is a union of settings/component schemas and remaining elements are component schemasAlso applies to: 26-26, 34-34, 38-41, 47-49
src/utils/validate/getValidationMessage.ts (2)
3-5: TODO comment acknowledged.The TODO to improve validation messages with detailed descriptions, types, and documentation links is a good enhancement for future work. Consider tracking this as a separate issue for better user experience.
44-47: LGTM! Unified validation message generation.The
getValidationMessagefunction provides a clean interface that consolidates path extraction and message formatting, replacing the previous two-function approach.src/utils/validate/restConfigSchema/restConfigSchema.ts (1)
7-7: LGTM! REST config schema simplified.The changes correctly remove the
plainObjectSchemawrapper for interceptors validation and simplify imports. This aligns with Zod v4's improved object validation and the PR's overall refactoring strategy.Also applies to: 15-15, 30-30
src/utils/validate/utils/entitiesSchema/entitiesSchema.ts (2)
14-17: Correct usage of Zod v4'sz.json()type.The migration to
z.json()is appropriate for representing arbitrary JSON values. This is a new Zod v4 feature that properly validates JSON-compatible data structures.
42-46: Good use ofz.looseObject()for Zod v4 migration.The
z.looseObject({})is the correct Zod v4 replacement for allowing unknown keys. The refine to excludecheckModeproperty is a clever pattern for the discriminated union dispatch.src/utils/validate/graphqlConfigSchema/graphqlConfigSchema.ts (2)
14-24: Shape spreading pattern is correct for Zod v4.Using
...baseRequestConfigSchema.shapeto compose strict objects is a valid pattern that maintains type safety while extending the base schema.
11-11: Simplified interceptors schema usage is cleaner.Removing the
plainObjectSchemawrapper and usinginterceptorsSchema.optional()directly is a good simplification that aligns with the broader refactoring in this PR.Also applies to: 35-35
src/utils/validate/restConfigSchema/routeConfigSchema/routeConfigSchema.ts (1)
73-78:oneKeyDispatchSchemacorrectly replaces the union-based dispatch.The migration to
oneKeyDispatchSchemafor routing based ondata,file, orqueuekeys is a good improvement that provides clearer error messages when validation fails, addressing the issue described in the comment block above.src/utils/validate/graphqlConfigSchema/routeConfigSchema/routeConfigSchema.ts (1)
8-18: Clean schema composition using shape spreading.The consistent use of
...baseRouteConfigSchema.shapefor schema composition and theoneKeyDispatchSchemafor route type dispatch aligns well with the patterns established in the REST config schema. The structure is maintainable and type-safe.Also applies to: 27-37
src/utils/validate/databaseConfigSchema/databaseConfigSchema.ts (1)
5-9: Correct Zod v4 migration for record schemas.The removal of
plainObjectSchemawrapper and direct use ofz.record(z.string(), z.unknown())is the proper Zod v4 approach. UsingstringForwardSlashSchema(a template literal schema) as both key and value type for routes is valid and provides strong validation that both keys and values start with/.
| message=$(cat "$1") | ||
|
|
||
| if echo "$message" | (! grep -qF "${branch} ${emoji}") &&\ | ||
| echo "$message" | (! grep -qF "Merge branch") &&\ | ||
| echo "$message" | (! grep -qF "Merge remote-tracking branch");\ | ||
| then | ||
| echo "$branch $emoji $(cat "$1")" > "$1" |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Inconsistent use of $message variable.
Line 3 reads the commit message into $message, but line 9 re-reads the file with $(cat "$1") instead of using the already-populated variable. Use $message consistently to avoid redundant I/O.
🔎 Proposed fix
- echo "$branch $emoji $(cat "$1")" > "$1"
+ echo "$branch $emoji $message" > "$1"🤖 Prompt for AI Agents
.husky/commit-msg around lines 3 to 9: the script reads the commit message into
the variable $message on line 3 but then re-reads the file with $(cat "$1") when
writing back on line 9; change that write to use the already-populated $message
variable (i.e., echo "$branch $emoji $message" > "$1") to avoid redundant file
I/O and ensure consistent use of the variable.
| if echo "$message" | (! grep -qF "${branch} ${emoji}") &&\ | ||
| echo "$message" | (! grep -qF "Merge branch") &&\ | ||
| echo "$message" | (! grep -qF "Merge remote-tracking branch");\ |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Unnecessary subshells around negation.
The parentheses in (! grep -qF ...) create subshells that aren't needed. The negation can be applied directly to grep.
🔎 Proposed fix
-if echo "$message" | (! grep -qF "${branch} ${emoji}") &&\
- echo "$message" | (! grep -qF "Merge branch") &&\
- echo "$message" | (! grep -qF "Merge remote-tracking branch");\
+if ! echo "$message" | grep -qF "${branch} ${emoji}" &&\
+ ! echo "$message" | grep -qF "Merge branch" &&\
+ ! echo "$message" | grep -qF "Merge remote-tracking branch";\
then📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if echo "$message" | (! grep -qF "${branch} ${emoji}") &&\ | |
| echo "$message" | (! grep -qF "Merge branch") &&\ | |
| echo "$message" | (! grep -qF "Merge remote-tracking branch");\ | |
| if ! echo "$message" | grep -qF "${branch} ${emoji}" &&\ | |
| ! echo "$message" | grep -qF "Merge branch" &&\ | |
| ! echo "$message" | grep -qF "Merge remote-tracking branch";\ | |
| then |
🤖 Prompt for AI Agents
.husky/commit-msg lines 5-7: the code uses subshell negation like '(! grep -qF
"...")' which is unnecessary; remove the parentheses so the negation is applied
directly to grep (replace each '(! grep -qF "…")' with '! grep -qF "…"') and
keep the existing pipe and line-continuation/backslash structure unchanged.
| type: 'string', | ||
| coerce(value: any) { | ||
| try { | ||
| return baseUrlSchema.parse(value); | ||
| } catch { | ||
| throw new Error(`Invalid value for 'baseUrl': ${JSON.stringify(value)}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for validation message utilities
rg -n --type=ts 'getValidationMessage|validationMessage|formatValidationError' --max-count=20Repository: siberiacancode/mock-config
Length of output: 3924
🏁 Script executed:
#!/bin/bash
# Look for files in src/utils directory
find . -path ./node_modules -prune -o -type f -name "*.ts" -print | grep -E '(utils|validate)' | head -20Repository: siberiacancode/mock-config
Length of output: 1159
🏁 Script executed:
#!/bin/bash
# Examine the full context of bin/cli.ts around the mentioned lines
cat -n bin/cli.ts | sed -n '1,60p'Repository: siberiacancode/mock-config
Length of output: 2011
🏁 Script executed:
#!/bin/bash
# Search for Zod error handling utilities or patterns
rg -n --type=ts 'treeifyError|z\.ZodError|formatZodError' --max-count=20Repository: siberiacancode/mock-config
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Check for any validation-related utilities in src directory
find . -path ./node_modules -prune -o -type f -name "*validat*" -printRepository: siberiacancode/mock-config
Length of output: 223
🏁 Script executed:
#!/bin/bash
# Examine the getValidationMessage implementation
cat -n src/utils/validate/getValidationMessage.tsRepository: siberiacancode/mock-config
Length of output: 2020
🏁 Script executed:
#!/bin/bash
# Look at how getValidationMessage is used in the validation functions
cat -n src/utils/validate/validateMockServerConfig.ts | sed -n '35,50p'Repository: siberiacancode/mock-config
Length of output: 615
🏁 Script executed:
#!/bin/bash
# Check what information is available in Zod errors
cat -n src/utils/validate/getValidationMessage.ts | head -20Repository: siberiacancode/mock-config
Length of output: 1100
🏁 Script executed:
#!/bin/bash
# Look at the validation schemas to understand error types
rg -n --type=ts 'baseUrlSchema|portSchema|staticPathSchema' -A 3 'src/utils/validate'Repository: siberiacancode/mock-config
Length of output: 8559
🏁 Script executed:
#!/bin/bash
# Check if there are any other error formatting patterns in the codebase
rg -n --type=ts 'issue\.message|issue\.code' src/utils/validate/ | head -20Repository: siberiacancode/mock-config
Length of output: 249
🏁 Script executed:
#!/bin/bash
# Check the actual schema definitions to understand what errors they can produce
cat -n src/utils/validate/portSchema/portSchema.tsRepository: siberiacancode/mock-config
Length of output: 219
🏁 Script executed:
#!/bin/bash
# Check baseUrlSchema
cat -n src/utils/validate/baseUrlSchema/baseUrlSchema.tsRepository: siberiacancode/mock-config
Length of output: 199
🏁 Script executed:
#!/bin/bash
# Look at the actual Zod error structure - check if issue.message is available
rg -n --type=ts 'issue\.message|z\.ZodIssue' src/utils/validate/getValidationMessage.ts -A 2Repository: siberiacancode/mock-config
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Check if there's a utility that extracts detailed error messages from Zod
rg -n --type=ts 'issue\[' src/utils/validate/ | head -10Repository: siberiacancode/mock-config
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Check the stringForwardSlashSchema to understand the full baseUrlSchema
rg -n --type=ts 'stringForwardSlashSchema' src/utils/validate/utils/ -A 5Repository: siberiacancode/mock-config
Length of output: 398
🏁 Script executed:
#!/bin/bash
# Look for test files that might show what errors these schemas produce
find src/utils/validate -name "*.test.ts" | head -5 | xargs grep -l "baseUrlSchema\|portSchema" 2>/dev/nullRepository: siberiacancode/mock-config
Length of output: 54
🏁 Script executed:
#!/bin/bash
# Check if Zod error object includes message property by looking at test expectations
rg -n --type=ts 'error\.message|issue\.message' src/utils/validate/Repository: siberiacancode/mock-config
Length of output: 52
🏁 Script executed:
#!/bin/bash
# Check what properties a Zod error issue has by examining how it's used
rg -n --type=ts 'issue\.' src/utils/validate/getValidationMessage.tsRepository: siberiacancode/mock-config
Length of output: 506
🏁 Script executed:
#!/bin/bash
# Try to understand better by looking at a test that uses getValidationMessage with actual errors
cat -n src/utils/validate/utils/entitiesSchema/entitiesSchema.test.tsRepository: siberiacancode/mock-config
Length of output: 1410
Use the existing getValidationMessage utility and extend error details from Zod validation failures.
The catch blocks discard Zod's validation error information and replace it with a generic message containing only the stringified value. This prevents users from understanding why a value is invalid.
The codebase already has a consolidated getValidationMessage utility in src/utils/validate/ that extracts validation paths, but it currently returns only the error location (e.g., .value), not the failure reason. A TODO comment in getValidationMessage.ts acknowledges this: "improve validation message with more detailed description as types and link to the documentation."
At minimum, use getValidationMessage to add the error path. Ideally, extend the error handling to include Zod's error code and message (available via issue.code and issue.message), so users see meaningful feedback like: Invalid value for 'port': -1 - Must be a non-negative integer instead of just Invalid value for 'port': -1.
Additional note on port coercion (line 34): The +value conversion is potentially redundant since yargs handles type: 'number', though this may be intentional to handle edge cases before schema validation.
Also applies to: 31-38, 43-50
| const { configs } = mergedApiMockServerConfig; | ||
| const [firstConfig] = Array.isArray(configs) ? configs : []; | ||
|
|
||
| if (isPlainObject(firstConfig) && 'path' in firstConfig) { | ||
| validateApiMockServerConfig(mockConfig, 'rest'); | ||
| return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig); | ||
| } | ||
|
|
||
| if ( | ||
| Array.isArray(mergedApiMockServerConfig.configs) && | ||
| isPlainObject(mergedApiMockServerConfig.configs[0]) && | ||
| ('query' in mergedApiMockServerConfig.configs[0] || | ||
| 'operationName' in mergedApiMockServerConfig.configs[0]) | ||
| isPlainObject(firstConfig) && | ||
| ('query' in firstConfig || 'operationName' in firstConfig) | ||
| ) { | ||
| validateApiMockServerConfig(mockConfig, 'graphql'); | ||
| return startGraphQLMockServer(mergedApiMockServerConfig as GraphQLMockServerConfig); | ||
| } | ||
|
|
||
| validateApiMockServerConfig(mockConfig, 'rest'); | ||
| return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig); | ||
| } | ||
|
|
||
| validateMockServerConfig(mockConfig); | ||
| return startMockServer(mergedMockServerConfig); |
There was a problem hiding this comment.
Critical: Validate the merged config, not the original.
The validation calls on lines 38, 46, 50, and 54 validate mockConfig (the original config from the file), but the servers are started with mergedApiMockServerConfig or mergedMockServerConfig (which include CLI argument overrides for baseUrl, port, and staticPath).
This means CLI-provided values bypass validation, potentially allowing invalid configurations to start the server.
🔎 Proposed fix to validate merged configs
if (isPlainObject(firstConfig) && 'path' in firstConfig) {
- validateApiMockServerConfig(mockConfig, 'rest');
+ validateApiMockServerConfig(mergedApiMockServerConfig, 'rest');
return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig);
}
if (
isPlainObject(firstConfig) &&
('query' in firstConfig || 'operationName' in firstConfig)
) {
- validateApiMockServerConfig(mockConfig, 'graphql');
+ validateApiMockServerConfig(mergedApiMockServerConfig, 'graphql');
return startGraphQLMockServer(mergedApiMockServerConfig as GraphQLMockServerConfig);
}
- validateApiMockServerConfig(mockConfig, 'rest');
+ validateApiMockServerConfig(mergedApiMockServerConfig, 'rest');
return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig);
}
- validateMockServerConfig(mockConfig);
+ validateMockServerConfig(mergedMockServerConfig);
return startMockServer(mergedMockServerConfig);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { configs } = mergedApiMockServerConfig; | |
| const [firstConfig] = Array.isArray(configs) ? configs : []; | |
| if (isPlainObject(firstConfig) && 'path' in firstConfig) { | |
| validateApiMockServerConfig(mockConfig, 'rest'); | |
| return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig); | |
| } | |
| if ( | |
| Array.isArray(mergedApiMockServerConfig.configs) && | |
| isPlainObject(mergedApiMockServerConfig.configs[0]) && | |
| ('query' in mergedApiMockServerConfig.configs[0] || | |
| 'operationName' in mergedApiMockServerConfig.configs[0]) | |
| isPlainObject(firstConfig) && | |
| ('query' in firstConfig || 'operationName' in firstConfig) | |
| ) { | |
| validateApiMockServerConfig(mockConfig, 'graphql'); | |
| return startGraphQLMockServer(mergedApiMockServerConfig as GraphQLMockServerConfig); | |
| } | |
| validateApiMockServerConfig(mockConfig, 'rest'); | |
| return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig); | |
| } | |
| validateMockServerConfig(mockConfig); | |
| return startMockServer(mergedMockServerConfig); | |
| const { configs } = mergedApiMockServerConfig; | |
| const [firstConfig] = Array.isArray(configs) ? configs : []; | |
| if (isPlainObject(firstConfig) && 'path' in firstConfig) { | |
| validateApiMockServerConfig(mergedApiMockServerConfig, 'rest'); | |
| return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig); | |
| } | |
| if ( | |
| isPlainObject(firstConfig) && | |
| ('query' in firstConfig || 'operationName' in firstConfig) | |
| ) { | |
| validateApiMockServerConfig(mergedApiMockServerConfig, 'graphql'); | |
| return startGraphQLMockServer(mergedApiMockServerConfig as GraphQLMockServerConfig); | |
| } | |
| validateApiMockServerConfig(mergedApiMockServerConfig, 'rest'); | |
| return startRestMockServer(mergedApiMockServerConfig as RestMockServerConfig); | |
| } | |
| validateMockServerConfig(mergedMockServerConfig); | |
| return startMockServer(mergedMockServerConfig); |
🤖 Prompt for AI Agents
In bin/run.ts around lines 34 to 55, the validation calls currently pass the
original mockConfig while the server is started with
mergedApiMockServerConfig/mergedMockServerConfig (which include CLI overrides);
update the four validate calls to validate the merged objects instead:
validateApiMockServerConfig(mergedApiMockServerConfig, 'rest'),
validateApiMockServerConfig(mergedApiMockServerConfig, 'graphql'), the fallback
validateApiMockServerConfig(mergedApiMockServerConfig, 'rest'), and finally
validateMockServerConfig(mergedMockServerConfig) so CLI-provided values are
validated before starting the servers.
| // TODO: | ||
| // this type doesn't corresponds to validation schema | ||
| configs: Array<GraphQLRequestConfig | RestRequestConfig>; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Document the type/schema mismatch specifics.
The TODO comment flags a mismatch between this TypeScript type and the validation schema. For better maintainability, consider expanding the comment to clarify:
- What specific aspect doesn't correspond?
- What is the actual schema shape?
- Is there a tracking issue for resolving this mismatch?
🤖 Prompt for AI Agents
In src/utils/types/server.ts around lines 94 to 96, the TODO comment about the
type/schema mismatch is too vague; update it to document exactly which
properties on GraphQLRequestConfig | RestRequestConfig don’t match the
validation schema (list differing field names/types), paste or summarize the
actual validation schema shape (required fields, optional fields, field types),
and add a tracking reference (issue or PR link/ID) and owner to resolve it; then
indicate next steps (either update the TS type to match schema or update schema
to match types) so future maintainers know how to fix it.
src/utils/validate/restConfigSchema/routeConfigSchema/routeConfigSchema.ts
Show resolved
Hide resolved
src/utils/validate/restConfigSchema/routeConfigSchema/routeConfigSchema.ts
Outdated
Show resolved
Hide resolved
| COMPARE_WITH_DESCRIPTOR_VALUE_CHECK_MODES | ||
| ); | ||
|
|
||
| // TODO why this fn in that file? |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
TODO comment flags architectural concern.
The TODO questions the placement of the entityDescriptorSchema function in this file. This is worth addressing to improve code organization.
Would you like me to analyze the function's dependencies and suggest a better location, or open an issue to track this refactoring?
🤖 Prompt for AI Agents
In src/utils/validate/utils/checkModeSchema/checkModeSchema.ts around line 24
there is a TODO questioning why entityDescriptorSchema is defined in this file;
analyze the function's dependencies and either relocate it to a more appropriate
module (e.g., a central schemas or validators folder such as src/schemas or
src/utils/validate/schemas) or open a tracked issue for the refactor. To fix:
inspect the function's imports/exports and usages across the codebase, create a
new file for the schema in the chosen location, move the function there and
export it, update all import sites to reference the new path, remove the TODO,
and add/update a small unit test or one-line comment pointing to the new
location so the architecture intent is clear.
| export const oneKeyDispatchSchema = <S extends Record<string, z.ZodTypeAny>>(schemasByKey: S) => { | ||
| const keys = Object.keys(schemasByKey); | ||
|
|
||
| return z | ||
| .custom((value) => isPlainObject(value)) | ||
| .superRefine((value, ctx) => { | ||
| const existingKeys = keys.filter((key) => Object.prototype.hasOwnProperty.call(value, key)); | ||
|
|
||
| if (existingKeys.length !== 1) { | ||
| ctx.addIssue({ | ||
| code: 'custom', | ||
| path: [] | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const [key] = existingKeys; | ||
| const schema = schemasByKey[key]; | ||
|
|
||
| const result = schema.safeParse(value); | ||
| if (!result.success) { | ||
| const issuePath = getMostSpecificPathFromError(result.error.issues); | ||
| ctx.addIssue({ | ||
| code: 'custom', | ||
| path: issuePath | ||
| }); | ||
| } | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Add descriptive error messages to custom validation issues.
The oneKeyDispatchSchema implementation correctly enforces single-key dispatch validation, but the custom issues lack descriptive messages:
- Lines 18-22: When
existingKeys.length !== 1, the issue provides no message explaining whether zero keys or multiple keys were found. - Lines 31-34: When schema validation fails, the original error details are discarded and replaced with a generic custom issue.
Without messages, debugging validation failures will be challenging for users.
🔎 Proposed fix to add descriptive error messages
export const oneKeyDispatchSchema = <S extends Record<string, z.ZodTypeAny>>(schemasByKey: S) => {
const keys = Object.keys(schemasByKey);
return z
.custom((value) => isPlainObject(value))
.superRefine((value, ctx) => {
const existingKeys = keys.filter((key) => Object.prototype.hasOwnProperty.call(value, key));
if (existingKeys.length !== 1) {
ctx.addIssue({
code: 'custom',
- path: []
+ path: [],
+ message:
+ existingKeys.length === 0
+ ? `Expected exactly one of [${keys.join(', ')}], but none were provided`
+ : `Expected exactly one of [${keys.join(', ')}], but found multiple: [${existingKeys.join(', ')}]`
});
return;
}
const [key] = existingKeys;
const schema = schemasByKey[key];
const result = schema.safeParse(value);
if (!result.success) {
const issuePath = getMostSpecificPathFromError(result.error.issues);
ctx.addIssue({
code: 'custom',
- path: issuePath
+ path: issuePath,
+ message: result.error.issues[0]?.message || 'Validation failed'
});
}
});
};🤖 Prompt for AI Agents
In src/utils/validate/utils/oneKeyDispatchSchema/oneKeyDispatchSchema.ts around
lines 9 to 37, the custom validation issues are missing descriptive messages;
update the two ctx.addIssue calls so they include helpful, specific messages:
when existingKeys.length !== 1, set message to indicate whether zero keys or
multiple keys were found and list the keys present (e.g., "expected exactly one
key but found 0" or "expected exactly one key but found 2: keyA, keyB"); when
the delegated schema safeParse fails, preserve the original error information by
including the most specific error path and the original Zod error message(s) in
the ctx.addIssue (or convert result.error.issues into one or more ctx.addIssue
calls) so the user sees the underlying validation message(s) along with the
path.
Summary by CodeRabbit
Release Notes
New Features
Dependency Updates
Build & Development
✏️ Tip: You can customize this high-level summary in your review settings.