Skip to content

Latest commit

 

History

History
117 lines (97 loc) · 3.18 KB

File metadata and controls

117 lines (97 loc) · 3.18 KB
id schema-web-standards-email
title Email Address Validation
category web-standards-validation
skillLevel beginner
tags
schema
validation
web-standards
email
branded-types
user-input
lessonOrder 18
rule
description
Email Address Validation using Schema.
summary You're accepting email addresses from user input or API requests. A simple `string` type tells you nothing—users submit "not-an-email", "@@invalid", or empty strings. You need validation that checks...

Problem

You're accepting email addresses from user input or API requests. A simple string type tells you nothing—users submit "not-an-email", "@@invalid", or empty strings. You need validation that checks format at runtime, creates a distinct type so emails can't be confused with other strings, and provides clear error messages.

Solution

import { Schema, Effect } from "effect"

// 1. Define branded Email type with validation
const Email = Schema.String.pipe(
  Schema.trimmed(),
  Schema.minLength(5),
  Schema.maxLength(254),
  Schema.pattern(
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  ).pipe(
    Schema.annotations({
      description: "Valid email format (local@domain.tld)",
    })
  ),
  Schema.brand("Email")
)

type Email = typeof Email.Type

// 2. Create parser
const parseEmail = Schema.decodeUnknown(Email)

// 3. Use in request schemas
const CreateUserRequest = Schema.Struct({
  name: Schema.String,
  email: Email,
  password: Schema.String.pipe(Schema.minLength(8)),
})

type CreateUserRequest = typeof CreateUserRequest.Type

// 4. Validate and use
const createUser = (input: unknown) =>
  Effect.gen(function* () {
    const request = yield* Schema.decodeUnknown(
      CreateUserRequest
    )(input).pipe(
      Effect.mapError((error) => ({
        _tag: "ValidationError" as const,
        message: `Invalid request: ${error.message}`,
      }))
    )

    // TypeScript knows request.email is Email, not string
    const confirmEmail = (email: Email) => {
      console.log(`Sending confirmation to ${email}`)
    }

    yield* Effect.sync(() => confirmEmail(request.email))
    return { success: true, email: request.email }
  })

// Usage
const userInput = {
  name: "Alice",
  email: "alice@example.com",
  password: "securepass123",
}

Effect.runPromise(createUser(userInput))
  .then((result) => console.log(result))
  .catch((error) =>
    console.error(`Error: ${error.message}`)
  )

Why This Works

Concept Explanation
Schema.pattern Regex validation at runtime—catches invalid format
Schema.trimmed() Remove leading/trailing whitespace
Schema.maxLength(254) Email RFC 5321 limit
Schema.brand("Email") Creates nominal type—Email ≠ string
Compile-time safety Functions requiring Email reject raw strings at compile time
Composable Reuse Email schema in any struct

When to Use

  • User registration forms
  • Contact/email collection forms
  • API request validation
  • Database operations where email is PK
  • Anywhere email is accepted and stored

Related Patterns