Skip to content

Commit 172edad

Browse files
committed
Merge branch 'Better-enumerable-type-check'
2 parents 8880dd5 + 13234f8 commit 172edad

3 files changed

Lines changed: 55 additions & 1 deletion

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ erl_crash.dump
2222
# Ignore package tarball (built via "mix hex.build").
2323
norm-*.tar
2424

25+
# Elixir Language Server (e.g. for VS Code users).
26+
/.elixir_ls

lib/norm/spec/collection.ex

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ defmodule Norm.Spec.Collection do
1212
alias Norm.Conformer.Conformable
1313

1414
def conform(%{spec: spec, opts: opts}, input, path) do
15-
with :ok <- check_distinct(input, path, opts),
15+
with :ok <- check_enumerable(input, path, opts),
16+
:ok <- check_map_of(input, path, opts),
17+
:ok <- check_distinct(input, path, opts),
1618
:ok <- check_counts(input, path, opts) do
1719
results =
1820
input
@@ -60,6 +62,29 @@ defmodule Norm.Spec.Collection do
6062
:ok
6163
end
6264
end
65+
66+
defp check_enumerable(input, path, _opts) do
67+
if Enumerable.impl_for(input) == nil do
68+
{:error, [Conformer.error(path, input, "not enumerable")]}
69+
else
70+
:ok
71+
end
72+
end
73+
74+
defp check_map_of(input, path, opts) do
75+
cond do
76+
# if coll_of was used, accept every kind of list
77+
opts[:into] == [] ->
78+
:ok
79+
80+
# in case of map_of, check the format of the enumerable
81+
Enum.all?(input, &match?({_, _}, &1)) ->
82+
:ok
83+
84+
true ->
85+
{:error, [Conformer.error(path, input, "not a map")]}
86+
end
87+
end
6388
end
6489

6590
if Code.ensure_loaded?(StreamData) do

test/norm_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@ defmodule NormTest do
161161
assert %{1 => :foo, 2 => :bar} == conform!(%{1 => :foo, 2 => :bar}, spec)
162162
end
163163

164+
test "doesn't throw for non-enumerable inputs" do
165+
spec = map_of(spec(is_integer()), spec(is_atom()))
166+
167+
assert {:error, errors} = conform("not-a-map!", spec)
168+
assert errors == [
169+
%{spec: "not enumerable", input: "not-a-map!", path: []}
170+
]
171+
end
172+
173+
test "doesn't throw for list inputs" do
174+
spec = map_of(spec(is_integer()), spec(is_atom()))
175+
176+
assert {:error, errors} = conform([1, 2, 3], spec)
177+
assert errors == [
178+
%{spec: "not a map", input: [1, 2, 3], path: []}
179+
]
180+
end
181+
164182
property "can be generated" do
165183
check all m <- gen(map_of(spec(is_integer()), spec(is_atom()))) do
166184
assert is_map(m)
@@ -181,6 +199,15 @@ defmodule NormTest do
181199
]
182200
end
183201

202+
test "doesn't throw for non-enumerable inputs" do
203+
spec = coll_of(spec(is_integer()))
204+
205+
assert {:error, errors} = conform("not-a-collection!", spec)
206+
assert errors == [
207+
%{spec: "not enumerable", input: "not-a-collection!", path: []}
208+
]
209+
end
210+
184211
test "conforming returns the conformed values" do
185212
spec = coll_of(schema(%{name: spec(is_binary())}))
186213
input = [

0 commit comments

Comments
 (0)