"""Validation Pydantic du document carte envoye au serveur. Le backend ne cherche pas a comprendre finement une carte : il valide sa structure, ses limites de taille, puis la stocke telle quelle. `extra="allow"` garantit qu'une version future du format frontend reste acceptee. """ from __future__ import annotations from typing import Any from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from app.config import get_settings MAX_SUPPORTED_MAP_VERSION = 100 class MapNodeIn(BaseModel): model_config = ConfigDict(extra="allow") id: str = Field(min_length=1, max_length=128) text: str = Field(default="", max_length=5000) class MapEdgeIn(BaseModel): model_config = ConfigDict(extra="allow") id: str = Field(min_length=1, max_length=256) source: str = Field(min_length=1, max_length=128) target: str = Field(min_length=1, max_length=128) class MapRelationIn(BaseModel): """Relation secondaire (format v4). Purement visuelle : le backend ne l'interprete pas, il verifie seulement qu'elle est bien formee. Une relation qui boucle sur elle-meme est refusee, comme cote frontend : les deux validations disent la meme chose. """ model_config = ConfigDict(extra="allow") id: str = Field(min_length=1, max_length=128) # Les noms suivent le document JSON du frontend, d'ou le camelCase. sourceId: str = Field(min_length=1, max_length=128) targetId: str = Field(min_length=1, max_length=128) label: str | None = Field(default=None, max_length=120) @model_validator(mode="after") def _check_endpoints(self) -> MapRelationIn: if self.sourceId == self.targetId: raise ValueError("Une relation doit relier deux noeuds differents.") return self class MapDocumentIn(BaseModel): """Document carte complet, tel que produit par le frontend.""" model_config = ConfigDict(extra="allow") version: int = Field(ge=1, le=MAX_SUPPORTED_MAP_VERSION) id: str = Field(min_length=1, max_length=128) name: str = Field(default="", max_length=255) nodes: list[MapNodeIn] = Field(default_factory=list) edges: list[MapEdgeIn] = Field(default_factory=list) relations: list[MapRelationIn] = Field(default_factory=list) viewport: dict[str, Any] = Field(default_factory=dict) settings: dict[str, Any] = Field(default_factory=dict) @field_validator("nodes") @classmethod def _check_node_count(cls, value: list[MapNodeIn]) -> list[MapNodeIn]: max_nodes = get_settings().max_nodes if len(value) > max_nodes: raise ValueError( f"La carte depasse la limite de {max_nodes} noeuds ({len(value)} noeuds fournis)." ) return value @model_validator(mode="after") def _check_edge_count(self) -> MapDocumentIn: max_edges = get_settings().max_nodes * 4 if len(self.edges) > max_edges: raise ValueError(f"La carte depasse la limite de {max_edges} liens.") max_relations = get_settings().max_nodes * 2 if len(self.relations) > max_relations: raise ValueError(f"La carte depasse la limite de {max_relations} relations.") return self