porchlight/docs/plans/2026-02-20-profile-validation-design.md
2026-02-20 15:01:55 +01:00

2.5 KiB

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:

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