Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ecto type breaking when no ecto #7

Merged
merged 1 commit into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The package can be installed by adding `uuid_v7` to your list of dependencies in
```elixir
def deps do
[
{:uuid_v7, "~> 0.4.4"}
{:uuid_v7, "~> 0.5.0"}
]
end
```
Expand All @@ -61,9 +61,9 @@ Use this the same way you would use `Ecto.UUID`. For example:
defmodule MyApp.Blog.Post do
use Ecto.Schema

@primary_key {:id, UUIDv7, autogenerate: true}
@primary_key {:id, UUIDv7.Type, autogenerate: true}

@foreign_key_type UUIDv7
@foreign_key_type UUIDv7.Type

schema "blog_posts" do
field :text, :string
Expand Down
156 changes: 156 additions & 0 deletions lib/type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
if Code.ensure_loaded?(Ecto.Type) do
defmodule UUIDv7.Type do
@moduledoc """
Ecto type for UUIDv7.
"""
use Ecto.Type

@type t :: UUIDv7.t()
@type raw :: UUIDv7.raw()

@doc false
@impl Ecto.Type
def type, do: :uuid

# Callback invoked by autogenerate fields.
@doc false
@impl Ecto.Type
def autogenerate, do: UUIDv7.generate()

@doc """
Casts either a string in the canonical, human-readable UUID format or a
16-byte binary to a UUID in its canonical, human-readable UUID format.

If `uuid` is neither of these, `:error` will be returned.

Since both binaries and strings are represent as binaries, this means some
strings you may not expect are actually also valid UUIDs in their binary form
and so will be casted into their string form.

## Examples

iex> raw = <<1, 141, 236, 237, 26, 200, 116, 82, 179, 112, 220, 56, 9, 179, 208, 93>>
iex> UUIDv7.cast(raw)
{:ok, "018deced-1ac8-7452-b370-dc3809b3d05d"}

iex> UUIDv7.cast("018deced-1ac8-7452-b370-dc3809b3d05d")
{:ok, "018deced-1ac8-7452-b370-dc3809b3d05d"}

iex> UUIDv7.cast("warehouse worker")
{:ok, "77617265-686f-7573-6520-776f726b6572"}

"""
@doc group: :ecto
@impl Ecto.Type
@spec cast(t | raw | any) :: {:ok, t} | :error
def cast(uuid)

def cast(
<<a1, a2, a3, a4, a5, a6, a7, a8, ?-, b1, b2, b3, b4, ?-, c1, c2, c3, c4, ?-, d1, d2,
d3, d4, ?-, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>
) do
<<c(a1), c(a2), c(a3), c(a4), c(a5), c(a6), c(a7), c(a8), ?-, c(b1), c(b2), c(b3), c(b4),
?-, c(c1), c(c2), c(c3), c(c4), ?-, c(d1), c(d2), c(d3), c(d4), ?-, c(e1), c(e2), c(e3),
c(e4), c(e5), c(e6), c(e7), c(e8), c(e9), c(e10), c(e11), c(e12)>>
catch
:error -> :error
else
hex_uuid -> {:ok, hex_uuid}
end

def cast(<<_::128>> = raw_uuid), do: {:ok, UUIDv7.encode(raw_uuid)}
def cast(_), do: :error

@doc """
Same as `cast/1` but raises `Ecto.CastError` on invalid arguments.
"""
@doc group: :ecto
@spec cast!(t | raw | any) :: t
def cast!(uuid) do
case cast(uuid) do
{:ok, hex_uuid} -> hex_uuid
:error -> raise Ecto.CastError, type: __MODULE__, value: uuid
end
end

@compile {:inline, c: 1}

defp c(?0), do: ?0
defp c(?1), do: ?1
defp c(?2), do: ?2
defp c(?3), do: ?3
defp c(?4), do: ?4
defp c(?5), do: ?5
defp c(?6), do: ?6
defp c(?7), do: ?7
defp c(?8), do: ?8
defp c(?9), do: ?9
defp c(?A), do: ?a
defp c(?B), do: ?b
defp c(?C), do: ?c
defp c(?D), do: ?d
defp c(?E), do: ?e
defp c(?F), do: ?f
defp c(?a), do: ?a
defp c(?b), do: ?b
defp c(?c), do: ?c
defp c(?d), do: ?d
defp c(?e), do: ?e
defp c(?f), do: ?f
defp c(_), do: throw(:error)

@doc """
Converts a string representing a UUID into a raw binary.
"""
@doc group: :ecto
@impl Ecto.Type
@spec dump(uuid_string :: t | any) :: {:ok, raw} | :error
def dump(uuid_string)

def dump(uuid_string) do
case UUIDv7.decode(uuid_string) do
:error -> :error
raw_uuid -> {:ok, raw_uuid}
end
end

@doc """
Same as `dump/1` but raises `Ecto.ArgumentError` on invalid arguments.
"""
@doc group: :ecto
@spec dump!(t | any) :: raw
def dump!(uuid) do
with :error <- UUIDv7.decode(uuid) do
raise ArgumentError, "cannot dump given UUID to binary: #{inspect(uuid)}"
end
end

@doc """
Converts a binary UUID into a string.
"""
@doc group: :ecto
@impl Ecto.Type
@spec load(raw | any) :: {:ok, t} | :error
def load(<<_::128>> = raw_uuid), do: {:ok, UUIDv7.encode(raw_uuid)}

def load(<<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> = string) do
raise ArgumentError,
"trying to load string UUID as UUID: #{inspect(string)}. " <>
"Maybe you wanted to declare :uuid as your database field?"
end

def load(_), do: :error

