TypeID
TypeIDs such as user_01h455vb4pex5vsknk084sn02q: a type prefix and a UUIDv7 in base32, made, read back to their UUID and time, and checked against the type a URL expects.
Version 1.0.0 · 4 functions · 2 crates
Functions
What use typeid.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use typeid_new, typeid_from_uuid from typeid (Quillo 0.6).
typeid_new(prefix: str) → stror an errorA new TypeID for prefix ("user", "invoice_line"; "" for none), its UUIDv7 taken now: ids of one prefix sort by the time they were made.
typeid_from_uuid(prefix: str, uuid: str) → stror an errorThe TypeID of an existing UUID, with prefix: to show database UUIDs as TypeIDs.
typeid_parse(id: str) → anyor an errorA TypeID read: { prefix, suffix, uuid, version, time }; time is when a UUIDv7 was made (ISO 8601, UTC), else null. An error value for an id that is not one.
typeid_valid(id: str, prefix: str) → boolWhether id is a valid TypeID whose prefix is prefix ("" to take any prefix): for ids in URLs, so /users/order_... is refused.
Get it
$ quillo install typeid # typeid.qwer into this folder$ quillo install typeid --example # and its example, typeid.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/typeid.qwer$ curl -O https://quillolang.org/plugins/files/typeid.qweThen, in your program, all of it or only what you call:
use typeid.qweruse typeid_new, typeid_from_uuid from typeid // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
uuid = { version = "1", features = ["v7"] }chrono = { version = "0.4", default-features = false, features = ["std"] }Example
// Ids that say what they are: user_..., invoice_..., sorted by when they were made.//// quillo run typeid.qwe then open http://localhost:8080/idsuse "typeid.qwer"route GET /ids { let user = typeid_new("user")? { user: user, read: typeid_parse(user)?, // The example from the TypeID spec. spec: typeid_from_uuid("prefix", "0110c853-1d09-52d8-d73e-1194e95b5f19")?, spec_back: typeid_parse("prefix_01h455vb4pex5vsknk084sn02q")?.uuid, nil: typeid_from_uuid("", "00000000-0000-0000-0000-000000000000")?, user_is_user: typeid_valid(user, "user"), user_is_invoice: typeid_valid(user, "invoice"), too_big: typeid_parse("user_8zzzzzzzzzzzzzzzzzzzzzzzzz").error, }}// /users/<typeid>: a TypeID of another type, or no TypeID at all, is refused.route GET /users/:id { if !typeid_valid(id, "user") { reply 404 { error: "not a user id", id: id } } { user: id, made: typeid_parse(id)?.time }}$ quillo run typeid.qwe$ curl 'localhost:8080/ids'Source
//! TypeIDs: type-safe, sortable ids such as `user_2x4y6z8a0b1c2d3e4f5g6h7j8k`, a type prefix and a UUIDv7 in base32, made, read back to their UUID and time, and checked.//! version: 1.0.0//!//! [dependencies]//! uuid = { version = "1", features = ["v7"] }//! chrono = { version = "0.4", default-features = false, features = ["std"] }use serde_json::{json, Value};const ALPHABET: &[u8; 32] = b"0123456789abcdefghjkmnpqrstvwxyz";fn encode(n: u128) -> String { (0..26).map(|i| ALPHABET[((n >> (5 * (25 - i))) & 31) as usize] as char).collect()}fn decode(s: &str) -> Result<u128, String> { if s.len() != 26 { return Err(format!("the part after the prefix is 26 characters, not {}", s.len())); } let mut n: u128 = 0; for (i, c) in s.bytes().enumerate() { let v = ALPHABET.iter().position(|&a| a == c).ok_or_else(|| format!("`{}` is not a TypeID character (0-9 and a-z without i, l, o, u)", c as char))?; if i == 0 && v > 7 { return Err("the first character after the prefix is 0 to 7: the id is larger than 128 bits".into()); } n = (n << 5) | v as u128; } Ok(n)}fn check_prefix(prefix: &str) -> Result<(), String> { if prefix.len() > 63 { return Err("a TypeID prefix is at most 63 characters".into()); } if !prefix.bytes().all(|b| b.is_ascii_lowercase() || b == b'_') { return Err(format!("the prefix `{}` may hold only lower-case a-z and _", prefix)); } if prefix.starts_with('_') || prefix.ends_with('_') { return Err(format!("the prefix `{}` may not start or end with _", prefix)); } Ok(())}fn join(prefix: &str, n: u128) -> String { if prefix.is_empty() { encode(n) } else { format!("{}_{}", prefix, encode(n)) }}fn split(id: &str) -> Result<(String, u128), String> { let (prefix, suffix) = match id.rfind('_') { Some(i) => { if i == 0 { return Err("a TypeID with an empty prefix has no `_`".into()); } (&id[..i], &id[i + 1..]) } None => ("", id), }; check_prefix(prefix)?; Ok((prefix.to_string(), decode(suffix)?))}/// A new TypeID for `prefix` (`"user"`, `"invoice_line"`; `""` for none), its UUIDv7 taken now: ids of one prefix sort by the time they were made.pub fn typeid_new(prefix: &str) -> Result<String, String> { check_prefix(prefix)?; Ok(join(prefix, uuid::Uuid::now_v7().as_u128()))}/// The TypeID of an existing UUID, with `prefix`: to show database UUIDs as TypeIDs.pub fn typeid_from_uuid(prefix: &str, uuid: &str) -> Result<String, String> { check_prefix(prefix)?; let u = uuid::Uuid::parse_str(uuid.trim()).map_err(|e| format!("`{}` is not a UUID: {}", uuid, e))?; Ok(join(prefix, u.as_u128()))}/// A TypeID read: `{ prefix, suffix, uuid, version, time }`; `time` is when a UUIDv7 was made (ISO 8601, UTC), else null. An error value for an id that is not one.pub fn typeid_parse(id: &str) -> Result<Value, String> { let (prefix, n) = split(id)?; let u = uuid::Uuid::from_u128(n); let time = if u.get_version_num() == 7 { let ms = (n >> 80) as i64; chrono::DateTime::from_timestamp_millis(ms).map(|t| t.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()) } else { None }; Ok(json!({ "prefix": prefix, "suffix": encode(n), "uuid": u.hyphenated().to_string(), "version": u.get_version_num(), "time": time, }))}/// Whether `id` is a valid TypeID whose prefix is `prefix` (`""` to take any prefix): for ids in URLs, so `/users/order_...` is refused.pub fn typeid_valid(id: &str, prefix: &str) -> bool { match split(id) { Ok((p, _)) => prefix.is_empty() || p == prefix, Err(_) => false, }}Related
short_ids.qwer · 1.0.0Short ids for URLs made from your database's numbers and read back (Sqids): your own alphabet, a minimum length, no rude words, and typos never reaching another row.6 views · 8 downloadsCUID2 cuid2.qwer · 1.0.0CUID2 ids: secure, collision-resistant, starting with a letter, of the length you choose, that give away neither the time nor a count; and a check that a string is one.7 views · 7 downloadsKSUID ksuid.qwer · 1.0.0KSUIDs, Segment's K-Sortable Unique IDs: 27 base62 characters sorted by the second they were made, with 128 random bits; made, read back and checked.8 views · 7 downloadsUUID tools uuid_tools.qwer · 1.0.0UUIDs beyond the random uuid(): version 7 sorted by time for database keys, versions 5 and 3 that give the same id for the same name, and any UUID read with its version and time.7 views · 6 downloadsNano ID nanoid.qwer · 1.0.0Short random ids for links, invites and coupon codes: Nano ID with secure random, your own alphabet and length, readable codes for print, and the odds of a collision.7 views · 7 downloadsReadable ids human_ids.qwer · 1.0.0Readable ids such as brave-blue-fox for projects, preview URLs and invite codes: random, alliterating, or always the same name for a given number.7 views · 7 downloadsThread 0 posts
On TypeID: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.