Search⌘ K

Validating Our Templates

Explore how to validate template data structures in Elixir by composing validators for required fields, lists, and function checkers. Understand error handling and leverage pattern matching to ensure template integrity using GenServer boundaries.

The template fields represent a sterner test. The checker and generators fields will require us to validate lists and functions. Still, our simple framework that’s based on composition will make quick work of them.

Declaring the main module and core functions

Let’s start with the basic errors function that composes validations over each field. As before, we’ll enumerate required and optional fields, in lib/mastery/boundary/template_validator.ex, like this:

C++
defmodule Mastery.Boundary.TemplateValidator do import Mastery.Boundary.Validator
def errors(fields) when is_list(fields) do
fields = Map.new(fields)
[]
|> require(fields, :name, &validate_name/1)
|> require(fields, :category, &validate_name/1)
|> optional(fields, :instructions, &validate_instructions/1)
|> require(fields, :raw, &validate_raw/1)
|> require(fields, :generators, &validate_generators/1)
|> require(fields, :checker, &validate_checker/1)
end
def errors(_fields), do: [{nil, "A keyword list of fields is required"}]

The technique works exactly as it did in the QuizValidator.

Let’s try out a few validations on the template as a whole:

Executable

C++
alias Mastery.Boundary.TemplateValidator
C++
checker = fn(sub, answer) ->
sub[:left] + sub[:right] == String.to_integer(answer)
end
C++
template =
[
name: :single_digit_addition,
category: :addition,
instructions: "Add the numbers",
raw: "<%= @left %> + <%= @right %>",
generators: %{
left: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
right: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
checker: checker
]
C++
TemplateValidator.errors(template)
C++
template =
[
name: :single_digit_addition,
instructions: "Add the numbers",
raw: "<%= @left %> + <%= @right %>",
generators: %{
left: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
right: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
},
checker: checker
]
C++
TemplateValidator.errors(template)

Output

iex(1)> alias Mastery.Boundary.{TemplateValidator}
[Mastery.Boundary.TemplateValidator]
iex(2)> checker = fn(sub, answer) ->
...(2)> sub[:left] + sub[:right] == String.to_integer(answer) 
...(2)> end
#Function<13.126501267/2 in :erl_eval.expr/5>
iex(3)> template =  
...(3)> [
...(3)>   name: :single_digit_addition,
...(3)>
...