ics-simlab-config-gen-claude/services/validation/config_validation.py

92 lines
3.1 KiB
Python

from __future__ import annotations
from typing import Any, Dict, List, Tuple
TOP_KEYS = ["ui", "hmis", "plcs", "sensors", "actuators", "hils", "serial_networks", "ip_networks"]
def validate_basic(cfg: dict[str, Any]) -> List[str]:
errors: List[str] = []
if not isinstance(cfg, dict):
return ["Top-level JSON is not an object"]
for k in TOP_KEYS:
if k not in cfg:
errors.append(f"Missing top-level key: {k}")
if errors:
return errors
if not isinstance(cfg["ui"], dict):
errors.append("ui must be an object")
for k in ["hmis", "plcs", "sensors", "actuators", "hils", "serial_networks", "ip_networks"]:
if not isinstance(cfg[k], list):
errors.append(f"{k} must be an array")
if errors:
return errors
ui = cfg.get("ui", {})
if not isinstance(ui, dict):
errors.append("ui must be an object")
return errors
uinet = ui.get("network")
if not isinstance(uinet, dict):
errors.append("ui.network must be an object")
return errors
for req in ["ip", "port", "docker_network"]:
if req not in uinet:
errors.append(f"ui.network missing key: {req}")
names: List[str] = []
for section in ["hmis", "plcs", "sensors", "actuators", "hils"]:
for dev in cfg.get(section, []):
if isinstance(dev, dict) and isinstance(dev.get("name"), str):
names.append(dev["name"])
dup = {n for n in set(names) if names.count(n) > 1}
if dup:
errors.append(f"Duplicate device names: {sorted(list(dup))}")
seen: Dict[Tuple[str, str], str] = {}
def check_net(dev: dict[str, Any]) -> None:
net = dev.get("network") or {}
dn = net.get("docker_network")
ip = net.get("ip")
name = dev.get("name", "<unnamed>")
if not isinstance(dn, str) or not isinstance(ip, str):
return
key = (dn, ip)
if key in seen:
errors.append(f"Duplicate IP {ip} on docker_network {dn} (devices: {seen[key]} and {name})")
else:
seen[key] = str(name)
for section in ["hmis", "plcs", "sensors", "actuators"]:
for dev in cfg.get(section, []):
if isinstance(dev, dict):
check_net(dev)
def uses_rtu(dev: dict[str, Any]) -> bool:
for c in (dev.get("inbound_connections") or []):
if isinstance(c, dict) and c.get("type") == "rtu":
return True
for c in (dev.get("outbound_connections") or []):
if isinstance(c, dict) and c.get("type") == "rtu":
return True
return False
any_rtu = False
for section in ["hmis", "plcs", "sensors", "actuators"]:
for dev in cfg.get(section, []):
if isinstance(dev, dict) and uses_rtu(dev):
any_rtu = True
serial_nets = cfg.get("serial_networks", [])
if any_rtu and len(serial_nets) == 0:
errors.append("RTU used but serial_networks is empty")
if (not any_rtu) and len(serial_nets) != 0:
errors.append("serial_networks must be empty when RTU is not used")
return errors