lib/surgex/parser/parsers/string_parser.ex
defmodule Surgex.Parser.StringParser do
@moduledoc """
Available options:
- **trim** is trimming whitespaces from the string, takes priority over min and max options
- **min** is a minimal length of the string, returns :too_short error symbol
- **max** is a maximal length of the string, returns :too_long error symbol
- **regex** - input string must match passed regular expression, this is done after trimming
"""
@type errors :: :too_short | :too_long | :invalid_string | :bad_format
@type option :: {:trim, boolean()} | {:min, integer()} | {:max, integer()} | {:regex, Regex.t()}
@opts [:trim, :min, :max, :regex]
@spec call(term(), [option()]) :: {:ok, String.t() | nil} | {:error, errors()}
def call(input, opts \\ [])
def call(nil, _opts), do: {:ok, nil}
def call(input, opts) when is_binary(input) do
validate_opts!(opts)
output =
%{value: input, error: nil, opts: opts}
|> process_opts()
|> nullify_empty_output()
case output do
%{value: output, error: nil} -> {:ok, output}
%{error: error} -> {:error, error}
end
end
def call(_input, _opts), do: {:error, :invalid_string}
@spec validate_opts!(list) :: :ok
defp validate_opts!([]), do: :ok
defp validate_opts!(opts) do
invalid_opts = Keyword.keys(opts) -- @opts
if Enum.any?(invalid_opts) do
raise ArgumentError, message: "opts: #{invalid_opts} are invalid for string parser"
end
end
defp process_opts(input = %{opts: []}), do: input
defp process_opts(input) do
input
|> trim()
|> validate_min()
|> validate_max()
|> check_regex()
end
@spec trim(%{opts: list, value: String.t(), error: nil}) :: %{
opts: list,
value: String.t(),
error: nil
}
def trim(input = %{opts: opts, value: value}) do
trim_value = Keyword.get(opts, :trim)
if is_nil(trim_value) do
input
else
%{input | value: String.trim(value)}
end
end
@spec validate_min(%{opts: list, value: String.t(), error: nil}) :: %{
opts: list,
value: String.t(),
error: nil | :too_short
}
def validate_min(input = %{opts: opts, value: value}) do
min_value = Keyword.get(opts, :min)
cond do
is_nil(min_value) -> input
String.length(value) >= min_value -> input
true -> %{input | error: :too_short}
end
end
@spec validate_max(%{opts: list, value: String.t(), error: nil}) :: %{
opts: list,
value: String.t(),
error: nil | :too_long
}
def validate_max(input = %{opts: opts, value: value}) do
max_value = Keyword.get(opts, :max)
cond do
is_nil(max_value) -> input
String.length(value) <= max_value -> input
true -> %{input | error: :too_long}
end
end
@spec check_regex(%{opts: list, value: String.t(), error: nil}) :: %{
opts: list,
value: String.t(),
error: nil | :bad_format
}
def check_regex(input = %{opts: opts, value: value}) do
regex = Keyword.get(opts, :regex)
cond do
is_nil(regex) -> input
Regex.match?(regex, value) -> input
true -> %{input | error: :bad_format}
end
end
defp nullify_empty_output(input = %{value: ""}), do: %{input | value: nil}
defp nullify_empty_output(input), do: input
end