-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
106 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
defmodule BitcrowdEcto.FixedWidthInteger do | ||
@moduledoc """ | ||
An Ecto type that automatically validates that the given integer fits the underlying DB type. | ||
This turns the ugly Postgrex errors into neat `validation: :cast` changeset errors without | ||
having to manually `validate_number` all `:integer` fields. | ||
Named widths are based on Postgres' integer types. | ||
https://www.postgresql.org/docs/current/datatype-numeric.html | ||
""" | ||
|
||
use Ecto.ParameterizedType | ||
|
||
@impl true | ||
def init(opts) do | ||
case Keyword.get(opts, :width, 4) do | ||
2 -> -32_768..32_767 | ||
4 -> -2_147_483_648..2_147_483_647 | ||
8 -> -9_223_372_036_854_775_808..9_223_372_036_854_775_807 | ||
:smallint -> -32_768..32_767 | ||
:integer -> -2_147_483_648..2_147_483_647 | ||
:bigint -> -9_223_372_036_854_775_808..9_223_372_036_854_775_807 | ||
:smallserial -> 1..32_767 | ||
:serial -> 1..2_147_483_647 | ||
:bigserial -> 1..9_223_372_036_854_775_807 | ||
end | ||
end | ||
|
||
@impl true | ||
def type(_range), do: :integer | ||
|
||
@impl true | ||
def cast(value, range) do | ||
if is_integer(value) and value not in range do | ||
:error | ||
else | ||
Ecto.Type.cast(:integer, value) | ||
end | ||
end | ||
|
||
@impl true | ||
def load(value, loader, _range) do | ||
Ecto.Type.load(:integer, value, loader) | ||
end | ||
|
||
@impl true | ||
def dump(value, dumper, _range) do | ||
Ecto.Type.dump(:integer, value, dumper) | ||
end | ||
|
||
@impl true | ||
def equal?(a, b, _range) do | ||
a == b | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
defmodule BitcrowdEcto.FixedWidthIntegerTest do | ||
use ExUnit.Case, async: true | ||
import BitcrowdEcto.Assertions | ||
import Ecto.Changeset | ||
|
||
defmodule TestSchema do | ||
use Ecto.Schema | ||
|
||
embedded_schema do | ||
field(:int_4, BitcrowdEcto.FixedWidthInteger, width: 4) | ||
field(:int_smallint, BitcrowdEcto.FixedWidthInteger, width: :smallint) | ||
field(:int_bigserial, BitcrowdEcto.FixedWidthInteger, width: :bigserial) | ||
end | ||
end | ||
|
||
test "casting an out-of-range value results in a changeset error" do | ||
for ok <- [-2, 2, 0, -2_147_483_648, 2_147_483_647] do | ||
cs = cast(%TestSchema{}, %{int_4: ok}, [:int_4]) | ||
assert cs.valid? | ||
end | ||
|
||
for not_ok <- [-2_147_483_649, 2_147_483_648] do | ||
cs = cast(%TestSchema{}, %{int_4: not_ok}, [:int_4]) | ||
refute cs.valid? | ||
assert_error_on(cs, :int_4, :cast) | ||
end | ||
|
||
for ok <- [-2, 2, 0, -32768, 32767] do | ||
Check warning on line 28 in test/bitcrowd_ecto/fixed_width_integer_test.exs GitHub Actions / Build and test (23.1.1, 1.12.2)
|
||
cs = cast(%TestSchema{}, %{int_smallint: ok}, [:int_smallint]) | ||
assert cs.valid? | ||
end | ||
|
||
for not_ok <- [-32769, 32768] do | ||
Check warning on line 33 in test/bitcrowd_ecto/fixed_width_integer_test.exs GitHub Actions / Build and test (23.1.1, 1.12.2)
|
||
cs = cast(%TestSchema{}, %{int_smallint: not_ok}, [:int_smallint]) | ||
refute cs.valid? | ||
assert_error_on(cs, :int_smallint, :cast) | ||
end | ||
|
||
for ok <- [1, 9_223_372_036_854_775_807] do | ||
cs = cast(%TestSchema{}, %{int_bigserial: ok}, [:int_bigserial]) | ||
assert cs.valid? | ||
end | ||
|
||
for not_ok <- [-1, 0, 9_223_372_036_854_775_808] do | ||
cs = cast(%TestSchema{}, %{int_bigserial: not_ok}, [:int_bigserial]) | ||
refute cs.valid? | ||
assert_error_on(cs, :int_bigserial, :cast) | ||
end | ||
end | ||
end |