Page MenuHomePhorge

No OneTemporary

Size
30 KB
Referenced Files
None
Subscribers
None
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..31d8db7
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,40 @@
+image: elixir:1.8.1
+
+variables:
+ MIX_ENV: test
+
+cache:
+ key: ${CI_COMMIT_REF_SLUG}
+ paths:
+ - deps
+ - _build
+stages:
+ - build
+ - test
+
+before_script:
+ - mix local.hex --force
+ - mix local.rebar --force
+
+build:
+ stage: build
+ script:
+ - mix deps.get
+ - mix compile --force
+
+unit-testing:
+ stage: test
+ coverage: '/(\d+\.\d+\%) \| Total/'
+ script:
+ - mix test --trace --cover
+
+lint:
+ stage: test
+ script:
+ - mix format --check-formatted
+
+analysis:
+ stage: test
+ script:
+ - mix deps.get
+ - mix credo --strict
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..dade3c7
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,9 @@
+# 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/).
+
+## [0.1.0] - TBD
+
+Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0a04128
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
index d8022eb..5bf0245 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,39 @@
-# FlakeId
+# ❄ FlakeId
-**TODO: Add description**
+> Decentralized, k-ordered ID generation service
## Installation
-If [available in Hex](https://hex.pm/docs/publish), the package can be installed
-by adding `flake_id` to your list of dependencies in `mix.exs`:
+Add `flake_id` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:flake_id, "~> 0.1.0"}
]
end
```
-Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
-and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
-be found at [https://hexdocs.pm/flake_id](https://hexdocs.pm/flake_id).
+## Usage
+```elixir
+iex> FlakeId.get()
+"9n3171dJZpdD77K3DU"
+```
+
+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.
+
+## 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-2019 [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 a9660fd..8253ee7 100644
--- a/lib/flake_id.ex
+++ b/lib/flake_id.ex
@@ -1,18 +1,100 @@
+# FlakeId: Decentralized, k-ordered ID generation service
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: LGPL-3.0-only
+
defmodule FlakeId do
@moduledoc """
- Documentation for FlakeId.
+ Decentralized, k-ordered ID generation service.
+ """
+
+ import Kernel, except: [to_string: 1]
+
+ @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>>
+ 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 """
- Hello world.
+ Converts a binary Flake to an integer
## Examples
- iex> FlakeId.hello()
- :world
+ iex> FlakeId.to_integer(<<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>)
+ 28939165907792829150732718047232
"""
- def hello do
- :world
+ @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()
+ def get, do: FlakeId.Worker.get() |> to_string()
+
+ @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/application.ex b/lib/flake_id/application.ex
index 4af3b55..c87f562 100644
--- a/lib/flake_id/application.ex
+++ b/lib/flake_id/application.ex
@@ -1,19 +1,22 @@
+# FlakeId: Decentralized, k-ordered ID generation service
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: LGPL-3.0-only
+
defmodule FlakeId.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
children = [
- # Starts a worker by calling: FlakeId.Worker.start_link(arg)
- # {FlakeId.Worker, arg}
+ FlakeId.Worker
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: FlakeId.Supervisor]
Supervisor.start_link(children, opts)
end
end
diff --git a/lib/flake_id/ecto/compat_type.ex b/lib/flake_id/ecto/compat_type.ex
new file mode 100644
index 0000000..8e6d8a6
--- /dev/null
+++ b/lib/flake_id/ecto/compat_type.ex
@@ -0,0 +1,68 @@
+# 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
+
+ """
+
+ 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
+
+ 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
new file mode 100644
index 0000000..914c864
--- /dev/null
+++ b/lib/flake_id/ecto/type.ex
@@ -0,0 +1,46 @@
+# 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 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/worker.ex b/lib/flake_id/worker.ex
new file mode 100644
index 0000000..c675255
--- /dev/null
+++ b/lib/flake_id/worker.ex
@@ -0,0 +1,70 @@
+# FlakeId: Decentralized, k-ordered ID generation service
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: LGPL-3.0-only
+
+defmodule FlakeId.Worker do
+ @moduledoc false
+
+ use GenServer
+
+ defstruct node: nil, time: 0, sq: 0
+
+ @type state :: %__MODULE__{
+ node: non_neg_integer,
+ time: non_neg_integer,
+ sq: non_neg_integer
+ }
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, [], name: __MODULE__)
+ end
+
+ @impl true
+ @spec init([]) :: {:ok, state}
+ def init([]) do
+ {:ok, %__MODULE__{node: worker_id(), time: time()}}
+ end
+
+ @impl true
+ def handle_call(:get, _from, state) do
+ {flake, new_state} = get(time(), state)
+ {:reply, flake, new_state}
+ end
+
+ @spec get :: binary
+ def get, do: GenServer.call(__MODULE__, :get)
+
+ # Matches when the calling time is the same as the state time. Incr. sq
+ @spec get(non_neg_integer, state) ::
+ {<<_::128>>, state} | {:error, :clock_running_backwards}
+ def get(time, %__MODULE__{time: time} = state) do
+ new_state = %__MODULE__{state | sq: state.sq + 1}
+ {gen_flake(new_state), new_state}
+ end
+
+ # Matches when the times are different, reset sq
+ def get(newtime, %__MODULE__{time: time} = state) when newtime > time do
+ new_state = %__MODULE__{state | time: newtime, sq: 0}
+ {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
+
+ @spec gen_flake(state) :: <<_::128>>
+ def gen_flake(%__MODULE__{time: time, node: node, sq: seq}) do
+ <<time::integer-size(64), node::integer-size(48), seq::integer-size(16)>>
+ end
+
+ def time do
+ {mega_seconds, seconds, micro_seconds} = :erlang.timestamp()
+ 1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000)
+ end
+
+ def worker_id do
+ <<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6)
+ worker
+ end
+end
diff --git a/mix.exs b/mix.exs
index e90f237..3438a20 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,29 +1,51 @@
defmodule FlakeId.MixProject do
use Mix.Project
def project do
[
app: :flake_id,
version: "0.1.0",
- elixir: "~> 1.9",
+ description: "Decentralized, k-ordered ID generation service",
+ elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
- deps: deps()
+ deps: deps(),
+ package: package(),
+
+ # Docs
+ name: "FlakeId",
+ source_url: "https://git.pleroma.social/pleroma/flake_id",
+ homepage_url: "https://git.pleroma.social/pleroma/flake_id",
+ docs: [
+ main: "readme",
+ extras: ["README.md"],
+ source_url_pattern:
+ "https://git.pleroma.social/pleroma/flake_id/blob/master/%{path}#L%{line}"
+ ]
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {FlakeId.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- # {:dep_from_hexpm, "~> 0.3.0"},
- # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
+ {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
+ {:ecto, ">= 2.0.0", optional: true},
+ {:ex_doc, "~> 0.21", only: :dev, runtime: false},
+ {:base62, "~> 1.2"}
+ ]
+ end
+
+ defp package do
+ [
+ licenses: ["LGPLv3"],
+ links: %{"GitLab" => "https://git.pleroma.social/pleroma/flake_id"}
]
end
end
diff --git a/mix.lock b/mix.lock
new file mode 100644
index 0000000..3fef1d4
--- /dev/null
+++ b/mix.lock
@@ -0,0 +1,14 @@
+%{
+ "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm"},
+ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
+ "credo": {:hex, :credo, "1.1.4", "c2f3b73c895d81d859cec7fcee7ffdb972c595fd8e85ab6f8c2adbf01cf7c29c", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
+ "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"},
+ "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
+ "earmark": {:hex, :earmark, "1.4.0", "397e750b879df18198afc66505ca87ecf6a96645545585899f6185178433cc09", [:mix], [], "hexpm"},
+ "ecto": {:hex, :ecto, "3.2.1", "a0f9af0fb50b19d3bb6237e512ac0ba56ea222c2bbea92e7c6c94897932c76ba", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
+ "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
+ "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
+ "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
+ "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
+ "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
+}
diff --git a/test/flake_id/ecto/compat_type_test.exs b/test/flake_id/ecto/compat_type_test.exs
new file mode 100644
index 0000000..2261c14
--- /dev/null
+++ b/test/flake_id/ecto/compat_type_test.exs
@@ -0,0 +1,57 @@
+defmodule FlakeId.Ecto.CompatTypeTest do
+ use ExUnit.Case, async: true
+
+ alias FlakeId.Ecto.CompatType
+
+ @flake_string "9n2ciuz1wdesFnrGJU"
+ @flake_binary <<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>
+ @fake_flake <<0::integer-size(64), 42::integer-size(64)>>
+
+ test "cast/1" do
+ assert CompatType.cast(@flake_binary) == {:ok, @flake_string}
+ assert CompatType.cast(@flake_string) == {:ok, @flake_string}
+ end
+
+ test "load/1" do
+ assert CompatType.load(@flake_binary) == {:ok, @flake_string}
+ assert CompatType.load(@flake_string) == {:ok, @flake_string}
+ end
+
+ test "dump/1" do
+ assert CompatType.dump(@flake_string) == {:ok, @flake_binary}
+ assert CompatType.dump(@flake_binary) == {:ok, @flake_binary}
+ end
+
+ test "equal?/2" do
+ assert CompatType.equal?(@flake_binary, @flake_binary) == true
+ end
+
+ test "autogenerate/0" do
+ flake = CompatType.autogenerate()
+
+ assert String.valid?(flake)
+ assert FlakeId.flake_id?(flake)
+ end
+
+ describe "fake flakes (compatibility with older serial integers)" do
+ test "with an integer" do
+ assert CompatType.dump(42) == {:ok, @fake_flake}
+ end
+
+ test "with an integer as string" do
+ assert CompatType.dump("42") == {:ok, @fake_flake}
+ end
+
+ test "zero or -1 is a null flake" do
+ null_flake = <<0::integer-size(128)>>
+ assert CompatType.dump(0) == {:ok, null_flake}
+ assert CompatType.dump(-1) == {:ok, null_flake}
+ assert CompatType.dump("0") == {:ok, null_flake}
+ assert CompatType.dump("-1") == {:ok, null_flake}
+ end
+
+ test "cast" do
+ assert CompatType.cast(@fake_flake) == {:ok, "42"}
+ end
+ end
+end
diff --git a/test/flake_id/ecto/type_test.exs b/test/flake_id/ecto/type_test.exs
new file mode 100644
index 0000000..8ea21a7
--- /dev/null
+++ b/test/flake_id/ecto/type_test.exs
@@ -0,0 +1,34 @@
+defmodule FlakeId.Ecto.TypeTest do
+ use ExUnit.Case, async: true
+
+ alias FlakeId.Ecto.Type
+
+ @flake_string "9n2ciuz1wdesFnrGJU"
+ @flake_binary <<0, 0, 1, 109, 67, 124, 251, 125, 95, 28, 30, 59, 36, 42, 0, 0>>
+
+ test "cast/1" do
+ assert Type.cast(@flake_binary) == {:ok, @flake_string}
+ assert Type.cast(@flake_string) == {:ok, @flake_string}
+ end
+
+ test "load/1" do
+ assert Type.load(@flake_binary) == {:ok, @flake_string}
+ assert Type.load(@flake_string) == {:ok, @flake_string}
+ end
+
+ test "dump/1" do
+ assert Type.dump(@flake_string) == {:ok, @flake_binary}
+ assert Type.dump(@flake_binary) == {:ok, @flake_binary}
+ end
+
+ test "equal?/2" do
+ assert Type.equal?(@flake_binary, @flake_binary) == true
+ end
+
+ test "autogenerate/0" do
+ flake = Type.autogenerate()
+
+ assert String.valid?(flake)
+ assert FlakeId.flake_id?(flake)
+ end
+end
diff --git a/test/flake_id/worker_test.exs b/test/flake_id/worker_test.exs
new file mode 100644
index 0000000..e4d8b6b
--- /dev/null
+++ b/test/flake_id/worker_test.exs
@@ -0,0 +1,38 @@
+defmodule FlakeId.WorkerTest do
+ use ExUnit.Case, async: true
+
+ alias FlakeId.Worker
+
+ test "get/1" do
+ flake = Worker.get()
+
+ assert is_binary(flake)
+ assert <<_::integer-size(128)>> = flake
+ end
+
+ describe "get/2" do
+ test "increment `:sq` when the calling time is the same as the state time" do
+ time = Worker.time()
+ node = Worker.worker_id()
+ state = %Worker{time: time, node: node, sq: 0}
+
+ assert {<<_::integer-size(128)>>, %Worker{node: ^node, time: ^time, sq: 1}} =
+ Worker.get(time, state)
+ end
+
+ test "reset `:sq` when the times are different" do
+ time = Worker.time()
+ node = Worker.worker_id()
+ state = %Worker{time: time - 1, node: node, sq: 42}
+
+ assert {<<_::integer-size(128)>>, %Worker{time: ^time, sq: 0}} = Worker.get(time, state)
+ end
+
+ test "wrror when clock is running backwards" do
+ time = Worker.time()
+ state = %Worker{time: time + 1}
+
+ assert Worker.get(time, state) == {:error, :clock_running_backwards}
+ end
+ end
+end
diff --git a/test/flake_id_test.exs b/test/flake_id_test.exs
index 397bb5c..c796377 100644
--- a/test/flake_id_test.exs
+++ b/test/flake_id_test.exs
@@ -1,8 +1,57 @@
defmodule FlakeIdTest do
- use ExUnit.Case
+ use ExUnit.Case, async: true
doctest FlakeId
- test "greets the world" do
- assert FlakeId.hello() == :world
+ @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 String.valid?(flake)
+ assert FlakeId.flake_id?(flake)
+ 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
diff --git a/test/install_version_test.exs b/test/install_version_test.exs
new file mode 100644
index 0000000..d135013
--- /dev/null
+++ b/test/install_version_test.exs
@@ -0,0 +1,27 @@
+defmodule InstallVersionTest do
+ use ExUnit.Case, async: true
+
+ describe("install version check") do
+ test "README.md" do
+ assert_version("README.md")
+ end
+ end
+
+ defp assert_version(filename) do
+ app = Keyword.get(Mix.Project.config(), :app)
+ app_version = app |> Application.spec(:vsn) |> to_string()
+
+ file = File.read!(filename)
+ [_, file_versions] = Regex.run(~r/{:#{app}, "(.+)"}/, file)
+
+ assert Version.match?(
+ app_version,
+ file_versions
+ ),
+ """
+ Install version constraint in `#{filename}` does not match to current app version.
+ Current App Version: #{app_version}
+ `#{filename}` Install Versions: #{file_versions}
+ """
+ end
+end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Oct 12, 6:12 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1786062
Default Alt Text
(30 KB)

Event Timeline