Files
rpa_vision_v3/visual_workflow_builder/backend/api/validation.py
Dom a27b74cf22 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>
2026-01-29 11:23:51 +01:00

137 lines
4.5 KiB
Python

"""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")