"""Slug value object for URL-friendly identifiers. This module defines the Slug value object for generating and validating URL-friendly slugs from titles. Enforces lowercase, alphanumeric, and hyphen-only format. """ import re from dataclasses import dataclass from app.domain.value_objects.base import ValueObject @dataclass(frozen=True, slots=True) class Slug(ValueObject[str]): """URL slug value object. Represents a URL-friendly identifier generated from titles. Validates format and provides factory method for slug generation. Attributes: value: The slug string value. MAX_LENGTH: Maximum allowed slug length (200 characters). SLUG_PATTERN: Regex pattern for valid slug format. Raises: ValueError: If slug format is invalid. Example: >>> slug = Slug.from_title("My First Post!") >>> print(slug.value) 'my-first-post' """ MAX_LENGTH: int = 200 SLUG_PATTERN: str = r"^[a-z0-9]+(?:-[a-z0-9]+)*$" def _validate(self) -> None: """Validate slug format. Ensures slug contains only lowercase letters, numbers, and hyphens. Raises: ValueError: If slug format is invalid. """ if not isinstance(self.value, str): raise ValueError("Slug must be a string") if len(self.value) > self.MAX_LENGTH: raise ValueError(f"Slug must be at most {self.MAX_LENGTH} characters") if not re.match(self.SLUG_PATTERN, self.value): raise ValueError("Slug must contain only lowercase letters, numbers, and hyphens") @classmethod def from_title(cls, title: str) -> "Slug": """Generate slug from title. Converts title to URL-friendly format by lowercasing, removing special characters, and replacing spaces with hyphens. Args: title: Source title string. Returns: New Slug instance with generated value. """ slug = title.lower().strip() slug = re.sub(r"[^a-z0-9\s-]", "", slug) slug = re.sub(r"[-\s]+", "-", slug) max_len = 200 slug = slug[:max_len].strip("-") if not slug: slug = "post" return cls(value=slug)