Skip to content

Commit 40563cc

Browse files
authored
Merge pull request #83 from uhoreg/type_casting
Allow defining a type as a cast
2 parents b3663fb + 85eae94 commit 40563cc

File tree

4 files changed

+78
-0
lines changed

4 files changed

+78
-0
lines changed

lib/drops/predicates/helpers.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ defmodule Drops.Predicates.Helpers do
2626
end
2727
end
2828

29+
def apply_predicate({:cast, input_type, output_type, cast_opts}, {:ok, value}) do
30+
caster = cast_opts[:caster] || Drops.Casters
31+
32+
try do
33+
casted_value =
34+
apply(caster, :cast, [input_type, output_type, value] ++ cast_opts)
35+
36+
{:ok, casted_value}
37+
38+
rescue
39+
exception -> {:error, [input: value, predicate: :cast, args: [Exception.message(exception)]]}
40+
end
41+
end
42+
2943
def apply_predicate(_, {:error, _} = error) do
3044
error
3145
end

lib/drops/type.ex

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,43 @@ defmodule Drops.Type do
135135
]}
136136
iex> Enum.map(errors, &to_string/1)
137137
["unit_price must be greater than 0"]
138+
139+
## Casting
140+
141+
defmodule IntegerString do
142+
use Drops.Type, cast(:string) |> integer()
143+
end
144+
145+
iex> defmodule IntegerStringContract do
146+
...> use Drops.Contract
147+
...>
148+
...> schema do
149+
...> %{
150+
...> number: IntegerString
151+
...> }
152+
...> end
153+
...> end
154+
iex> IntegerStringContract.conform(%{number: "1"})
155+
{:ok, %{number: 1}}
156+
157+
defmodule IntegerOrIntegerString do
158+
use Drops.Type, union([:integer, cast(:string) |> integer()])
159+
end
160+
161+
iex> defmodule IntegerOrIntegerStringContract do
162+
...> use Drops.Contract
163+
...>
164+
...> schema do
165+
...> %{
166+
...> number: IntegerOrIntegerString,
167+
...> }
168+
...> end
169+
...> end
170+
iex> IntegerOrIntegerStringContract.conform(%{number: "1"})
171+
{:ok, %{number: 1}}
172+
iex> IntegerOrIntegerStringContract.conform(%{number: 1})
173+
{:ok, %{number: 1}}
174+
138175
"""
139176
@doc since: "0.2.0"
140177

@@ -266,6 +303,7 @@ defmodule Drops.Type do
266303
def infer_primitive(map) when is_map(map), do: :map
267304
def infer_primitive(name) when is_atom(name), do: name
268305
def infer_primitive({:type, {name, _}}), do: name
306+
def infer_primitive({:cast, {_input_type, output_type, _cast_opts}}), do: infer_primitive(output_type)
269307
def infer_primitive(_), do: nil
270308

271309
@doc false
@@ -285,6 +323,16 @@ defmodule Drops.Type do
285323
[predicate(:type?, [type])]
286324
end
287325

326+
def infer_constraints({:cast, {{:type, {input_type, input_predicates}}, {:type, {output_type, output_predicates}}, cast_opts}}) do
327+
{:and,
328+
Enum.concat([
329+
infer_constraints({:type, {input_type, input_predicates}}),
330+
[{:cast, input_type, output_type, cast_opts}],
331+
infer_constraints({:type, {output_type, output_predicates}})
332+
])
333+
}
334+
end
335+
288336
@doc false
289337
def predicate({:match?, %Regex{} = regex}) do
290338
{:predicate, {:match?, {:regex, regex.source, regex.opts}}}

lib/drops/type/dsl.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ defmodule Drops.Type.DSL do
146146
{:cast, {type(input_type), output_type, cast_opts}}
147147
end
148148

149+
def type({:cast, {input_type, output_type, cast_opts}}, more_predicates) when is_tuple(input_type) do
150+
{:cast, {input_type, type(output_type, more_predicates), cast_opts}}
151+
end
152+
149153
@doc ~S"""
150154
Returns a union type specification.
151155
@@ -465,4 +469,8 @@ defmodule Drops.Type.DSL do
465469
def map(predicates) when is_list(predicates) do
466470
type(:map, predicates)
467471
end
472+
473+
def map({:cast, _} = cast_spec, predicates \\ []) do
474+
type(cast_spec, map(predicates))
475+
end
468476
end

test/drops/type_test.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ defmodule Drops.TypeTest do
2323
use Drops.Type, union([:integer, :float], gt?: 0)
2424
end
2525

26+
defmodule IntegerString do
27+
use Drops.Type, cast(:string) |> integer()
28+
end
29+
30+
defmodule IntegerOrIntegerString do
31+
use Drops.Type, union([:integer, cast(:string) |> integer()])
32+
end
33+
2634
doctest Drops.Type
2735

2836
describe "type registry" do

0 commit comments

Comments
 (0)