Source code for openswmm_mcp._util.validation
"""Input validation utilities for MCP tool handlers."""
from __future__ import annotations
from pathlib import Path
from fastmcp.exceptions import ToolError
# Canonical element types supported by the OpenSWMM engine queries.
VALID_ELEMENT_TYPES: frozenset[str] = frozenset({"node", "link", "subcatchment", "gage", "system"})
[docs]
def resolve_path(path: str, working_dir: str) -> Path:
"""Resolve *path* against *working_dir*, expanding ``~``.
Parameters
----------
path:
A file-system path that may be relative or contain ``~``.
working_dir:
The directory used as the base when *path* is relative.
Returns
-------
Path
A fully resolved, absolute :class:`~pathlib.Path`.
Examples
--------
>>> resolve_path("model.inp", "/data/projects")
PosixPath('/data/projects/model.inp')
>>> resolve_path("~/models/test.inp", "/ignored") # doctest: +SKIP
PosixPath('/home/user/models/test.inp')
"""
expanded = Path(path).expanduser()
if expanded.is_absolute():
return expanded.resolve()
return (Path(working_dir).expanduser() / expanded).resolve()
[docs]
def validate_element_type(element_type: str) -> str:
"""Validate that *element_type* is a recognised SWMM element category.
The comparison is case-insensitive; the returned value is always lowercase.
Parameters
----------
element_type:
A string such as ``"node"``, ``"Link"``, or ``"SUBCATCHMENT"``.
Returns
-------
str
The normalised (lowercase) element type.
Raises
------
ToolError
If *element_type* is not one of the accepted values.
"""
normalised = element_type.strip().lower()
if normalised not in VALID_ELEMENT_TYPES:
sorted_types = ", ".join(f"'{t}'" for t in sorted(VALID_ELEMENT_TYPES))
raise ToolError(f"Unknown element type '{element_type}'. Valid types are: {sorted_types}.")
return normalised
[docs]
def validate_session_state(
session,
*valid_states: str,
action: str = "perform this action",
) -> None:
"""Raise :class:`ToolError` if the session is not in an accepted state.
Parameters
----------
session:
A session object exposing a ``.state`` attribute.
*valid_states:
One or more acceptable state strings (e.g. ``"running"``, ``"paused"``).
action:
A human-readable description of what the caller is trying to do,
used in the error message. Defaults to ``"perform this action"``.
Raises
------
ToolError
If ``session.state`` is not among *valid_states*.
"""
if session.state not in valid_states:
allowed = ", ".join(f"'{s}'" for s in valid_states)
raise ToolError(
f"Cannot {action}: session is in state '{session.state}', "
f"but must be in one of: {allowed}."
)