Current section
Files
Jump to
Current section
Files
lib/parseus.ex
defmodule Parseus do
@moduledoc """
Legendary Elixir parser to tame all your input enumerables for good.
## Usage
Here's a basic usage:
input = %{
"name" => "Mike",
"email" => "mike@example.com"
"age" => "21",
"license-agreement" => "1",
"notes" => "Please don't send me e-mails!",
}
import Parseus
%{output: output, errors: []} =
input
|> cast(["name", "email", "license-agreement", "age", "notes"])
|> validate_required([:name, :email, :license_agreement])
|> validate_format(:email, ~r/^.*@example\\.com$/)
|> parse_boolean(:lincense_agreement)
|> validate_equal(:license_agreement, true)
|> drop_key(:license_agreement)
|> parse_integer(:age)
IO.inspect(output)
# [full_name: "Mike", email: "mike@example.com", age: 21, notes: "..."]
## Details
### Parsing key(s)
Key parsers get invoked via all `parse_*` built-in parsing functions which ultimately call generic
`parse/4` (which can also be invoked with user-defined parsers).
Here's how they work:
- if there's no specific key in the input, the parser will not execute
- if there's already an error associated with the key, the parser will not execute
- otherwise the parser gets called with the current value of the key
- if parser succeeds, the output value associated with the key gets updated
- if parser fails, the key gets removed from the output and appropriate error gets added
This basically means that if you pipe multiple parsers on the same key, they'll all get executed
in a sequence with the output from previous parser getting passed to the next one, until the first
parser failure, in which case subsequent parsers will not be called at all. In case of failure,
the input value is no longer considered usable as an output and gets removed from it.
### Validating key(s)
Key validators get invoked via all `validate_*` built-in validation functions which ultimately
call generic `validate/4` (which can also be invoked with user-defined validators).
Here's how they work:
- if there's no specific key in the input, the parser will not execute
- otherwise the validator gets called with the current value of the key
- if validator succeeds, nothing happens
- if validator fails, an appropriate error gets added
Key validators are a bit similar to key parsers, but they don't change the output (because they're
not meant for that) and they still get called if there's already an error associated with the key
(because we want to have as many errors as possible).
> Note that there's still a way to avoid calling the specific validator upon some previous failed
> assertion - this is where the parser's property of removing failed keys comes to use. You can
> just call the parser before the validator and if parser fails, the validator won't get called.
### Validating multiple keys
Global validators get invoked via the generic `validate_all/3` which can be invoked with
user-defined validators.
Here's how they work:
- the validator gets called with the set of current values
- if validator succeeds, nothing happens
- if validator fails, an appropriate error or set of errors gets added
As opposed to key validators, global validators get the whole set of current values as its input
instead of a value of a single key. This allows them to implement a cross-key logical validation.
They're also called regardless of which keys are filled in the input.
"""
alias __MODULE__.{
BlankStringToNilMapper,
BooleanParser,
DateParser,
EnumParser,
FloatParser,
IntegerParser,
ListParser,
AddErrorProcessor,
CastAllInProcessor,
CastInProcessor,
CastProcessor,
DropInvalidProcessor,
DropNilProcessor,
DropProcessor,
FilterProcessor,
ForkProcessor,
JoinProcessor,
MapProcessor,
ParseProcessor,
RenameProcessor,
ValidateAllProcessor,
ValidateProcessor,
FlattenErrorsUtil,
GetInputPathUtil,
ResolveUtil,
ResolveTupleUtil,
AcceptanceValidator,
BooleanValidator,
ExclusionValidator,
FormatValidator,
InclusionValidator,
LengthValidator,
NumberValidator,
RequiredValidator,
TypeValidator,
}
def add_error(set, output_key_or_path, error) do
AddErrorProcessor.call(set, output_key_or_path, error)
end
def cast(input, input_key_or_keys) do
CastProcessor.call(input, input_key_or_keys)
end
def cast_all_in(input, input_key_or_path, output_key, mod_or_func) do
CastAllInProcessor.call(input, input_key_or_path, output_key, mod_or_func)
end
def cast_in(input, input_key_or_path, output_key \\ nil, mod_or_func) do
CastInProcessor.call(input, input_key_or_path, output_key, mod_or_func)
end
def drop(set, key_or_keys) do
DropProcessor.call(set, key_or_keys)
end
def drop_invalid(set, key_or_keys \\ nil) do
DropInvalidProcessor.call(set, key_or_keys)
end
def drop_nil(set, key_or_keys \\ nil) do
DropNilProcessor.call(set, key_or_keys)
end
def filter(set, key_or_keys, mod_or_func) do
FilterProcessor.call(set, key_or_keys, mod_or_func)
end
def flatten_errors(set) do
FlattenErrorsUtil.call(set)
end
def fork(set, source_key, target_key) do
ForkProcessor.call(set, source_key, target_key)
end
def join(set, old_keys, new_key, opts \\ []) do
JoinProcessor.call(set, old_keys, new_key, opts)
end
def get_input_path(set, output_key_or_path) do
GetInputPathUtil.call(set, output_key_or_path)
end
def map(set, key_or_keys, mod_or_func) do
MapProcessor.call(set, key_or_keys, mod_or_func)
end
def map_blank_string_to_nil(set, key_or_keys) do
map(set, key_or_keys, BlankStringToNilMapper)
end
def parse(set, key_or_keys, mod_or_func, opts \\ []) do
ParseProcessor.call(set, key_or_keys, mod_or_func, opts)
end
def parse_boolean(set, key_or_keys) do
parse(set, key_or_keys, BooleanParser)
end
def parse_date(set, key_or_keys) do
parse(set, key_or_keys, DateParser)
end
def parse_enum(set, key_or_keys, allowed_values) do
parse(set, key_or_keys, EnumParser, allowed_values)
end
def parse_float(set, key_or_keys) do
parse(set, key_or_keys, FloatParser)
end
def parse_integer(set, key_or_keys) do
parse(set, key_or_keys, IntegerParser)
end
def parse_list(set, key_or_keys, opts \\ []) do
parse(set, key_or_keys, ListParser, opts)
end
def rename(set, old_key, new_key) do
RenameProcessor.call(set, old_key, new_key)
end
def resolve(set) do
ResolveUtil.call(set)
end
def resolve_tuple(set, key_or_keys) do
ResolveTupleUtil.call(set, key_or_keys)
end
def validate(set, key_or_keys, mod_or_func, opts \\ []) do
ValidateProcessor.call(set, key_or_keys, mod_or_func, opts)
end
def validate_acceptance(set, key_or_keys) do
validate(set, key_or_keys, AcceptanceValidator)
end
def validate_all(set, validator, opts \\ []) do
ValidateAllProcessor.call(set, validator, opts)
end
def validate_boolean(set, key_or_keys) do
validate(set, key_or_keys, BooleanValidator)
end
def validate_exclusion(set, key_or_keys, forbidden_values) do
validate(set, key_or_keys, ExclusionValidator, forbidden_values)
end
def validate_format(set, key_or_keys, format) do
validate(set, key_or_keys, FormatValidator, format)
end
def validate_inclusion(set, key_or_keys, allowed_values) do
validate(set, key_or_keys, InclusionValidator, allowed_values)
end
def validate_length(set, key_or_keys, opts) do
validate(set, key_or_keys, LengthValidator, opts)
end
def validate_number(set, key_or_keys, opts \\ []) do
validate(set, key_or_keys, NumberValidator, opts)
end
def validate_required(set, key_or_keys) do
validate_all(set, RequiredValidator, key_or_keys)
end
def validate_type(set, key_or_keys, type_or_types) do
validate(set, key_or_keys, TypeValidator, type_or_types)
end
end