Files
clan-core/pkgs/clan-cli/clan_lib/persist/validate.py
2025-10-16 11:57:38 +02:00

71 lines
2.6 KiB
Python

from typing import Any
from clan_lib.errors import ClanError
from clan_lib.persist.path_utils import (
PathTuple,
find_duplicates,
path_starts_with,
path_to_string,
)
from clan_lib.persist.write_rules import AttributeMap, is_writeable_path
def validate_no_static_deletion(
path: PathTuple, new_list: list[Any], static_items: list[Any]
) -> None:
"""Validate that we're not trying to delete static items from a list."""
missing_static = [item for item in static_items if item not in new_list]
if missing_static:
msg = f"Path '{path_to_string(path)}' doesn't contain static items {missing_static} - They are readonly - since they are defined via a .nix file"
raise ClanError(msg)
def validate_writeability(path: PathTuple, writeables: AttributeMap) -> None:
"""Validate that a path is writeable."""
if not is_writeable_path(path, writeables):
msg = f"Path '{path_to_string(path)}' is readonly. - It seems its value is statically defined in nix."
raise ClanError(msg)
def validate_type_compatibility(path: tuple, old_value: Any, new_value: Any) -> None:
"""Validate that type changes are allowed."""
if old_value is not None and type(old_value) is not type(new_value):
if new_value is None:
return # Deletion is handled separately
path_str = path_to_string(path)
msg = f"Type mismatch for path '{path_str}'. Cannot update {type(old_value)} with {type(new_value)}"
description = f"""
Previous value is of type '{type(old_value)}' this operation would change it to '{type(new_value)}'.
Prev: {old_value}
->
After: {new_value}
"""
raise ClanError(msg, description=description)
def validate_list_uniqueness(path: tuple, value: Any) -> None:
"""Validate that lists don't contain duplicates."""
if isinstance(value, list):
duplicates = find_duplicates(value)
if duplicates:
msg = f"Path '{path_to_string(path)}' contains list duplicates: {duplicates} - List values must be unique."
raise ClanError(msg)
def validate_patch_conflicts(
patches: set[PathTuple], delete_paths: set[PathTuple]
) -> None:
"""Ensure patches don't conflict with deletions."""
conflicts = {
path
for delete_path in delete_paths
for path in patches
if path_starts_with(path, delete_path)
}
if conflicts:
conflict_list = ", ".join(path_to_string(path) for path in sorted(conflicts))
msg = f"The following paths are marked for deletion but also have update values: {conflict_list}. - You cannot delete and patch the same path and its subpaths."
raise ClanError(msg)