@doc """
Same as `load/1` but raises `Ecto.ArgumentError` on invalid arguments.
"""
@doc group: :ecto
@spec load!(raw | any) :: t
def load!(value) do
case load(value) do
{:ok, hex_uuid} -> hex_uuid
:error -> raise ArgumentError, "cannot load given binary as UUID: #{inspect(value)}"
end
end
end
end
149 changes: 0 additions & 149 deletions lib/uuidv7.ex
Original file line number Diff line number Diff line change
Expand Up @@ -179,153 +179,4 @@ defmodule UUIDv7 do
defp d(?e), do: 14
defp d(?f), do: 15
defp d(_), do: throw(:error)

if Code.ensure_loaded?(Ecto.Type) do
use Ecto.Type

@doc false
@impl Ecto.Type
def type, do: :uuid

# Callback invoked by autogenerate fields.
@doc false
@impl Ecto.Type
def autogenerate, do: generate()

@doc """
Casts either a string in the canonical, human-readable UUID format or a
16-byte binary to a UUID in its canonical, human-readable UUID format.

If `uuid` is neither of these, `:error` will be returned.

Since both binaries and strings are represent as binaries, this means some
strings you may not expect are actually also valid UUIDs in their binary form
and so will be casted into their string form.

## Examples

iex> raw = <<1, 141, 236, 237, 26, 200, 116, 82, 179, 112, 220, 56, 9, 179, 208, 93>>
iex> UUIDv7.cast(raw)
{:ok, "018deced-1ac8-7452-b370-dc3809b3d05d"}

iex> UUIDv7.cast("018deced-1ac8-7452-b370-dc3809b3d05d")
{:ok, "018deced-1ac8-7452-b370-dc3809b3d05d"}

iex> UUIDv7.cast("warehouse worker")
{:ok, "77617265-686f-7573-6520-776f726b6572"}

"""
@doc group: :ecto
@impl Ecto.Type
@spec cast(t | raw | any) :: {:ok, t} | :error
def cast(uuid)

def cast(
<<a1, a2, a3, a4, a5, a6, a7, a8, ?-, b1, b2, b3, b4, ?-, c1, c2, c3, c4, ?-, d1, d2,
d3, d4, ?-, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>
) do
<<c(a1), c(a2), c(a3), c(a4), c(a5), c(a6), c(a7), c(a8), ?-, c(b1), c(b2), c(b3), c(b4),
?-, c(c1), c(c2), c(c3), c(c4), ?-, c(d1), c(d2), c(d3), c(d4), ?-, c(e1), c(e2), c(e3),
c(e4), c(e5), c(e6), c(e7), c(e8), c(e9), c(e10), c(e11), c(e12)>>
catch
:error -> :error
else
hex_uuid -> {:ok, hex_uuid}
end

def cast(<<_::128>> = raw_uuid), do: {:ok, encode(raw_uuid)}
def cast(_), do: :error

@doc """
Same as `cast/1` but raises `Ecto.CastError` on invalid arguments.
"""
@doc group: :ecto
@spec cast!(t | raw | any) :: t
def cast!(uuid) do
case cast(uuid) do
{:ok, hex_uuid} -> hex_uuid
:error -> raise Ecto.CastError, type: __MODULE__, value: uuid
end
end

@compile {:inline, c: 1}

defp c(?0), do: ?0
defp c(?1), do: ?1
defp c(?2), do: ?2
defp c(?3), do: ?3
defp c(?4), do: ?4
defp c(?5), do: ?5
defp c(?6), do: ?6
defp c(?7), do: ?7
defp c(?8), do: ?8
defp c(?9), do: ?9
defp c(?A), do: ?a
defp c(?B), do: ?b
defp c(?C), do: ?c
defp c(?D), do: ?d
defp c(?E), do: ?e
defp c(?F), do: ?f
defp c(?a), do: ?a
defp c(?b), do: ?b
defp c(?c), do: ?c
defp c(?d), do: ?d
defp c(?e), do: ?e
defp c(?f), do: ?f
defp c(_), do: throw(:error)

@doc """
Converts a string representing a UUID into a raw binary.
"""
@doc group: :ecto
@impl Ecto.Type
@spec dump(uuid_string :: t | any) :: {:ok, raw} | :error
def dump(uuid_string)

def dump(uuid_string) do
case decode(uuid_string) do
:error -> :error
raw_uuid -> {:ok, raw_uuid}
end
end

@doc """
Same as `dump/1` but raises `Ecto.ArgumentError` on invalid arguments.
"""
@doc group: :ecto
@spec dump!(t | any) :: raw
def dump!(uuid) do
with :error <- decode(uuid) do
raise ArgumentError, "cannot dump given UUID to binary: #{inspect(uuid)}"
end
end

@doc """
Converts a binary UUID into a string.
"""
@doc group: :ecto
@impl Ecto.Type
@spec load(raw | any) :: {:ok, t} | :error
def load(<<_::128>> = raw_uuid), do: {:ok, encode(raw_uuid)}

def load(<<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> = string) do
raise ArgumentError,
"trying to load string UUID as UUID: #{inspect(string)}. " <>
"Maybe you wanted to declare :uuid as your database field?"
end

def load(_), do: :error

@doc """
Same as `load/1` but raises `Ecto.ArgumentError` on invalid arguments.
"""
@doc group: :ecto
@spec load!(raw | any) :: t
def load!(value) do
case load(value) do
{:ok, hex_uuid} -> hex_uuid
:error -> raise ArgumentError, "cannot load given binary as UUID: #{inspect(value)}"
end
end
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule UUIDv7.MixProject do
use Mix.Project

@version "0.4.4"
@version "0.5.0"

@repo_url "https://github.com/ryanwinchester/uuidv7"

Expand Down
Loading
Loading