"""
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
"""
import enum
from typing import Dict, Optional
from ._const import Platform
def _to_error_code(code: int) -> str:
return f"PV{code:04d}"
class ErrorAttrKey:
BYTE_COUNT = "byte_count"
DESCRIPTION = "description"
FS_ENCODING = "fs_encoding"
PLATFORM = "platform"
REASON = "reason"
RESERVED_NAME = "reserved_name"
REUSABLE_NAME = "reusable_name"
[docs]
@enum.unique
class ErrorReason(enum.Enum):
"""
Validation error reasons.
"""
NULL_NAME = (_to_error_code(1001), "NULL_NAME", "the value must not be an empty")
RESERVED_NAME = (
_to_error_code(1002),
"RESERVED_NAME",
"found a reserved name by a platform",
)
INVALID_CHARACTER = (
_to_error_code(1100),
"INVALID_CHARACTER",
"invalid characters found",
)
INVALID_LENGTH = (
_to_error_code(1101),
"INVALID_LENGTH",
"found an invalid string length",
)
FOUND_ABS_PATH = (
_to_error_code(1200),
"FOUND_ABS_PATH",
"found an absolute path where must be a relative path",
)
MALFORMED_ABS_PATH = (
_to_error_code(1201),
"MALFORMED_ABS_PATH",
"found a malformed absolute path",
)
INVALID_AFTER_SANITIZE = (
_to_error_code(2000),
"INVALID_AFTER_SANITIZE",
"found invalid value after sanitizing",
)
@property
def code(self) -> str:
"""str: Error code."""
return self.__code
@property
def name(self) -> str:
"""str: Error reason name."""
return self.__name
@property
def description(self) -> str:
"""str: Error reason description."""
return self.__description
def __init__(self, code: str, name: str, description: str) -> None:
self.__name = name
self.__code = code
self.__description = description
def __str__(self) -> str:
return f"[{self.__code}] {self.__description}"
[docs]
class ValidationError(ValueError):
"""
Exception class of validation errors.
"""
@property
def platform(self) -> Optional[Platform]:
"""
:py:class:`~pathvalidate.Platform`: Platform information.
"""
return self.__platform
@property
def reason(self) -> ErrorReason:
"""
:py:class:`~pathvalidate.error.ErrorReason`: The cause of the error.
"""
return self.__reason
@property
def description(self) -> Optional[str]:
"""Optional[str]: Error description."""
return self.__description
@property
def reserved_name(self) -> str:
"""str: Reserved name."""
return self.__reserved_name
@property
def reusable_name(self) -> Optional[bool]:
"""Optional[bool]: Whether the name is reusable or not."""
return self.__reusable_name
@property
def fs_encoding(self) -> Optional[str]:
"""Optional[str]: File system encoding."""
return self.__fs_encoding
@property
def byte_count(self) -> Optional[int]:
"""Optional[int]: Byte count of the path."""
return self.__byte_count
def __init__(self, *args, **kwargs) -> None: # type: ignore
if ErrorAttrKey.REASON not in kwargs:
raise ValueError(f"{ErrorAttrKey.REASON} must be specified")
self.__reason: ErrorReason = kwargs.pop(ErrorAttrKey.REASON)
self.__byte_count: Optional[int] = kwargs.pop(ErrorAttrKey.BYTE_COUNT, None)
self.__platform: Optional[Platform] = kwargs.pop(ErrorAttrKey.PLATFORM, None)
self.__description: Optional[str] = kwargs.pop(ErrorAttrKey.DESCRIPTION, None)
self.__reserved_name: str = kwargs.pop(ErrorAttrKey.RESERVED_NAME, "")
self.__reusable_name: Optional[bool] = kwargs.pop(ErrorAttrKey.REUSABLE_NAME, None)
self.__fs_encoding: Optional[str] = kwargs.pop(ErrorAttrKey.FS_ENCODING, None)
try:
super().__init__(*args[0], **kwargs)
except IndexError:
super().__init__(*args, **kwargs)
[docs]
def as_slog(self) -> Dict[str, str]:
"""Return a dictionary representation of the error.
Returns:
Dict[str, str]: A dictionary representation of the error.
"""
slog: Dict[str, str] = {
"code": self.reason.code,
ErrorAttrKey.DESCRIPTION: self.reason.description,
}
if self.platform:
slog[ErrorAttrKey.PLATFORM] = self.platform.value
if self.description:
slog[ErrorAttrKey.DESCRIPTION] = self.description
if self.__reusable_name is not None:
slog[ErrorAttrKey.REUSABLE_NAME] = str(self.__reusable_name)
if self.__fs_encoding:
slog[ErrorAttrKey.FS_ENCODING] = self.__fs_encoding
if self.__byte_count:
slog[ErrorAttrKey.BYTE_COUNT] = str(self.__byte_count)
return slog
def __str__(self) -> str:
item_list = []
header = str(self.reason)
if Exception.__str__(self):
item_list.append(Exception.__str__(self))
if self.platform:
item_list.append(f"{ErrorAttrKey.PLATFORM}={self.platform.value}")
if self.description:
item_list.append(f"{ErrorAttrKey.DESCRIPTION}={self.description}")
if self.__reusable_name is not None:
item_list.append(f"{ErrorAttrKey.REUSABLE_NAME}={self.reusable_name}")
if self.__fs_encoding:
item_list.append(f"{ErrorAttrKey.FS_ENCODING}={self.__fs_encoding}")
if self.__byte_count is not None:
item_list.append(f"{ErrorAttrKey.BYTE_COUNT}={self.__byte_count:,d}")
if item_list:
header += ": "
return header + ", ".join(item_list).strip()
def __repr__(self) -> str:
return self.__str__()
class NullNameError(ValidationError):
"""[Deprecated]
Exception raised when a name is empty.
"""
def __init__(self, *args, **kwargs) -> None: # type: ignore
kwargs[ErrorAttrKey.REASON] = ErrorReason.NULL_NAME
super().__init__(args, **kwargs)
class InvalidCharError(ValidationError):
"""
Exception raised when includes invalid character(s) within a string.
"""
def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
kwargs[ErrorAttrKey.REASON] = ErrorReason.INVALID_CHARACTER
super().__init__(args, **kwargs)
class ReservedNameError(ValidationError):
"""
Exception raised when a string matched a reserved name.
"""
def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
kwargs[ErrorAttrKey.REASON] = ErrorReason.RESERVED_NAME
super().__init__(args, **kwargs)
class ValidReservedNameError(ReservedNameError):
"""[Deprecated]
Exception raised when a string matched a reserved name.
However, it can be used as a name.
"""
def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
kwargs[ErrorAttrKey.REUSABLE_NAME] = True
super().__init__(args, **kwargs)
class InvalidReservedNameError(ReservedNameError):
"""[Deprecated]
Exception raised when a string matched a reserved name.
Moreover, the reserved name is invalid as a name.
"""
def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
kwargs[ErrorAttrKey.REUSABLE_NAME] = False
super().__init__(args, **kwargs)