v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution
- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
137
visual_workflow_builder/backend/api/validation.py
Normal file
137
visual_workflow_builder/backend/api/validation.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""backend/api/validation.py
|
||||
|
||||
Validation légère pour les payloads de l'API workflows.
|
||||
|
||||
Auteur : Dom, Alice, Kiro - 08 janvier 2026
|
||||
|
||||
Patch #1:
|
||||
- Ce module manquait et bloquait le boot via api/__init__.py
|
||||
- On reste volontairement permissif (on valide les essentiels)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Iterable
|
||||
|
||||
from .errors import ValidationError
|
||||
|
||||
|
||||
_ALLOWED_UPDATE_FIELDS = {
|
||||
"name",
|
||||
"description",
|
||||
"nodes",
|
||||
"edges",
|
||||
"variables",
|
||||
"settings",
|
||||
"tags",
|
||||
"category",
|
||||
"is_template",
|
||||
# Champs additionnels envoyés par le frontend VWB
|
||||
"id",
|
||||
"steps",
|
||||
"connections",
|
||||
}
|
||||
|
||||
|
||||
def _ensure_dict(data: Any, context: str = "payload") -> Dict[str, Any]:
|
||||
"""S'assure que les données sont un dictionnaire."""
|
||||
if not isinstance(data, dict):
|
||||
raise ValidationError(f"{context} doit être un objet")
|
||||
return data
|
||||
|
||||
|
||||
def _ensure_list(value: Any, context: str) -> Iterable[Any]:
|
||||
"""S'assure que la valeur est une liste."""
|
||||
if value is None:
|
||||
return []
|
||||
if not isinstance(value, list):
|
||||
raise ValidationError(f"{context} doit être un tableau")
|
||||
return value
|
||||
|
||||
|
||||
def validate_workflow_data(data: Any) -> None:
|
||||
"""Valide les données d'un workflow lors de la création."""
|
||||
data = _ensure_dict(data, "workflow")
|
||||
|
||||
# Champs requis (création)
|
||||
if "name" not in data or not str(data.get("name") or "").strip():
|
||||
raise ValidationError("Le champ 'name' est requis")
|
||||
if "created_by" not in data or not str(data.get("created_by") or "").strip():
|
||||
raise ValidationError("Le champ 'created_by' est requis")
|
||||
|
||||
# Champs structurés optionnels
|
||||
if "nodes" in data:
|
||||
for n in _ensure_list(data.get("nodes"), "nodes"):
|
||||
validate_node_data(n)
|
||||
|
||||
if "edges" in data:
|
||||
for e in _ensure_list(data.get("edges"), "edges"):
|
||||
validate_edge_data(e)
|
||||
|
||||
if "variables" in data:
|
||||
for v in _ensure_list(data.get("variables"), "variables"):
|
||||
validate_variable_data(v)
|
||||
|
||||
if "settings" in data and data.get("settings") is not None:
|
||||
validate_settings_data(data.get("settings"))
|
||||
|
||||
if "tags" in data and data.get("tags") is not None and not isinstance(data.get("tags"), list):
|
||||
raise ValidationError("Le champ 'tags' doit être un tableau")
|
||||
|
||||
|
||||
def validate_update_data(data: Any) -> None:
|
||||
"""Valide les données d'un workflow lors de la mise à jour."""
|
||||
data = _ensure_dict(data, "update")
|
||||
|
||||
unknown = set(data.keys()) - _ALLOWED_UPDATE_FIELDS
|
||||
if unknown:
|
||||
raise ValidationError(f"Champ(s) inconnu(s) dans la mise à jour: {', '.join(sorted(unknown))}")
|
||||
|
||||
if "nodes" in data:
|
||||
for n in _ensure_list(data.get("nodes"), "nodes"):
|
||||
validate_node_data(n)
|
||||
|
||||
if "edges" in data:
|
||||
for e in _ensure_list(data.get("edges"), "edges"):
|
||||
validate_edge_data(e)
|
||||
|
||||
if "variables" in data:
|
||||
for v in _ensure_list(data.get("variables"), "variables"):
|
||||
validate_variable_data(v)
|
||||
|
||||
if "settings" in data and data.get("settings") is not None:
|
||||
validate_settings_data(data.get("settings"))
|
||||
|
||||
if "tags" in data and data.get("tags") is not None and not isinstance(data.get("tags"), list):
|
||||
raise ValidationError("Le champ 'tags' doit être un tableau")
|
||||
|
||||
|
||||
def validate_node_data(node: Any) -> None:
|
||||
"""Valide les données d'un nœud."""
|
||||
node = _ensure_dict(node, "node")
|
||||
if "id" not in node or not str(node.get("id") or "").strip():
|
||||
raise ValidationError("Le champ 'id' du nœud est requis")
|
||||
if "type" not in node or not str(node.get("type") or "").strip():
|
||||
# Les nœuds ReactFlow ont toujours un type (default est ok)
|
||||
raise ValidationError("Le champ 'type' du nœud est requis")
|
||||
|
||||
|
||||
def validate_edge_data(edge: Any) -> None:
|
||||
"""Valide les données d'une connexion."""
|
||||
edge = _ensure_dict(edge, "edge")
|
||||
if "source" not in edge or not str(edge.get("source") or "").strip():
|
||||
raise ValidationError("Le champ 'source' de la connexion est requis")
|
||||
if "target" not in edge or not str(edge.get("target") or "").strip():
|
||||
raise ValidationError("Le champ 'target' de la connexion est requis")
|
||||
|
||||
|
||||
def validate_variable_data(variable: Any) -> None:
|
||||
"""Valide les données d'une variable."""
|
||||
variable = _ensure_dict(variable, "variable")
|
||||
if "name" not in variable and "key" not in variable:
|
||||
raise ValidationError("Le champ 'name' de la variable est requis")
|
||||
|
||||
|
||||
def validate_settings_data(settings: Any) -> None:
|
||||
"""Valide les données des paramètres."""
|
||||
_ensure_dict(settings, "settings")
|
||||
Reference in New Issue
Block a user