Page MenuHomePhorge

No OneTemporary

Size
23 KB
Referenced Files
None
Subscribers
None
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f753032..3396a54 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,19 +1,29 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
-## unreleased
-
-* Use time from `os:system_time` which follows OS time changes
-* Errors when the sequence became too big for 16bit integer (when clock is not advancing)
-* Split up build functions in a separate module
-* Local (in-process) generation strategy
-* (Still WIP) Configurable worker-id strategy (random, mac, node name, configurable integer)
-* Backdate
-* (WIP) Possibility to persist a time-hint file to protect against clock going backwards between restarts
+## Unreleased
+
+### Changed
+
+* `FlakeId.get/0` function now returns `{:ok, flake}` or `{:error, some_atom}`.
+* `FlakeId.Ecto.Type.autogenerate/0` will throw an error when a flake cannot be generated.
+* Split up build functions in a separate `FlakeId.Builder` module.
+
+### Fixed
+
+* Use time from `os:system_time` which follows OS time changes.
+* Errors when the sequence became too big for 16bit integer (when clock is not advancing).
+
+### Added
+
+* `FlakeId.get_local/0` local (in-process) generation strategy.
+* (Still WIP) Configurable worker-id strategy (random, mac, node name, configurable integer).
+* `FlakeId.backdate/0`.
+* (WIP) Possibility to persist a time-hint file to protect against clock going backwards between restarts.
## [0.1.0] - 2019-09-25
Initial release
diff --git a/README.md b/README.md
index 8bfe85a..e8126fb 100644
--- a/README.md
+++ b/README.md
@@ -1,107 +1,108 @@
# ❄ FlakeId
> Decentralized, k-ordered ID generation service
Flake Ids are 128-bit identifiers, sortable by time, and can be generated safely from a multi-node system without
coordination.
Flake Ids consist of a 64 bit timestamp, 48 bit worker identifier, and 16 bit sequence. They are usually
represented as a Base 62 string and can be sorted either by bit or by their string representation.
The sort precision is limited to a millisecond, which is probably fine enough for most usages.
## Installation
Add `flake_id` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:flake_id, "~> 0.1.0"}
]
end
```
## Configuration
### Worker ID
The Worker ID is a very important part of a flake. It guarantees that individual nodes within the same system
can generate IDs without risks of collision. Take care to choose Worker IDs that are guaranteed to be unique
for your system.
You have multiple strategies available depending on your setup:
* random byte-string, generated at node boot
* erlang node name
* mac address (either random, or manually specified)
* fixed string or integer
It is configured as `:worker` in the `:flake_id` app environement and is picked up at boot time.
Worker: `:mac | {:mac, iface} | :node | :random | String.t | Integer.t`
### Persisted Time Hint
Flake's time is dependent on your OS system clock. At runtime Flake will refuse to generate IDs if the system clock goes
backwards. In some cases after an OS reboot the clock may be incorrectly set to a time in the past which will trigger
this condition. Enabling a time hint file will prevent this by refusing to start the Flake worker.
Persisted Time hint is configured as `:time_hint` in the `:flake_id` app environement. By default it is disabled (`false`).
Other accepted values are `true` which stores the time hint file in flake_id priv dir or it can be set to a string pointing
to a file.
## Usage
### get/0
The most common usage will be via `get/0`:
```elixir
iex> FlakeId.get()
-"9n3171dJZpdD77K3DU"
+{:ok, "9n3171dJZpdD77K3DU"}
```
### get_local/0
You can also generate flakes in-process without hitting a node-wide worker. This is especially useful when you have
long-running processes that do a lot of insertions/ID generation. Note that the first call will be slowest as it
has to setup the Flake state.
Each process can have its Flake state stored in the process dictionary and the Worker ID is derived from the global
Worker ID and the current PID:
```elixir
iex> FlakeId.get_local()
+{:ok, "9uAiavFlMUeyVxbNGT"}
```
### backdate/2
If you wish to insert historical data while preserving sorting, you can achieve this using a backdated flake. In that
case you have to provide yourself a **UNIQUE** Worker ID (for the given time). Be very careful to ensure the uniqueness
of the Worker ID otherwise you may generate the same flake twice.
```elixir
iex> FlakeId.backdate("https://flakesocial.com/status/390929393992", ~U[2019-12-05 07:15:00.793018Z])
-""
+{:ok, "9peCDV9GeXwzM15BE9"}
```
See [https://hexdocs.pm/flake_id](https://hexdocs.pm/flake_id) for the complete documentation.
### With Ecto
It is possible to use `FlakeId` with [`Ecto`](https://github.com/elixir-ecto/ecto/).
See [`FlakeId.Ecto.Type`](https://hexdocs.pm/flake_id/FlakeId.Ecto.Type.html) documentation for detailed instructions.
If you wish to migrate from integer serial ids to Flake, see [`FlakeId.Ecto.CompatType`](https://hexdocs.pm/flake_id/FlakeId.Ecto.Type.html) for instructions.
## Prior Art
* [flaky](https://github.com/nirvana/flaky), released under the terms of the Truly Free License,
* [Flake](https://github.com/boundary/flake), Copyright 2012, Boundary, Apache License, Version 2.0
## Copyright and License
Copyright © 2017-2020 [Pleroma Authors](https://pleroma.social/)
FlakeId source code is licensed under the GNU LGPLv3 License.
diff --git a/lib/flake_id.ex b/lib/flake_id.ex
index 197daf4..cf1f8d0 100644
--- a/lib/flake_id.ex
+++ b/lib/flake_id.ex
@@ -1,128 +1,140 @@
# FlakeId: Decentralized, k-ordered ID generation service
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: LGPL-3.0-only
defmodule FlakeId do
@moduledoc """
Decentralized, k-ordered ID generation service.
"""
import Kernel, except: [to_string: 1]
alias FlakeId.Builder
alias FlakeId.Worker
alias FlakeId.Local
@type error :: {:error, :clock_running_backwards} | {:error, :clock_stuck} | {:error, :invalid_state}
@doc """
Converts a binary Flake to a String
## Examples
iex> FlakeId.to_string(<<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>)
"9n2ciuz1wdesFnrGJU"
"""
def to_string(<<_::integer-size(64), _::integer-size(48), _::integer-size(16)>> = binary_flake) do
<<integer::integer-size(128)>> = binary_flake
Base62.encode(integer)
end
def to_string(string), do: string
@doc """
Converts a String to a binary Flake
## Examples
iex> FlakeId.from_string("9n2ciuz1wdesFnrGJU")
<<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>
"""
- @spec from_string(binary()) :: nil | <<_::128>>
+ @spec from_string(String.t()) :: nil | <<_::128>>
def from_string(string)
def from_string(<<_::integer-size(128)>> = flake), do: flake
def from_string(string) when is_binary(string) and byte_size(string) < 18, do: nil
def from_string(string), do: string |> Base62.decode!() |> from_integer
@doc """
Converts a binary Flake to an integer
## Examples
iex> FlakeId.to_integer(<<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>)
28939165907792829150732718047232
"""
@spec to_integer(<<_::128>>) :: non_neg_integer
def to_integer(binary_flake)
def to_integer(<<integer::integer-size(128)>>), do: integer
@doc """
Converts an integer to a binary Flake
## Examples
iex> FlakeId.from_integer(28939165907792829150732718047232)
<<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>
"""
@spec from_integer(integer) :: <<_::128>>
def from_integer(integer) do
<<_time::integer-size(64), _node::integer-size(48), _seq::integer-size(16)>> =
<<integer::integer-size(128)>>
end
@doc """
Generates a string with Flake
"""
- @spec get :: String.t() | error()
- def get, do: Worker.get() |> to_string()
+ @spec get :: {:ok, String.t()} | error()
+ def get do
+ case Worker.get() do
+ {:ok, flake} -> {:ok, to_string(flake)}
+ error -> error
+ end
+ end
@doc """
Generates a local Flake (in-process state). Worker ID will be derived from global worker-id and the process pid.
First call will be slower but subsequent calls will be faster than worker based `get/0` and is quite useful for long running processes.
"""
- @spec get_local :: String.t() | error()
- def get_local, do: Local.get() |> to_string()
+ @spec get_local :: {:ok, String.t()} | error()
+ def get_local do
+ case Local.get() do
+ {:ok, flake} -> {:ok, to_string(flake)}
+ error -> error
+ end
+ end
@doc """
Generates a predictable and back-dated Flake.
This can be useful when you want to insert historical data without messing with sorting.
Be **very careful** at choosing a `unique` value that is _really unique_ for the given `datetime`, otherwise, collisons will happen.
"""
- @spec backdate(any(), DateTime.t()) :: String.t() | error()
+ @spec backdate(any(), DateTime.t()) :: {:ok, String.t()} | error()
def backdate(unique, datetime) do
hash = :erlang.phash2(unique)
time = DateTime.to_unix(datetime, :millisecond)
- {flake, _} = Builder.get(time, %Builder{node: hash, time: time, sq: 0})
- to_string(flake)
+ case Builder.get(time, %Builder{node: hash, time: time, sq: 0}) do
+ {:ok, flake, _} -> {:ok, to_string(flake)}
+ error -> error
+ end
end
@doc """
Checks that ID is a valid FlakeId
## Examples
iex> FlakeId.flake_id?("9n2ciuz1wdesFnrGJU")
true
iex> FlakeId.flake_id?("#cofe")
false
iex> FlakeId.flake_id?("pleroma.social")
false
"""
@spec flake_id?(String.t()) :: boolean
def flake_id?(id), do: flake_id?(String.to_charlist(id), true)
defp flake_id?([c | cs], true) when c >= ?0 and c <= ?9, do: flake_id?(cs, true)
defp flake_id?([c | cs], true) when c >= ?A and c <= ?Z, do: flake_id?(cs, true)
defp flake_id?([c | cs], true) when c >= ?a and c <= ?z, do: flake_id?(cs, true)
defp flake_id?([], true), do: true
defp flake_id?(_, _), do: false
end
diff --git a/lib/flake_id/builder.ex b/lib/flake_id/builder.ex
index 3ab2a5d..b75fa03 100644
--- a/lib/flake_id/builder.ex
+++ b/lib/flake_id/builder.ex
@@ -1,91 +1,91 @@
# FlakeId: Decentralized, k-ordered ID generation service
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: LGPL-3.0-only
defmodule FlakeId.Builder do
@moduledoc false
defstruct node: nil, time: 0, sq: 0
@type state :: %__MODULE__{
node: non_neg_integer,
time: non_neg_integer,
sq: non_neg_integer
}
def new(worker_id \\ nil) do
%__MODULE__{node: worker_id || worker_id(), time: time()}
end
def time do
:os.system_time(:millisecond)
end
def worker_id do
worker_id(Application.get_env(:flake_id, :worker_id, :random))
end
- @spec get(state) :: {<<_::128>>, state} | FlakeId.error()
+ @spec get(state) :: {:ok, <<_::128>>, state} | FlakeId.error()
def get(state) do
get(time(), state)
end
- @spec get(non_neg_integer, state) :: {<<_::128>>, state} | FlakeId.error()
+ @spec get(non_neg_integer, state) :: {:ok, <<_::128>>, state} | FlakeId.error()
# Matches when time didn't change and sequence is too big for 16bit int.
def get(newtime, %__MODULE__{time: time, sq: sq}) when newtime == time and sq >= 65_535 do
{:error, :clock_stuck}
end
# Matches when the calling time is the same as the state time. Incr. sq
def get(time, %__MODULE__{node: node, time: time, sq: seq} = state) when is_integer(time) and is_integer(node) and is_integer(seq) do
new_state = %__MODULE__{state | sq: state.sq + 1}
- {gen_flake(new_state), new_state}
+ {:ok, gen_flake(new_state), new_state}
end
# Matches when the times are different, reset sq
def get(newtime, %__MODULE__{node: node, time: time, sq: seq} = state) when is_integer(time) and is_integer(node) and is_integer(seq) and newtime > time do
new_state = %__MODULE__{state | time: newtime, sq: 0}
- {gen_flake(new_state), new_state}
+ {:ok, gen_flake(new_state), new_state}
end
# Error when clock is running backwards
def get(newtime, %__MODULE__{time: time}) when newtime < time do
{:error, :clock_running_backwards}
end
def get(_, _) do
{:error, :invalid_state}
end
@spec gen_flake(state) :: <<_::128>>
defp gen_flake(%__MODULE__{time: time, node: node, sq: seq}) do
<<time::integer-size(64), node::integer-size(48), seq::integer-size(16)>>
end
defp worker_id(:node) do
:erlang.phash2(node())
end
defp worker_id(:mac) do
"derp derp derp"
end
defp worker_id(:random) do
<<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6)
worker
end
defp worker_id(integer) when is_integer(integer) do
integer
end
defp worker_id(binary) when is_binary(binary) do
#<<worker::integer-size(48)>> = :erlang.phash2(binary)
#worker
:erlang.phash2(binary)
end
end
diff --git a/lib/flake_id/ecto/compat_type.ex b/lib/flake_id/ecto/compat_type.ex
index 4b6105f..a0e20f1 100644
--- a/lib/flake_id/ecto/compat_type.ex
+++ b/lib/flake_id/ecto/compat_type.ex
@@ -1,80 +1,80 @@
# FlakeId: Decentralized, k-ordered ID generation service
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: LGPL-3.0-only
defmodule FlakeId.Ecto.CompatType do
@moduledoc """
Provides a compatibility Ecto type for someone wishes to migrate from integer ids to flakes.
## Schema Example
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
schema "posts" do
add :body, :string
end
## Migrating
The compat type allows to keep your old ids as they used to look before, while the newest ones will be fully fledged flakes.
This works by altering your ids columns from serial/int/bigint to an UUID, while rewriting the UUID to look like a zero-padded plus the old integer number.
Such a feat can be accomplished in your migration by using:
ALTER COLUMN user_id SET DATA TYPE uuid USING CAST( LPAD( TO_HEX(user_id), 32, '0' ) AS uuid)
A full example of a migration can be found on [Pleroma's code](https://git.pleroma.social/pleroma/pleroma/-/blob/develop/priv/repo/migrations/20181218172826_users_and_activities_flake_id.exs).
"""
import Kernel, except: [to_string: 1]
@behaviour Ecto.Type
def embed_as(_), do: :self
def equal?(term1, term2), do: term1 == term2
def type, do: :uuid
- defdelegate autogenerate, to: FlakeId, as: :get
+ defdelegate autogenerate, to: FlakeId.Ecto.Type, as: :autogenerate
def cast(value) do
{:ok, to_string(value)}
end
def load(value) do
{:ok, to_string(value)}
end
def dump(value) do
{:ok, from_string(value)}
end
defp to_string(<<0::integer-size(64), id::integer-size(64)>>), do: Kernel.to_string(id)
defp to_string(binary_flake), do: FlakeId.to_string(binary_flake)
# zero or -1 is a null flake
for i <- [-1, 0] do
defp from_string(unquote(i)), do: <<0::integer-size(128)>>
defp from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>>
end
defp from_string(int) when is_integer(int) do
int
|> Kernel.to_string()
|> from_string()
end
defp from_string(<<_::integer-size(128)>> = flake), do: flake
defp from_string(string) when is_binary(string) and byte_size(string) < 18 do
case Integer.parse(string) do
{id, ""} -> <<0::integer-size(64), id::integer-size(64)>>
_ -> nil
end
end
defp from_string(string), do: FlakeId.from_string(string)
end
diff --git a/lib/flake_id/ecto/type.ex b/lib/flake_id/ecto/type.ex
index 914c864..571fd39 100644
--- a/lib/flake_id/ecto/type.ex
+++ b/lib/flake_id/ecto/type.ex
@@ -1,46 +1,51 @@
# FlakeId: Decentralized, k-ordered ID generation service
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: LGPL-3.0-only
defmodule FlakeId.Ecto.Type do
@moduledoc """
Provides a type for Ecto usage.
The underlying data type should be an `:uuid`.
## Migration Example
create table(:posts, primary_key: false) do
add :id, :uuid, primary_key: true
add :body, :text
end
## Schema Example
@primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
schema "posts" do
add :body, :string
end
"""
@behaviour Ecto.Type
def embed_as(_), do: :self
def equal?(term1, term2), do: term1 == term2
def type, do: :uuid
- defdelegate autogenerate, to: FlakeId, as: :get
+ def autogenerate do
+ case FlakeId.get() do
+ {:ok, flake} -> flake
+ {:error, error} -> throw({:error, {FlakeId, error}})
+ end
+ end
def cast(value) do
{:ok, FlakeId.to_string(value)}
end
def load(value) do
{:ok, FlakeId.to_string(value)}
end
def dump(value) do
{:ok, FlakeId.from_string(value)}
end
end
diff --git a/lib/flake_id/local.ex b/lib/flake_id/local.ex
index 2711189..1817035 100644
--- a/lib/flake_id/local.ex
+++ b/lib/flake_id/local.ex
@@ -1,27 +1,29 @@
# FlakeId: Decentralized, k-ordered ID generation service
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: LGPL-3.0-only
defmodule FlakeId.Local do
@moduledoc false
alias FlakeId.Builder
+ @spec get() :: {:ok, String.t()} | FlakeId.error()
def get() do
case Builder.get(get_state()) do
- error = {:error, _} -> error
- {flake, state} ->
+ {:ok, flake, state} ->
Process.put(__MODULE__, state)
- flake
+ {:ok, flake}
+ error = {:error, _} ->
+ error
end
end
defp get_state() do
Process.get(__MODULE__, Builder.new(worker_id()))
end
defp worker_id() do
:erlang.phash2({Builder.worker_id(), self()})
end
end
diff --git a/lib/flake_id/worker.ex b/lib/flake_id/worker.ex
index 9547c14..c0ada20 100644
--- a/lib/flake_id/worker.ex
+++ b/lib/flake_id/worker.ex
@@ -1,30 +1,34 @@
# FlakeId: Decentralized, k-ordered ID generation service
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: LGPL-3.0-only
defmodule FlakeId.Worker do
@moduledoc false
use GenServer
alias FlakeId.Builder
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
- @spec get :: binary
+ @spec get :: {:ok, String.t()} | FlakeId.error()
def get, do: GenServer.call(__MODULE__, :get)
@impl true
@spec init([]) :: {:ok, Builder.t}
def init([]) do
{:ok, Builder.new()}
end
@impl true
def handle_call(:get, _from, state) do
- {flake, new_state} = Builder.get(state)
- {:reply, flake, new_state}
+ case Builder.get(state) do
+ {:ok, flake, new_state} ->
+ {:reply, {:ok, flake}, new_state}
+ error ->
+ {:reply, error, state}
+ end
end
end
diff --git a/test/flake_id/builder_test.exs b/test/flake_id/builder_test.exs
index cb8a207..6a380cf 100644
--- a/test/flake_id/builder_test.exs
+++ b/test/flake_id/builder_test.exs
@@ -1,40 +1,40 @@
defmodule FlakeId.BuilderTest do
use ExUnit.Case, async: true
alias FlakeId.Builder
describe "get/2" do
test "increment `:sq` when the calling time is the same as the state time" do
time = Builder.time()
node = Builder.worker_id()
state = %Builder{time: time, node: node, sq: 0}
- assert {<<_::integer-size(128)>>, %Builder{node: ^node, time: ^time, sq: 1}} =
+ assert {:ok, <<_::integer-size(128)>>, %Builder{node: ^node, time: ^time, sq: 1}} =
Builder.get(time, state)
end
test "reset `:sq` when the times are different" do
time = Builder.time()
node = Builder.worker_id()
state = %Builder{time: time - 1, node: node, sq: 42}
- assert {<<_::integer-size(128)>>, %Builder{time: ^time, sq: 0}} = Builder.get(time, state)
+ assert {:ok, _, %Builder{time: ^time, sq: 0}} = Builder.get(time, state)
end
test "error when clock is running backwards" do
time = Builder.time()
state = %Builder{time: time + 1}
assert Builder.get(time, state) == {:error, :clock_running_backwards}
end
- test "error when clock not advancing and seq overflows" do
+ test "error when clock is stuck and `sq` overflows" do
time = Builder.time()
state = %Builder{node: 0, time: time, sq: 65534}
- assert {<<_::integer-size(128)>>, %Builder{sq: sq}} = Builder.get(time, state)
+ assert {:ok, _, %Builder{sq: sq}} = Builder.get(time, state)
state = %Builder{state | node: 0, sq: sq}
assert Builder.get(time, state) == {:error, :clock_stuck}
end
end
end
diff --git a/test/flake_id_test.exs b/test/flake_id_test.exs
index 43bc475..fa94af5 100644
--- a/test/flake_id_test.exs
+++ b/test/flake_id_test.exs
@@ -1,78 +1,78 @@
defmodule FlakeIdTest do
use ExUnit.Case, async: true
doctest FlakeId
@flake_string "9n2ciuz1wdesFnrGJU"
@flake_binary <<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>
@flake_integer 28_939_165_907_792_829_150_732_718_047_232
test "flake_id?/1" do
assert FlakeId.flake_id?(@flake_string)
refute FlakeId.flake_id?("http://example.com/activities/3ebbadd1-eb14-4e20-8118-b6f79c0c7b0b")
refute FlakeId.flake_id?("#cofe")
end
test "get/0" do
- flake = FlakeId.get()
+ assert {:ok, flake} = FlakeId.get()
assert String.valid?(flake)
assert FlakeId.flake_id?(flake)
end
describe "get_local/0" do
test "generates a flake" do
- flake = FlakeId.get_local()
+ assert {:ok, flake} = FlakeId.get_local()
assert String.valid?(flake)
assert FlakeId.flake_id?(flake)
end
test "worker id is different from get/0, and different between processes" do
- flake1 = FlakeId.get()
+ {:ok, flake1} = FlakeId.get()
me = self()
spawn(fn() -> send(me, FlakeId.get_local()) end)
- assert_receive flake2
+ assert_receive {:ok, flake2}
spawn(fn() -> send(me, FlakeId.get_local()) end)
- assert_receive flake3
+ assert_receive {:ok, flake3}
<<_::integer-size(64), worker_id1::integer-size(48), _::integer-size(16)>> = FlakeId.from_string(flake1)
<<_::integer-size(64), worker_id2::integer-size(48), _::integer-size(16)>> = FlakeId.from_string(flake2)
<<_::integer-size(64), worker_id3::integer-size(48), _::integer-size(16)>> = FlakeId.from_string(flake3)
refute worker_id1 == worker_id2 == worker_id3
end
end
describe "to_string/1" do
test "with binary" do
assert FlakeId.to_string(@flake_binary) == @flake_string
bin = <<1::integer-size(64), 2::integer-size(48), 3::integer-size(16)>>
assert FlakeId.to_string(bin) == "LygHa16ApeN"
end
test "does nothing with other types" do
assert FlakeId.to_string("cofe") == "cofe"
assert FlakeId.to_string(42) == 42
end
end
describe "from_string/1" do
test "with a flake string" do
assert FlakeId.from_string(@flake_string) == @flake_binary
end
test "with a flake binary" do
assert FlakeId.from_string(@flake_binary) == @flake_binary
end
test "with a non flake string" do
assert FlakeId.from_string("cofe") == nil
end
end
test "to_integer/1" do
assert FlakeId.to_integer(@flake_binary) == @flake_integer
end
test "from_integer/1" do
assert FlakeId.from_integer(@flake_integer) == @flake_binary
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 9:28 AM (21 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38936
Default Alt Text
(23 KB)

Event Timeline