"""Shared validation utilities for tools."""
from typing import Any
from jana_mcp.constants import (
BBOX_COORDINATES_REQUIRED,
MAX_LATITUDE,
MAX_LONGITUDE,
MIN_LATITUDE,
MIN_LONGITUDE,
POINT_COORDINATES_REQUIRED,
)
def require_location_filter(arguments: dict[str, Any]) -> str | None:
"""
Validate that at least one location filter is provided.
Args:
arguments: Tool arguments dictionary
Returns:
Error message if validation fails, None if valid
"""
location_bbox = arguments.get("location_bbox")
location_point = arguments.get("location_point")
country_codes = arguments.get("country_codes")
# Check for valid (non-empty) location filters
has_location = (
(location_bbox and len(location_bbox) >= BBOX_COORDINATES_REQUIRED)
or (location_point and len(location_point) >= POINT_COORDINATES_REQUIRED)
or (country_codes and len(country_codes) > 0)
)
if not has_location:
return "At least one location filter is required (location_bbox, location_point+radius_km, or country_codes)"
return None
def validate_coordinates(lon: float, lat: float) -> str | None:
"""
Validate coordinate bounds.
Args:
lon: Longitude (-180 to 180)
lat: Latitude (-90 to 90)
Returns:
Error message if invalid, None if valid
"""
if not (MIN_LONGITUDE <= lon <= MAX_LONGITUDE):
return f"Longitude {lon} out of bounds ({MIN_LONGITUDE} to {MAX_LONGITUDE})"
if not (MIN_LATITUDE <= lat <= MAX_LATITUDE):
return f"Latitude {lat} out of bounds ({MIN_LATITUDE} to {MAX_LATITUDE})"
return None
def validate_bbox(bbox: list[float]) -> str | None:
"""
Validate bounding box coordinates and order.
Args:
bbox: Bounding box [min_lon, min_lat, max_lon, max_lat]
Returns:
Error message if invalid, None if valid
"""
if len(bbox) != BBOX_COORDINATES_REQUIRED:
return f"Bounding box must have {BBOX_COORDINATES_REQUIRED} coordinates, got {len(bbox)}"
min_lon, min_lat, max_lon, max_lat = bbox
# Validate coordinate bounds
coord_error = validate_coordinates(min_lon, min_lat)
if coord_error:
return coord_error
coord_error = validate_coordinates(max_lon, max_lat)
if coord_error:
return coord_error
# Validate order
if min_lon >= max_lon:
return f"Invalid bbox: min_lon ({min_lon}) must be < max_lon ({max_lon})"
if min_lat >= max_lat:
return f"Invalid bbox: min_lat ({min_lat}) must be < max_lat ({max_lat})"
return None
def validate_date_format(date_str: str) -> str | None:
"""
Validate ISO 8601 date format.
Args:
date_str: Date string to validate
Returns:
Error message if invalid, None if valid
"""
from datetime import datetime
if not isinstance(date_str, str):
return f"Date must be a string, got {type(date_str)}"
try:
# Try ISO format first
datetime.fromisoformat(date_str.replace("Z", "+00:00"))
return None
except ValueError:
# Try common date-only formats
try:
datetime.strptime(date_str, "%Y-%m-%d")
return None
except ValueError as e:
return f"Invalid date format '{date_str}': {e}"
__all__ = [
"require_location_filter",
"validate_coordinates",
"validate_bbox",
"validate_date_format",
]