Source code for pain001.templates.guardrails
# Copyright (C) 2023-2026 Pain001. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Guardrails that detect template/schema drift."""
from __future__ import annotations
import re
from pathlib import Path
from pain001.templates.registry import (
DEFAULT_TEMPLATE_REGISTRY,
TemplateMetadata,
TemplateRegistry,
)
class SchemaGuardrailError(ValueError):
"""Raised when template metadata or namespaces drift."""
_MESSAGE_TYPE_PATTERN = re.compile(r"pain\.\d{3}\.\d{3}\.\d{2}")
def _extract_message_type(path: Path) -> str:
"""Extract the pain.NNN.NNN.NN message type referenced inside a file."""
match = _MESSAGE_TYPE_PATTERN.search(path.read_text(encoding="utf-8"))
if not match:
raise SchemaGuardrailError(
f"Could not determine message type from {path}"
)
return match.group(0)
def validate_template_bundle(metadata: TemplateMetadata) -> None:
"""Validate one template bundle for existence and drift."""
for required_path in [metadata.template_path, metadata.xsd_path]:
if not required_path.exists():
raise SchemaGuardrailError(
f"Missing required file: {required_path}"
)
for candidate in [
metadata.template_path,
metadata.xsd_path,
metadata.example_xml_path,
]:
if candidate is None: # pragma: no cover
continue # pragma: no cover
discovered_message_type = _extract_message_type(candidate)
if discovered_message_type != metadata.message_type:
raise SchemaGuardrailError(
f"Message type drift for {candidate}: "
f"expected {metadata.message_type}, "
f"found {discovered_message_type}"
)
[docs]
def validate_registry(
registry: TemplateRegistry = DEFAULT_TEMPLATE_REGISTRY,
) -> list[str]:
"""Validate every registered template and return validated IDs."""
validated: list[str] = []
for metadata in registry.list_templates():
validate_template_bundle(metadata)
validated.append(metadata.message_type)
return validated