refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View 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);
}

View 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};
}

View 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 {};
}
}

View 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};
};

View File

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