# Profile Form Validation Design ## Problem The self-service (`/manage/profile`) and admin (`/admin/users/{id}/profile`) profile forms have minimal validation: email only checks for `@`, phone number has no format validation at all. Validation logic is duplicated inline in both route handlers. ## Decision Use Pydantic types for field validation via a shared `ProfileUpdate` model. - **Email**: `pydantic.EmailStr` (uses `email-validator` under the hood) - **Phone**: `pydantic_extra_types.phone_numbers.PhoneNumberValidator` with `number_format="E164"` — accepts various input formats, normalizes to strict E.164 (`+46701234567`). Backed by Google's `libphonenumber`. - **Picture URL**: custom Pydantic validator (http/https scheme, has netloc) - **Field lengths**: `Field(max_length=...)` on each field ## New dependency `pydantic-extra-types[phonenumbers]` — brings in the `phonenumbers` package. ## Shared validation module `src/porchlight/validation.py`: ```python from typing import Annotated from pydantic import BaseModel, EmailStr, Field from pydantic_extra_types.phone_numbers import PhoneNumberValidator E164Phone = Annotated[ str, PhoneNumberValidator(number_format="E164") ] class ProfileUpdate(BaseModel): given_name: str = Field(default="", max_length=255) family_name: str = Field(default="", max_length=255) preferred_username: str = Field(default="", max_length=255) email: EmailStr | None = None phone_number: E164Phone | None = None picture: str = Field(default="", max_length=2048) # + URL validator locale: str = Field(default="", max_length=20) ``` Route handlers instantiate `ProfileUpdate` from form data, catch `ValidationError`, and return the first user-friendly error as the existing `HTMLResponse` with `role="alert"`. ## Route changes Both `manage/routes.py` and `admin/routes.py` profile POST handlers will: 1. Parse form data through `ProfileUpdate` 2. Use validated/normalized values from the model 3. Replace all inline validation with a single try/except ## Template changes Add `pattern` and `title` attributes on the phone input for browser-native hints about E.164 format. ## Validation: server-side only No client-side JS validation. The HTML `type="email"` and `type="tel"` attributes already provide basic browser hints. Server-side validation via htmx response handles all error display. ## Tests - Unit tests for `ProfileUpdate` (valid/invalid emails, phones, URLs, lengths) - Integration tests for manage profile POST (happy path + validation errors) - Existing admin tests may be tightened