From 956ffd93d854fb3a721aa7763c3da509cffedd41 Mon Sep 17 00:00:00 2001 From: Mustafa Turan Date: Sun, 24 Jun 2018 21:32:05 -0700 Subject: [PATCH] Move unique_id functionality to base62 module --- README.md | 2 +- lib/event_bus/utils/base62.ex | 53 ++++++++++++++++++++++++++++ lib/event_bus/utils/string.ex | 37 ++----------------- test/event_bus/utils/base62_test.exs | 19 ++++++++++ test/event_bus/utils/string_test.exs | 5 --- 5 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 lib/event_bus/utils/base62.ex create mode 100644 test/event_bus/utils/base62_test.exs diff --git a/README.md b/README.md index 4d8bf0d..1815969 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,7 @@ config :event_bus, topics: [], # list of atoms ttl: 30_000_000, # integer time_unit: :micro_seconds, # atom - id_generator: EventBus.Util.String # module: must implement 'unique_id/0' function + id_generator: EventBus.Util.Base62 # module: must implement 'unique_id/0' function ``` After having such config like above, you can generate events without providing optional attributes like below: diff --git a/lib/event_bus/utils/base62.ex b/lib/event_bus/utils/base62.ex new file mode 100644 index 0000000..0dc1ff4 --- /dev/null +++ b/lib/event_bus/utils/base62.ex @@ -0,0 +1,53 @@ +defmodule EventBus.Util.Base62 do + @moduledoc false + + @mapping '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + + @doc """ + Generates partially sequential, base62 unique identifier + """ + @spec unique_id() :: String.t() + def unique_id do + now() <> node_id() <> random(4, 14_776_336) + end + + @doc """ + Converts given integer to base62 + """ + @spec encode(integer()) :: String.t() + def encode(num) when num < 62 do + << Enum.at(@mapping, num) >> + end + + def encode(num) do + encode(div(num, 62)) <> encode(rem(num, 62)) + end + + # Generates random base62 string with crypto:strong_rand_bytes + defp random(size, max) do + size + |> :crypto.strong_rand_bytes() + |> :crypto.bytes_to_integer() + |> rem(max) + |> encode() + |> String.pad_leading(size, "0") + end + + # Current time (microseconds) encoded in base62 + defp now do + encode(System.os_time(:microseconds)) + end + + # Assigns a random node_id on first call + defp node_id do + case Application.get_env(:event_bus, :node_id) do + nil -> save_node_id(random(3, 238_328)) + nid -> nid + end + end + + defp save_node_id(node_id) do + Application.put_env(:event_bus, :node_id, node_id, persistent: true) + node_id + end +end diff --git a/lib/event_bus/utils/string.ex b/lib/event_bus/utils/string.ex index 930cb25..492fe88 100644 --- a/lib/event_bus/utils/string.ex +++ b/lib/event_bus/utils/string.ex @@ -2,40 +2,9 @@ defmodule EventBus.Util.String do @moduledoc false # String util for event bus - @base62_map '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' - @max_str_reminder 238_327 # 'zzz' + alias EventBus.Util.Base62 - @doc """ - Generates partially sequential random id - """ @spec unique_id() :: String.t() - def unique_id do - base62_encode(now()) <> random() - end - - def base62_encode(num) do - new_num = div(num, 62) - unless new_num == 0, do: merge(new_num, num), else: to_str(num) - end - - defp merge(new_num, num) do - base62_encode(new_num) <> to_str(num) - end - - defp to_str(num) do - << Enum.at(@base62_map, rem(num, 62)) >> - end - - # Random string based on system's monotonic time - defp random do - System.monotonic_time(:nano_seconds) - |> rem(1_000_000) # Take last 6 digits - |> rem(@max_str_reminder) # Set max to 'zzz' - |> base62_encode() - |> String.pad_leading(3, "0") - end - - defp now do - System.os_time(:micro_seconds) - end + defdelegate unique_id, + to: Base62 end diff --git a/test/event_bus/utils/base62_test.exs b/test/event_bus/utils/base62_test.exs new file mode 100644 index 0000000..fcb7a23 --- /dev/null +++ b/test/event_bus/utils/base62_test.exs @@ -0,0 +1,19 @@ +defmodule EventBus.Util.Base62Test do + use ExUnit.Case + alias EventBus.Util.Base62 + + test ".encode" do + assert "0" == Base62.encode(0) + assert "z" == Base62.encode(61) + assert "10" == Base62.encode(62) + assert "1p0uwg6tOzJ" == Base62.encode(1529891323138833953) + end + + test ".unique_id" do + refute Base62.unique_id() == Base62.unique_id() + end + + test ".unique_id length" do + assert 16 == String.length(Base62.unique_id()) + end +end diff --git a/test/event_bus/utils/string_test.exs b/test/event_bus/utils/string_test.exs index cf5e81a..451431b 100644 --- a/test/event_bus/utils/string_test.exs +++ b/test/event_bus/utils/string_test.exs @@ -1,8 +1,3 @@ defmodule EventBus.Util.StringTest do use ExUnit.Case - alias EventBus.Util.String, as: StringUtil - - test "generates unique_id" do - refute StringUtil.unique_id() == StringUtil.unique_id() - end end