0
Fork 0
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:
Florian Lefebvre 2024-04-29 19:20:02 +02:00
parent a59f15d60a
commit a200928ac8
5 changed files with 13 additions and 115 deletions

View file

@ -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"

View file

@ -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('.');

View file

@ -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('.');

View file

@ -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';

View file

@ -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';