diff --git a/lib/norm.ex b/lib/norm.ex index 95b01f5..f2724f6 100644 --- a/lib/norm.ex +++ b/lib/norm.ex @@ -12,6 +12,7 @@ defmodule Norm do alias Norm.Core.{ Alt, AnyOf, + AllOf, Collection, Schema, Selection, @@ -245,6 +246,18 @@ defmodule Norm do AnyOf.new(specs) end + @doc """ + Composes multiple specs into a single spec. The passed in value must conform + to each of the specs or an error is returned. + + ## Examples + iex> conform!(42, all_of([spec(is_integer), spec(& &1 > 1)])) + 42 + """ + def all_of(specs) when is_list(specs) and length(specs) > 0 do + AllOf.new(specs) + end + @doc ~S""" Specifies a generic collection. Collections can be any enumerable type. diff --git a/lib/norm/core/all_of.ex b/lib/norm/core/all_of.ex index cd5f75c..703393d 100644 --- a/lib/norm/core/all_of.ex +++ b/lib/norm/core/all_of.ex @@ -24,5 +24,13 @@ defmodule Norm.Core.AllOf do end end end + + defimpl Inspect do + import Inspect.Algebra + + def inspect(sum, opts) do + concat(["#Norm.AllOf<", to_doc(sum.specs, opts), ">"]) + end + end end diff --git a/test/norm/core/all_of_test.exs b/test/norm/core/all_of_test.exs new file mode 100644 index 0000000..9e942ac --- /dev/null +++ b/test/norm/core/all_of_test.exs @@ -0,0 +1,33 @@ +defmodule Norm.Core.AllOfTest do + use Norm.Case, async: true + + describe "conforming" do + test "all specs must hold true" do + all = all_of([spec(is_atom), spec(fn x -> x == :foo end)]) + + assert :foo == conform!(:foo, all) + assert {:error, errors} = conform(:bar, all) + assert errors == [ + %{input: :bar, path: [], spec: "fn x -> x == :foo end"} + ] + + assert {:error, errors} = conform("bar", all) + assert errors == [ + %{input: "bar", path: [], spec: "is_atom()"}, + %{input: "bar", path: [], spec: "fn x -> x == :foo end"} + ] + end + end + + describe "generation" do + @tag :skip + test "generated values conform to all of the specs" do + pos_numbers = all_of([spec(is_integer), spec(fn x -> x > 0 end)]) + + check all num <- gen(pos_numbers) do + assert is_integer(num) + assert num > 0 + end + end + end +end