diff --git a/README.md b/README.md index 3c4b570..99f49e1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# dataspec +# dataspecs -![CI](https://github.com/visciang/dataspec/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/visciang/dataspec/badge.svg?branch=master)](https://coveralls.io/github/visciang/dataspec?branch=master) +![CI](https://github.com/visciang/dataspecs/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/visciang/dataspecs/badge.svg?branch=master)](https://coveralls.io/github/visciang/dataspecs?branch=master) Typespec based data loader and validator (inspired by [forma](https://github.com/soundtrackyourbrand/forma)). -DataSpec **validate and load** elixir data into a more structured form +DataSpecs **validate and load** elixir data into a more structured form by trying to map it to conform to a **typespec**. It support most typespec specification: **basic** types, **literal** types, **built-in** types, **union** type, **parametrized** types, **maps**, **remote** types and **user defined** types. @@ -44,7 +44,7 @@ defmodule Address do } end -DataSpec.load(%{ +DataSpecs.load(%{ "name" => "Joe", "surname" => "Smith", "gender" => "male", @@ -71,7 +71,7 @@ DataSpec.load(%{ # } ``` -DataSpec tries to figure out how to translate its input to a typespec. +DataSpecs tries to figure out how to translate its input to a typespec. Scalar types (such as booleans, integers, etc.) and some composite types (such as lists, plain maps), can be simply mapped one to one after validation without any additional transformation. @@ -84,7 +84,7 @@ Refer to the library test suite for more examples. ```elixir def deps do [ - {:dataspec, "~> xxx"} + {:dataspecs, "~> xxx"} ] end ``` @@ -121,7 +121,7 @@ end ## Custom type loaders In these cases you can pass a set of custom type loaders along as an optional argument -to the `DataSpec.load` function +to the `DataSpecs.load` function ```elixir defmodule LogRow do @@ -147,7 +147,7 @@ def custom_isodatetime_loader(value, _custom_type_loaders, []) do end end -DataSpec.load( +DataSpecs.load( %{"log" => "An error occurred", "timestamp" => "2021-07-14 20:22:49.653077Z"}, %{{DateTime, :t, 0} => &custom_isodatetime_loader/3} ) @@ -228,7 +228,7 @@ defmodule AStruct do @type field :: String.t() def custom_field_loader(value, custom_type_loaders, type_params_loaders) do - name = DataSpec.Loaders.binary(value, custom_type_loaders, type_params_loaders) + name = DataSpecs.Loaders.binary(value, custom_type_loaders, type_params_loaders) if name == String.upcase(name) do {:ok, name} @@ -238,7 +238,7 @@ defmodule AStruct do end end -DataSpec.load(%{field: "AAA"}, {AStruct, :t}, %{{AStruct, :field, 0} => &AStruct.custom_field_loader/3}) +DataSpecs.load(%{field: "AAA"}, {AStruct, :t}, %{{AStruct, :field, 0} => &AStruct.custom_field_loader/3}) # => %AStruct{field: "AAA"} ``` diff --git a/lib/dataspec.ex b/lib/dataspecs.ex similarity index 87% rename from lib/dataspec.ex rename to lib/dataspecs.ex index c5428b0..542ef47 100644 --- a/lib/dataspec.ex +++ b/lib/dataspecs.ex @@ -1,8 +1,8 @@ -defmodule DataSpec do +defmodule DataSpecs do @moduledoc File.read!("README.md") use Application - alias DataSpec.Typespecs + alias DataSpecs.Typespecs @type value() :: any() @type reason :: [String.t() | reason()] @@ -19,6 +19,6 @@ defmodule DataSpec do end def start(_type, _args) do - Supervisor.start_link([DataSpec.Cache], strategy: :one_for_one) + Supervisor.start_link([DataSpecs.Cache], strategy: :one_for_one) end end diff --git a/lib/dataspec/cache.ex b/lib/dataspecs/cache.ex similarity index 96% rename from lib/dataspec/cache.ex rename to lib/dataspecs/cache.ex index 8843154..4f2671d 100644 --- a/lib/dataspec/cache.ex +++ b/lib/dataspecs/cache.ex @@ -1,4 +1,4 @@ -defmodule DataSpec.Cache do +defmodule DataSpecs.Cache do @moduledoc false use GenServer diff --git a/lib/dataspec/loaders.ex b/lib/dataspecs/loaders.ex similarity index 99% rename from lib/dataspec/loaders.ex rename to lib/dataspecs/loaders.ex index 9b1cdec..69bb865 100644 --- a/lib/dataspec/loaders.ex +++ b/lib/dataspecs/loaders.ex @@ -1,4 +1,4 @@ -defmodule DataSpec.Loaders do +defmodule DataSpecs.Loaders do @moduledoc false def any(value, _custom_type_loaders, _type_params_loaders) do diff --git a/lib/dataspec/typespecs.ex b/lib/dataspecs/typespecs.ex similarity index 99% rename from lib/dataspec/typespecs.ex rename to lib/dataspecs/typespecs.ex index 919befc..baea150 100644 --- a/lib/dataspec/typespecs.ex +++ b/lib/dataspecs/typespecs.ex @@ -1,7 +1,7 @@ -defmodule DataSpec.Typespecs do +defmodule DataSpecs.Typespecs do @moduledoc false - alias DataSpec.{Cache, Loaders} + alias DataSpecs.{Cache, Loaders} require Logger @@ -346,7 +346,7 @@ defmodule DataSpec.Typespecs do # erlang abstract type format: # {:type, 48, :map, # [ - # {:type, 48, :map_field_exact, [{:atom, 0, :__struct__}, {:atom, 0, Test.DataSpec.SampleStructType}]}, + # {:type, 48, :map_field_exact, [{:atom, 0, :__struct__}, {:atom, 0, Test.DataSpecs.SampleStructType}]}, # {:type, 48, :map_field_exact, [{:atom, 0, :f_1}, {:type, 49, :atom, []}]}, # {:type, 48, :map_field_exact, [{:atom, 0, :f_2}, {:type, 50, :integer, []}]} # ] diff --git a/mix.exs b/mix.exs index 029a0f1..cc0212b 100644 --- a/mix.exs +++ b/mix.exs @@ -1,10 +1,10 @@ -defmodule DataSpec.Mixfile do +defmodule DataSpecs.Mixfile do use Mix.Project def project do [ - app: :dataspec, - name: "dataspec", + app: :dataspecs, + name: "dataspecs", version: "0.0.1", elixir: "~> 1.7", elixirc_paths: elixirc_paths(Mix.env()), @@ -14,7 +14,7 @@ defmodule DataSpec.Mixfile do preferred_cli_env: preferred_cli_env(), description: description(), package: package(), - source_url: "https://github.com/visciang/dataspec" + source_url: "https://github.com/visciang/dataspecs" ] end @@ -24,18 +24,18 @@ defmodule DataSpec.Mixfile do defp package do [ - name: "dataspec", + name: "dataspecs", licenses: ["MIT"], files: ["lib", "README.md", "LICENSE", "mix.exs"], maintainers: ["Giovanni Visciano"], - links: %{"GitHub" => "https://github.com/visciang/dataspec"} + links: %{"GitHub" => "https://github.com/visciang/dataspecs"} ] end def application do [ extra_applications: [:logger], - mod: {DataSpec, []} + mod: {DataSpecs, []} ] end diff --git a/test/dataspec_test.exs b/test/dataspec_test.exs index a36f295..6ea1ac2 100644 --- a/test/dataspec_test.exs +++ b/test/dataspec_test.exs @@ -1,187 +1,188 @@ -defmodule Test.DataSpec do +defmodule Test.DataSpecs do use ExUnit.Case - alias DataSpec.Loaders - alias Test.DataSpec.CustomLoader + alias DataSpecs.Loaders + alias Test.DataSpecs.CustomLoader - @types_module Test.DataSpec.SampleType - @types_struct_module Test.DataSpec.SampleStructType + @types_module Test.DataSpecs.SampleType + @types_struct_module Test.DataSpecs.SampleStructType describe "Unknown" do test "module" do assert_raise RuntimeError, "Can't fetch type specifications for module :unknown_module", fn -> - DataSpec.load(:a, {:unknown_module, :t}) + DataSpecs.load(:a, {:unknown_module, :t}) end end test "type in module" do assert_raise RuntimeError, "Unknown type #{inspect(@types_module)}.this_type_does_not_exist/0", fn -> - DataSpec.load(:a, {@types_module, :this_type_does_not_exist}) + DataSpecs.load(:a, {@types_module, :this_type_does_not_exist}) end end end describe "literal" do test "ok" do - assert {:ok, :a} == DataSpec.load(:a, {@types_module, :t_literal_atom}) - assert {:ok, 1} == DataSpec.load(1, {@types_module, :t_literal_integer}) + assert {:ok, :a} == DataSpecs.load(:a, {@types_module, :t_literal_atom}) + assert {:ok, 1} == DataSpecs.load(1, {@types_module, :t_literal_integer}) end test "error" do reason = ["value :not_a doesn't match literal value :a"] - assert {:error, ^reason} = DataSpec.load(:not_a, {@types_module, :t_literal_atom}) + assert {:error, ^reason} = DataSpecs.load(:not_a, {@types_module, :t_literal_atom}) reason = ["can't convert \"not an atom\" to an existing atom"] - assert {:error, ^reason} = DataSpec.load("not an atom", {@types_module, :t_literal_atom}) + assert {:error, ^reason} = DataSpecs.load("not an atom", {@types_module, :t_literal_atom}) reason = ["can't convert :not_an_integer to an integer"] - assert {:error, ^reason} = DataSpec.load(:not_an_integer, {@types_module, :t_literal_integer}) + assert {:error, ^reason} = DataSpecs.load(:not_an_integer, {@types_module, :t_literal_integer}) end end describe "any type" do test "any" do - assert {:ok, {:test, 1, ["a", "b"], 1..2}} == DataSpec.load({:test, 1, ["a", "b"], 1..2}, {@types_module, :t_any}) + assert {:ok, {:test, 1, ["a", "b"], 1..2}} == + DataSpecs.load({:test, 1, ["a", "b"], 1..2}, {@types_module, :t_any}) end test "term" do - assert {:ok, {"a_term"}} == DataSpec.load({"a_term"}, {@types_module, :t_term}) + assert {:ok, {"a_term"}} == DataSpecs.load({"a_term"}, {@types_module, :t_term}) end end describe "pid" do test "ok" do - assert {:ok, self()} == DataSpec.load(self(), {@types_module, :t_pid}) + assert {:ok, self()} == DataSpecs.load(self(), {@types_module, :t_pid}) end test "error" do reason = ["can't convert 1 to a pid"] - assert {:error, ^reason} = DataSpec.load(1, {@types_module, :t_pid}) + assert {:error, ^reason} = DataSpecs.load(1, {@types_module, :t_pid}) end end describe "atom" do test "ok" do - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_atom}) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_atom}) end test "error" do reason = ["can't convert \"this_is_a_non_existing_atom\" to an existing atom"] - assert {:error, ^reason} = DataSpec.load("this_is_a_non_existing_atom", {@types_module, :t_atom}) + assert {:error, ^reason} = DataSpecs.load("this_is_a_non_existing_atom", {@types_module, :t_atom}) end end describe "boolean" do test "ok" do - assert {:ok, true} == DataSpec.load(true, {@types_module, :t_boolean}) + assert {:ok, true} == DataSpecs.load(true, {@types_module, :t_boolean}) end test "error" do reason = ["can't convert 1 to a boolean"] - assert {:error, ^reason} = DataSpec.load(1, {@types_module, :t_boolean}) + assert {:error, ^reason} = DataSpecs.load(1, {@types_module, :t_boolean}) end end describe "binary" do test "ok" do - assert {:ok, "binary"} == DataSpec.load("binary", {@types_module, :t_binary}) + assert {:ok, "binary"} == DataSpecs.load("binary", {@types_module, :t_binary}) end test "error" do reason = ["can't convert 1 to a binary"] - assert {:error, ^reason} = DataSpec.load(1, {@types_module, :t_binary}) + assert {:error, ^reason} = DataSpecs.load(1, {@types_module, :t_binary}) end end describe "reference" do test "ok" do ref = make_ref() - assert {:ok, ref} == DataSpec.load(ref, {@types_module, :t_reference}) + assert {:ok, ref} == DataSpecs.load(ref, {@types_module, :t_reference}) end test "error" do reason = ["can't convert 1 to a reference"] - assert {:error, ^reason} = DataSpec.load(1, {@types_module, :t_reference}) + assert {:error, ^reason} = DataSpecs.load(1, {@types_module, :t_reference}) end end describe "number" do test "ok" do - assert {:ok, 123} == DataSpec.load(123, {@types_module, :t_number}) - assert {:ok, 123.1} == DataSpec.load(123.1, {@types_module, :t_number}) + assert {:ok, 123} == DataSpecs.load(123, {@types_module, :t_number}) + assert {:ok, 123.1} == DataSpecs.load(123.1, {@types_module, :t_number}) end test "error" do reason = ["can't convert :a to a number"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_number}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_number}) end end describe "float" do test "ok" do - assert {:ok, 123} == DataSpec.load(123, {@types_module, :t_float}) - assert {:ok, 123.1} == DataSpec.load(123.1, {@types_module, :t_float}) + assert {:ok, 123} == DataSpecs.load(123, {@types_module, :t_float}) + assert {:ok, 123.1} == DataSpecs.load(123.1, {@types_module, :t_float}) end test "error" do reason = ["can't convert :a to a float"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_float}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_float}) end end describe "integer" do test "ok" do - assert {:ok, 123} == DataSpec.load(123, {@types_module, :t_integer}) - assert {:ok, -123} == DataSpec.load(-123, {@types_module, :t_neg_integer}) - assert {:ok, 0} == DataSpec.load(0, {@types_module, :t_non_neg_integer}) - assert {:ok, 123} == DataSpec.load(123, {@types_module, :t_pos_integer}) + assert {:ok, 123} == DataSpecs.load(123, {@types_module, :t_integer}) + assert {:ok, -123} == DataSpecs.load(-123, {@types_module, :t_neg_integer}) + assert {:ok, 0} == DataSpecs.load(0, {@types_module, :t_non_neg_integer}) + assert {:ok, 123} == DataSpecs.load(123, {@types_module, :t_pos_integer}) end test "error" do reason = ["can't convert :a to an integer"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_integer}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_integer}) reason = ["can't convert :a to a neg_integer"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_neg_integer}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_neg_integer}) reason = ["can't convert :a to a non_neg_integer"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_non_neg_integer}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_non_neg_integer}) reason = ["can't convert :a to a pos_integer"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_pos_integer}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_pos_integer}) reason = ["can't convert 1 to a neg_integer"] - assert {:error, ^reason} = DataSpec.load(1, {@types_module, :t_neg_integer}) + assert {:error, ^reason} = DataSpecs.load(1, {@types_module, :t_neg_integer}) reason = ["can't convert -1 to a non_neg_integer"] - assert {:error, ^reason} = DataSpec.load(-1, {@types_module, :t_non_neg_integer}) + assert {:error, ^reason} = DataSpecs.load(-1, {@types_module, :t_non_neg_integer}) reason = ["can't convert 0 to a pos_integer"] - assert {:error, ^reason} = DataSpec.load(0, {@types_module, :t_pos_integer}) + assert {:error, ^reason} = DataSpecs.load(0, {@types_module, :t_pos_integer}) end end describe "range" do test "ok" do - assert {:ok, 5} == DataSpec.load(5, {@types_module, :t_range}) - assert {:error, _} = DataSpec.load(:a, {@types_module, :t_range}) + assert {:ok, 5} == DataSpecs.load(5, {@types_module, :t_range}) + assert {:error, _} = DataSpecs.load(:a, {@types_module, :t_range}) end test "error" do reason = ["can't convert 0 to a range 1..10"] - assert {:error, ^reason} = DataSpec.load(0, {@types_module, :t_range}) + assert {:error, ^reason} = DataSpecs.load(0, {@types_module, :t_range}) end end describe "union" do test "ok" do float = &Loaders.float/3 - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_union_0}, %{}, [float]) - assert {:ok, 1} == DataSpec.load(1, {@types_module, :t_union_0}, %{}, [float]) - assert {:ok, 1.1} == DataSpec.load(1.1, {@types_module, :t_union_0}, %{}, [float]) - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_union_1}) - assert {:ok, {0, 1, 2}} == DataSpec.load({0, 1, 2}, {@types_module, :t_union_1}) - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_union_1}) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_union_0}, %{}, [float]) + assert {:ok, 1} == DataSpecs.load(1, {@types_module, :t_union_0}, %{}, [float]) + assert {:ok, 1.1} == DataSpecs.load(1.1, {@types_module, :t_union_0}, %{}, [float]) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_union_1}) + assert {:ok, {0, 1, 2}} == DataSpecs.load({0, 1, 2}, {@types_module, :t_union_1}) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_union_1}) end test "error" do @@ -190,111 +191,111 @@ defmodule Test.DataSpec do ["can't convert %{} to a tuple", "can't convert %{} to an atom", "can't convert %{} to an integer"] ] - assert {:error, ^reason} = DataSpec.load(%{}, {@types_module, :t_union_1}) + assert {:error, ^reason} = DataSpecs.load(%{}, {@types_module, :t_union_1}) end end describe "list" do test "ok" do integer = &Loaders.integer/3 - assert {:ok, []} == DataSpec.load([], {@types_module, :t_empty_list}) - assert {:ok, [:a, :b]} == DataSpec.load([:a, :b], {@types_module, :t_list}) - assert {:ok, [1, 2]} == DataSpec.load([1, 2], {@types_module, :t_list_param}, %{}, [integer]) - assert {:ok, [1, :a]} == DataSpec.load([1, :a], {@types_module, :t_nonempty_list_0}) - assert {:ok, [:a, :b]} == DataSpec.load([:a, :b], {@types_module, :t_nonempty_list_1}) - assert {:ok, [:a, 1]} == DataSpec.load([:a, 1], {@types_module, :t_list_of_any}) + assert {:ok, []} == DataSpecs.load([], {@types_module, :t_empty_list}) + assert {:ok, [:a, :b]} == DataSpecs.load([:a, :b], {@types_module, :t_list}) + assert {:ok, [1, 2]} == DataSpecs.load([1, 2], {@types_module, :t_list_param}, %{}, [integer]) + assert {:ok, [1, :a]} == DataSpecs.load([1, :a], {@types_module, :t_nonempty_list_0}) + assert {:ok, [:a, :b]} == DataSpecs.load([:a, :b], {@types_module, :t_nonempty_list_1}) + assert {:ok, [:a, 1]} == DataSpecs.load([:a, 1], {@types_module, :t_list_of_any}) end test "error" do reason = ["can't convert :a to a list"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_list}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_list}) reason = ["can't convert [:a, 1] to a list, bad item at index=1", ["can't convert 1 to an atom"]] - assert {:error, ^reason} = DataSpec.load([:a, 1], {@types_module, :t_list}) + assert {:error, ^reason} = DataSpecs.load([:a, 1], {@types_module, :t_list}) reason = ["can't convert [1] to an empty list"] - assert {:error, ^reason} = DataSpec.load([1], {@types_module, :t_empty_list}) + assert {:error, ^reason} = DataSpecs.load([1], {@types_module, :t_empty_list}) reason = ["can't convert [] to a non empty list"] - assert {:error, ^reason} = DataSpec.load([], {@types_module, :t_nonempty_list_0}) + assert {:error, ^reason} = DataSpecs.load([], {@types_module, :t_nonempty_list_0}) reason = ["can't convert :not_a_list to a list"] - assert {:error, ^reason} = DataSpec.load(:not_a_list, {@types_module, :t_list_of_any}) + assert {:error, ^reason} = DataSpecs.load(:not_a_list, {@types_module, :t_list_of_any}) end end describe "keyword list" do test "ok" do - assert {:ok, [a: 1, b: :test]} == DataSpec.load([a: 1, b: :test], {@types_module, :t_keyword_list}) + assert {:ok, [a: 1, b: :test]} == DataSpecs.load([a: 1, b: :test], {@types_module, :t_keyword_list}) end test "error" do - assert {:error, _} = DataSpec.load([{:a, 1}, :bad], {@types_module, :t_keyword_list}) + assert {:error, _} = DataSpecs.load([{:a, 1}, :bad], {@types_module, :t_keyword_list}) end end describe "tuple" do test "ok" do - assert {:ok, {}} == DataSpec.load({}, {@types_module, :t_empty_tuple}) - assert {:ok, {1, 2}} == DataSpec.load({1, 2}, {@types_module, :t_tuple}) - assert {:ok, {1, 2}} == DataSpec.load([1, 2], {@types_module, :t_tuple}) - assert {:ok, {1, "a"}} == DataSpec.load({1, "a"}, {@types_module, :t_tuple_any_size}) + assert {:ok, {}} == DataSpecs.load({}, {@types_module, :t_empty_tuple}) + assert {:ok, {1, 2}} == DataSpecs.load({1, 2}, {@types_module, :t_tuple}) + assert {:ok, {1, 2}} == DataSpecs.load([1, 2], {@types_module, :t_tuple}) + assert {:ok, {1, "a"}} == DataSpecs.load({1, "a"}, {@types_module, :t_tuple_any_size}) end test "error" do reason = ["can't convert nil to a tuple"] - assert {:error, ^reason} = DataSpec.load(nil, {@types_module, :t_tuple}) + assert {:error, ^reason} = DataSpecs.load(nil, {@types_module, :t_tuple}) reason = ["can't convert {:a, 2} to a tuple, bad item at index=0", ["can't convert :a to an integer"]] - assert {:error, ^reason} = DataSpec.load({:a, 2}, {@types_module, :t_tuple}) + assert {:error, ^reason} = DataSpecs.load({:a, 2}, {@types_module, :t_tuple}) reason = ["can't convert {1, 2, 3} to a tuple of size 2"] - assert {:error, ^reason} = DataSpec.load({1, 2, 3}, {@types_module, :t_tuple}) + assert {:error, ^reason} = DataSpecs.load({1, 2, 3}, {@types_module, :t_tuple}) reason = ["can't convert :not_a_tuple to a tuple"] - assert {:error, ^reason} = DataSpec.load(:not_a_tuple, {@types_module, :t_tuple_any_size}) + assert {:error, ^reason} = DataSpecs.load(:not_a_tuple, {@types_module, :t_tuple_any_size}) end end describe "map" do test "ok" do integer = &Loaders.integer/3 - assert {:ok, %{}} == DataSpec.load(%{}, {@types_module, :t_empty_map}) - assert {:ok, %{required_key: 1}} == DataSpec.load(%{required_key: 1}, {@types_module, :t_map_0}) - assert {:ok, %{required_key: 1}} == DataSpec.load(%{"required_key" => 1}, {@types_module, :t_map_0}) - assert {:ok, %{0 => :a}} == DataSpec.load(%{0 => :a}, {@types_module, :t_map_1}) - assert {:ok, %{0 => :a}} == DataSpec.load(%{0 => :a}, {@types_module, :t_map_2}) - assert {:ok, %{0 => :a, :b => 1}} == DataSpec.load(%{0 => :a, :b => 1}, {@types_module, :t_map_3}) + assert {:ok, %{}} == DataSpecs.load(%{}, {@types_module, :t_empty_map}) + assert {:ok, %{required_key: 1}} == DataSpecs.load(%{required_key: 1}, {@types_module, :t_map_0}) + assert {:ok, %{required_key: 1}} == DataSpecs.load(%{"required_key" => 1}, {@types_module, :t_map_0}) + assert {:ok, %{0 => :a}} == DataSpecs.load(%{0 => :a}, {@types_module, :t_map_1}) + assert {:ok, %{0 => :a}} == DataSpecs.load(%{0 => :a}, {@types_module, :t_map_2}) + assert {:ok, %{0 => :a, :b => 1}} == DataSpecs.load(%{0 => :a, :b => 1}, {@types_module, :t_map_3}) assert {:ok, %{0 => %{a: true}, 1 => %{b: false}}} == - DataSpec.load(%{0 => %{a: true}, 1 => %{b: false}}, {@types_module, :t_map_4}) + DataSpecs.load(%{0 => %{a: true}, 1 => %{b: false}}, {@types_module, :t_map_4}) - assert {:ok, %{0 => :a}} == DataSpec.load(%{0 => :a}, {@types_module, :t_map_param}, %{}, [integer]) + assert {:ok, %{0 => :a}} == DataSpecs.load(%{0 => :a}, {@types_module, :t_map_param}, %{}, [integer]) end test "error" do reason = ["can't convert %{a: 1} to an empty map"] - assert {:error, ^reason} = DataSpec.load(%{a: 1}, {@types_module, :t_empty_map}) + assert {:error, ^reason} = DataSpecs.load(%{a: 1}, {@types_module, :t_empty_map}) reason = ["can't convert :a to a map"] - assert {:error, ^reason} = DataSpec.load(:a, {@types_module, :t_map_0}) + assert {:error, ^reason} = DataSpecs.load(:a, {@types_module, :t_map_0}) reason = [ "can't convert %{required_key_missing: 1} to a map, missing required k/v", ["value :required_key_missing doesn't match literal value :required_key"] ] - assert {:error, ^reason} = DataSpec.load(%{required_key_missing: 1}, {@types_module, :t_map_0}) + assert {:error, ^reason} = DataSpecs.load(%{required_key_missing: 1}, {@types_module, :t_map_0}) reason = ["can't convert %{b: 1} to a map, missing required k/v", ["can't convert :b to an integer"]] - assert {:error, ^reason} = DataSpec.load(%{:b => 1}, {@types_module, :t_map_3}) + assert {:error, ^reason} = DataSpecs.load(%{:b => 1}, {@types_module, :t_map_3}) reason = [ "can't convert %{0 => :a, 1.1 => 1, :b => 1} to a map, bad k/v pairs: %{1.1 => 1}", ["can't convert 1.1 to an integer", "can't convert :b to an integer"] ] - assert {:error, ^reason} = DataSpec.load(%{0 => :a, :b => 1, 1.1 => 1}, {@types_module, :t_map_3}) + assert {:error, ^reason} = DataSpecs.load(%{0 => :a, :b => 1, 1.1 => 1}, {@types_module, :t_map_3}) reason = [ "can't convert %{0 => %{a: true}, 1 => %{b: \"not a bool\"}} to a map, bad k/v pairs: %{1 => %{b: \"not a bool\"}}", @@ -304,65 +305,65 @@ defmodule Test.DataSpec do ] ] - assert {:error, ^reason} = DataSpec.load(%{0 => %{a: true}, 1 => %{b: "not a bool"}}, {@types_module, :t_map_4}) + assert {:error, ^reason} = DataSpecs.load(%{0 => %{a: true}, 1 => %{b: "not a bool"}}, {@types_module, :t_map_4}) end end test "user type parametrized" do integer = &Loaders.integer/3 - assert {:ok, {0, 1, 2}} == DataSpec.load({0, 1, 2}, {@types_module, :t_user_type_param_0}) + assert {:ok, {0, 1, 2}} == DataSpecs.load({0, 1, 2}, {@types_module, :t_user_type_param_0}) assert {:ok, {0, 1, 2}} == - DataSpec.load({0, 1, 2}, {@types_module, :t_user_type_param_1}, %{}, [integer, integer]) + DataSpecs.load({0, 1, 2}, {@types_module, :t_user_type_param_1}, %{}, [integer, integer]) - assert {:ok, 1} == DataSpec.load(1, {@types_module, :t_user_type_param_2}, %{}, [integer]) + assert {:ok, 1} == DataSpecs.load(1, {@types_module, :t_user_type_param_2}, %{}, [integer]) end test "same type name with different arities" do atom = &Loaders.atom/3 - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_type_arity}) - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_type_arity}, %{}, [atom]) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_type_arity}) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_type_arity}, %{}, [atom]) end describe "struct" do test "ok" do assert {:ok, %@types_struct_module{f_1: :a, f_2: 1, f_3: "s"}} == - DataSpec.load(%{f_1: :a, f_2: 1, f_3: "s"}, {@types_struct_module, :t}) + DataSpecs.load(%{f_1: :a, f_2: 1, f_3: "s"}, {@types_struct_module, :t}) assert {:ok, %@types_struct_module{f_1: :a, f_2: nil, f_3: nil}} == - DataSpec.load(%{f_1: :a}, {@types_struct_module, :t}) + DataSpecs.load(%{f_1: :a}, {@types_struct_module, :t}) assert {:ok, %@types_struct_module{f_1: :a, f_2: nil, f_3: nil}} == - DataSpec.load(%@types_struct_module{f_1: :a}, {@types_struct_module, :t}) + DataSpecs.load(%@types_struct_module{f_1: :a}, {@types_struct_module, :t}) end test "error" do reason = "the following keys must also be given when building struct #{inspect(@types_struct_module)}: [:f_1]" - assert {:error, [^reason]} = DataSpec.load(%{}, {@types_struct_module, :t}) + assert {:error, [^reason]} = DataSpecs.load(%{}, {@types_struct_module, :t}) reason = [ "can't convert %{f_1: \"not an atom\"} to a %#{inspect(@types_struct_module)}{} struct", ["can't convert %{f_1: \"not an atom\"} to a map, bad k/v pairs: %{f_1: \"not an atom\"}", []] ] - assert {:error, ^reason} = DataSpec.load(%{f_1: "not an atom"}, {@types_struct_module, :t}) + assert {:error, ^reason} = DataSpecs.load(%{f_1: "not an atom"}, {@types_struct_module, :t}) end end test "remote type" do integer = &Loaders.integer/3 - assert {:ok, 1} == DataSpec.load(1, {@types_module, :t_remote_type}, %{}, [integer]) - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_remote_type}, %{}, [integer]) - assert {:ok, "string"} == DataSpec.load("string", {@types_module, :t_remote_type_string}) + assert {:ok, 1} == DataSpecs.load(1, {@types_module, :t_remote_type}, %{}, [integer]) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_remote_type}, %{}, [integer]) + assert {:ok, "string"} == DataSpecs.load("string", {@types_module, :t_remote_type_string}) end test "recursive type" do - assert {:ok, :test} == DataSpec.load(:test, {@types_module, :t_recursive}) - assert {:ok, %{recursive: :test}} == DataSpec.load(%{recursive: :test}, {@types_module, :t_recursive}) + assert {:ok, :test} == DataSpecs.load(:test, {@types_module, :t_recursive}) + assert {:ok, %{recursive: :test}} == DataSpecs.load(%{recursive: :test}, {@types_module, :t_recursive}) assert {:ok, %{recursive: %{recursive: :test}}} == - DataSpec.load(%{recursive: %{recursive: :test}}, {@types_module, :t_recursive}) + DataSpecs.load(%{recursive: %{recursive: :test}}, {@types_module, :t_recursive}) end describe "opaque type" do @@ -370,10 +371,10 @@ defmodule Test.DataSpec do integer = &Loaders.integer/3 reason = ["opaque type #{inspect(@types_module)}.t_opaque/1 has no custom type loader defined"] - assert {:error, ^reason} = DataSpec.load(:opaque, {@types_module, :t_opaque}, %{}, [integer]) + assert {:error, ^reason} = DataSpecs.load(:opaque, {@types_module, :t_opaque}, %{}, [integer]) reason = ["opaque type MapSet.t/1 has no custom type loader defined"] - assert {:error, ^reason} = DataSpec.load(:opaque, {@types_module, :t_mapset}) + assert {:error, ^reason} = DataSpecs.load(:opaque, {@types_module, :t_mapset}) end test "with custom type loader" do @@ -385,26 +386,26 @@ defmodule Test.DataSpec do {DateTime, :t, 0} => &CustomLoader.isodatetime/3 } - assert {:ok, {:custom_opaque, 1}} == DataSpec.load(1, {@types_module, :t_opaque}, custom_type_loaders, [integer]) + assert {:ok, {:custom_opaque, 1}} == DataSpecs.load(1, {@types_module, :t_opaque}, custom_type_loaders, [integer]) datetime = ~U[2021-07-14 20:22:49.653077Z] iso_datetime_string = DateTime.to_iso8601(datetime) - assert {:ok, MapSet.new(1..3)} == DataSpec.load(1..3, {@types_module, :t_mapset}, custom_type_loaders) + assert {:ok, MapSet.new(1..3)} == DataSpecs.load(1..3, {@types_module, :t_mapset}, custom_type_loaders) assert {:ok, MapSet.new(["1", :a, 1])} == - DataSpec.load(["1", :a, 1], {@types_module, :t_mapset_1}, custom_type_loaders) + DataSpecs.load(["1", :a, 1], {@types_module, :t_mapset_1}, custom_type_loaders) - assert {:ok, datetime} == DataSpec.load(iso_datetime_string, {@types_module, :t_datetime}, custom_type_loaders) + assert {:ok, datetime} == DataSpecs.load(iso_datetime_string, {@types_module, :t_datetime}, custom_type_loaders) end end test "typep" do - assert {:ok, :a} == DataSpec.load(:a, {@types_module, :t_reference_to_private_type}) + assert {:ok, :a} == DataSpecs.load(:a, {@types_module, :t_reference_to_private_type}) end end -defmodule Test.DataSpec.CustomLoader do - alias DataSpec.Loaders +defmodule Test.DataSpecs.CustomLoader do + alias DataSpecs.Loaders def opaque(value, custom_type_loaders, [type_params_loader]) do type_params_loader.(value, custom_type_loaders, []) diff --git a/test/support/sample_type.ex b/test/support/sample_type.ex index 4be39d9..5195f55 100644 --- a/test/support/sample_type.ex +++ b/test/support/sample_type.ex @@ -1,4 +1,4 @@ -defmodule Test.DataSpec.SampleType do +defmodule Test.DataSpecs.SampleType do @moduledoc false @type t_literal_atom :: :a @@ -52,7 +52,7 @@ defmodule Test.DataSpec.SampleType do @type t_type_arity :: atom() @type t_type_arity(x) :: x - @type t_remote_type(x) :: Test.DataSpec.SampleRemoteModuleType.t_remote(x) + @type t_remote_type(x) :: Test.DataSpecs.SampleRemoteModuleType.t_remote(x) @type t_remote_type_string :: String.t() @type t_mapset :: MapSet.t(integer()) @type t_mapset_1 :: MapSet.t(t_union_0(binary())) @@ -86,13 +86,13 @@ defmodule Test.DataSpec.SampleType do # @type t_timeout :: timeout() end -defmodule Test.DataSpec.SampleRemoteModuleType do +defmodule Test.DataSpecs.SampleRemoteModuleType do @moduledoc false @type t_remote(x) :: x | atom() end -defmodule Test.DataSpec.SampleStructType do +defmodule Test.DataSpecs.SampleStructType do @moduledoc false @enforce_keys [:f_1]