From 5cd814f54148955ca8c83adfc0f78a1127e8a42d Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Fri, 27 Mar 2026 14:47:48 +0100 Subject: [PATCH] refactor(backend-defaults): migrate internal Zod usage from v3 to v4 The auditor's severity log level mappings previously used a `zod/v3` `z.record()` schema with manual fallbacks for defaults and relied on casting into Zod error internals (`.received`, `.options`) that changed between v3 and v4. This replaces it with a `z.object()` schema using `.default()` so that Zod owns the default values and type inference, and derives the valid values and received input without reaching into undocumented error properties. This does not migrate all `zod/v3` imports in the package, as the remaining usages are tied to public API types (e.g. `AnyZodObject` from `@backstage/backend-plugin-api`). Signed-off-by: Jon Koops --- .changeset/auditor-zod-v4-refactor.md | 5 ++ .../src/entrypoints/auditor/types.ts | 26 -------- .../src/entrypoints/auditor/utils.ts | 63 ++++++++++--------- .../src/entrypoints/scheduler/lib/types.ts | 2 +- 4 files changed, 39 insertions(+), 57 deletions(-) create mode 100644 .changeset/auditor-zod-v4-refactor.md delete mode 100644 packages/backend-defaults/src/entrypoints/auditor/types.ts diff --git a/.changeset/auditor-zod-v4-refactor.md b/.changeset/auditor-zod-v4-refactor.md new file mode 100644 index 0000000000..c1ac7ce0aa --- /dev/null +++ b/.changeset/auditor-zod-v4-refactor.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference. diff --git a/packages/backend-defaults/src/entrypoints/auditor/types.ts b/packages/backend-defaults/src/entrypoints/auditor/types.ts deleted file mode 100644 index 49a6de75ea..0000000000 --- a/packages/backend-defaults/src/entrypoints/auditor/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { z } from 'zod/v3'; - -/** @internal */ -export const severityLogLevelMappingsSchema = z.record( - z.enum(['low', 'medium', 'high', 'critical']), - z.enum(['debug', 'info', 'warn', 'error']), -); - -/** @internal */ -export const CONFIG_ROOT_KEY = 'backend.auditor'; diff --git a/packages/backend-defaults/src/entrypoints/auditor/utils.ts b/packages/backend-defaults/src/entrypoints/auditor/utils.ts index bf1e1f4756..0513aa84b2 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/utils.ts +++ b/packages/backend-defaults/src/entrypoints/auditor/utils.ts @@ -16,8 +16,20 @@ import type { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import { z } from 'zod/v3'; -import { CONFIG_ROOT_KEY, severityLogLevelMappingsSchema } from './types'; +import { z } from 'zod/v4'; + +const CONFIG_ROOT_KEY = 'backend.auditor'; + +const logLevel = z.enum(['debug', 'info', 'warn', 'error']); + +const severityLogLevelMappingsSchema = z.object({ + low: logLevel.default('debug'), + medium: logLevel.default('info'), + high: logLevel.default('info'), + critical: logLevel.default('info'), +}); + +type SeverityLogLevelMappings = z.infer; /** * Gets the `backend.auditor.severityLogLevelMappings` configuration. @@ -26,41 +38,32 @@ import { CONFIG_ROOT_KEY, severityLogLevelMappingsSchema } from './types'; * @returns The validated severity-to-log-level mappings. * @throws error - {@link @backstage/errors#InputError} if the mapping configuration is invalid. */ -export function getSeverityLogLevelMappings(config: Config) { +export function getSeverityLogLevelMappings( + config: Config, +): SeverityLogLevelMappings { const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY); - const severityLogLevelMappings = { - low: - auditorConfig?.getOptionalString('severityLogLevelMappings.low') ?? - 'debug', - medium: - auditorConfig?.getOptionalString('severityLogLevelMappings.medium') ?? - 'info', - high: - auditorConfig?.getOptionalString('severityLogLevelMappings.high') ?? - 'info', - critical: - auditorConfig?.getOptionalString('severityLogLevelMappings.critical') ?? - 'info', - } as Required>; + const input = { + low: auditorConfig?.getOptionalString('severityLogLevelMappings.low'), + medium: auditorConfig?.getOptionalString('severityLogLevelMappings.medium'), + high: auditorConfig?.getOptionalString('severityLogLevelMappings.high'), + critical: auditorConfig?.getOptionalString( + 'severityLogLevelMappings.critical', + ), + }; - const res = severityLogLevelMappingsSchema.safeParse( - severityLogLevelMappings, - ); - if (!res.success) { - const key = res.error.issues.at(0)?.path.at(0) as string; - const value = ( - res.error.issues.at(0) as unknown as Record - ).received as string; - const validKeys = ( - res.error.issues.at(0) as unknown as Record - ).options as string[]; + const parsed = severityLogLevelMappingsSchema.safeParse(input); + + if (!parsed.success) { + const issue = parsed.error.issues[0]; + const key = issue.path[0] as keyof typeof input; + const receivedValue = input[key]; throw new InputError( - `The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join( + `The configuration value for '${CONFIG_ROOT_KEY}.severityLogLevelMappings.${key}' was given an invalid value: '${receivedValue}'. Expected one of the following valid values: '${logLevel.options.join( ', ', )}'.`, ); } - return severityLogLevelMappings; + return parsed.data; } diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts index c7f77e7a85..64f7d00899 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts @@ -17,7 +17,7 @@ import { JsonObject } from '@backstage/types'; import { CronTime } from 'cron'; import { Duration } from 'luxon'; -import { z } from 'zod/v3'; +import { z } from 'zod/v4'; function isValidOptionalDurationString(d: string | undefined): boolean { try {