defmodule TUID.Base58 do @moduledoc """ `Base58` provides heper functions to encode and decode base58 encoded strings https://en.wikipedia.org/wiki/Binary-to-text_encoding#Base58 credit to blog post: https://danschultzer.com/posts/prefixed-base62-uuidv7-object-ids-with-ecto """ @b58_char_list ~c"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" for {digit, idx} <- Enum.with_index(@b58_char_list) do def encode(unquote(idx)), do: unquote(<>) end def encode(number) do encode(div(number, unquote(length(@b58_char_list)))) <> encode(rem(number, unquote(length(@b58_char_list)))) end def decode(string) do string |> String.split("", trim: true) |> Enum.reverse() |> Enum.reduce_while({:ok, {0, 0}}, fn char, {:ok, {acc, step}} -> case decode_base58_char(char) do {:ok, number} -> {:cont, {:ok, {acc + number * Integer.pow(unquote(length(@b58_char_list)), step), step + 1}}} {:error, error} -> {:halt, {:error, error}} end end) |> case do {:ok, {number, _step}} -> {:ok, number} {:error, error} -> {:error, error} end end for {digit, idx} <- Enum.with_index(@b58_char_list) do defp decode_base58_char(unquote(<>)), do: {:ok, unquote(idx)} end defp decode_base58_char(char), do: {:error, "got invalid base58 character; #{inspect(char)}"} @base58_uuid_length 21 @uuid_length 32 def encode_uuid(uuid) do uuid |> String.replace("-", "") |> String.to_integer(16) |> encode() |> String.pad_leading(@base58_uuid_length, "1") end def decode_uuid(string) do with {:ok, number} <- decode(string) do number_to_uuid(number) end end defp number_to_uuid(number) do number |> Integer.to_string(16) |> String.downcase() |> String.pad_leading(@uuid_length, "0") |> case do <> -> {:ok, "#{g1}-#{g2}-#{g3}-#{g4}-#{g5}"} other -> {:error, "got invalid base58 uuid; #{inspect(other)}"} end end end