| title | Validating and Parsing Branded Types | |||||||
|---|---|---|---|---|---|---|---|---|
| id | brand-validate-parse | |||||||
| skillLevel | intermediate | |||||||
| applicationPatternId | domain-modeling | |||||||
| summary | Use Schema and Brand together to validate and parse branded types at runtime, ensuring only valid values are constructed. | |||||||
| tags |
|
|||||||
| rule |
|
|||||||
| related |
|
|||||||
| author | PaulJPhilp | |||||||
| lessonOrder | 12 |
Use Schema in combination with Brand to validate and parse branded types at runtime.
This ensures that only values passing your validation logic can be constructed as branded types, making your domain models robust and type-safe.
While branding types at the type level prevents accidental misuse, runtime validation is needed to ensure only valid values are constructed from user input, APIs, or external sources.
import { Brand, Effect, Schema } from "effect";
// Define a branded type for Email
type Email = string & Brand.Brand<"Email">;
// Create a Schema for Email validation
const EmailSchema = Schema.String.pipe(
Schema.pattern(/^[^@]+@[^@]+\.[^@]+$/), // Simple email regex
Schema.brand("Email" as const) // Attach the brand
);
// Parse and validate an email at runtime
const parseEmail = (input: string) =>
Effect.try({
try: () => Schema.decodeSync(EmailSchema)(input),
catch: (err) => `Invalid email: ${String(err)}`,
});
// Usage
parseEmail("user@example.com").pipe(
Effect.match({
onSuccess: (email) => console.log("Valid email:", email),
onFailure: (err) => console.error(err),
})
);Explanation:
Schemais used to define validation logic for the branded type.Brand.schema<Email>()attaches the brand to the schema, so only validated values can be constructed asEmail.- This pattern ensures both compile-time and runtime safety.
Branding values without runtime validation, or accepting unvalidated user input as branded types, which can lead to invalid domain values and runtime bugs.