id.ex (5524B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule Zenflows.DB.ID do 19 @moduledoc """ 20 An Ecto type that implements ULIDs with Crockford's Base32. It also 21 provides validation for IDs in GraphQL schemas. 22 """ 23 24 use Ecto.Type 25 26 @typedoc "A Crockford's Base32-encoded string." 27 @type t() :: <<_::208>> # 26*8 28 29 @typedoc "A raw binary representation of a ULID." 30 @type raw() :: <<_::128>> 31 32 @impl true 33 def type(), do: :uuid 34 35 @impl true 36 def cast(<<_::208>> = data) do 37 with {:ok, _} <- decode(data) do 38 {:ok, data} 39 end 40 end 41 def cast(<<_::128>> = raw), do: encode(raw) 42 def cast(_), do: :error 43 44 @impl true 45 def dump(id) when byte_size(id) == 26, do: decode(id) 46 def dump(_), do: :error 47 48 @impl true 49 def load(<<_::128>> = raw), do: encode(raw) 50 def load(_), do: :error 51 52 @impl true 53 def autogenerate(), do: gen() 54 55 @doc "Generates a random ULID." 56 @spec gen() :: t() 57 def gen() do 58 {:ok, id} = encode(bingen()) 59 id 60 end 61 62 @doc "Generates a random ULID in binary format." 63 @spec bingen() :: raw() 64 def bingen() do 65 import DateTime 66 67 <<ts::binary-6, _::binary>> = 68 utc_now() |> to_unix(:millisecond) |> :binary.encode_unsigned() 69 rand = :crypto.strong_rand_bytes(10) 70 ts <> rand 71 end 72 73 @doc "Fetch the timestamp out of an encoded or raw ULID." 74 @spec ts(t() | raw()) :: {:ok, DateTime.t()} | {:error, atom()} 75 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 76 import DateTime 77 78 <<ts::binary-6, _::bitstring-2>> = << 79 d(x0)::5, d(x1)::5, d(x2)::5, d(x3)::5, d(x4)::5, 80 d(x5)::5, d(x6)::5, d(x7)::5, d(x8)::5, d(x9)::5, 81 >> 82 :binary.decode_unsigned(ts) |> from_unix(:millisecond) 83 end 84 def ts(<<ts::binary-6, _::binary-10>>) do 85 import DateTime 86 87 :binary.decode_unsigned(ts) |> from_unix(:millisecond) 88 end 89 def ts(_) do 90 {:error, :invalid} 91 end 92 93 defp encode(<<x00::5, x01::5, x02::5, x03::5, x04::5, x05::5, x06::5, x07::5, 94 x08::5, x09::5, x10::5, x11::5, x12::5, x13::5, x14::5, x15::5, x16::5, 95 x17::5, x18::5, x19::5, x20::5, x21::5, x22::5, x23::5, x24::5, x25::3>>) do 96 import Bitwise 97 98 {:ok, << 99 e(x00), e(x01), e(x02), e(x03), e(x04), e(x05), e(x06), e(x07), e(x08), 100 e(x09), e(x10), e(x11), e(x12), e(x13), e(x14), e(x15), e(x16), e(x17), 101 e(x18), e(x19), e(x20), e(x21), e(x22), e(x23), e(x24), e(bsl(x25, 2)), 102 >>} 103 end 104 defp encode(_), do: :error 105 106 defp decode(<<x00::8, x01::8, x02::8, x03::8, x04::8, x05::8, x06::8, x07::8, 107 x08::8, x09::8, x10::8, x11::8, x12::8, x13::8, x14::8, x15::8, x16::8, 108 x17::8, x18::8, x19::8, x20::8, x21::8, x22::8, x23::8, x24::8, x25::8>>) do 109 import Bitwise 110 111 {:ok, << 112 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, 113 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, 114 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, 115 >>} 116 rescue 117 ArgumentError -> :error 118 end 119 defp decode(_), do: :error 120 121 @compile {:inline, e: 1} 122 defp e(0), do: ?0 123 defp e(1), do: ?1 124 defp e(2), do: ?2 125 defp e(3), do: ?3 126 defp e(4), do: ?4 127 defp e(5), do: ?5 128 defp e(6), do: ?6 129 defp e(7), do: ?7 130 defp e(8), do: ?8 131 defp e(9), do: ?9 132 defp e(10), do: ?A 133 defp e(11), do: ?B 134 defp e(12), do: ?C 135 defp e(13), do: ?D 136 defp e(14), do: ?E 137 defp e(15), do: ?F 138 defp e(16), do: ?G 139 defp e(17), do: ?H 140 defp e(18), do: ?J 141 defp e(19), do: ?K 142 defp e(20), do: ?M 143 defp e(21), do: ?N 144 defp e(22), do: ?P 145 defp e(23), do: ?Q 146 defp e(24), do: ?R 147 defp e(25), do: ?S 148 defp e(26), do: ?T 149 defp e(27), do: ?V 150 defp e(28), do: ?W 151 defp e(29), do: ?X 152 defp e(30), do: ?Y 153 defp e(31), do: ?Z 154 155 @compile {:inline, d: 1} 156 defp d(?0), do: 0 157 defp d(?O), do: 0 158 defp d(?o), do: 0 159 defp d(?1), do: 1 160 defp d(?I), do: 1 161 defp d(?i), do: 1 162 defp d(?L), do: 1 163 defp d(?l), do: 1 164 defp d(?2), do: 2 165 defp d(?3), do: 3 166 defp d(?4), do: 4 167 defp d(?5), do: 5 168 defp d(?6), do: 6 169 defp d(?7), do: 7 170 defp d(?8), do: 8 171 defp d(?9), do: 9 172 defp d(?A), do: 10 173 defp d(?a), do: 10 174 defp d(?B), do: 11 175 defp d(?b), do: 11 176 defp d(?C), do: 12 177 defp d(?c), do: 12 178 defp d(?D), do: 13 179 defp d(?d), do: 13 180 defp d(?E), do: 14 181 defp d(?e), do: 14 182 defp d(?F), do: 15 183 defp d(?f), do: 15 184 defp d(?G), do: 16 185 defp d(?g), do: 16 186 defp d(?H), do: 17 187 defp d(?h), do: 17 188 defp d(?J), do: 18 189 defp d(?j), do: 18 190 defp d(?K), do: 19 191 defp d(?k), do: 19 192 defp d(?M), do: 20 193 defp d(?m), do: 20 194 defp d(?N), do: 21 195 defp d(?n), do: 21 196 defp d(?P), do: 22 197 defp d(?p), do: 22 198 defp d(?Q), do: 23 199 defp d(?q), do: 23 200 defp d(?R), do: 24 201 defp d(?r), do: 24 202 defp d(?S), do: 25 203 defp d(?s), do: 25 204 defp d(?T), do: 26 205 defp d(?t), do: 26 206 defp d(?V), do: 27 207 defp d(?v), do: 27 208 defp d(?W), do: 28 209 defp d(?w), do: 28 210 defp d(?X), do: 29 211 defp d(?x), do: 29 212 defp d(?Y), do: 30 213 defp d(?y), do: 30 214 defp d(?Z), do: 31 215 defp d(?z), do: 31 216 defp d(_), do: raise ArgumentError 217 end