Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ notifications:
- ben.wilson@cargosense.com
matrix:
include:
- elixir: 1.18.4
otp_release: 28.0
- elixir: 1.6
otp_release: 21.0
- elixir: 1.7
Expand Down
46 changes: 42 additions & 4 deletions lib/vex/struct.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,63 @@ defmodule Vex.Struct do

defmacro __using__(_) do
quote do
@vex_validations %{}
Module.register_attribute(__MODULE__, :vex_validation_specs, accumulate: true)
@before_compile unquote(__MODULE__)
import unquote(__MODULE__)
def valid?(self), do: Vex.valid?(self)
end
end

defmacro __before_compile__(_) do
defmacro __before_compile__(env) do
specs =
env.module
|> Module.get_attribute(:vex_validation_specs, [])
|> Enum.reverse()

# Build the function body with pattern matching on each field
validation_map_ast = build_validation_map(specs)

quote do
def __vex_validations__(), do: @vex_validations
def __vex_validations__() do
unquote(validation_map_ast)
end

require Vex.Extract.Struct
Vex.Extract.Struct.for_struct()
end
end

# Build the validation map AST at compile time
@spec build_validation_map([{atom(), list()}]) :: Macro.t()
defp build_validation_map(specs) do
map_contents =
Enum.map(specs, fn {name, validations_escaped} ->
# The validations are escaped, so we need to create AST that will
# produce them at runtime
{name, process_validation_list(validations_escaped)}
end)

{:%{}, [], map_contents}
end

# Process the escaped validation list to handle regex patterns
@spec process_validation_list(Keyword.t()) :: Keyword.t()
defp process_validation_list(validations_escaped) do
# Create AST that will build the list at runtime
Enum.map(validations_escaped, fn
{:format, {:sigil_r, meta, args}} ->
# This is an escaped regex, convert it back to runtime regex creation
{:format, {:sigil_r, meta, args}}

{key, value} ->
{key, value}
end)
end

defmacro validates(name, validations \\ []) do
quote do
@vex_validations Map.put(@vex_validations, unquote(name), unquote(validations))
# Store the raw validations - they'll be processed at compile time
@vex_validation_specs {unquote(name), unquote(Macro.escape(validations))}
end
end
end