refactor progress
This commit is contained in:
52
packages/validation/src/validator/ValidatorErrorFactory.tsx
Normal file
52
packages/validation/src/validator/ValidatorErrorFactory.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
InputValidationError,
|
||||
type LocalizedValidationError,
|
||||
} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import type {ValidationError} from '@fluxer/errors/src/domains/core/ValidationError';
|
||||
import {resolveValidationErrorCode} from '@fluxer/validation/src/shared/ValidationErrorCodeUtils';
|
||||
import {extractValidatorIssueVariables} from '@fluxer/validation/src/validator/ValidatorIssueVariables';
|
||||
import type {ZodError} from 'zod';
|
||||
|
||||
type ZodValidationIssue = ZodError['issues'][number];
|
||||
|
||||
function getIssuePath(issue: ZodValidationIssue): string {
|
||||
if (issue.path.length === 0) {
|
||||
return 'root';
|
||||
}
|
||||
return issue.path.join('.');
|
||||
}
|
||||
|
||||
export function createInputValidationError(issues: Array<ZodValidationIssue>): InputValidationError {
|
||||
const errors: Array<ValidationError> = [];
|
||||
const localizedErrors: Array<LocalizedValidationError> = [];
|
||||
|
||||
for (const issue of issues) {
|
||||
const path = getIssuePath(issue);
|
||||
const code = resolveValidationErrorCode(issue.message);
|
||||
const variables = extractValidatorIssueVariables(issue);
|
||||
|
||||
errors.push({path, message: code, code});
|
||||
localizedErrors.push({path, code, variables});
|
||||
}
|
||||
|
||||
return new InputValidationError(errors, localizedErrors);
|
||||
}
|
||||
109
packages/validation/src/validator/ValidatorIssueVariables.tsx
Normal file
109
packages/validation/src/validator/ValidatorIssueVariables.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {ZodError} from 'zod';
|
||||
|
||||
interface ZodTooSmallIssue {
|
||||
code: 'too_small';
|
||||
minimum: number | bigint;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface ZodTooBigIssue {
|
||||
code: 'too_big';
|
||||
maximum: number | bigint;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface ZodInvalidTypeIssue {
|
||||
code: 'invalid_type';
|
||||
expected: string;
|
||||
received: string;
|
||||
}
|
||||
|
||||
interface ZodCustomIssue {
|
||||
code: 'custom';
|
||||
params?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
type ZodValidationIssue = ZodError['issues'][number];
|
||||
|
||||
function getIssueFieldName(issue: ZodValidationIssue): string {
|
||||
if (issue.path.length === 0) {
|
||||
return 'field';
|
||||
}
|
||||
return String(issue.path[issue.path.length - 1]);
|
||||
}
|
||||
|
||||
function isTooSmallIssue(issue: ZodValidationIssue): issue is ZodValidationIssue & ZodTooSmallIssue {
|
||||
return issue.code === 'too_small' && 'minimum' in issue && 'type' in issue;
|
||||
}
|
||||
|
||||
function isTooBigIssue(issue: ZodValidationIssue): issue is ZodValidationIssue & ZodTooBigIssue {
|
||||
return issue.code === 'too_big' && 'maximum' in issue && 'type' in issue;
|
||||
}
|
||||
|
||||
function isInvalidTypeIssue(issue: ZodValidationIssue): issue is ZodValidationIssue & ZodInvalidTypeIssue {
|
||||
return issue.code === 'invalid_type' && 'expected' in issue && 'received' in issue;
|
||||
}
|
||||
|
||||
function isCustomIssue(issue: ZodValidationIssue): issue is ZodValidationIssue & ZodCustomIssue {
|
||||
return issue.code === 'custom';
|
||||
}
|
||||
|
||||
export function extractValidatorIssueVariables(issue: ZodValidationIssue): Record<string, unknown> | undefined {
|
||||
const fieldName = getIssueFieldName(issue);
|
||||
|
||||
if (isTooSmallIssue(issue)) {
|
||||
return {
|
||||
name: fieldName,
|
||||
min: issue.minimum,
|
||||
minValue: issue.minimum,
|
||||
minimum: issue.minimum,
|
||||
type: issue.type,
|
||||
};
|
||||
}
|
||||
|
||||
if (isTooBigIssue(issue)) {
|
||||
return {
|
||||
name: fieldName,
|
||||
max: issue.maximum,
|
||||
maxValue: issue.maximum,
|
||||
maximum: issue.maximum,
|
||||
type: issue.type,
|
||||
};
|
||||
}
|
||||
|
||||
if (isInvalidTypeIssue(issue)) {
|
||||
return {
|
||||
name: fieldName,
|
||||
expected: issue.expected,
|
||||
received: issue.received,
|
||||
};
|
||||
}
|
||||
|
||||
if (isCustomIssue(issue) && issue.params !== undefined && issue.params !== null) {
|
||||
return {
|
||||
name: fieldName,
|
||||
...issue.params,
|
||||
};
|
||||
}
|
||||
|
||||
return {name: fieldName};
|
||||
}
|
||||
92
packages/validation/src/validator/ValidatorRequestValue.tsx
Normal file
92
packages/validation/src/validator/ValidatorRequestValue.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {Context, Env, Input, ValidationTargets} from 'hono';
|
||||
import {getCookie} from 'hono/cookie';
|
||||
|
||||
type FormDataEntryValue = File | string;
|
||||
type FormValue = FormDataEntryValue | Array<FormDataEntryValue>;
|
||||
|
||||
async function extractJsonValue<E extends Env, P extends string, V extends Input>(
|
||||
c: Context<E, P, V>,
|
||||
): Promise<unknown> {
|
||||
try {
|
||||
return await c.req.json<unknown>();
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function extractFormValue<E extends Env, P extends string, V extends Input>(
|
||||
c: Context<E, P, V>,
|
||||
): Promise<unknown> {
|
||||
const formData = await c.req.formData();
|
||||
const form: Record<string, FormValue> = {};
|
||||
|
||||
formData.forEach((value, key) => {
|
||||
const existingValue = form[key];
|
||||
if (key.endsWith('[]')) {
|
||||
const list = Array.isArray(existingValue) ? existingValue : existingValue !== undefined ? [existingValue] : [];
|
||||
list.push(value);
|
||||
form[key] = list;
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(existingValue)) {
|
||||
existingValue.push(value);
|
||||
return;
|
||||
}
|
||||
if (existingValue !== undefined) {
|
||||
form[key] = [existingValue, value];
|
||||
return;
|
||||
}
|
||||
form[key] = value;
|
||||
});
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
function extractQueryValue<E extends Env, P extends string, V extends Input>(c: Context<E, P, V>): unknown {
|
||||
return Object.fromEntries(
|
||||
Object.entries(c.req.queries()).map(([key, values]) => (values.length === 1 ? [key, values[0]] : [key, values])),
|
||||
);
|
||||
}
|
||||
|
||||
export async function extractValidatorRequestValue<
|
||||
E extends Env,
|
||||
P extends string,
|
||||
V extends Input,
|
||||
Target extends keyof ValidationTargets,
|
||||
>(c: Context<E, P, V>, target: Target): Promise<unknown> {
|
||||
switch (target) {
|
||||
case 'json':
|
||||
return extractJsonValue(c);
|
||||
case 'form':
|
||||
return extractFormValue(c);
|
||||
case 'query':
|
||||
return extractQueryValue(c);
|
||||
case 'param':
|
||||
return c.req.param();
|
||||
case 'header':
|
||||
return c.req.header();
|
||||
case 'cookie':
|
||||
return getCookie(c);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
79
packages/validation/src/validator/ValidatorTypes.tsx
Normal file
79
packages/validation/src/validator/ValidatorTypes.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {Context, Env, Input, TypedResponse, ValidationTargets} from 'hono';
|
||||
import type {ZodError, ZodType} from 'zod';
|
||||
|
||||
type HasUndefined<T> = undefined extends T ? true : false;
|
||||
|
||||
export type ValidatorSafeParseResult<T extends ZodType> =
|
||||
| {success: true; data: T['_output']}
|
||||
| {success: false; error: ZodError<T['_input']>};
|
||||
|
||||
export type ValidatorPostHookResult<O = Record<string, unknown>> = Response | undefined | TypedResponse<O>;
|
||||
|
||||
export type ValidatorPostHook<
|
||||
T extends ZodType,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
Target extends keyof ValidationTargets = keyof ValidationTargets,
|
||||
V extends Input = Input,
|
||||
O = Record<string, unknown>,
|
||||
> = (
|
||||
result: ValidatorSafeParseResult<T> & {target: Target},
|
||||
c: Context<E, P, V>,
|
||||
) => ValidatorPostHookResult<O> | Promise<ValidatorPostHookResult<O>>;
|
||||
|
||||
export type ValidatorPreHook<
|
||||
E extends Env,
|
||||
P extends string,
|
||||
Target extends keyof ValidationTargets,
|
||||
V extends Input,
|
||||
> = (value: unknown, c: Context<E, P, V>, target: Target) => unknown | Promise<unknown>;
|
||||
|
||||
export interface ValidatorOptions<
|
||||
T extends ZodType,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
Target extends keyof ValidationTargets,
|
||||
V extends Input,
|
||||
> {
|
||||
pre?: ValidatorPreHook<E, P, Target, V>;
|
||||
post?: ValidatorPostHook<T, E, P, Target, V>;
|
||||
}
|
||||
|
||||
export type ValidatorHookOrOptions<
|
||||
T extends ZodType,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
Target extends keyof ValidationTargets,
|
||||
V extends Input,
|
||||
> = ValidatorPostHook<T, E, P, Target, V> | ValidatorOptions<T, E, P, Target, V>;
|
||||
|
||||
export type ValidatorInput<
|
||||
T extends ZodType,
|
||||
Target extends keyof ValidationTargets,
|
||||
In = T['_input'],
|
||||
Out = T['_output'],
|
||||
> = {
|
||||
in: HasUndefined<In> extends true
|
||||
? {[K in Target]?: In extends ValidationTargets[K] ? In : {[K2 in keyof In]?: ValidationTargets[K][K2]}}
|
||||
: {[K in Target]: In extends ValidationTargets[K] ? In : {[K2 in keyof In]: ValidationTargets[K][K2]}};
|
||||
out: {[K in Target]: Out};
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function isEmptyObject(value: object): boolean {
|
||||
return Object.keys(value).length === 0;
|
||||
}
|
||||
|
||||
export function normalizeValidatorValue(value: unknown, isRoot = true): unknown {
|
||||
if (typeof value === 'string' && value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => normalizeValidatorValue(item, false));
|
||||
}
|
||||
|
||||
if (value !== null && typeof value === 'object') {
|
||||
if (!isRoot && isEmptyObject(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const processedValue = Object.fromEntries(
|
||||
Object.entries(value).map(([key, nestedValue]) => [key, normalizeValidatorValue(nestedValue, false)]),
|
||||
);
|
||||
|
||||
if (!isRoot && Object.values(processedValue).every((nestedValue) => nestedValue === null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return processedValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
Reference in New Issue
Block a user