"""Per-service configuration loading and validation.
:func:`load_and_validate` loads the shared :class:`config.Config` and
checks that the keys a given service actually needs are present and
pass lightweight semantic checks (e.g. ``redis_url`` must look like a
Redis URL). On any missing or invalid key it raises ``SystemExit`` so
a misconfigured service fails fast at boot instead of running in a
broken state.
"""
import re
from config import Config
# Semantic validators for specific key types
_VALIDATORS = {
"redis_url": lambda v: isinstance(v, str) and v.startswith(("redis://", "rediss://")),
"discord_token": lambda v: isinstance(v, str) and len(v) > 50, # Discord tokens are long
"api_key": lambda v: isinstance(v, str) and len(v) > 10,
"gemini_api_key": lambda v: isinstance(v, str) and len(v) > 10,
}
[docs]
def load_and_validate(required_keys: list[str]) -> Config:
"""Load the shared Config and fail fast unless the required keys are valid.
Loads the monolithic :class:`config.Config` and, for every key a service
declares it needs, checks that the value is present (non-``None``,
non-empty) and passes any matching semantic validator in
:data:`_VALIDATORS` (e.g. ``redis_url`` must start with a Redis scheme). If
anything is missing or fails its check, it raises ``SystemExit`` with a
summary so a misconfigured service crashes at boot rather than running in a
half-broken state. Reads no Redis or network resources of its own beyond
whatever ``Config.load()`` does.
Called at service startup by each microservice entrypoint with the subset
of keys that service requires; also exercised directly by
``tests/core/migration/test_config_loader.py``.
Args:
required_keys: Attribute names on :class:`config.Config` that must be
present (and valid where a semantic validator exists).
Returns:
Config: The loaded, validated configuration object.
Raises:
SystemExit: If any required key is missing or fails semantic validation.
"""
cfg = Config.load()
missing = []
invalid = []
for key in required_keys:
val = getattr(cfg, key, None)
if val is None or (isinstance(val, str) and val == ""):
missing.append(key)
elif key in _VALIDATORS and not _VALIDATORS[key](val):
invalid.append(f"{key} (failed semantic validation)")
if missing or invalid:
raise SystemExit(
f"FATAL: Config errors — missing: {missing}, invalid: {invalid}"
)
return cfg