Source code for veupath_chatbot.services.strategies.engine.validation
"""Validation and error-payload helpers for strategy tools."""
from veupath_chatbot.domain.strategy.ast import PlanStepNode
from veupath_chatbot.domain.strategy.session import StrategyGraph
from veupath_chatbot.platform.errors import ErrorCode, ValidationError
from veupath_chatbot.platform.tool_errors import tool_error
from veupath_chatbot.platform.types import JSONObject, JSONValue
from .base import StrategyToolsBase
[docs]
class ValidationMixin(StrategyToolsBase):
def _get_graph(self, graph_id: str | None) -> StrategyGraph | None:
graph = self.session.get_graph(graph_id)
if graph:
return graph
# Fallback to active graph if an invalid id was provided.
return self.session.get_graph(None)
def _graph_not_found(self, graph_id: str | None) -> JSONObject:
if graph_id:
return tool_error(ErrorCode.NOT_FOUND, "Graph not found", graphId=graph_id)
return tool_error(
ErrorCode.NOT_FOUND, "Graph not found. Provide a graphId.", graphId=graph_id
)
def _step_not_found(self, step_id: str) -> JSONObject:
"""Standard error payload for a missing step."""
return tool_error(
ErrorCode.STEP_NOT_FOUND, f"Step not found: {step_id}", stepId=step_id
)
def _get_graph_and_step(
self, graph_id: str | None, step_id: str
) -> tuple[StrategyGraph, PlanStepNode] | JSONObject:
"""Look up graph and step, returning an error dict on failure.
Callers should check ``isinstance(result, dict)`` -- if True the
result is a ready-to-return error payload. Otherwise it is a
``(graph, step)`` tuple.
"""
graph = self._get_graph(graph_id)
if not graph:
return self._graph_not_found(graph_id)
step = graph.get_step(step_id)
if not step:
return self._step_not_found(step_id)
return graph, step
def _validation_error_payload(
self, exc: ValidationError, **context: JSONValue
) -> JSONObject:
details: JSONObject = {}
if exc.detail:
details["detail"] = exc.detail
if exc.errors is not None:
details["errors"] = exc.errors
for error in exc.errors:
if not isinstance(error, dict):
continue
extra = error.get("context")
if isinstance(extra, dict):
context.update({k: v for k, v in extra.items() if v is not None})
details.update({k: v for k, v in context.items() if v is not None})
return tool_error(ErrorCode.VALIDATION_ERROR, exc.title, **details)
def _is_placeholder_name(self, name: str | None) -> bool:
if not name:
return True
return name.strip().lower() in {
"draft graph",
"draft strategy",
"draft",
"new conversation",
}