mirror of
https://github.com/sloanelybutsurely/typeid-elixir.git
synced 2024-11-28 01:42:54 -05:00
modify to support valid specs
This commit is contained in:
parent
3337d6b2cd
commit
6d4a555585
7 changed files with 216 additions and 4 deletions
|
@ -73,6 +73,10 @@ defmodule TypeID do
|
|||
|
||||
"""
|
||||
@spec to_string(tid :: t()) :: String.t()
|
||||
def to_string(%__MODULE__{prefix: "", suffix: suffix}) do
|
||||
suffix
|
||||
end
|
||||
|
||||
def to_string(%__MODULE__{prefix: prefix, suffix: suffix}) do
|
||||
prefix <> "_" <> suffix
|
||||
end
|
||||
|
@ -142,8 +146,13 @@ defmodule TypeID do
|
|||
"""
|
||||
@spec from_string!(String.t()) :: t() | no_return()
|
||||
def from_string!(str) do
|
||||
[prefix, suffix] = String.split(str, "_")
|
||||
from!(prefix, suffix)
|
||||
case String.split(str, "_") do
|
||||
[prefix, suffix] ->
|
||||
from!(prefix, suffix)
|
||||
|
||||
[suffix] ->
|
||||
from!("", suffix)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -168,7 +177,7 @@ defmodule TypeID do
|
|||
"""
|
||||
@spec from_uuid!(prefix :: String.t(), uuid :: String.t()) :: t() | no_return()
|
||||
def from_uuid!(prefix, uuid) do
|
||||
{:ok, %Uniq.UUID{bytes: uuid_bytes, version: 7}} = Uniq.UUID.parse(uuid)
|
||||
uuid_bytes = Uniq.UUID.string_to_binary!(uuid)
|
||||
from_uuid_bytes!(prefix, uuid_bytes)
|
||||
end
|
||||
|
||||
|
|
36
lib/type_id/uuid.ex
Normal file
36
lib/type_id/uuid.ex
Normal file
|
@ -0,0 +1,36 @@
|
|||
defmodule TypeID.UUID do
|
||||
def uuid7(opts \\ []) do
|
||||
time = Keyword.get_lazy(opts, :time, fn -> System.system_time(:millisecond) end)
|
||||
<<rand_a::12, _::6, rand_b::62>> = :crypto.strong_rand_bytes(10)
|
||||
|
||||
<<time::big-unsigned-integer-size(48), 7::4, rand_a::12, 2::2, rand_b::62>>
|
||||
end
|
||||
|
||||
def to_string(
|
||||
<<a1::4, a2::4, a3::4, a4::4, a5::4, a6::4, a7::4, a8::4, b1::4, b2::4, b3::4, b4::4,
|
||||
c1::4, c2::4, c3::4, c4::4, d1::4, d2::4, d3::4, d4::4, e1::4, e2::4, e3::4, e4::4,
|
||||
e5::4, e6::4, e7::4, e8::4, e9::4, e10::4, e11::4, e12::4>>
|
||||
) do
|
||||
<<e(a1), e(a2), e(a3), e(a4), e(a5), e(a6), e(a7), e(a8), ?-, e(b1), e(b2), e(b3), e(b4), ?-,
|
||||
e(c1), e(c2), e(c3), e(c4), ?-, e(d1), e(d2), e(d3), e(d4), ?-, e(e1), e(e2), e(e3), e(e4),
|
||||
e(e5), e(e6), e(e7), e(e8), e(e9), e(e10), e(e11), e(e12)>>
|
||||
end
|
||||
|
||||
@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
|
||||
end
|
3
mix.exs
3
mix.exs
|
@ -33,7 +33,8 @@ defmodule TypeID.MixProject do
|
|||
defp deps do
|
||||
[
|
||||
{:uniq, "~> 0.5.4"},
|
||||
{:ex_doc, "~> 0.27", only: :dev, runtime: false}
|
||||
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
|
||||
{:yaml_elixir, "~> 2.9", only: [:dev, :test], runtime: false}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -6,4 +6,6 @@
|
|||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
|
||||
"uniq": {:hex, :uniq, "0.5.4", "0602d0c75682f14863c1edea48920bd3d278b29ff7801d56e040e5f9f51ae0e2", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "856ec690d713f97373a3746afada340143c7e37a6488fca5afa720bfb08f0fe5"},
|
||||
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"},
|
||||
}
|
||||
|
|
83
priv/spec/invalid.yml
Normal file
83
priv/spec/invalid.yml
Normal file
|
@ -0,0 +1,83 @@
|
|||
# This file contains test data that should be treated as *invalid* TypeIDs by
|
||||
# conforming implementations.
|
||||
#
|
||||
# Each example contains an invalid TypeID string. Implementations are expected
|
||||
# to throw an error when attempting to parse/validate these strings.
|
||||
#
|
||||
# Last updated: 2023-06-29
|
||||
|
||||
- name: prefix-uppercase
|
||||
typeid: "PREFIX_00000000000000000000000000"
|
||||
description: "The prefix should be lowercase with no uppercase letters"
|
||||
|
||||
- name: prefix-numeric
|
||||
typeid: "12345_00000000000000000000000000"
|
||||
description: "The prefix can't have numbers, it needs to be alphabetic"
|
||||
|
||||
- name: prefix-period
|
||||
typeid: "pre.fix_00000000000000000000000000"
|
||||
description: "The prefix can't have symbols, it needs to be alphabetic"
|
||||
|
||||
- name: prefix-underscore
|
||||
typeid: "pre_fix_00000000000000000000000000"
|
||||
description: "The prefix can't have symbols, it needs to be alphabetic"
|
||||
|
||||
- name: prefix-non-ascii
|
||||
typeid: "préfix_00000000000000000000000000"
|
||||
description: "The prefix can only have ascii letters"
|
||||
|
||||
- name: prefix-spaces
|
||||
typeid: " prefix_00000000000000000000000000"
|
||||
description: "The prefix can't have any spaces"
|
||||
|
||||
- name: prefix-64-chars
|
||||
# 123456789 123456789 123456789 123456789 123456789 123456789 1234
|
||||
typeid: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl_00000000000000000000000000"
|
||||
description: "The prefix can't be 64 characters, it needs to be 63 characters or less"
|
||||
|
||||
- name: separator-empty-prefix
|
||||
typeid: "_00000000000000000000000000"
|
||||
description: "If the prefix is empty, the separator should not be there"
|
||||
|
||||
- name: separator-empty
|
||||
typeid: "_"
|
||||
description: "A separator by itself should not be treated as the empty string"
|
||||
|
||||
- name: suffix-short
|
||||
typeid: "prefix_1234567890123456789012345"
|
||||
description: "The suffix can't be 25 characters, it needs to be exactly 26 characters"
|
||||
|
||||
- name: suffix-long
|
||||
typeid: "prefix_123456789012345678901234567"
|
||||
description: "The suffix can't be 27 characters, it needs to be exactly 26 characters"
|
||||
|
||||
- name: suffix-spaces
|
||||
# This example has the right length, so that the failure is caused by the space
|
||||
# and not the suffix length
|
||||
typeid: "prefix_1234567890123456789012345 "
|
||||
description: "The suffix can't have any spaces"
|
||||
|
||||
- name: suffix-uppercase
|
||||
# This example is picked because it would be valid in lowercase
|
||||
typeid: "prefix_0123456789ABCDEFGHJKMNPQRS"
|
||||
description: "The suffix should be lowercase with no uppercase letters"
|
||||
|
||||
- name: suffix-hyphens
|
||||
# This example has the right length, so that the failure is caused by the hyphens
|
||||
# and not the suffix length
|
||||
typeid: "prefix_123456789-123456789-123456"
|
||||
description: "The suffix should be lowercase with no uppercase letters"
|
||||
|
||||
- name: suffix-wrong-alphabet
|
||||
typeid: "prefix_ooooooiiiiiiuuuuuuulllllll"
|
||||
description: "The suffix should only have letters from the spec's alphabet"
|
||||
|
||||
- name: suffix-ambiguous-crockford
|
||||
# This example would be valid if we were using the crockford disambiguation rules
|
||||
typeid: "prefix_i23456789ol23456789oi23456"
|
||||
description: "The suffix should not have any ambiguous characters from the crockford encoding"
|
||||
|
||||
- name: suffix-hyphens-crockford
|
||||
# This example would be valid if we were using the crockford hyphenation rules
|
||||
typeid: "prefix_123456789-0123456789-0123456"
|
||||
description: "The suffix can't ignore hyphens as in the crockford encoding"
|
61
priv/spec/valid.yml
Normal file
61
priv/spec/valid.yml
Normal file
|
@ -0,0 +1,61 @@
|
|||
# This file contains test data that should parse as valid TypeIDs by conforming
|
||||
# implementations.
|
||||
#
|
||||
# Each example contains:
|
||||
# - The TypeID in its canonical string representation.
|
||||
# - The prefix
|
||||
# - The decoded UUID as a hex string
|
||||
#
|
||||
# Implementations should verify that they can encode/decode the data
|
||||
# in both directions:
|
||||
# 1. If the TypeID is decoded, it should result in the given prefix and UUID.
|
||||
# 2. If the UUID is encoded as a TypeID with the given prefix, it should
|
||||
# result in the given TypeID.
|
||||
#
|
||||
# In addition to using these examples, it's recommended that implementations
|
||||
# generate a thousands of random ids during testing, and verify that after
|
||||
# decoding and re-encoding the id, the result is the same as the original.
|
||||
#
|
||||
# In other words, the following property should always hold:
|
||||
# random_typeid == encode(decode(random_typeid))
|
||||
#
|
||||
# Finally, while implementations should be able to decode the values below,
|
||||
# note that not all of them are UUIDv7s. When *generating* new random typeids,
|
||||
# implementations should always use UUIDv7s.
|
||||
#
|
||||
# Last updated: 2023-06-29
|
||||
|
||||
- name: nil
|
||||
typeid: "00000000000000000000000000"
|
||||
prefix: ""
|
||||
uuid: "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
- name: one
|
||||
typeid: "00000000000000000000000001"
|
||||
prefix: ""
|
||||
uuid: "00000000-0000-0000-0000-000000000001"
|
||||
|
||||
- name: ten
|
||||
typeid: "0000000000000000000000000a"
|
||||
prefix: ""
|
||||
uuid: "00000000-0000-0000-0000-00000000000a"
|
||||
|
||||
- name: sixteen
|
||||
typeid: "0000000000000000000000000g"
|
||||
prefix: ""
|
||||
uuid: "00000000-0000-0000-0000-000000000010"
|
||||
|
||||
- name: thirty-two
|
||||
typeid: "00000000000000000000000010"
|
||||
prefix: ""
|
||||
uuid: "00000000-0000-0000-0000-000000000020"
|
||||
|
||||
- name: valid-alphabet
|
||||
typeid: "prefix_0123456789abcdefghjkmnpqrs"
|
||||
prefix: "prefix"
|
||||
uuid: "0110c853-1d09-52d8-d73e-1194e95b5f19"
|
||||
|
||||
- name: valid-uuidv7
|
||||
typeid: "prefix_01h455vb4pex5vsknk084sn02q"
|
||||
prefix: "prefix"
|
||||
uuid: "01890a5d-ac96-774b-bcce-b302099a8057"
|
20
test/spec_test.exs
Normal file
20
test/spec_test.exs
Normal file
|
@ -0,0 +1,20 @@
|
|||
defmodule TypeID.SpecTest do
|
||||
use ExUnit.Case
|
||||
|
||||
@valid_specs :code.priv_dir(:typeid_elixir)
|
||||
|> Path.join("spec/valid.yml")
|
||||
|> YamlElixir.read_from_file!()
|
||||
|
||||
describe "valid" do
|
||||
for %{"name" => name, "typeid" => typeid, "prefix" => prefix, "uuid" => uuid} <- @valid_specs do
|
||||
test "#{name}" do
|
||||
assert {:ok, tid} = TypeID.from_uuid(unquote(prefix), unquote(uuid))
|
||||
assert unquote(typeid) == TypeID.to_string(tid)
|
||||
assert {:ok, ^tid} = TypeID.from_string(unquote(typeid))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# describe "invalid" do
|
||||
# end
|
||||
end
|
Loading…
Reference in a new issue