Coverage for snekql/validation.py: 100%

21 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-07 21:13 +0300

1"""Boundary validation helpers for constrained public API values.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Callable 

6from functools import wraps 

7from typing import Annotated 

8 

9from annotated_types import Ge, Gt 

10from pydantic import ConfigDict, ValidationError, validate_call 

11 

12from snekql.errors import SnekqlError 

13 

14# Constrained numeric aliases used after public boundary validation. 

15type NonNegativeFloat = Annotated[float, Ge(0)] 

16type NonNegativeInt = Annotated[int, Ge(0)] 

17type PositiveInt = Annotated[int, Gt(0)] 

18 

19 

20def validate_boundary[ErrorT: SnekqlError, **P, R]( 

21 *, 

22 error_type: type[ErrorT], 

23) -> Callable[[Callable[P, R]], Callable[P, R]]: 

24 """Validate public API calls and wrap validation errors as domain errors.""" 

25 

26 def decorate(function: Callable[P, R]) -> Callable[P, R]: 

27 validated = validate_call( 

28 config=ConfigDict(arbitrary_types_allowed=True, strict=True), 

29 )(function) 

30 

31 @wraps(function) 

32 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: 

33 try: 

34 return validated(*args, **kwargs) 

35 except ValidationError as error: 

36 raise error_type(str(error)) from error 

37 

38 return wrapper 

39 

40 return decorate