Time and IDs

Nano ID

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

Version 1.0.0 · 3 functions · 1 crate

#ids#urls#security#coupons

Functions

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

nanoid(size: int) → stror an error

A random id of size characters (0 for the usual 21) from A-Z, a-z, 0-9, "_" and "-": as unlikely to repeat as a UUID v4, and safe in URLs.

nanoid_with(size: int, alphabet: str) → stror an error

A random id of size characters (1 to 1,000) from alphabet: your own characters ("ABC123"), or "numbers", "lowercase", "hex", "readable" (no 0/O, 1/l/I to confuse) or "" (the default). Secure random, without bias toward any character.

nanoid_safe_count(size: int, alphabet: str) → floator an error

How many ids of size characters from alphabet can be made before there is a 1% chance two are the same: 21 default characters is about 1.3 × 10^18; 8 readable ones about 1.4 million. Make ids longer when this is near the number you will make.

Get it

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

Or with curl:

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

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

main.qwe
use nanoid.qweruse nanoid, nanoid_with from nanoid      // Quillo 0.6: only these

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

[dependencies]
nanoid = "0.5"

Example

nanoid.qwe
// Invite links and coupon codes: random ids in a URL-safe alphabet, and readable ones for print.////   quillo run nanoid.qwe      then open http://localhost:8080/inviteuse "nanoid.qwer"route GET /invite = { url: `https://app.example.com/join/${nanoid(0)?}` }route GET /coupons ? { n: int = 3 } = range(0, n) | map((i) => `SALE-${nanoid_with(8, "readable")?}`)route GET /odds = {    default_21: nanoid_safe_count(21, "")?,    readable_8: nanoid_safe_count(8, "readable")?,    pin: nanoid_with(6, "numbers")?,    own: nanoid_with(10, "ABC123")?,    length: len(nanoid(0)?),}
$ quillo run nanoid.qwe$ curl 'localhost:8080/invite'$ curl 'localhost:8080/odds'

Source

nanoid.qwer
//! Short random ids for links, invites, coupon codes and public keys: Nano ID, secure random, with your own alphabet and length, and the odds of two ever being the same.//! version: 1.0.0//!//! [dependencies]//! nanoid = "0.5"const DEFAULT: &str = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";fn alphabet(a: &str) -> Result<Vec<char>, String> {    let chars: Vec<char> = match a {        "" | "default" => DEFAULT.chars().collect(),        "numbers" => "0123456789".chars().collect(),        "lowercase" => "abcdefghijklmnopqrstuvwxyz0123456789".chars().collect(),        "hex" => "0123456789abcdef".chars().collect(),        // No 0/O, 1/l/I or other pairs people misread: for codes read aloud or typed from paper.        "readable" => "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz".chars().collect(),        other => other.chars().collect(),    };    let mut seen = std::collections::HashSet::new();    if chars.len() < 2 || chars.len() > 255 || !chars.iter().all(|c| seen.insert(*c)) {        return Err("an alphabet has 2 to 255 different characters".into());    }    Ok(chars)}/// A random id of `size` characters (0 for the usual 21) from A-Z, a-z, 0-9, "_" and "-": as unlikely to repeat as a UUID v4, and safe in URLs.pub fn nanoid(size: i64) -> Result<String, String> {    nanoid_with(if size == 0 { 21 } else { size }, "")}/// A random id of `size` characters (1 to 1,000) from `alphabet`: your own characters ("ABC123"), or "numbers", "lowercase", "hex", "readable" (no 0/O, 1/l/I to confuse) or "" (the default). Secure random, without bias toward any character.pub fn nanoid_with(size: i64, alphabet: &str) -> Result<String, String> {    if !(1..=1000).contains(&size) {        return Err("size from 1 to 1,000".into());    }    let a = self::alphabet(alphabet)?;    Ok(nanoid::format(nanoid::rngs::default, &a, size as usize))}/// How many ids of `size` characters from `alphabet` can be made before there is a 1% chance two are the same: 21 default characters is about 1.3 × 10^18; 8 readable ones about 1.4 million. Make ids longer when this is near the number you will make.pub fn nanoid_safe_count(size: i64, alphabet: &str) -> Result<f64, String> {    let n = self::alphabet(alphabet)?.len() as f64;    let space = (size.clamp(1, 1000) as f64) * n.ln();    // Birthday bound: k ≈ sqrt(2 N ln(1 / (1 - p))), with N = n^size, in logs to stay finite.    let k = (0.5 * (space + (2.0 * (1.0f64 / 0.99).ln()).ln())).exp();    Ok(if k.is_finite() { (k * 1000.0).round() / 1000.0 } else { f64::MAX })}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On Nano ID: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.