Packages
spark
2.2.35
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.1
2.4.0
2.3.14
2.3.13
2.3.12
2.3.11
2.3.10
2.3.9
2.3.8
2.3.7
2.3.6
2.3.5
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.69
2.2.68
2.2.67
2.2.66
2.2.65
2.2.64
2.2.63
2.2.62
2.2.61
2.2.60
2.2.59
2.2.58
2.2.57
2.2.56
2.2.55
2.2.54
2.2.53
2.2.52
2.2.51
2.2.50
2.2.49
2.2.48
2.2.47
2.2.46
2.2.45
2.2.44
2.2.43
2.2.42
2.2.41
2.2.40
2.2.39
2.2.38
2.2.37
2.2.36
2.2.35
2.2.34
2.2.33
2.2.32
2.2.31
2.2.30
2.2.29
2.2.28
2.2.27
2.2.26
2.2.25
2.2.24
2.2.23
2.2.22
2.2.21
2.2.20
2.2.19
2.2.18
2.2.17
2.2.16
2.2.15
2.2.14
2.2.13
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.24
2.1.23
2.1.22
2.1.21
2.1.20
2.1.19
2.1.18
2.1.17
2.1.16
2.1.15
2.1.14
2.1.13
2.1.12
2.1.11
2.1.10
2.1.9
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.1
2.0.0
1.1.55
1.1.54
1.1.53
1.1.52
1.1.51
1.1.50
1.1.49
1.1.48
1.1.47
1.1.46
1.1.45
1.1.44
1.1.43
1.1.42
1.1.41
1.1.40
1.1.39
1.1.38
1.1.37
1.1.36
1.1.35
1.1.34
1.1.32
1.1.31
1.1.30
1.1.29
1.1.28
1.1.27
1.1.26
1.1.25
1.1.24
1.1.22
1.1.21
1.1.20
1.1.19
1.1.18
1.1.17
1.1.16
1.1.15
1.1.14
retired
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.16
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.29
0.1.28
0.1.27
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Generic tooling for building DSLs
Current section
Files
Jump to
Current section
Files
lib/spark/options/validator.ex
defmodule Spark.Options.Validator do
@moduledoc """
Defines a validator module for an option schema.
Validators create structs with keys for each option in their schema,
and an optimized `validate`, and `validate!` function on that struct.
## Upgrading from options lists
You can pass the option `define_deprecated_access?: true` to `use Spark.Options.Validator`,
which will make it such that `options[:foo]` will still work, but will emit a deprecation warning.
This cane help with smoother upgrades.
## Example
Given a module like the following:
```elixir
defmodule MyOptions do
use Spark.Options.Validator, schema: [
foo: [
type: :string,
required: true
],
bar: [
type: :string
],
baz: [
type: :integer,
default: 10
]
]
end
```
You can use it like so:
```elixir
# validate options
MyOptions.validate!(foo: "foo")
# %MyOptions{foo: "foo", bar: nil, baz: 10}
# retrieve original schema
MyOptions.schema()
# foo: [type: :string, required: true], bar: [type: :string], baz: [type: :integer, default: 10]
```
"""
defmacro __using__(opts) do
schema = opts[:schema]
define_deprecated_access? = opts[:define_deprecated_access?]
[
quote bind_quoted: [schema: schema, define_deprecated_access?: define_deprecated_access?] do
schema = Spark.Options.new!(schema).schema
@schema Keyword.new(schema)
struct_fields =
Keyword.new(@schema, fn {key, config} ->
case Keyword.fetch(config, :default) do
{:ok, default} ->
case Spark.Options.validate_single_value(config[:type], key, default) do
{:ok, default} ->
{key, default}
{:error, error} ->
raise error
end
:error ->
{key, nil}
end
end)
@defaults @schema
|> Enum.filter(fn {_key, config} ->
Keyword.has_key?(config, :default)
end)
|> Enum.map(&elem(&1, 0))
defstruct struct_fields ++ [__set__: @defaults]
schema_specs = Spark.Options.Docs.schema_specs(@schema, true)
quote do
@type t :: %__MODULE__{unquote_splicing(schema_specs)}
end
|> Code.eval_quoted([], __ENV__)
@type options :: [unquote_splicing(schema_specs)]
required_fields =
Spark.Options.Validator.validate_schema!(@schema)
@required @schema
|> Enum.filter(fn {_key, config} ->
config[:required]
end)
|> Enum.map(&elem(&1, 0))
@valid_options schema
|> Enum.reject(fn {_key, config} ->
config[:private?]
end)
|> Enum.map(&elem(&1, 0))
@spec schema :: Spark.Options.schema()
def schema do
@schema
end
@spec docs(Keyword.t()) :: String.t()
@spec docs() :: String.t()
def docs(opts \\ []) do
Spark.Options.docs(@schema |> Keyword.take(@valid_options), opts)
end
if define_deprecated_access? do
def fetch(%__MODULE__{} = data, key) do
IO.warn(
"Accessing options from #{__MODULE__} is deprecated. Use `opts.#{key}` instead."
)
Map.fetch(data, key)
end
end
@spec to_options(t(), Keyword.t() | nil) :: options()
@spec to_options(t()) :: options()
def to_options(self, take \\ nil)
def to_options(self, nil) do
Enum.reduce(self.__set__, [], fn key, acc ->
[{key, Map.get(self, key)} | acc]
end)
end
def to_options(self, take) do
self
|> to_options()
|> Keyword.take(take)
end
@spec validate!(options()) :: t() | no_return
def validate!(%__MODULE__{} = opts), do: opts
def validate!(options) do
Enum.reduce(options, {%__MODULE__{}, @required}, fn {key, value}, acc ->
case validate_option(key, value, acc) do
{:cont, {struct, missing}} ->
{mark_set(struct, key), missing}
{:halt, {:error, error}} ->
raise error
end
end)
|> case do
{schema, []} ->
schema
{_schema, missing} ->
raise %Spark.Options.ValidationError{
key: missing,
message: "Missing required keys: #{inspect(missing)}"
}
end
end
@spec validate(options) :: {:ok, t()} | {:error, term()}
def validate(%__MODULE__{} = opts), do: {:ok, opts}
def validate(options) do
Enum.reduce_while(options, {%__MODULE__{}, @required}, fn {key, value}, acc ->
case validate_option(key, value, acc) do
{:cont, {struct, missing}} -> {:cont, {mark_set(struct, key), missing}}
{:halt, {:error, error}} -> {:halt, {:error, error}}
end
end)
|> case do
{:error, error} ->
{:error, error}
{schema, []} ->
{:ok, schema}
{_schema, missing} ->
{:error,
%Spark.Options.ValidationError{
key: missing,
message: "Missing required keys: #{inspect(missing)}"
}}
end
end
end,
quote bind_quoted: [schema: schema] do
@compile {:inline, validate_option: 3, use_key: 2, mark_set: 2, warn_deprecated: 1}
for {key, config} <- Keyword.new(schema) do
type = Macro.escape(config[:type])
cond do
config[:private?] ->
defp validate_option(unquote(key), value, {acc, required_fields}) do
{:cont, {acc, required_fields}}
end
# we can add as many of these as we like to front load validations
type == :integer ->
defp validate_option(unquote(key), value, {acc, required_fields})
when is_integer(value) do
{:cont, {%{acc | unquote(key) => value}, use_key(required_fields, unquote(key))}}
end
defp validate_option(unquote(key), value, _) do
{:halt,
{:error,
%Spark.Options.ValidationError{
key: unquote(key),
message:
"invalid value for #{Spark.Options.render_key(unquote(key))}: expected integer, got: #{inspect(value)}",
value: value
}}}
end
type == :string ->
defp validate_option(unquote(key), value, {acc, required_fields})
when is_binary(value) do
{:cont, {%{acc | unquote(key) => value}, use_key(required_fields, unquote(key))}}
end
defp validate_option(unquote(key), value, _) do
{:halt,
{:error,
%Spark.Options.ValidationError{
key: unquote(key),
message:
"invalid value for #{Spark.Options.render_key(unquote(key))}: expected integer, got: #{inspect(value)}",
value: value
}}}
end
true ->
defp validate_option(unquote(key), value, {acc, required_fields}) do
case Spark.Options.validate_single_value(unquote(type), unquote(key), value) do
{:ok, value} ->
{:cont,
{%{acc | unquote(key) => value}, use_key(required_fields, unquote(key))}}
{:error, error} ->
{:halt, {:error, error}}
end
end
end
end
defp validate_option(unknown_key, value, _) do
{:halt,
{:error,
%Spark.Options.ValidationError{
key: unknown_key,
message:
"unknown options #{inspect([unknown_key])}, valid options are: #{inspect(@valid_options)}",
value: value
}}}
end
for {key, config} <- Keyword.new(schema), !config[:private?] do
if config[:required] do
defp use_key(list, unquote(key)) do
warn_deprecated(unquote(key))
List.delete(list, unquote(key))
end
else
defp use_key(list, unquote(key)) do
warn_deprecated(unquote(key))
list
end
end
end
for {key, config} <- Keyword.new(schema),
Keyword.has_key?(config, :default),
!config[:private?] do
defp mark_set(struct, unquote(key)) do
struct
end
end
defp mark_set(struct, key) do
%{struct | __set__: [key | struct.__set__]}
end
for {key, config} <- Keyword.new(schema), config[:deprecated] do
defp warn_deprecated(unquote(key)) do
IO.warn("#{unquote(key)} is deprecated")
end
end
defp warn_deprecated(_key) do
:ok
end
end
]
end
@doc false
def validate_schema!(schema) do
if schema[:*] do
raise "Schema with * not supported in validator"
else
Enum.each(schema, fn {_key, config} ->
if config[:type] == :keyword do
# When we support nested keywords, they should get structs
# auto defined for them as well.
raise "Nested keywords not accepted in validators yet"
end
end)
end
end
end