-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #52 from danielberkompas/43-decimal-type
✨ Add `Decimal` type
- Loading branch information
Showing
4 changed files
with
113 additions
and
3 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,57 @@ | ||
defmodule Cloak.Ecto.Decimal do | ||
@moduledoc """ | ||
An `Ecto.Type` to encrypt a decimal field, relying on the `Decimal` | ||
library, which is a dependency of Ecto. | ||
Decimals are more precise than floats. | ||
## Migration | ||
The database field must be of type `:binary`. | ||
add :encrypted_field, :binary | ||
## Usage | ||
Define an `Encrypted.Decimal` module in your project: | ||
defmodule MyApp.Encrypted.Decimal do | ||
use Cloak.Ecto.Decimal, vault: MyApp.Vault | ||
end | ||
Then, define the type of your desired fields: | ||
schema "table_name" do | ||
field :encrypted_field, MyApp.Encrypted.Decimal | ||
end | ||
""" | ||
|
||
@doc false | ||
defmacro __using__(opts) do | ||
opts = Keyword.merge(opts, vault: Keyword.fetch!(opts, :vault)) | ||
|
||
quote do | ||
use Cloak.Ecto.Type, unquote(opts) | ||
|
||
def cast(closure) when is_function(closure, 0) do | ||
cast(closure.()) | ||
end | ||
|
||
def cast(value), do: Ecto.Type.cast(:decimal, value) | ||
|
||
def before_encrypt(value) do | ||
case Ecto.Type.cast(:decimal, value) do | ||
{:ok, d} -> Decimal.to_string(d) | ||
_error -> :error | ||
end | ||
end | ||
|
||
def after_decrypt(value) do | ||
case Decimal.new(value) do | ||
%Decimal{} = d -> d | ||
_error -> :error | ||
end | ||
end | ||
end | ||
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
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
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,51 @@ | ||
defmodule Cloak.Ecto.DecimalTest do | ||
use ExUnit.Case | ||
|
||
defmodule Field do | ||
use Cloak.Ecto.Decimal, vault: Cloak.Ecto.TestVault | ||
end | ||
|
||
defmodule ClosureField do | ||
use Cloak.Ecto.Decimal, vault: Cloak.Ecto.TestVault, closure: true | ||
end | ||
|
||
test ".type is :binary" do | ||
assert Field.type() == :binary | ||
end | ||
|
||
test ".cast rejects invalid input" do | ||
assert :error == Field.cast("blahblah") | ||
end | ||
|
||
test ".cast converts integers, floats and string to Decimals" do | ||
assert {:ok, Decimal.from_float(21.0)} == Field.cast(21.0) | ||
assert {:ok, Decimal.new(21)} == Field.cast(21) | ||
assert {:ok, Decimal.new(21)} == Field.cast("21") | ||
assert {:ok, Decimal.from_float(21.0)} == Field.cast("21.0") | ||
end | ||
|
||
test ".cast unwraps closures" do | ||
assert {:ok, Decimal.from_float(21.0)} == Field.cast(fn -> 21.0 end) | ||
assert {:ok, Decimal.new(21)} == Field.cast(fn -> 21 end) | ||
assert {:ok, Decimal.new(21)} == Field.cast(fn -> "21" end) | ||
assert {:ok, Decimal.from_float(21.0)} == Field.cast(fn -> "21.0" end) | ||
end | ||
|
||
test ".dump encrypts the value" do | ||
{:ok, ciphertext} = Field.dump(1.0) | ||
assert ciphertext != Decimal.from_float(1.0) | ||
assert ciphertext != "1.0" | ||
end | ||
|
||
test ".load decrypts an encrypted value" do | ||
{:ok, ciphertext} = Field.dump(1.0) | ||
assert {:ok, Decimal.from_float(1.0)} == Field.load(ciphertext) | ||
end | ||
|
||
test ".load with closure option wraps decrypted value" do | ||
{:ok, ciphertext} = ClosureField.dump(1.0) | ||
assert {:ok, closure} = ClosureField.load(ciphertext) | ||
assert is_function(closure, 0) | ||
assert Decimal.from_float(1.0) == closure.() | ||
end | ||
end |