Time and IDs

Short ids

Short 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.

Version 1.0.0 · 4 functions · 1 crate

#ids#urls#database#api

Functions

What use short_ids.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use short_id_encode, short_id_decode from short_ids (Quillo 0.6).

short_id_encode(numbers: list, alphabet: str, min_length: int) → stror an error

An id for numbers (whole numbers from 0, one or several: [1042] or [user, post]), at least min_length characters, written with alphabet ("" for a-z, A-Z and 0-9; shuffle it, so others cannot read your ids back with the default). It hides numbers from casual eyes only: it is not encryption.

short_id_decode(id: str, alphabet: str, min_length: int) → listor an error

The numbers in an id made by short_id_encode with the same alphabet and min_length, or an empty list when it is not one of yours (ids are checked to be the one way of writing their numbers, so a typo cannot reach another row).

short_id(number: int) → stror an error

One number's id with the default alphabet, at least 6 characters: short_id(1042).

short_id_number(id: str) → int?or an error

The number of an id made by short_id, or null when it is not one.

Get it

$ quillo install short_ids               # short_ids.qwer into this folder$ quillo install short_ids --example     # and its example, short_ids.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/short_ids.qwer$ curl -O https://quillolang.org/plugins/files/short_ids.qwe

Then, in your program, all of it or only what you call:

main.qwe
use short_ids.qweruse short_id_encode, short_id_decode from short_ids      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
sqids = "0.4"

Example

short_ids.qwe
// Order pages at /orders/<short id> instead of /orders/1042, and the id read back to find the row.////   quillo run short_ids.qwe      then open http://localhost:8080/ordersuse "short_ids.qwer"// Your own shuffle of the alphabet, kept with the program: ids made with it read back only with it.let ALPHABET = "k3G7QAe51FCsPW92uEOyq4Bg6Sp8YzVTmnU0liwDdHXLajZrfxNvobJIRchMKt"let orders = [{ id: 1042, total: 99 }, { id: 1043, total: 15 }]route GET /orders = orders | map((o) => { url: `/orders/${short_id_encode([o.id], ALPHABET, 6)?}`, total: o.total })route GET /orders/:code {    let ids = short_id_decode(code, ALPHABET, 6)?    if len(ids) != 1 { reply 404 "No such order" }    find(orders, (o) => o.id == ids[0]) ?? { error: "gone" }}route GET /plain = { id: short_id(1042)?, back: short_id_number(short_id(1042)?)?, pair: short_id_encode([7, 99], "", 0)?, junk: short_id_number("nope!")? }
$ quillo run short_ids.qwe$ curl 'localhost:8080/orders'$ curl 'localhost:8080/orders/QKGlWg'$ curl 'localhost:8080/plain'

Source

short_ids.qwer
//! Short ids for URLs made from your database's numbers and read back (Sqids, formerly Hashids): /orders/86Rf07 instead of /orders/1042, with your own alphabet and no rude words.//! version: 1.0.0//!//! [dependencies]//! sqids = "0.4"use sqids::Sqids;use std::collections::HashMap;use std::sync::{Arc, Mutex};fn coder(alphabet: &str, min_length: i64) -> Result<Arc<Sqids>, String> {    static CACHE: Mutex<Option<HashMap<(String, u8), Arc<Sqids>>>> = Mutex::new(None);    let min = min_length.clamp(0, 255) as u8;    let mut g = CACHE.lock().unwrap_or_else(|e| e.into_inner());    let map = g.get_or_insert_with(HashMap::new);    if let Some(s) = map.get(&(alphabet.to_string(), min)) {        return Ok(s.clone());    }    let mut b = Sqids::builder().min_length(min);    if !alphabet.is_empty() {        b = b.alphabet(alphabet.chars().collect());    }    let s = Arc::new(b.build().map_err(|e| e.to_string())?);    if map.len() < 100 {        map.insert((alphabet.to_string(), min), s.clone());    }    Ok(s)}/// An id for `numbers` (whole numbers from 0, one or several: [1042] or [user, post]), at least `min_length` characters, written with `alphabet` ("" for a-z, A-Z and 0-9; shuffle it, so others cannot read your ids back with the default). It hides numbers from casual eyes only: it is not encryption.pub fn short_id_encode(numbers: Vec<i64>, alphabet: &str, min_length: i64) -> Result<String, String> {    if numbers.is_empty() || numbers.len() > 100 || numbers.iter().any(|&n| n < 0) {        return Err("1 to 100 numbers, none negative".into());    }    let ns: Vec<u64> = numbers.iter().map(|&n| n as u64).collect();    coder(alphabet, min_length)?.encode(&ns).map_err(|e| e.to_string())}/// The numbers in an id made by `short_id_encode` with the same `alphabet` and `min_length`, or an empty list when it is not one of yours (ids are checked to be the one way of writing their numbers, so a typo cannot reach another row).pub fn short_id_decode(id: &str, alphabet: &str, min_length: i64) -> Result<Vec<i64>, String> {    if id.len() > 1000 {        return Ok(vec![]);    }    let c = coder(alphabet, min_length)?;    let ns = c.decode(id);    if ns.is_empty() || ns.iter().any(|&n| n > i64::MAX as u64) || c.encode(&ns).ok().as_deref() != Some(id) {        return Ok(vec![]);    }    Ok(ns.into_iter().map(|n| n as i64).collect())}/// One number's id with the default alphabet, at least 6 characters: `short_id(1042)`.pub fn short_id(number: i64) -> Result<String, String> {    short_id_encode(vec![number], "", 6)}/// The number of an id made by `short_id`, or null when it is not one.pub fn short_id_number(id: &str) -> Result<Option<i64>, String> {    let ns = short_id_decode(id, "", 6)?;    Ok(if ns.len() == 1 { Some(ns[0]) } else { None })}
Thread
68Useful? Like it, or add to its thread below.

Thread 0 posts

On Short ids: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.