From ff8542f1640c01c53ba655ab47d1f639602aee5a Mon Sep 17 00:00:00 2001 From: Sloane Perrault Date: Fri, 30 Jun 2023 06:50:00 -0400 Subject: [PATCH] add examples, link to spec --- README.md | 3 + lib/type_id.ex | 144 ++++++++++++++++++++++++++++++++++++------ test/type_id_test.exs | 1 + 3 files changed, 130 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e77367a..b29bd21 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,6 @@ def deps do end ``` +## Spec + +The original TypeID spec is defined [here](https://github.com/jetpack-io/typeid). diff --git a/lib/type_id.ex b/lib/type_id.ex index 5ec44d0..cb7acef 100644 --- a/lib/type_id.ex +++ b/lib/type_id.ex @@ -1,18 +1,28 @@ defmodule TypeID do - @moduledoc """ - Documentation for `TypeID`. - """ + @moduledoc File.cwd!() |> Path.join("README.md") |> File.read!() alias TypeID.Base32 @enforce_keys [:prefix, :suffix] defstruct @enforce_keys + @typedoc """ + An internal struct representing a `TypeID`. + """ @opaque t() :: %__MODULE__{ prefix: String.t(), suffix: String.t() } + @doc """ + Generates a new `t:t/0` with the given prefix. + + ### Example + + iex> TypeID.new("acct") + TypeID.from_string!("acct_01h45y0sxkfmntta78gqs1vsw6") + + """ @spec new(prefix :: String.t()) :: t() def new(prefix) do suffix = @@ -22,39 +32,86 @@ defmodule TypeID do %__MODULE__{prefix: prefix, suffix: suffix} end + @doc """ + Returns the type of the given `t:t/0`. + + ### Example + + iex> tid = TypeID.new("doc") + iex> TypeID.type(tid) + "doc" + + """ @spec type(tid :: t()) :: String.t() def type(%__MODULE__{prefix: prefix}) do prefix end + @doc """ + Returns the base 32 encoded suffix of the given `t:t/0` + + ### Example + + iex> tid = TypeID.from_string!("invite_01h45y3ps9e18adjv9zvx743s2") + iex> TypeID.suffix(tid) + "01h45y3ps9e18adjv9zvx743s2" + + """ @spec suffix(tid :: t()) :: String.t() def suffix(%__MODULE__{suffix: suffix}) do suffix end + @doc """ + Returns a string representation of the given `t:t/0` + + ### Example + + iex> tid = TypeID.from_string!("user_01h45y6thxeyg95gnpgqqefgpa") + iex> TypeID.to_string(tid) + "user_01h45y6thxeyg95gnpgqqefgpa" + + """ @spec to_string(tid :: t()) :: String.t() def to_string(%__MODULE__{prefix: prefix, suffix: suffix}) do prefix <> "_" <> suffix end + @doc """ + Returns the raw binary representation of the `t:t/0`'s UUID. + + ### Example + + iex> tid = TypeID.from_string!("order_01h45y849qfqvbeayxmwkxg5x9") + iex> TypeID.uuid_bytes(tid) + <<1, 137, 11, 228, 17, 55, 125, 246, 183, 43, 221, 167, 39, 216, 23, 169>> + + """ @spec uuid_bytes(tid :: t()) :: binary() def uuid_bytes(%__MODULE__{suffix: suffix}) do Base32.decode!(suffix) end + @doc """ + Returns `t:t/0`'s UUID as a string. + + ### Example + + iex> tid = TypeID.from_string!("item_01h45ybmy7fj7b4r9vvp74ms6k") + iex> TypeID.uuid(tid) + "01890be5-d3c7-7c8e-b261-3bdd8e4a64d3" + + """ @spec uuid(tid :: t()) :: String.t() - def uuid(%__MODULE__{suffix: suffix}) do - suffix - |> Base32.decode!() + def uuid(%__MODULE__{} = tid) do + tid + |> uuid_bytes() |> Uniq.UUID.to_string(:default) end - @spec from_string!(String.t()) :: t() | no_return() - def from_string!(str) do - [prefix, suffix] = String.split(str, "_") - from!(prefix, suffix) - end - + @doc """ + Like `from/2` but raises an error if the `prefix` or `suffix` are invalid. + """ @spec from!(prefix :: String.t(), suffix :: String.t()) :: t() | no_return() def from!(prefix, suffix) do validate_prefix!(prefix) @@ -63,6 +120,14 @@ defmodule TypeID do %__MODULE__{prefix: prefix, suffix: suffix} end + @doc """ + Parses a `t:t/0` from a prefix and suffix. + + ### Example + iex> TypeID.from("invoice", "01h45ydzqkemsb9x8gq2q7vpvb") + {:ok, TypeID.from_string!("invoice_01h45ydzqkemsb9x8gq2q7vpvb")} + + """ @spec from(prefix :: String.t(), suffix :: String.t()) :: {:ok, t()} | :error def from(prefix, suffix) do {:ok, from!(prefix, suffix)} @@ -70,25 +135,49 @@ defmodule TypeID do ArgumentError -> :error end - @spec from_string!(String.t()) :: {:ok, t()} | :error + @doc """ + Like `from_string/1` but raises an error if the string is invalid. + """ + @spec from_string!(String.t()) :: t() | no_return() + def from_string!(str) do + [prefix, suffix] = String.split(str, "_") + from!(prefix, suffix) + end + + @doc """ + Parses a `t:t/0` from a string. + + ### Example + + iex> TypeID.from_string("game_01h45yhtgqfhxbcrsfbhxdsdvy") + {:ok, TypeID.from_string!("game_01h45yhtgqfhxbcrsfbhxdsdvy")} + + """ + @spec from_string(String.t()) :: {:ok, t()} | :error def from_string(str) do {:ok, from_string!(str)} rescue ArgumentError -> :error end + @doc """ + Like `from_uuid/2` but raises an error if the `prefix` or `uuid` are invalid. + """ @spec from_uuid!(prefix :: String.t(), uuid :: String.t()) :: t() | no_return() def from_uuid!(prefix, uuid) do {:ok, %Uniq.UUID{bytes: uuid_bytes, version: 7}} = Uniq.UUID.parse(uuid) from_uuid_bytes!(prefix, uuid_bytes) end - @spec from_uuid_bytes!(prefix :: String.t(), uuid_bytes :: binary()) :: t() | no_return() - def from_uuid_bytes!(prefix, <>) do - suffix = Base32.encode(uuid_bytes) - from!(prefix, suffix) - end + @doc """ + Parses a `t:t/0` from a prefix and a string representation of a uuid. + ### Example + + iex> TypeID.from_uuid("device", "01890be9-b248-777e-964e-af1d244f997d") + {:ok, TypeID.from_string!("device_01h45ykcj8exz9cknf3mj4z6bx")} + + """ @spec from_uuid(prefix :: String.t(), uuid :: String.t()) :: {:ok, t()} | :error def from_uuid(prefix, uuid) do {:ok, from_uuid!(prefix, uuid)} @@ -96,6 +185,25 @@ defmodule TypeID do ArgumentError -> :error end + @doc """ + Like `from_uuid_bytes/2` but raises an error if the `prefix` or `uuid_bytes` + are invalid. + """ + @spec from_uuid_bytes!(prefix :: String.t(), uuid_bytes :: binary()) :: t() | no_return() + def from_uuid_bytes!(prefix, <>) do + suffix = Base32.encode(uuid_bytes) + from!(prefix, suffix) + end + + @doc """ + Parses a `t:t/0` from a prefix and a raw binary uuid. + + ### Example + + iex> TypeID.from_uuid_bytes("policy", <<1, 137, 11, 235, 83, 221, 116, 212, 161, 42, 205, 139, 182, 243, 175, 110>>) + {:ok, TypeID.from_string!("policy_01h45ypmyxekaa2apdhevf7bve")} + + """ @spec from_uuid_bytes(prefix :: String.t(), uuid_bytes :: binary()) :: {:ok, t()} | :error def from_uuid_bytes(prefix, uuid_bytes) do {:ok, from_uuid_bytes!(prefix, uuid_bytes)} diff --git a/test/type_id_test.exs b/test/type_id_test.exs index daa2676..b75dedd 100644 --- a/test/type_id_test.exs +++ b/test/type_id_test.exs @@ -1,5 +1,6 @@ defmodule TypeIDTest do use ExUnit.Case + doctest TypeID, except: [new: 1] describe "new/1" do test "returns a new TypeID struct" do