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