feat: normalize and validate component model structure
This commit is contained in:
109
src/component-models/structure.normalizer.ts
Normal file
109
src/component-models/structure.normalizer.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import type { ComponentModelStructure } from '../shared/types/inventory';
|
||||||
|
|
||||||
|
type InputValue = Record<string, unknown> | null | undefined;
|
||||||
|
|
||||||
|
const toArray = (value: unknown): unknown[] => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [value];
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeRole = (role: unknown): string | undefined => {
|
||||||
|
if (role === undefined || role === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const stringValue = String(role).trim();
|
||||||
|
return stringValue ? stringValue : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeAlias = (alias: unknown): string | undefined => {
|
||||||
|
if (alias === undefined || alias === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const stringValue = String(alias).trim();
|
||||||
|
return stringValue ? stringValue : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureString = (value: unknown): string => String(value ?? '');
|
||||||
|
|
||||||
|
export function normalizeComponentModelStructure(
|
||||||
|
input: unknown,
|
||||||
|
): ComponentModelStructure {
|
||||||
|
const structure = (input ?? {}) as InputValue;
|
||||||
|
|
||||||
|
const pieces = toArray((structure as any)?.pieces).map((piece) => {
|
||||||
|
const candidate = piece as Record<string, unknown> | null | undefined;
|
||||||
|
if (candidate?.familyCode) {
|
||||||
|
return {
|
||||||
|
familyCode: ensureString(candidate.familyCode).trim() || 'UNKNOWN',
|
||||||
|
role: sanitizeRole(candidate.role),
|
||||||
|
} as ComponentModelStructure['pieces'][number];
|
||||||
|
}
|
||||||
|
if (candidate?.typePieceId) {
|
||||||
|
return {
|
||||||
|
typePieceId: ensureString(candidate.typePieceId).trim() || 'UNKNOWN',
|
||||||
|
role: sanitizeRole(candidate.role),
|
||||||
|
} as ComponentModelStructure['pieces'][number];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
familyCode:
|
||||||
|
ensureString(
|
||||||
|
candidate?.familyCode ?? candidate?.name ?? candidate?.typePieceLabel ?? 'UNKNOWN',
|
||||||
|
).trim() || 'UNKNOWN',
|
||||||
|
role: sanitizeRole(candidate?.role),
|
||||||
|
} as ComponentModelStructure['pieces'][number];
|
||||||
|
});
|
||||||
|
|
||||||
|
const customFields = toArray((structure as any)?.customFields).map((field) => {
|
||||||
|
const candidate = field as Record<string, unknown> | null | undefined;
|
||||||
|
const key = ensureString(candidate?.key ?? candidate?.name ?? 'unknown').trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: key || 'unknown',
|
||||||
|
value: candidate?.value ?? null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawSubcomponents = toArray(
|
||||||
|
(structure as any)?.subcomponents ?? (structure as any)?.subComponents,
|
||||||
|
);
|
||||||
|
|
||||||
|
const subcomponents = rawSubcomponents.map((subcomponent) => {
|
||||||
|
const candidate = subcomponent as Record<string, unknown> | null | undefined;
|
||||||
|
|
||||||
|
if (candidate?.modelId) {
|
||||||
|
return {
|
||||||
|
modelId: ensureString(candidate.modelId).trim() || 'UNKNOWN',
|
||||||
|
alias: sanitizeAlias(candidate?.alias ?? candidate?.name),
|
||||||
|
} as ComponentModelStructure['subcomponents'][number];
|
||||||
|
}
|
||||||
|
if (candidate?.familyCode) {
|
||||||
|
return {
|
||||||
|
familyCode: ensureString(candidate.familyCode).trim() || 'UNKNOWN',
|
||||||
|
alias: sanitizeAlias(candidate?.alias ?? candidate?.name),
|
||||||
|
} as ComponentModelStructure['subcomponents'][number];
|
||||||
|
}
|
||||||
|
if (candidate?.typeComposantId) {
|
||||||
|
return {
|
||||||
|
typeComposantId: ensureString(candidate.typeComposantId).trim() || 'UNKNOWN',
|
||||||
|
alias: sanitizeAlias(candidate?.alias ?? candidate?.name),
|
||||||
|
} as ComponentModelStructure['subcomponents'][number];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
familyCode: ensureString(candidate?.name ?? 'UNKNOWN').trim() || 'UNKNOWN',
|
||||||
|
alias: sanitizeAlias(candidate?.alias ?? candidate?.name),
|
||||||
|
} as ComponentModelStructure['subcomponents'][number];
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
pieces,
|
||||||
|
customFields,
|
||||||
|
subcomponents,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -123,7 +123,8 @@ export class ComposantsService {
|
|||||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
})) as ComposantWithRelations;
|
})) as ComposantWithRelations;
|
||||||
|
|
||||||
return this.getComponentWithHierarchy(created.id);
|
const component = await this.getComponentWithHierarchy(created.id);
|
||||||
|
return component ?? created;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
@@ -244,9 +245,13 @@ export class ComposantsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const subComponents = Array.isArray(structure?.subComponents)
|
const rawSubcomponents =
|
||||||
? structure.subComponents
|
(structure as any)?.subcomponents ?? structure?.subComponents;
|
||||||
: [];
|
const subComponents = Array.isArray(rawSubcomponents)
|
||||||
|
? rawSubcomponents
|
||||||
|
: rawSubcomponents
|
||||||
|
? [rawSubcomponents]
|
||||||
|
: [];
|
||||||
for (const sub of subComponents) {
|
for (const sub of subComponents) {
|
||||||
const subTypeId = this.extractTypeComposantId(sub);
|
const subTypeId = this.extractTypeComposantId(sub);
|
||||||
if (!subTypeId) {
|
if (!subTypeId) {
|
||||||
|
|||||||
@@ -152,9 +152,3 @@ describe('CustomFieldsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
expect(prisma.customField.findFirst).toHaveBeenCalledWith({
|
|
||||||
where: {
|
|
||||||
name: 'Température maximale',
|
|
||||||
typeMachineId: 'type-1',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -537,11 +537,19 @@ export class MachinesService {
|
|||||||
: prepared.pieces
|
: prepared.pieces
|
||||||
? [prepared.pieces]
|
? [prepared.pieces]
|
||||||
: [];
|
: [];
|
||||||
prepared.subComponents = Array.isArray(prepared.subComponents)
|
const rawSubcomponents =
|
||||||
? prepared.subComponents
|
(definition as any)?.subcomponents ??
|
||||||
: prepared.subComponents
|
(definition as any)?.subComponents ??
|
||||||
? [prepared.subComponents]
|
prepared.subcomponents ??
|
||||||
|
prepared.subComponents ??
|
||||||
|
[];
|
||||||
|
const subcomponents = Array.isArray(rawSubcomponents)
|
||||||
|
? rawSubcomponents
|
||||||
|
: rawSubcomponents
|
||||||
|
? [rawSubcomponents]
|
||||||
: [];
|
: [];
|
||||||
|
prepared.subcomponents = subcomponents;
|
||||||
|
prepared.subComponents = subcomponents;
|
||||||
|
|
||||||
prepared.typeComposantId =
|
prepared.typeComposantId =
|
||||||
prepared.typeComposantId ||
|
prepared.typeComposantId ||
|
||||||
@@ -608,9 +616,13 @@ export class MachinesService {
|
|||||||
const componentPieces = Array.isArray(component.pieces)
|
const componentPieces = Array.isArray(component.pieces)
|
||||||
? component.pieces
|
? component.pieces
|
||||||
: [];
|
: [];
|
||||||
const subComponents = Array.isArray(component.subComponents)
|
const rawSubcomponents =
|
||||||
? component.subComponents
|
component.subcomponents ?? component.subComponents ?? [];
|
||||||
: [];
|
const subComponents = Array.isArray(rawSubcomponents)
|
||||||
|
? rawSubcomponents
|
||||||
|
: rawSubcomponents
|
||||||
|
? [rawSubcomponents]
|
||||||
|
: [];
|
||||||
|
|
||||||
const componentModelId = component.__componentModelId ?? null;
|
const componentModelId = component.__componentModelId ?? null;
|
||||||
const requirementId = component.__requirementId ?? null;
|
const requirementId = component.__requirementId ?? null;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { ValidateNested } from 'class-validator';
|
import { ValidateNested } from 'class-validator';
|
||||||
|
import type { ComponentModelStructure } from '../types/inventory';
|
||||||
|
|
||||||
export enum CustomFieldType {
|
export enum CustomFieldType {
|
||||||
TEXT = 'text',
|
TEXT = 'text',
|
||||||
@@ -251,7 +252,7 @@ export class CreateComposantModelDto {
|
|||||||
typeComposantId: string;
|
typeComposantId: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
structure?: any;
|
structure?: ComponentModelStructure;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateComposantModelDto {
|
export class UpdateComposantModelDto {
|
||||||
@@ -268,7 +269,7 @@ export class UpdateComposantModelDto {
|
|||||||
typeComposantId?: string;
|
typeComposantId?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
structure?: any;
|
structure?: ComponentModelStructure;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreatePieceModelDto {
|
export class CreatePieceModelDto {
|
||||||
|
|||||||
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),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
41
src/shared/types/inventory.ts
Normal file
41
src/shared/types/inventory.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Structure canonique d'un ComponentModel.
|
||||||
|
*/
|
||||||
|
export type ComponentModelStructure = {
|
||||||
|
/**
|
||||||
|
* Familles de pièces autorisées (ou identifiant de famille) — pas de quantité ici.
|
||||||
|
*/
|
||||||
|
pieces: Array<
|
||||||
|
| {
|
||||||
|
familyCode: string;
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
typePieceId: string;
|
||||||
|
role?: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valeurs par défaut au niveau "modèle" (libres, mais clé obligatoire).
|
||||||
|
*/
|
||||||
|
customFields: Array<{ key: string; value: unknown }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sous-composants : soit on pointe un modèle précis, soit on autorise une famille.
|
||||||
|
*/
|
||||||
|
subcomponents: Array<
|
||||||
|
| {
|
||||||
|
modelId: string;
|
||||||
|
alias?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
familyCode: string;
|
||||||
|
alias?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
typeComposantId: string;
|
||||||
|
alias?: string;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ComposantModelsRepository } from '../../common/repositories/composant-models.repository';
|
import { ComposantModelsRepository } from '../../common/repositories/composant-models.repository';
|
||||||
|
import type { Prisma } from '@prisma/client';
|
||||||
import {
|
import {
|
||||||
CreateComposantModelDto,
|
CreateComposantModelDto,
|
||||||
UpdateComposantModelDto,
|
UpdateComposantModelDto,
|
||||||
} from '../../shared/dto/type.dto';
|
} from '../../shared/dto/type.dto';
|
||||||
|
import { ComponentModelStructureSchema } from '../../shared/schemas/inventory';
|
||||||
|
import type { ComponentModelStructure } from '../../shared/types/inventory';
|
||||||
|
|
||||||
const COMPOSANT_MODEL_INCLUDE = {
|
const COMPOSANT_MODEL_INCLUDE = {
|
||||||
typeComposant: true,
|
typeComposant: true,
|
||||||
@@ -14,40 +17,81 @@ export class ComposantModelService {
|
|||||||
constructor(private readonly repository: ComposantModelsRepository) {}
|
constructor(private readonly repository: ComposantModelsRepository) {}
|
||||||
|
|
||||||
async create(dto: CreateComposantModelDto) {
|
async create(dto: CreateComposantModelDto) {
|
||||||
const { typeComposantId, ...data } = dto;
|
const { typeComposantId, structure, ...data } = dto;
|
||||||
return this.repository.create(
|
const parsedStructure = this.parseStructure(structure);
|
||||||
|
|
||||||
|
const created = await this.repository.create(
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
|
structure: parsedStructure as Prisma.InputJsonValue,
|
||||||
typeComposant: { connect: { id: typeComposantId } },
|
typeComposant: { connect: { id: typeComposantId } },
|
||||||
},
|
},
|
||||||
COMPOSANT_MODEL_INCLUDE,
|
COMPOSANT_MODEL_INCLUDE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return this.withParsedStructure(created);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(typeComposantId?: string) {
|
async findAll(typeComposantId?: string) {
|
||||||
return this.repository.findAll(typeComposantId, COMPOSANT_MODEL_INCLUDE);
|
const models = await this.repository.findAll(
|
||||||
|
typeComposantId,
|
||||||
|
COMPOSANT_MODEL_INCLUDE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return models.map((model) => this.mapStructure(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
return this.repository.findOne(id, COMPOSANT_MODEL_INCLUDE);
|
const model = await this.repository.findOne(id, COMPOSANT_MODEL_INCLUDE);
|
||||||
|
return this.withParsedStructure(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, dto: UpdateComposantModelDto) {
|
async update(id: string, dto: UpdateComposantModelDto) {
|
||||||
const { typeComposantId, ...data } = dto;
|
const { typeComposantId, structure, ...data } = dto;
|
||||||
|
|
||||||
return this.repository.update(
|
const parsedStructure =
|
||||||
|
structure !== undefined ? this.parseStructure(structure) : undefined;
|
||||||
|
|
||||||
|
const updated = await this.repository.update(
|
||||||
id,
|
id,
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
|
...(parsedStructure
|
||||||
|
? { structure: parsedStructure as Prisma.InputJsonValue }
|
||||||
|
: {}),
|
||||||
...(typeComposantId
|
...(typeComposantId
|
||||||
? { typeComposant: { connect: { id: typeComposantId } } }
|
? { typeComposant: { connect: { id: typeComposantId } } }
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
COMPOSANT_MODEL_INCLUDE,
|
COMPOSANT_MODEL_INCLUDE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return this.withParsedStructure(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string) {
|
async remove(id: string) {
|
||||||
return this.repository.delete(id);
|
return this.repository.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseStructure(
|
||||||
|
structure: unknown | undefined,
|
||||||
|
): ComponentModelStructure {
|
||||||
|
return ComponentModelStructureSchema.parse(structure);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapStructure<T extends { structure?: unknown }>(
|
||||||
|
model: T,
|
||||||
|
): T & { structure: ComponentModelStructure } {
|
||||||
|
const structure = this.parseStructure((model as any).structure);
|
||||||
|
return {
|
||||||
|
...model,
|
||||||
|
structure,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private withParsedStructure<T extends { structure?: unknown }>(
|
||||||
|
model: T | null,
|
||||||
|
): (T & { structure: ComponentModelStructure }) | null {
|
||||||
|
return model ? this.mapStructure(model) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user