mirror of
https://github.com/withastro/astro.git
synced 2025-01-06 22:10:10 -05:00
feat: reuse error map in astro db
This commit is contained in:
parent
a59f15d60a
commit
a200928ac8
5 changed files with 13 additions and 115 deletions
|
@ -70,6 +70,7 @@
|
|||
"default": "./zod.mjs"
|
||||
},
|
||||
"./errors": "./dist/core/errors/userError.js",
|
||||
"./errors/zod-error-map": "./dist/core/errors/zod-error-map.js",
|
||||
"./middleware": {
|
||||
"types": "./dist/core/middleware/index.d.ts",
|
||||
"default": "./dist/core/middleware/index.js"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { ZodErrorMap } from 'zod';
|
||||
|
||||
type TypeOrLiteralErrByPathEntry = {
|
||||
interface TypeOrLiteralErrByPathEntry {
|
||||
code: 'invalid_type' | 'invalid_literal';
|
||||
received: unknown;
|
||||
expected: unknown[];
|
||||
|
@ -14,12 +14,13 @@ export const errorMap: ZodErrorMap = (baseError, ctx) => {
|
|||
// raise a single error when `key` does not match:
|
||||
// > Did not match union.
|
||||
// > key: Expected `'tutorial' | 'blog'`, received 'foo'
|
||||
let typeOrLiteralErrByPath = new Map<string, TypeOrLiteralErrByPathEntry>();
|
||||
for (const unionError of baseError.unionErrors.map((e) => e.errors).flat()) {
|
||||
const typeOrLiteralErrByPath = new Map<string, TypeOrLiteralErrByPathEntry>();
|
||||
for (const unionError of baseError.unionErrors.flatMap((e) => e.errors)) {
|
||||
if (unionError.code === 'invalid_type' || unionError.code === 'invalid_literal') {
|
||||
const flattenedErrorPath = flattenErrorPath(unionError.path);
|
||||
if (typeOrLiteralErrByPath.has(flattenedErrorPath)) {
|
||||
typeOrLiteralErrByPath.get(flattenedErrorPath)!.expected.push(unionError.expected);
|
||||
const typeOrLiteralErr = typeOrLiteralErrByPath.get(flattenedErrorPath);
|
||||
if (typeOrLiteralErr) {
|
||||
typeOrLiteralErr.expected.push(unionError.expected);
|
||||
} else {
|
||||
typeOrLiteralErrByPath.set(flattenedErrorPath, {
|
||||
code: unionError.code,
|
||||
|
@ -29,7 +30,7 @@ export const errorMap: ZodErrorMap = (baseError, ctx) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
let messages: string[] = [
|
||||
const messages: string[] = [
|
||||
prefix(
|
||||
baseErrorPath,
|
||||
typeOrLiteralErrByPath.size ? 'Did not match union:' : 'Did not match union.'
|
||||
|
@ -43,9 +44,9 @@ export const errorMap: ZodErrorMap = (baseError, ctx) => {
|
|||
// filter it out. Can lead to confusing noise.
|
||||
.filter(([, error]) => error.expected.length === baseError.unionErrors.length)
|
||||
.map(([key, error]) =>
|
||||
// Avoid printing the key again if it's a base error
|
||||
key === baseErrorPath
|
||||
? // Avoid printing the key again if it's a base error
|
||||
`> ${getTypeOrLiteralMsg(error)}`
|
||||
? `> ${getTypeOrLiteralMsg(error)}`
|
||||
: `> ${prefix(key, getTypeOrLiteralMsg(error))}`
|
||||
)
|
||||
)
|
||||
|
@ -96,4 +97,4 @@ const unionExpectedVals = (expectedVals: Set<unknown>) =>
|
|||
})
|
||||
.join('');
|
||||
|
||||
const flattenErrorPath = (errorPath: (string | number)[]) => errorPath.join('.');
|
||||
const flattenErrorPath = (errorPath: Array<string | number>) => errorPath.join('.');
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
/**
|
||||
* This is a modified version of Astro's error map. source:
|
||||
* https://github.com/withastro/astro/blob/main/packages/astro/src/content/error-map.ts
|
||||
*/
|
||||
import type { z } from 'astro/zod';
|
||||
|
||||
interface TypeOrLiteralErrByPathEntry {
|
||||
code: 'invalid_type' | 'invalid_literal';
|
||||
received: unknown;
|
||||
expected: unknown[];
|
||||
}
|
||||
|
||||
export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
|
||||
const baseErrorPath = flattenErrorPath(baseError.path);
|
||||
if (baseError.code === 'invalid_union') {
|
||||
// Optimization: Combine type and literal errors for keys that are common across ALL union types
|
||||
// Ex. a union between `{ key: z.literal('tutorial') }` and `{ key: z.literal('blog') }` will
|
||||
// raise a single error when `key` does not match:
|
||||
// > Did not match union.
|
||||
// > key: Expected `'tutorial' | 'blog'`, received 'foo'
|
||||
const typeOrLiteralErrByPath = new Map<string, TypeOrLiteralErrByPathEntry>();
|
||||
for (const unionError of baseError.unionErrors.flatMap((e) => e.errors)) {
|
||||
if (unionError.code === 'invalid_type' || unionError.code === 'invalid_literal') {
|
||||
const flattenedErrorPath = flattenErrorPath(unionError.path);
|
||||
const typeOrLiteralErr = typeOrLiteralErrByPath.get(flattenedErrorPath);
|
||||
if (typeOrLiteralErr) {
|
||||
typeOrLiteralErr.expected.push(unionError.expected);
|
||||
} else {
|
||||
typeOrLiteralErrByPath.set(flattenedErrorPath, {
|
||||
code: unionError.code,
|
||||
received: (unionError as any).received,
|
||||
expected: [unionError.expected],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const messages: string[] = [
|
||||
prefix(
|
||||
baseErrorPath,
|
||||
typeOrLiteralErrByPath.size ? 'Did not match union:' : 'Did not match union.'
|
||||
),
|
||||
];
|
||||
return {
|
||||
message: messages
|
||||
.concat(
|
||||
[...typeOrLiteralErrByPath.entries()]
|
||||
// If type or literal error isn't common to ALL union types,
|
||||
// filter it out. Can lead to confusing noise.
|
||||
.filter(([, error]) => error.expected.length === baseError.unionErrors.length)
|
||||
.map(([key, error]) =>
|
||||
// Avoid printing the key again if it's a base error
|
||||
key === baseErrorPath
|
||||
? `> ${getTypeOrLiteralMsg(error)}`
|
||||
: `> ${prefix(key, getTypeOrLiteralMsg(error))}`
|
||||
)
|
||||
)
|
||||
.join('\n'),
|
||||
};
|
||||
}
|
||||
if (baseError.code === 'invalid_literal' || baseError.code === 'invalid_type') {
|
||||
return {
|
||||
message: prefix(
|
||||
baseErrorPath,
|
||||
getTypeOrLiteralMsg({
|
||||
code: baseError.code,
|
||||
received: (baseError as any).received,
|
||||
expected: [baseError.expected],
|
||||
})
|
||||
),
|
||||
};
|
||||
} else if (baseError.message) {
|
||||
return { message: prefix(baseErrorPath, baseError.message) };
|
||||
} else {
|
||||
return { message: prefix(baseErrorPath, ctx.defaultError) };
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeOrLiteralMsg = (error: TypeOrLiteralErrByPathEntry): string => {
|
||||
if (error.received === 'undefined') return 'Required';
|
||||
const expectedDeduped = new Set(error.expected);
|
||||
switch (error.code) {
|
||||
case 'invalid_type':
|
||||
return `Expected type \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
|
||||
error.received
|
||||
)}`;
|
||||
case 'invalid_literal':
|
||||
return `Expected \`${unionExpectedVals(expectedDeduped)}\`, received ${JSON.stringify(
|
||||
error.received
|
||||
)}`;
|
||||
}
|
||||
};
|
||||
|
||||
const prefix = (key: string, msg: string) => (key.length ? `**${key}**: ${msg}` : msg);
|
||||
|
||||
const unionExpectedVals = (expectedVals: Set<unknown>) =>
|
||||
[...expectedVals]
|
||||
.map((expectedVal, idx) => {
|
||||
if (idx === 0) return JSON.stringify(expectedVal);
|
||||
const sep = ' | ';
|
||||
return `${sep}${JSON.stringify(expectedVal)}`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
const flattenErrorPath = (errorPath: Array<string | number>) => errorPath.join('.');
|
|
@ -6,7 +6,7 @@ import type { AstroConfig, AstroIntegration } from 'astro';
|
|||
import { build as esbuild } from 'esbuild';
|
||||
import { CONFIG_FILE_NAMES, VIRTUAL_MODULE_ID } from './consts.js';
|
||||
import { INTEGRATION_TABLE_CONFLICT_ERROR } from './errors.js';
|
||||
import { errorMap } from './integration/error-map.js';
|
||||
import { errorMap } from 'astro/errors/zod-error-map';
|
||||
import { getConfigVirtualModContents } from './integration/vite-plugin-db.js';
|
||||
import { dbConfigSchema } from './schemas.js';
|
||||
import { type AstroDbHooks } from './types.js';
|
||||
|
|
|
@ -2,7 +2,7 @@ import { SQL } from 'drizzle-orm';
|
|||
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
|
||||
import { type ZodTypeDef, z } from 'zod';
|
||||
import { SERIALIZED_SQL_KEY, type SerializedSQL } from '../runtime/types.js';
|
||||
import { errorMap } from './integration/error-map.js';
|
||||
import { errorMap } from 'astro/errors/zod-error-map';
|
||||
import type { NumberColumn, TextColumn } from './types.js';
|
||||
import { mapObject } from './utils.js';
|
||||
|
||||
|
|
Loading…
Reference in a new issue