mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-28 09:34:59 +00:00
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:
@@ -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 body = Record<string, boolean | string | number | null>;
|
||||||
export type fetchOption = Record<string, boolean | string | number | null>;
|
export type fetchOption = Record<string, boolean | string | number | null>;
|
||||||
@@ -25,50 +31,10 @@ export interface TransportExceptionInterface {
|
|||||||
name: string;
|
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<
|
export class ValidationException<
|
||||||
M extends Record<string, Record<string, unknown>> = Record<
|
M extends Record<string, Record<string, string|number>> = Record<
|
||||||
string,
|
string,
|
||||||
Record<string, unknown>
|
Record<string, string|number>
|
||||||
>,
|
>,
|
||||||
>
|
>
|
||||||
extends Error
|
extends Error
|
||||||
@@ -79,7 +45,7 @@ export class ValidationException<
|
|||||||
public readonly violations: string[];
|
public readonly violations: string[];
|
||||||
public readonly violationsList: ViolationFromMap<M>[];
|
public readonly violationsList: ViolationFromMap<M>[];
|
||||||
public readonly titles: string[];
|
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[]>;
|
public readonly byProperty: Record<Extract<keyof M, string>, string[]>;
|
||||||
|
|
||||||
constructor(problem: ValidationProblemFromMap<M>) {
|
constructor(problem: ValidationProblemFromMap<M>) {
|
||||||
@@ -98,11 +64,11 @@ export class ValidationException<
|
|||||||
|
|
||||||
this.propertyPaths = problem.violations.map(
|
this.propertyPaths = problem.violations.map(
|
||||||
(v) => v.propertyPath,
|
(v) => v.propertyPath,
|
||||||
) as Extract<keyof M, string>[];
|
) as DynamicKeys<M> & string[];
|
||||||
|
|
||||||
this.byProperty = problem.violations.reduce(
|
this.byProperty = problem.violations.reduce(
|
||||||
(acc, v) => {
|
(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);
|
(acc[key] ||= []).push(v.title);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
@@ -113,13 +79,38 @@ export class ValidationException<
|
|||||||
Error.captureStackTrace(this, 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
|
* Check that the exception is a ValidationExceptionInterface
|
||||||
* @param x
|
* @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: unknown,
|
||||||
): x is ValidationExceptionInterface<M> {
|
): x is ValidationExceptionInterface<M> {
|
||||||
return (
|
return (
|
||||||
@@ -315,9 +306,9 @@ export interface ConflictHttpExceptionInterface
|
|||||||
export const makeFetch = async <
|
export const makeFetch = async <
|
||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
M extends Record<string, Record<string, unknown>> = Record<
|
M extends Record<string, Record<string, string|number>> = Record<
|
||||||
string,
|
string,
|
||||||
Record<string, Primitive>
|
Record<string, string|number>
|
||||||
>,
|
>,
|
||||||
>(
|
>(
|
||||||
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",
|
method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE",
|
||||||
|
@@ -275,13 +275,63 @@ export interface TransportExceptionInterface {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ValidationExceptionInterface
|
type IndexedKey<Base extends string> = `${Base}[${number}]`;
|
||||||
extends TransportExceptionInterface {
|
type BaseKeys<M> = Extract<keyof M, string>;
|
||||||
|
|
||||||
|
export type DynamicKeys<M extends Record<string, Record<string, unknown>>> =
|
||||||
|
| BaseKeys<M>
|
||||||
|
| { [K in BaseKeys<M> as IndexedKey<K>]: K }[IndexedKey<BaseKeys<M>>];
|
||||||
|
|
||||||
|
type NormalizeKey<K extends string> = K extends `${infer B}[${number}]` ? B : K;
|
||||||
|
|
||||||
|
export type ViolationFromMap<M extends Record<string, Record<string, unknown>>> = {
|
||||||
|
[K in DynamicKeys<M> & string]: { // <- note le "& string" ici
|
||||||
|
propertyPath: K;
|
||||||
|
title: string;
|
||||||
|
parameters?: M[NormalizeKey<K>];
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
}[DynamicKeys<M> & string];
|
||||||
|
|
||||||
|
export type ValidationProblemFromMap<
|
||||||
|
M extends Record<string, Record<string, string|number>>,
|
||||||
|
> = {
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
detail?: string;
|
||||||
|
violations: ViolationFromMap<M>[];
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
|
||||||
|
export interface ValidationExceptionInterface<
|
||||||
|
M extends Record<string, Record<string, string|number>> = Record<
|
||||||
|
string,
|
||||||
|
Record<string, string|number>
|
||||||
|
>,
|
||||||
|
> extends Error {
|
||||||
name: "ValidationException";
|
name: "ValidationException";
|
||||||
error: object;
|
/** Full server payload copy */
|
||||||
|
problems: ValidationProblemFromMap<M>;
|
||||||
|
/** A list of all violations, with property key */
|
||||||
|
violationsList: ViolationFromMap<M>[];
|
||||||
|
/** Compact list "Title: path" */
|
||||||
violations: string[];
|
violations: string[];
|
||||||
|
/** Only titles */
|
||||||
titles: string[];
|
titles: string[];
|
||||||
propertyPaths: string[];
|
/** Only property paths */
|
||||||
|
propertyPaths: DynamicKeys<M> & string[];
|
||||||
|
/** Indexing by property (useful for display by field) */
|
||||||
|
byProperty: Record<Extract<keyof M, string>, string[]>;
|
||||||
|
|
||||||
|
violationsByNormalizedProperty(property: Extract<keyof M, string>): ViolationFromMap<M>[];
|
||||||
|
|
||||||
|
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>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessExceptionInterface extends TransportExceptionInterface {
|
export interface AccessExceptionInterface extends TransportExceptionInterface {
|
||||||
|
@@ -45,15 +45,15 @@ export const getPersonIdentifiers = async (): Promise<
|
|||||||
> => fetchResults("/api/1.0/person/identifiers/workers");
|
> => fetchResults("/api/1.0/person/identifiers/workers");
|
||||||
|
|
||||||
export interface WritePersonViolationMap
|
export interface WritePersonViolationMap
|
||||||
extends Record<string, Record<string, unknown>> {
|
extends Record<string, Record<string, string>> {
|
||||||
firstName: {
|
firstName: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string
|
||||||
};
|
};
|
||||||
lastName: {
|
lastName: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string;
|
||||||
};
|
};
|
||||||
gender: {
|
gender: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string;
|
||||||
};
|
};
|
||||||
mobilenumber: {
|
mobilenumber: {
|
||||||
"{{ types }}": string; // ex: "mobile number"
|
"{{ types }}": string; // ex: "mobile number"
|
||||||
@@ -64,17 +64,17 @@ export interface WritePersonViolationMap
|
|||||||
"{{ value }}": string; // ex: "+33 1 02 03 04 05"
|
"{{ value }}": string; // ex: "+33 1 02 03 04 05"
|
||||||
};
|
};
|
||||||
email: {
|
email: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string;
|
||||||
};
|
};
|
||||||
center: {
|
center: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string;
|
||||||
};
|
};
|
||||||
civility: {
|
civility: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string;
|
||||||
};
|
};
|
||||||
birthdate: {};
|
birthdate: {};
|
||||||
identifiers: {
|
identifiers: {
|
||||||
"{{ value }}": string | null;
|
"{{ value }}": string;
|
||||||
"definition_id": string;
|
"definition_id": string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -366,7 +366,7 @@ import {
|
|||||||
Center,
|
Center,
|
||||||
Civility,
|
Civility,
|
||||||
Gender,
|
Gender,
|
||||||
DateTimeWrite,
|
DateTimeWrite, ValidationExceptionInterface,
|
||||||
} from "ChillMainAssets/types";
|
} from "ChillMainAssets/types";
|
||||||
import {
|
import {
|
||||||
AltName,
|
AltName,
|
||||||
@@ -378,7 +378,6 @@ import {
|
|||||||
} from "ChillPersonAssets/types";
|
} from "ChillPersonAssets/types";
|
||||||
import {
|
import {
|
||||||
isValidationException,
|
isValidationException,
|
||||||
ValidationExceptionInterface,
|
|
||||||
} from "ChillMainAssets/lib/api/apiMethods";
|
} from "ChillMainAssets/lib/api/apiMethods";
|
||||||
import {useToast} from "vue-toast-notification";
|
import {useToast} from "vue-toast-notification";
|
||||||
import {getTimezoneOffsetString, ISOToDate} from "ChillMainAssets/chill/js/date";
|
import {getTimezoneOffsetString, ISOToDate} from "ChillMainAssets/chill/js/date";
|
||||||
@@ -610,10 +609,13 @@ function addQueryItem(field: "lastName" | "firstName", queryItem: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WritePersonViolationKey = Extract<keyof WritePersonViolationMap, string>;
|
type WritePersonViolationKey = Extract<keyof WritePersonViolationMap, string>;
|
||||||
const violationsList = ref<ValidationExceptionInterface<WritePersonViolationMap>["violationsList"]>([]);
|
const violationsList = ref<ValidationExceptionInterface<WritePersonViolationMap>|null>(null);
|
||||||
|
|
||||||
function violationTitles<P extends WritePersonViolationKey>(property: P): string[] {
|
function violationTitles<P extends WritePersonViolationKey>(property: P): string[] {
|
||||||
return violationsList.value.filter((v) => v.propertyPath === property).map((v) => v.title);
|
if (null === violationsList.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return violationsList.value.violationsByNormalizedProperty(property).map((v) => v.title);
|
||||||
|
|
||||||
}
|
}
|
||||||
function violationTitlesWithParameter<
|
function violationTitlesWithParameter<
|
||||||
@@ -624,20 +626,11 @@ function violationTitlesWithParameter<
|
|||||||
with_parameter: Param,
|
with_parameter: Param,
|
||||||
with_parameter_value: WritePersonViolationMap[P][Param],
|
with_parameter_value: WritePersonViolationMap[P][Param],
|
||||||
): string[] {
|
): string[] {
|
||||||
const list = violationsList.value.filter((v) => v.propertyPath === property);
|
if (violationsList.value === null) {
|
||||||
|
return [];
|
||||||
const filtered = list.filter(
|
}
|
||||||
(v): boolean =>
|
return violationsList.value.violationsByNormalizedPropertyAndParams(property, with_parameter, with_parameter_value)
|
||||||
!!v.parameters &&
|
.map((v) => v.title);
|
||||||
// `with_parameter in v.parameters` check indexing
|
|
||||||
with_parameter in v.parameters &&
|
|
||||||
// the cast is safe, because we have overloading that bind the types
|
|
||||||
(v.parameters as WritePersonViolationMap[P])[with_parameter] === with_parameter_value
|
|
||||||
);
|
|
||||||
return filtered.map((v) => v.title);
|
|
||||||
|
|
||||||
// Sinon, retourner simplement les titres de la propriété
|
|
||||||
return list.map((v) => v.title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -660,14 +653,13 @@ function submitNewAddress(payload: { addressId: number }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function postPerson(): Promise<void> {
|
async function postPerson(): Promise<void> {
|
||||||
console.log("postPerson");
|
|
||||||
try {
|
try {
|
||||||
const createdPerson = await createPerson(person);
|
const createdPerson = await createPerson(person);
|
||||||
|
|
||||||
emit("onPersonCreated", { person: createdPerson });
|
emit("onPersonCreated", { person: createdPerson });
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (isValidationException<WritePersonViolationMap>(e)) {
|
if (isValidationException<WritePersonViolationMap>(e)) {
|
||||||
violationsList.value = e.violationsList;
|
violationsList.value = e;
|
||||||
} else {
|
} else {
|
||||||
toast.error(trans(PERSON_EDIT_ERROR_WHILE_SAVING));
|
toast.error(trans(PERSON_EDIT_ERROR_WHILE_SAVING));
|
||||||
}
|
}
|
||||||
|
@@ -75,6 +75,7 @@ person_creation:
|
|||||||
|
|
||||||
person_identifier:
|
person_identifier:
|
||||||
This identifier must be set: Cet identifiant doit être présent.
|
This identifier must be set: Cet identifiant doit être présent.
|
||||||
|
Identifier must be unique. The same identifier already exists for {{ persons }}: Identifiant déjà utilisé pour {{ persons }}
|
||||||
|
|
||||||
accompanying_course_work:
|
accompanying_course_work:
|
||||||
The endDate should be greater or equal than the start date: La date de fin doit être égale ou supérieure à la date de début
|
The endDate should be greater or equal than the start date: La date de fin doit être égale ou supérieure à la date de début
|
||||||
|
Reference in New Issue
Block a user