"""Validation and normalization helpers for run configuration payloads.""" from __future__ import annotations from typing import Any from python_app.models.run_config_schema import ComboModel, GprModel, RingEndpointModel, SwitchModel def load_switch_payload( payload: dict[str, Any], target: SwitchModel, ) -> None: """Populate switch model from payload preserving defaults for missing values.""" target.name = str(payload.get("name", target.name)) target.driver_mode = str(payload.get("driver_mode", target.driver_mode)) target.driver = str(payload.get("driver", target.driver)) target.radar_port = int(payload.get("radar_port", target.radar_port)) target.positions = int(payload.get("positions", target.positions)) target.default_position = int(payload.get("default_position", target.default_position)) target.gpio_chip = str(payload.get("gpio_chip", target.gpio_chip)) target.pin_a = int(payload.get("pin_a", target.pin_a)) target.pin_b = int(payload.get("pin_b", target.pin_b)) target.invert_logic = bool(payload.get("invert_logic", target.invert_logic)) def load_ring_payload(payload: dict[str, Any], target: RingEndpointModel) -> None: """Populate ring endpoint model from payload preserving defaults.""" target.name = str(payload.get("name", target.name)) target.capacity = int(payload.get("capacity", target.capacity)) target.slot_size_bytes = int(payload.get("slot_size_bytes", target.slot_size_bytes)) def validate_gpr_model( gpr: GprModel, *, input_switch_positions: int, output_switch_positions: int, ) -> None: """Validate stable GPR config against current switch dimensions.""" if gpr.mode not in {"point", "extended"}: raise ValueError("gpr.mode must be either 'point' or 'extended'") if float(gpr.relative_permittivity) <= 0.0: raise ValueError("gpr.relative_permittivity must be > 0") seen_output_positions: set[int] = set() for entry in gpr.tx_geometry: output_pos = int(entry.output_pos) if output_pos < 0 or output_pos >= int(output_switch_positions): raise ValueError("gpr.tx_geometry output_pos is out of range") if output_pos in seen_output_positions: raise ValueError("gpr.tx_geometry contains duplicate output_pos") seen_output_positions.add(output_pos) seen_input_positions: set[int] = set() for entry in gpr.rx_geometry: input_pos = int(entry.input_pos) if input_pos < 0 or input_pos >= int(input_switch_positions): raise ValueError("gpr.rx_geometry input_pos is out of range") if input_pos in seen_input_positions: raise ValueError("gpr.rx_geometry contains duplicate input_pos") seen_input_positions.add(input_pos) def parse_combos_from_text(text: str) -> list[ComboModel]: """Parse UI combos string in `input:output,input:output` format.""" cleaned = text.strip() if not cleaned: return [] combos: list[ComboModel] = [] for item in cleaned.split(","): pair = item.strip() if not pair: continue if ":" not in pair: raise ValueError(f"Invalid combo syntax: {pair!r}. Expected input:output") input_text, output_text = pair.split(":", 1) combos.append(ComboModel(input=int(input_text.strip()), output=int(output_text.strip()))) if not combos: raise ValueError("No valid combos were provided") return combos