diff --git a/lib/nimble_options.ex b/lib/nimble_options.ex index 397b6c1..f3ecf7e 100644 --- a/lib/nimble_options.ex +++ b/lib/nimble_options.ex @@ -601,8 +601,8 @@ defmodule NimbleOptions do end end - defp validate_type(:integer, key, value, redact) when not is_integer(value) do - structured_error_tuple(key, value, "integer", redact) + defp validate_type(:integer = type, key, value, redact) when not is_integer(value) do + structured_error_tuple(key, value, {:type, type}, "integer", redact) end defp validate_type(:non_neg_integer, key, value, redact) @@ -1019,11 +1019,16 @@ defmodule NimbleOptions do {:error, %ValidationError{key: key, message: message, redact: redact, value: value}} end + # FIX: remove after error types are implemented defp structured_error_tuple(key, value, expected, redact?) do - structured_error_tuple(key, value, expected, inspect(value), redact?) + structured_error_tuple(key, value, false, expected, redact?) end - defp structured_error_tuple(key, value, expected, got, redact?) do + defp structured_error_tuple(key, value, type, expected, redact?) do + structured_error_tuple(key, value, type, expected, inspect(value), redact?) + end + + defp structured_error_tuple(key, value, validation, expected, got, redact?) do message = if redact? do "invalid value for #{render_key(key)}: expected #{expected}" @@ -1031,7 +1036,14 @@ defmodule NimbleOptions do "invalid value for #{render_key(key)}: expected #{expected}, got: #{got}" end - {:error, %ValidationError{key: key, message: message, redact: redact?, value: value}} + {:error, + %ValidationError{ + key: key, + message: message, + redact: redact?, + value: value, + validation: validation + }} end defp render_key({__MODULE__, :key}), do: "map key" diff --git a/lib/nimble_options/validation_error.ex b/lib/nimble_options/validation_error.ex index 0115cfe..ab391e7 100644 --- a/lib/nimble_options/validation_error.ex +++ b/lib/nimble_options/validation_error.ex @@ -12,6 +12,7 @@ defmodule NimbleOptions.ValidationError do key: atom(), keys_path: [atom()], redact: boolean, + validation: :required | {:type, term()}, value: term() } @@ -28,8 +29,10 @@ defmodule NimbleOptions.ValidationError do * `:value` (`t:term/0`) - The value that failed to validate. This field is `nil` if there was no value provided. + * `:validation` (`:required` or `{:type, type}`) - The validation that failed. + """ - defexception [:message, :key, :value, keys_path: [], redact: false] + defexception [:message, :key, :validation, :value, keys_path: [], redact: false] @impl true def message(%__MODULE__{message: message, keys_path: keys_path}) do diff --git a/test/nimble_options_test.exs b/test/nimble_options_test.exs index 3992c75..d74ab8b 100644 --- a/test/nimble_options_test.exs +++ b/test/nimble_options_test.exs @@ -292,6 +292,7 @@ defmodule NimbleOptionsTest do %ValidationError{ key: :min_demand, value: 1.5, + validation: {:type, :integer}, message: "invalid value for :min_demand option: expected integer, got: 1.5" }} @@ -300,6 +301,7 @@ defmodule NimbleOptionsTest do %ValidationError{ key: :min_demand, value: :an_atom, + validation: {:type, :integer}, message: "invalid value for :min_demand option: expected integer, got: :an_atom" }} end @@ -312,6 +314,7 @@ defmodule NimbleOptionsTest do %ValidationError{ key: :min_demand, value: 1.5, + validation: {:type, :integer}, message: "invalid value for :min_demand option: expected integer", redact: true }} @@ -2250,7 +2253,7 @@ defmodule NimbleOptionsTest do end # No other test is passing in `opts` as a map, so these are just some white box tests for sanity checking - describe "can use a map for validate/2" do + test "can use a map for validate/2" do schema = [] opts = %{} assert NimbleOptions.validate(opts, schema) == {:ok, %{}}