Current section
Files
Jump to
Current section
Files
lib/uxid.ex
defmodule UXID do
@moduledoc """
UXIDs are identifiers which:
* describe the resource (to help in debugging and investigation)
* work well with copy and paste (double clicking should select the entire ID)
* can be shortened for low cardinality resources
* are very unlikely to collide
* are secure against enumeration attacks
* can be generated by application code (not tied to the datastore)
* are K-sortable (lexicographically sortable by time - works well with datastore indexing)
* do not require any coordination (human or automated) at startup, or generation
Many of the concepts of Stripe IDs have been used in this library.
"""
defstruct [:encoded, :prefix, :rand_size, :rand, :rand_encoded, :string, :time, :time_encoded]
@typedoc "Options for generating a UXID"
@type option :: {:time, integer()} | {:rand_size, integer()} | {:prefix, String.t()}
@type options :: [option()]
@typedoc "A UXID represented as a String"
@type uxid_string :: String.t()
@typedoc "An error string returned by the library if generation fails"
@type error_string :: String.t()
@typedoc "A UXID struct"
@type t() :: %__MODULE__{
encoded: String.t() | nil,
prefix: String.t() | nil,
rand_size: pos_integer() | nil,
rand: binary() | nil,
rand_encoded: String.t() | nil,
string: String.t() | nil,
time: pos_integer() | nil,
time_encoded: String.t() | nil
}
alias UXID.Decoder
alias UXID.Encoder
@spec generate(opts :: options()) :: {:ok, uxid_string()} | {:error, error_string()}
@doc """
Returns an encoded UXID string along with response status.
"""
def generate(opts \\ []) do
case new(opts) do
{:ok, %__MODULE__{string: string}} ->
{:ok, string}
{:error, error} ->
{:error, error}
end
end
@spec generate!(opts :: options()) :: uxid_string()
@doc """
Returns an unwrapped encoded UXID string or raises on error.
"""
def generate!(opts \\ []) do
case generate(opts) do
{:ok, uxid} -> uxid
{:error, error} -> raise error
end
end
@spec new(opts :: options()) :: {:ok, __MODULE__.t()} | {:error, error_string()}
@doc """
Returns a new UXID struct. This is useful for development.
"""
def new(opts \\ []) do
timestamp = Keyword.get(opts, :time, System.system_time(:millisecond))
rand_size = Keyword.get(opts, :rand_size, 10)
prefix = Keyword.get(opts, :prefix)
%__MODULE__{
prefix: prefix,
rand_size: rand_size,
time: timestamp
}
|> Encoder.process()
|> case do
{:ok, %__MODULE__{string: string} = struct} when not is_nil(string) ->
{:ok, struct}
{:error, error} ->
{:error, error}
:error ->
{:error, "Unknown error occurred"}
end
end
@doc """
Decodes a UXID from a string.
"""
def decode(string) do
Decoder.process(string)
end
end