-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
202 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
//// Module for generating TypeIDs. | ||
|
||
import gleam/bit_array | ||
import gleam/regex | ||
import gleam/result | ||
import gleam/string | ||
import ids/utils | ||
import ids/uuid | ||
|
||
@internal | ||
pub const alphabet = "0123456789abcdefghjkmnpqrstvwxyz" | ||
|
||
/// Generate a TypeID using the supplied prefix. Prefix may be an empty string. | ||
/// | ||
/// ### Usage | ||
/// ```gleam | ||
/// generate("user") // Ok("user_01hz7jxhgpfxh9m33bn41tcg9t") | ||
/// generate("") // Ok("01hz7jv26zfnx9gpb0hvyc4pss") | ||
/// ``` | ||
pub fn generate(prefix prefix: String) -> Result(String, String) { | ||
use uuid <- result.try(uuid.generate_v7()) | ||
from_uuid(prefix: prefix, uuid: uuid) | ||
} | ||
|
||
/// Generate a TypeID using the supplied prefix and UUID. Prefix may be an empty string. | ||
/// | ||
/// ### Usage | ||
/// ```gleam | ||
/// from_uuid("user", "018fcec0-b44b-7ce2-b187-2f08349beab9") // Ok("user_01hz7c1d2bfkhb31sf10t9qtns") | ||
/// ``` | ||
pub fn from_uuid( | ||
prefix prefix: String, | ||
uuid uuid: String, | ||
) -> Result(String, String) { | ||
let assert Ok(re) = regex.from_string("^([a-z]([a-z_]{0,61}[a-z])?)?$") | ||
|
||
case regex.check(re, prefix) { | ||
True -> { | ||
let p = case prefix { | ||
"" -> "" | ||
_ -> prefix <> "_" | ||
} | ||
|
||
use raw_uuid <- result.try( | ||
uuid | ||
|> bit_array.from_string | ||
|> uuid.dump, | ||
) | ||
|
||
let id = utils.encode_base32(raw_uuid, alphabet) | ||
|
||
Ok(p <> id) | ||
} | ||
False -> | ||
Error( | ||
"Error: Prefix must contain at most 63 characters and only lowercase alphabetic ASCII characters [a-z], or an underscore.", | ||
) | ||
} | ||
} | ||
|
||
/// Decode a TypeID into a tuple of #(prefix, uuid) | ||
/// | ||
/// ### Usage | ||
/// ```gleam | ||
/// decode("user_01hz7c1d2bfkhb31sf10t9qtns") // Ok(#("user", "018fcec0-b44b-7ce2-b187-2f08349beab9")) | ||
/// ``` | ||
pub fn decode(tid) -> Result(#(String, String), String) { | ||
case tid |> string.reverse |> string.split_once(on: "_") { | ||
Ok(#(xiffus, xiferp)) -> { | ||
use uuid <- result.try( | ||
xiffus | ||
|> string.reverse | ||
|> decode_suffix, | ||
) | ||
Ok(#(string.reverse(xiferp), uuid)) | ||
} | ||
Error(Nil) -> { | ||
use uuid <- result.try(decode_suffix(tid)) | ||
Ok(#("", uuid)) | ||
} | ||
} | ||
} | ||
|
||
fn decode_suffix(suffix) -> Result(String, String) { | ||
use raw_uuid <- result.try( | ||
suffix | ||
|> utils.decode_base32(alphabet) | ||
|> result.replace_error("Error: Couldn't decode suffix."), | ||
) | ||
|
||
use uuid <- result.try( | ||
raw_uuid | ||
|> uuid.cast | ||
|> result.replace_error("Error: Couldn't decode UUID."), | ||
) | ||
|
||
Ok(uuid) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import gleam/bit_array | ||
import gleam/string | ||
import gleeunit/should | ||
import ids/typeid | ||
|
||
pub fn gen_test() { | ||
typeid.generate("test") | ||
|> should.be_ok | ||
|
||
typeid.generate("te_st") | ||
|> should.be_ok | ||
|
||
typeid.generate("123") | ||
|> should.be_error | ||
|
||
let assert Ok(id) = typeid.generate("test") | ||
|
||
id | ||
|> string.starts_with("test_") | ||
|> should.be_true | ||
|
||
id | ||
|> string.length | ||
|> should.equal(31) | ||
} | ||
|
||
pub fn gen_empty_prefix_test() { | ||
let assert Ok(id) = typeid.generate("") | ||
|
||
id | ||
|> string.contains("_") | ||
|> should.be_false | ||
|
||
id | ||
|> string.length | ||
|> should.equal(26) | ||
} | ||
|
||
pub fn from_uuid_test() { | ||
typeid.from_uuid("test", "wobble") | ||
|> should.be_error | ||
|
||
typeid.from_uuid("test", "018fcec0-b44b-7ce2-b187-2f08349beab9") | ||
|> should.be_ok | ||
|
||
let assert Ok(id) = | ||
typeid.from_uuid("test", "018fcec0-b44b-7ce2-b187-2f08349beab9") | ||
|
||
id | ||
|> should.equal("test_01hz7c1d2bfkhb31sf10t9qtns") | ||
} | ||
|
||
pub fn decode_test() { | ||
let assert Ok(id1) = typeid.generate("test") | ||
let assert Ok(id2) = typeid.generate("") | ||
let assert Ok(id3) = | ||
typeid.from_uuid("test", "018fcec0-b44b-7ce2-b187-2f08349beab9") | ||
|
||
id1 | ||
|> typeid.decode | ||
|> should.be_ok | ||
|
||
id2 | ||
|> typeid.decode | ||
|> should.be_ok | ||
|
||
"" | ||
|> typeid.decode | ||
|> should.be_error | ||
|
||
"lkjgijkldsa" | ||
|> typeid.decode | ||
|> should.be_error | ||
|
||
let assert Ok(#(prefix1, suffix1)) = typeid.decode(id1) | ||
let assert Ok(#(prefix2, _suffix2)) = typeid.decode(id2) | ||
let assert Ok(#(_prefix3, suffix3)) = typeid.decode(id3) | ||
|
||
prefix1 | ||
|> should.equal("test") | ||
|
||
prefix2 | ||
|> should.equal("") | ||
|
||
let assert << | ||
_:size(64), | ||
45, | ||
_:size(32), | ||
45, | ||
_:size(32), | ||
45, | ||
_:size(32), | ||
45, | ||
_:size(96), | ||
>> = bit_array.from_string(suffix1) | ||
|
||
suffix3 | ||
|> should.equal("018fcec0-b44b-7ce2-b187-2f08349beab9") | ||
} |