Current section
Files
Jump to
Current section
Files
lib/pika.ex
defmodule Pika do
alias Pika.Snowflake
import Pika.Utils, only: [filter_prefixes: 2]
@moduledoc nil
@spec valid_prefix?(binary()) :: boolean()
defp valid_prefix?(prefix) do
# Checks if `prefix` is alphanumeric
Regex.match?(~r/^[0-9a-z_]+$/, prefix)
end
@doc false
defp _gen(prefix, snowflake, nil) do
{:ok, "#{prefix}_#{Base.encode64(snowflake, padding: false)}"}
end
@doc false
defp _gen(prefix, snowflake, false) do
{:ok, "#{prefix}_#{Base.encode64(snowflake, padding: false)}"}
end
@doc false
defp _gen(prefix, snowflake, true) do
bytes = :rand.bytes(16)
tail =
"_s_#{Base.encode32(bytes, padding: false, case: :lower)}_#{snowflake}"
{:ok, "#{prefix}_#{Base.encode64(tail, padding: false)}"}
end
@spec gen(binary()) :: {:error, binary()} | {:ok, binary()}
@doc """
Generates an ID given a prefix (which should be configured).
This function will return an `{:error, binary()}` if one of the follow conditions are met:
1. The prefix isn't valid
2. The prefix isn't configured
"""
def gen(prefix) do
case valid_prefix?(prefix) do
true ->
prefixes = Application.get_env(:pika, :prefixes)
case filter_prefixes(prefix, prefixes) do
[prefix_record] ->
snowflake = Snowflake.generate() |> Integer.to_string()
_gen(prefix, snowflake, prefix_record[:secure])
_ ->
{:error, "Prefix is undefined"}
end
_ ->
{:error, "Prefix is invalid (must be Alphanumeric)"}
end
end
@spec gen!(binary()) :: binary()
def gen!(prefix) do
{:ok, id} = gen(prefix)
id
end
@spec gen() :: {:error, binary()}
def gen do
{:error, "No prefix was specified"}
end
@doc """
Deconstructs a Pika ID and returns it's metadata:
- prefix
- tail
- snowflake
- timestamp
- prefix_record
- epoch
- node_id
- seq
"""
def deconstruct(id) do
prefixes = Application.get_env(:pika, :prefixes)
fragments = id |> String.split("_")
[prefix, tail] = fragments
[prefix_record] = Enum.filter(prefixes, fn m -> m.prefix == prefix end)
decoded_tail = Base.decode64!(tail, padding: false)
tail_fragments = decoded_tail |> String.split("_")
snowflake = tail_fragments |> Enum.at(length(tail_fragments) - 1) |> String.to_integer()
decoded_snowflake = Snowflake.decode(snowflake)
Map.merge(decoded_snowflake, %{
prefix: prefix,
tail: tail,
snowflake: snowflake,
prefix_record: prefix_record
})
end
end