"""A plug-able validator to help raise exception when some value is unexpected.""" from typing import Any, Dict, Generic, Optional, TypeVar, cast from samtranslator.model.exceptions import ( ExpectedType, InvalidEventException, InvalidResourceAttributeTypeException, InvalidResourceException, InvalidResourcePropertyTypeException, ) T = TypeVar("T") class _ResourcePropertyValueValidator(Generic[T]): value: Optional[T] resource_id: str key_path: str is_sam_event: bool is_resource_attribute: bool def __init__( self, value: Optional[T], resource_id: str, key_path: str, is_sam_event: bool = False, is_resource_attribute: bool = False, ) -> None: self.value = value self.resource_id = resource_id self.key_path = key_path self.is_sam_event = is_sam_event self.is_resource_attribute = is_resource_attribute @property def resource_logical_id(self) -> Optional[str]: return None if self.is_sam_event else self.resource_id @property def event_id(self) -> Optional[str]: return self.resource_id if self.is_sam_event else None def to_be_a(self, expected_type: ExpectedType, message: Optional[str] = "") -> T: """ Validate the type of the value and return the value if valid. raise InvalidResourceException for invalid values. """ type_description, type_class = expected_type.value if not isinstance(self.value, type_class): if self.event_id: raise InvalidEventException( self.event_id, message or f"Property '{self.key_path}' should be a {type_description}." ) if self.resource_logical_id: if self.is_resource_attribute: raise InvalidResourceAttributeTypeException( self.resource_logical_id, self.key_path, expected_type, message ) raise InvalidResourcePropertyTypeException( self.resource_logical_id, self.key_path, expected_type, message ) raise RuntimeError("event_id and resource_logical_id are both None") # mypy is not smart to derive class from expected_type.value[1], ignore types: return self.value # type: ignore def to_not_be_none(self, message: Optional[str] = "") -> T: """ Validate the value is not None and return the value if valid. raise InvalidResourceException for None values. """ if self.value is None: if not message: message = f"Property '{self.key_path}' is required." if self.event_id: raise InvalidEventException(self.event_id, message) if self.resource_logical_id: raise InvalidResourceException(self.resource_logical_id, message) raise RuntimeError("event_id and resource_logical_id are both None") return self.value # # alias methods: # def to_be_a_map(self, message: Optional[str] = "") -> Dict[str, Any]: """ Return the value with type hint "Dict[str, Any]". Raise InvalidResourceException/InvalidEventException if the value is not. """ return cast(Dict[str, Any], self.to_be_a(ExpectedType.MAP, message)) def to_be_a_list(self, message: Optional[str] = "") -> T: return self.to_be_a(ExpectedType.LIST, message) def to_be_a_list_of(self, expected_type: ExpectedType, message: Optional[str] = "") -> T: """ Return the value with type hint "List[T]". Raise InvalidResourceException/InvalidEventException if the value is not. """ value = self.to_be_a(ExpectedType.LIST, message) for index, item in enumerate(value): # type: ignore sam_expect(item, self.resource_id, f"{self.key_path}[{index}]", is_sam_event=self.is_sam_event).to_be_a( expected_type, message ) return value def to_be_a_string(self, message: Optional[str] = "") -> str: """ Return the value with type hint "str". Raise InvalidResourceException/InvalidEventException if the value is not. """ return cast(str, self.to_be_a(ExpectedType.STRING, message)) def to_be_an_integer(self, message: Optional[str] = "") -> int: """ Return the value with type hint "int". Raise InvalidResourceException/InvalidEventException if the value is not. """ return cast(int, self.to_be_a(ExpectedType.INTEGER, message)) def to_be_a_bool(self, message: Optional[str] = "") -> bool: """ Return the value with type hint "bool". Raise InvalidResourceException/InvalidEventException if the value is not. """ return cast(bool, self.to_be_a(ExpectedType.BOOLEAN, message)) sam_expect = _ResourcePropertyValueValidator