Skip to content

Commit

Permalink
Refactor Changeset validators
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieuprog committed May 2, 2021
1 parent 6c8c6c3 commit 9c0a6b1
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 18 deletions.
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# TzExtra

`tz_extra` provides a few utilities to work with time zones.
`tz_extra` provides a few utilities to work with time zones:

* [`TzExtra.countries_time_zones/1`](#`TzExtra.countries_time_zones/1`): returns a list of time zone data by country
* [`TzExtra.time_zone_identifiers/1`](#`TzExtra.time_zone_identifiers/1`): returns a list of time zone identifiers
* [`TzExtra.civil_time_zone_identifiers/1`](#`TzExtra.civil_time_zone_identifiers/1`): returns a list of time zone identifiers that are tied to a country
* [`TzExtra.countries/0`](#`TzExtra.countries/0`): returns a list of ISO country codes with their English name
* [`TzExtra.Changeset.validate_time_zone/3`](#`TzExtra.Changeset.validate_time_zone/3`): an Ecto Changeset validator, validating that the user input is a valid time zone
* [`TzExtra.Changeset.validate_civil_time_zone/3`](#`TzExtra.Changeset.validate_civil_time_zone/3`): an Ecto Changeset validator, validating that the user input is a valid civil time zone
* [`TzExtra.Changeset.validate_iso_country_code/3`](#`TzExtra.Changeset.validate_iso_country_code/3`): an Ecto Changeset validator, validating that the user input is a valid ISO country code

### `TzExtra.countries_time_zones/1`

Expand Down Expand Up @@ -102,7 +110,7 @@ iex> TzExtra.countries() |> Enum.take(5)
]
```

### `TzExtra.validate_time_zone/3`
### `TzExtra.Changeset.validate_time_zone/3`

```elixir
import TzExtra.Changeset
Expand All @@ -111,9 +119,20 @@ changeset
|> validate_time_zone(:time_zone)
```

You may pass the options `:exclude_non_civil` and `:exclude_alias` described above, as well as the `:message` option to customize the error message.
You may pass the option `:include_alias` as described above, as well as the `:message` option to customize the error message.

### `TzExtra.Changeset.validate_civil_time_zone/3`

```elixir
import TzExtra.Changeset

changeset
|> validate_civil_time_zone(:time_zone)
```

You may pass the options `:include_alias` and `:prepend_utc` as described above, as well as the `:message` option to customize the error message.

### `TzExtra.validate_time_zone/3`
### `TzExtra.Changeset.validate_iso_country_code/3`

```elixir
import TzExtra.Changeset
Expand Down
14 changes: 11 additions & 3 deletions lib/changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ if Code.ensure_loaded?(Ecto.Changeset) do
defmodule TzExtra.Changeset do
require TzExtra.Compiler

def validate_time_zone(%Ecto.Changeset{} = changeset, field, opts \\ []) when is_atom(field) do
Ecto.Changeset.validate_change changeset, field, {:time_zone, []}, fn _field, value ->
def validate_time_zone_identifier(%Ecto.Changeset{} = changeset, field, opts \\ []) when is_atom(field) do
Ecto.Changeset.validate_change changeset, field, {:time_zone_identifier, []}, fn _field, value ->
if Enum.member?(TzExtra.time_zone_identifiers(opts), value),
do: [],
else: [{field, {message(opts, "is not a valid time zone identifier"), [validation: :time_zone]}}]
else: [{field, {message(opts, "is not a valid time zone identifier"), [validation: :time_zone_identifier]}}]
end
end

def validate_civil_time_zone_identifier(%Ecto.Changeset{} = changeset, field, opts \\ []) when is_atom(field) do
Ecto.Changeset.validate_change changeset, field, {:civil_time_zone_identifier, []}, fn _field, value ->
if Enum.member?(TzExtra.civil_time_zone_identifiers(opts), value),
do: [],
else: [{field, {message(opts, "is not a valid civil time zone identifier"), [validation: :civil_time_zone_identifier]}}]
end
end

Expand Down
46 changes: 35 additions & 11 deletions test/changeset_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ defmodule TzExtra.ChangesetTest do
data = %{time_zone: ""}
types = %{time_zone: :string}
changeset =
cast({data, types}, %{time_zone: "Europe/London"}, [:time_zone])
|> validate_time_zone(:time_zone)
cast({data, types}, %{time_zone: "Etc/UTC"}, [:time_zone])
|> validate_time_zone_identifier(:time_zone)

assert changeset.valid?
assert validations(changeset) == [time_zone: {:time_zone, []}]
assert validations(changeset) == [time_zone: {:time_zone_identifier, []}]
assert changeset.errors == []
end

Expand All @@ -21,10 +21,10 @@ defmodule TzExtra.ChangesetTest do
types = %{time_zone: :string}
changeset =
cast({data, types}, %{time_zone: "America/Guadeloupe"}, [:time_zone])
|> validate_time_zone(:time_zone, include_alias: true)
|> validate_time_zone_identifier(:time_zone, include_alias: true)

assert changeset.valid?
assert validations(changeset) == [time_zone: {:time_zone, []}]
assert validations(changeset) == [time_zone: {:time_zone_identifier, []}]
assert changeset.errors == []
end

Expand All @@ -33,23 +33,47 @@ defmodule TzExtra.ChangesetTest do
types = %{time_zone: :string}
changeset =
cast({data, types}, %{time_zone: "America/Guadeloupe"}, [:time_zone])
|> validate_time_zone(:time_zone)
|> validate_time_zone_identifier(:time_zone)

refute changeset.valid?
assert validations(changeset) == [time_zone: {:time_zone, []}]
assert changeset.errors == [time_zone: {"is not a valid time zone identifier", [validation: :time_zone]}]
assert validations(changeset) == [time_zone: {:time_zone_identifier, []}]
assert changeset.errors == [time_zone: {"is not a valid time zone identifier", [validation: :time_zone_identifier]}]
end

test "invalid time zone with custom message" do
data = %{time_zone: ""}
types = %{time_zone: :string}
changeset =
cast({data, types}, %{time_zone: "Europe/Foo"}, [:time_zone])
|> validate_time_zone(:time_zone, message: "foo")
|> validate_time_zone_identifier(:time_zone, message: "foo")

refute changeset.valid?
assert validations(changeset) == [time_zone: {:time_zone_identifier, []}]
assert changeset.errors == [time_zone: {"foo", [validation: :time_zone_identifier]}]
end

test "valid civil time zone" do
data = %{time_zone: ""}
types = %{time_zone: :string}
changeset =
cast({data, types}, %{time_zone: "Europe/London"}, [:time_zone])
|> validate_civil_time_zone_identifier(:time_zone)

assert changeset.valid?
assert validations(changeset) == [time_zone: {:civil_time_zone_identifier, []}]
assert changeset.errors == []
end

test "invalid civil time zone" do
data = %{time_zone: ""}
types = %{time_zone: :string}
changeset =
cast({data, types}, %{time_zone: "Etc/UTC"}, [:time_zone])
|> validate_civil_time_zone_identifier(:time_zone)

refute changeset.valid?
assert validations(changeset) == [time_zone: {:time_zone, []}]
assert changeset.errors == [time_zone: {"foo", [validation: :time_zone]}]
assert validations(changeset) == [time_zone: {:civil_time_zone_identifier, []}]
assert changeset.errors == [time_zone: {"is not a valid civil time zone identifier", [validation: :civil_time_zone_identifier]}]
end

test "valid iso country code" do
Expand Down

0 comments on commit 9c0a6b1

Please sign in to comment.