feat: normalize and validate component model structure
This commit is contained in:
152
src/shared/schemas/inventory.ts
Normal file
152
src/shared/schemas/inventory.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { normalizeComponentModelStructure } from '../../component-models/structure.normalizer';
|
||||
import type { ComponentModelStructure } from '../types/inventory';
|
||||
|
||||
export class ComponentModelStructureValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ComponentModelStructureValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
function assertString(value: unknown, context: string): string {
|
||||
if (typeof value !== 'string') {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`${context} doit être une chaîne de caractères`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function sanitizeOptionalString(value: unknown): string | undefined {
|
||||
if (value === undefined || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function validatePieces(
|
||||
pieces: ComponentModelStructure['pieces'],
|
||||
): ComponentModelStructure['pieces'] {
|
||||
return pieces.map((piece, index) => {
|
||||
if ('familyCode' in piece) {
|
||||
const familyCode = assertString(
|
||||
piece.familyCode,
|
||||
`pieces[${index}].familyCode`,
|
||||
).trim();
|
||||
if (!familyCode) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`pieces[${index}].familyCode ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
familyCode,
|
||||
role: sanitizeOptionalString(piece.role),
|
||||
};
|
||||
}
|
||||
|
||||
if ('typePieceId' in piece) {
|
||||
const typePieceId = assertString(
|
||||
piece.typePieceId,
|
||||
`pieces[${index}].typePieceId`,
|
||||
).trim();
|
||||
if (!typePieceId) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`pieces[${index}].typePieceId ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
typePieceId,
|
||||
role: sanitizeOptionalString(piece.role),
|
||||
};
|
||||
}
|
||||
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`pieces[${index}] doit définir "familyCode" ou "typePieceId"`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function validateCustomFields(
|
||||
customFields: ComponentModelStructure['customFields'],
|
||||
): ComponentModelStructure['customFields'] {
|
||||
return customFields.map((field, index) => {
|
||||
const key = assertString(field.key, `customFields[${index}].key`).trim();
|
||||
if (!key) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`customFields[${index}].key ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
|
||||
return { key, value: field.value };
|
||||
});
|
||||
}
|
||||
|
||||
function validateSubcomponents(
|
||||
subcomponents: ComponentModelStructure['subcomponents'],
|
||||
): ComponentModelStructure['subcomponents'] {
|
||||
return subcomponents.map((subcomponent, index) => {
|
||||
if ('modelId' in subcomponent) {
|
||||
const modelId = assertString(
|
||||
subcomponent.modelId,
|
||||
`subcomponents[${index}].modelId`,
|
||||
).trim();
|
||||
if (!modelId) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`subcomponents[${index}].modelId ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
modelId,
|
||||
alias: sanitizeOptionalString(subcomponent.alias),
|
||||
};
|
||||
}
|
||||
|
||||
if ('familyCode' in subcomponent) {
|
||||
const familyCode = assertString(
|
||||
subcomponent.familyCode,
|
||||
`subcomponents[${index}].familyCode`,
|
||||
).trim();
|
||||
if (!familyCode) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`subcomponents[${index}].familyCode ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
familyCode,
|
||||
alias: sanitizeOptionalString(subcomponent.alias),
|
||||
};
|
||||
}
|
||||
|
||||
if ('typeComposantId' in subcomponent) {
|
||||
const typeComposantId = assertString(
|
||||
subcomponent.typeComposantId,
|
||||
`subcomponents[${index}].typeComposantId`,
|
||||
).trim();
|
||||
if (!typeComposantId) {
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`subcomponents[${index}].typeComposantId ne peut pas être vide`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
typeComposantId,
|
||||
alias: sanitizeOptionalString(subcomponent.alias),
|
||||
};
|
||||
}
|
||||
|
||||
throw new ComponentModelStructureValidationError(
|
||||
`subcomponents[${index}] doit définir "modelId", "familyCode" ou "typeComposantId"`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export const ComponentModelStructureSchema = {
|
||||
parse(input: unknown): ComponentModelStructure {
|
||||
const normalized = normalizeComponentModelStructure(input);
|
||||
|
||||
return {
|
||||
pieces: validatePieces(normalized.pieces),
|
||||
customFields: validateCustomFields(normalized.customFields),
|
||||
subcomponents: validateSubcomponents(normalized.subcomponents),
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user