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 <jonkoops@gmail.com>
This commit is contained in:
Jon Koops
2026-03-27 14:47:48 +01:00
parent 96882bbfb1
commit 5cd814f541
4 changed files with 39 additions and 57 deletions
+5
View File
@@ -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.
@@ -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';
@@ -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<typeof severityLogLevelMappingsSchema>;
/**
* 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<z.infer<typeof severityLogLevelMappingsSchema>>;
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<PropertyKey, unknown>
).received as string;
const validKeys = (
res.error.issues.at(0) as unknown as Record<PropertyKey, unknown>
).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;
}
@@ -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 {