Refactor and enhance ValidationException handling across types and components

- Simplify and extend type definitions in `types.ts` for dynamic and normalized keys.
- Update `ValidationExceptionInterface` to include new methods for filtering violations.
- Refactor `apiMethods.ts` to leverage updated exception types and key parsing.
- Adjust `WritePersonViolationMap` for stricter type definitions.
- Enhance `PersonEdit.vue` to use refined violation methods, improving validation error handling.
This commit is contained in:
2025-09-23 21:26:12 +02:00
parent a1fd395868
commit 34af53130b
5 changed files with 115 additions and 81 deletions

View File

@@ -1,4 +1,10 @@
import { Scope } from "../../types";
import {
DynamicKeys,
Scope,
ValidationExceptionInterface,
ValidationProblemFromMap,
ViolationFromMap
} from "../../types";
export type body = Record<string, boolean | string | number | null>;
export type fetchOption = Record<string, boolean | string | number | null>;
@@ -25,50 +31,10 @@ export interface TransportExceptionInterface {
name: string;
}
export type ViolationFromMap<
M extends Record<string, Record<string, unknown>>,
> = {
[K in Extract<keyof M, string>]: {
propertyPath: K;
title: string;
parameters?: M[K];
type?: string;
};
}[Extract<keyof M, string>];
export type ValidationProblemFromMap<
M extends Record<string, Record<string, unknown>>,
> = {
type: string;
title: string;
detail?: string;
violations: ViolationFromMap<M>[];
} & Record<string, unknown>;
export interface ValidationExceptionInterface<
M extends Record<string, Record<string, unknown>> = Record<
string,
Record<string, unknown>
>,
> extends Error {
name: "ValidationException";
/** Full server payload copy */
problems: ValidationProblemFromMap<M>;
/** A list of all violations, with property key */
violationsList: ViolationFromMap<M>[];
/** Compact list "Title: path" */
violations: string[];
/** Only titles */
titles: string[];
/** Only property paths */
propertyPaths: Extract<keyof M, string>[];
/** Indexing by property (useful for display by field) */
byProperty: Record<Extract<keyof M, string>, string[]>;
}
export class ValidationException<
M extends Record<string, Record<string, unknown>> = Record<
M extends Record<string, Record<string, string|number>> = Record<
string,
Record<string, unknown>
Record<string, string|number>
>,
>
extends Error
@@ -79,7 +45,7 @@ export class ValidationException<
public readonly violations: string[];
public readonly violationsList: ViolationFromMap<M>[];
public readonly titles: string[];
public readonly propertyPaths: Extract<keyof M, string>[];
public readonly propertyPaths: DynamicKeys<M> & string[];
public readonly byProperty: Record<Extract<keyof M, string>, string[]>;
constructor(problem: ValidationProblemFromMap<M>) {
@@ -98,11 +64,11 @@ export class ValidationException<
this.propertyPaths = problem.violations.map(
(v) => v.propertyPath,
) as Extract<keyof M, string>[];
) as DynamicKeys<M> & string[];
this.byProperty = problem.violations.reduce(
(acc, v) => {
const key = v.propertyPath as Extract<keyof M, string>;
const key = v.propertyPath.replace('/\[\d+\]$/', "") as Extract<keyof M, string>;
(acc[key] ||= []).push(v.title);
return acc;
},
@@ -113,13 +79,38 @@ export class ValidationException<
Error.captureStackTrace(this, ValidationException);
}
}
violationsByNormalizedProperty(property: Extract<keyof M, string>): ViolationFromMap<M>[] {
return this.violationsList.filter((v) => v.propertyPath.replace(/\[\d+\]$/, "") === property);
}
violationsByNormalizedPropertyAndParams<
P extends Extract<keyof M, string>,
K extends Extract<keyof M[P], string>
>(
property: P,
param: K,
param_value: M[P][K]
): ViolationFromMap<M>[]
{
const list = this.violationsByNormalizedProperty(property);
return list.filter(
(v): boolean =>
!!v.parameters &&
// `with_parameter in v.parameters` check indexing
param in v.parameters &&
// the cast is safe, because we have overloading that bind the types
(v.parameters as M[P])[param] === param_value
);
}
}
/**
* Check that the exception is a ValidationExceptionInterface
* @param x
*/
export function isValidationException<M extends Record<string, Record<string, unknown>>>(
export function isValidationException<M extends Record<string, Record<string, string|number>>>(
x: unknown,
): x is ValidationExceptionInterface<M> {
return (
@@ -315,9 +306,9 @@ export interface ConflictHttpExceptionInterface
export const makeFetch = async <
Input,
Output,
M extends Record<string, Record<string, unknown>> = Record<
M extends Record<string, Record<string, string|number>> = Record<
string,
Record<string, Primitive>
Record<string, string|number>
>,
>(
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",