78 lines
2 KiB
Elixir
78 lines
2 KiB
Elixir
defmodule Mv.Membership.Email do
|
|
@moduledoc """
|
|
Custom Ash type for validated email addresses.
|
|
|
|
## Overview
|
|
This type extends `:string` with email-specific validation constraints.
|
|
It ensures that email values stored in CustomFieldValue resources are valid email
|
|
addresses according to a standard regex pattern.
|
|
|
|
## Validation Rules
|
|
- **Optional**: `nil` and empty strings are allowed (custom fields are optional)
|
|
- Minimum length: 5 characters (for non-empty values)
|
|
- Maximum length: 254 characters (RFC 5321 maximum)
|
|
- Pattern: Standard email format (username@domain.tld)
|
|
- Automatic trimming of leading/trailing whitespace (empty strings become `nil`)
|
|
|
|
## Usage
|
|
This type is used in the CustomFieldValue union type for custom fields with
|
|
`value_type: :email` in CustomField definitions.
|
|
|
|
## Example
|
|
# In a custom field definition
|
|
CustomField.create!(%{
|
|
name: "work_email",
|
|
value_type: :email
|
|
})
|
|
|
|
# Valid values
|
|
"user@example.com"
|
|
"first.last@company.co.uk"
|
|
|
|
# Invalid values
|
|
"not-an-email" # Missing @ and domain
|
|
"a@b" # Too short
|
|
"""
|
|
@match_pattern ~S/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
|
|
@match_regex Regex.compile!(@match_pattern)
|
|
@min_length 5
|
|
@max_length 254
|
|
|
|
use Ash.Type.NewType,
|
|
subtype_of: :string,
|
|
constraints: [
|
|
match: @match_pattern,
|
|
trim?: true,
|
|
min_length: @min_length,
|
|
max_length: @max_length
|
|
]
|
|
|
|
@impl true
|
|
def cast_input(nil, _), do: {:ok, nil}
|
|
|
|
@impl true
|
|
def cast_input(value, _) when is_binary(value) do
|
|
value = String.trim(value)
|
|
|
|
cond do
|
|
# Empty string after trim becomes nil (optional field)
|
|
value == "" ->
|
|
{:ok, nil}
|
|
|
|
String.length(value) < @min_length ->
|
|
:error
|
|
|
|
String.length(value) > @max_length ->
|
|
:error
|
|
|
|
!Regex.match?(@match_regex, value) ->
|
|
:error
|
|
|
|
true ->
|
|
{:ok, value}
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def cast_input(_, _), do: :error
|
|
end
|