zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

commit 76f7772b6d1fe09655cba3c88d670c0f946ed112
parent 4000570536ddc8d678b7fef416c6494905ec929c
Author: srfsh <dev@srf.sh>
Date:   Sun, 21 Aug 2022 20:00:42 +0300

Zenflows.DB.ID: switch to ULID

Diffstat:
Msrc/zenflows/db/id.ex | 299++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
1 file changed, 167 insertions(+), 132 deletions(-)

diff --git a/src/zenflows/db/id.ex b/src/zenflows/db/id.ex @@ -17,166 +17,201 @@ defmodule Zenflows.DB.ID do @moduledoc """ -An Ecto type to `Base.url_encode64/2` UUIDs. It provides validation for -IDs in GraphQL schemas and just makes the IDs shorter. +An Ecto type that implements ULIDs with Crockford's Base32. It also +provides validation for IDs in GraphQL schemas. """ use Ecto.Type -@typedoc "URL-safe Base64-encoded string." -@type t() :: <<_::176>> # 22*8 +@typedoc "A Crockford's Base32-encoded string." +@type t() :: <<_::208>> # 26*8 -@typedoc "A raw binary representation of a UUID." -@type raw() :: Ecto.UUID.raw() +@typedoc "A raw binary representation of a ULID." +@type raw() :: <<_::128>> @impl true -def type() do - :uuid -end +def type(), do: :uuid -# credo:disable-for-lines:3 Credo.Check.Refactor.CyclomaticComplexity -# credo:disable-for-lines:7 Credo.Check.Consistency.TabsOrSpaces @impl true -def cast(<<c01::8, c02::8, c03::8, c04::8, - c05::8, c06::8, c07::8, c08::8, - c09::8, c10::8, c11::8, c12::8, - c13::8, c14::8, c15::8, c16::8, - c17::8, c18::8, c19::8, c20::8, - c21::8, c22::8>> = val) do - valid? = v(c01) and v(c02) and v(c03) and v(c04) - and v(c05) and v(c06) and v(c07) and v(c08) - and v(c09) and v(c10) and v(c11) and v(c12) - and v(c13) and v(c14) and v(c15) and v(c16) - and v(c17) and v(c18) and v(c19) and v(c20) - and v(c21) and v(c22) - if valid? do - {:ok, val} - else - :error +def cast(<<_::208>> = data) do + with {:ok, _} <- decode(data) do + {:ok, data} end end - -def cast(<<_::128>> = raw) do - {:ok, encode(raw)} -end - -def cast(_) do - :error -end +def cast(<<_::128>> = raw), do: encode(raw) +def cast(_), do: :error @impl true -def dump(id) when byte_size(id) == 22 do - decode(id) -end - -def dump(_) do - :error -end +def dump(id) when byte_size(id) == 26, do: decode(id) +def dump(_), do: :error @impl true -def load(<<_::128>> = raw) do - {:ok, encode(raw)} -end - -def load(_) do - :error -end +def load(<<_::128>> = raw), do: encode(raw) +def load(_), do: :error @impl true -def autogenerate() do - gen() -end +def autogenerate(), do: gen() -@doc "Generates a random UUIDv4 encoded as URL-safe Base64 string." +@doc "Generates a random ULID." @spec gen() :: t() def gen() do - encode(bingen()) + {:ok, id} = encode(bingen()) + id end -@doc "Generates a random UUIDv4 in binary format." +@doc "Generates a random ULID in binary format." @spec bingen() :: raw() def bingen() do - Ecto.UUID.bingenerate() + import DateTime + + <<ts::binary-6, _::binary>> = + utc_now() |> to_unix(:millisecond) |> :binary.encode_unsigned() + rand = :crypto.strong_rand_bytes(10) + ts <> rand end -@spec decode(String.t()) :: {:ok, raw()} | :error -defp decode(str) do - case Base.url_decode64(str, padding: false) do - {:ok, raw} -> {:ok, raw} - :error -> :error - end +@doc "Fetch the timestamp out of an encoded or raw ULID." +@spec ts(t() | raw()) :: {:ok, DateTime.t()} | {:error, atom()} +def ts(<<x0::8, x1::8, x2::8, x3::8, x4::8, x5::8, x6::8, x7::8, x8::8, x9::8, _::binary-16>>) do + import DateTime + + <<ts::binary-6, _::bitstring-2>> = << + d(x0)::5, d(x1)::5, d(x2)::5, d(x3)::5, d(x4)::5, + d(x5)::5, d(x6)::5, d(x7)::5, d(x8)::5, d(x9)::5, + >> + :binary.decode_unsigned(ts) |> from_unix(:millisecond) end +def ts(<<ts::binary-6, _::binary-10>>) do + import DateTime -@spec encode(raw()) :: t() -defp encode(raw) do - Base.url_encode64(raw, padding: false) + :binary.decode_unsigned(ts) |> from_unix(:millisecond) end +def ts(_) do + {:error, :invalid} +end + +defp encode(<<x00::5, x01::5, x02::5, x03::5, x04::5, x05::5, x06::5, x07::5, + x08::5, x09::5, x10::5, x11::5, x12::5, x13::5, x14::5, x15::5, x16::5, + x17::5, x18::5, x19::5, x20::5, x21::5, x22::5, x23::5, x24::5, x25::3>>) do + import Bitwise -@compile {:inline, v: 1} -@spec v(any()) :: boolean() -defp v(?_), do: true -defp v(?-), do: true -defp v(?0), do: true -defp v(?1), do: true -defp v(?2), do: true -defp v(?3), do: true -defp v(?4), do: true -defp v(?5), do: true -defp v(?6), do: true -defp v(?7), do: true -defp v(?8), do: true -defp v(?9), do: true -defp v(?A), do: true -defp v(?B), do: true -defp v(?C), do: true -defp v(?D), do: true -defp v(?E), do: true -defp v(?F), do: true -defp v(?G), do: true -defp v(?H), do: true -defp v(?I), do: true -defp v(?J), do: true -defp v(?K), do: true -defp v(?L), do: true -defp v(?M), do: true -defp v(?N), do: true -defp v(?O), do: true -defp v(?P), do: true -defp v(?Q), do: true -defp v(?R), do: true -defp v(?S), do: true -defp v(?T), do: true -defp v(?U), do: true -defp v(?V), do: true -defp v(?W), do: true -defp v(?X), do: true -defp v(?Y), do: true -defp v(?Z), do: true -defp v(?a), do: true -defp v(?b), do: true -defp v(?c), do: true -defp v(?d), do: true -defp v(?e), do: true -defp v(?f), do: true -defp v(?g), do: true -defp v(?h), do: true -defp v(?i), do: true -defp v(?j), do: true -defp v(?k), do: true -defp v(?l), do: true -defp v(?m), do: true -defp v(?n), do: true -defp v(?o), do: true -defp v(?p), do: true -defp v(?q), do: true -defp v(?r), do: true -defp v(?s), do: true -defp v(?t), do: true -defp v(?u), do: true -defp v(?v), do: true -defp v(?w), do: true -defp v(?x), do: true -defp v(?y), do: true -defp v(?z), do: true -defp v(_), do: false + {:ok, << + e(x00), e(x01), e(x02), e(x03), e(x04), e(x05), e(x06), e(x07), e(x08), + e(x09), e(x10), e(x11), e(x12), e(x13), e(x14), e(x15), e(x16), e(x17), + e(x18), e(x19), e(x20), e(x21), e(x22), e(x23), e(x24), e(bsl(x25, 2)), + >>} +end +defp encode(_), do: :error + +defp decode(<<x00::8, x01::8, x02::8, x03::8, x04::8, x05::8, x06::8, x07::8, + x08::8, x09::8, x10::8, x11::8, x12::8, x13::8, x14::8, x15::8, x16::8, + x17::8, x18::8, x19::8, x20::8, x21::8, x22::8, x23::8, x24::8, x25::8>>) do + import Bitwise + + {:ok, << + d(x00)::5, d(x01)::5, d(x02)::5, d(x03)::5, d(x04)::5, d(x05)::5, d(x06)::5, d(x07)::5, d(x08)::5, + d(x09)::5, d(x10)::5, d(x11)::5, d(x12)::5, d(x13)::5, d(x14)::5, d(x15)::5, d(x16)::5, d(x17)::5, + d(x18)::5, d(x19)::5, d(x20)::5, d(x21)::5, d(x22)::5, d(x23)::5, d(x24)::5, bsr(d(x25), 2)::3, + >>} +rescue + ArgumentError -> :error +end +defp decode(_), do: :error + +@compile {:inline, e: 1} +defp e(0), do: ?0 +defp e(1), do: ?1 +defp e(2), do: ?2 +defp e(3), do: ?3 +defp e(4), do: ?4 +defp e(5), do: ?5 +defp e(6), do: ?6 +defp e(7), do: ?7 +defp e(8), do: ?8 +defp e(9), do: ?9 +defp e(10), do: ?A +defp e(11), do: ?B +defp e(12), do: ?C +defp e(13), do: ?D +defp e(14), do: ?E +defp e(15), do: ?F +defp e(16), do: ?G +defp e(17), do: ?H +defp e(18), do: ?J +defp e(19), do: ?K +defp e(20), do: ?M +defp e(21), do: ?N +defp e(22), do: ?P +defp e(23), do: ?Q +defp e(24), do: ?R +defp e(25), do: ?S +defp e(26), do: ?T +defp e(27), do: ?V +defp e(28), do: ?W +defp e(29), do: ?X +defp e(30), do: ?Y +defp e(31), do: ?Z + +@compile {:inline, d: 1} +defp d(?0), do: 0 +defp d(?O), do: 0 +defp d(?o), do: 0 +defp d(?1), do: 1 +defp d(?I), do: 1 +defp d(?i), do: 1 +defp d(?L), do: 1 +defp d(?l), do: 1 +defp d(?2), do: 2 +defp d(?3), do: 3 +defp d(?4), do: 4 +defp d(?5), do: 5 +defp d(?6), do: 6 +defp d(?7), do: 7 +defp d(?8), do: 8 +defp d(?9), do: 9 +defp d(?A), do: 10 +defp d(?a), do: 10 +defp d(?B), do: 11 +defp d(?b), do: 11 +defp d(?C), do: 12 +defp d(?c), do: 12 +defp d(?D), do: 13 +defp d(?d), do: 13 +defp d(?E), do: 14 +defp d(?e), do: 14 +defp d(?F), do: 15 +defp d(?f), do: 15 +defp d(?G), do: 16 +defp d(?g), do: 16 +defp d(?H), do: 17 +defp d(?h), do: 17 +defp d(?J), do: 18 +defp d(?j), do: 18 +defp d(?K), do: 19 +defp d(?k), do: 19 +defp d(?M), do: 20 +defp d(?m), do: 20 +defp d(?N), do: 21 +defp d(?n), do: 21 +defp d(?P), do: 22 +defp d(?p), do: 22 +defp d(?Q), do: 23 +defp d(?q), do: 23 +defp d(?R), do: 24 +defp d(?r), do: 24 +defp d(?S), do: 25 +defp d(?s), do: 25 +defp d(?T), do: 26 +defp d(?t), do: 26 +defp d(?V), do: 27 +defp d(?v), do: 27 +defp d(?W), do: 28 +defp d(?w), do: 28 +defp d(?X), do: 29 +defp d(?x), do: 29 +defp d(?Y), do: 30 +defp d(?y), do: 30 +defp d(?Z), do: 31 +defp d(?z), do: 31 +defp d(_), do: raise ArgumentError end