bento-kit
A bento box of common Rust utilities, packaged as one crate with cargo
features. This is the Rust port of du-node-utils;
the public API tracks the Node version function-for-function so projects
in either ecosystem can speak the same vocabulary (same UID format, same
session-ID layout, same masking semantics).
Design
- One crate, many modules. Add a single dependency, opt into modules via cargo features. No workspace sprawl.
- Node parity first. Every Node function from
du-node-utilshas a Rust counterpart with matching behavior. Rust-specific extensions (UUID v7, nanoid) are clearly labeled. - Idiomatic Rust on top. Module paths and naming follow Rust
conventions (snake_case,
Option<&str>for optional args,Resultfor fallible ops). The behavior is identical; only the spelling changes.
Modules
| feature | what you get | default |
|---|---|---|
id |
UUID v4/v7, nanoid, session/user/db-key IDs | yes |
time |
Multi-tag profiler (TimeUse) + timestamp formatting |
yes |
mask |
Sensitive-data masking (phone, email, token, secret …) | yes |
full |
Alias for all of the above | no |
Install
# default features (id + time + mask)
= "0.1"
# only what you need
= { = "0.1", = false, = ["id"] }
use ;
Node-to-Rust API map
The du-node-utils lib exports JS functions in camelCase; their Rust
equivalents are listed below. Import paths are relative to the crate root.
id module
| Node | Rust |
|---|---|
id.makeUUID(false) |
[id::uuid_v4] |
id.makeUUID(true) |
[id::uuid_v4_simple] |
id.randomInt(min, max) |
[id::random_int] |
id.randomString(len) |
[id::random_string] |
id.makeUidPostfix() |
[id::make_uid_postfix] |
id.makeUserId(appid, uid, cuid) |
[id::make_user_id] |
id.parseUserid(s) |
[id::parse_user_id] → Option<UserId> |
id.makeDbKey(uid) |
[id::make_db_key] |
id.setSessionPrefix(p) |
[id::set_session_prefix] |
id.generateSessionId() |
[id::generate_session_id] |
| — | [id::uuid_v7] / [id::uuid_v7_simple] |
| — | [id::nanoid] / [id::nanoid_with_len] / [id::nanoid_with_alphabet] |
time module (Node lib/TimeUse.ts → bento_kit::time::TimeUse)
Node XTimeUse method |
Rust TimeUse method |
|---|---|
new XTimeUse() |
[time::TimeUse::new] |
start(tag?) |
start(tag: Option<&str>) |
stop(tag?) |
stop(tag: Option<&str>) -> Duration (idempotent) |
restart(tag?) |
restart(tag: Option<&str>) -> Duration |
elapsed(tag?) |
elapsed(tag: Option<&str>) -> Duration (peek) |
get(tag?) |
intentionally not ported — see below |
Plus timestamp helpers not in the Node lib: [time::now_millis],
[time::now_seconds], [time::now_micros], [time::format_now_utc],
[time::format_now_local], [time::format_timestamp_utc],
[time::format_timestamp_local].
get(tag?) is intentionally omitted. The Node version returns a Unix
millisecond timestamp; in Rust the underlying Instant is monotonic
and not directly convertible to wall-clock without surrendering its
correctness guarantees. Use [time::now_millis] if a wall-clock
timestamp is what you actually need.
mask module (Node lib/MaskSecret.ts + du-node-utils per-field helpers)
| Node | Rust |
|---|---|
maskSecret(value, keepChars=3) |
[mask::mask_secret](value: Option<&str>, keep_chars: usize) |
| (Rust addition) | [mask::mask_phone] / [mask::mask_email] / [mask::mask_id_card] / [mask::mask_bank_card] / [mask::mask_token] / [mask::mask_name] / [mask::mask_middle] |
Quick tour
id
use id;
// UUIDs
let v4 = uuid_v4; // 36 chars, hyphenated
let v4s = uuid_v4_simple; // 32 hex chars, no hyphens
let v7 = uuid_v7; // time-ordered, monotonic per ms
// Random helpers (not crypto-secure — same trade-off as Node's Math.random)
let n = random_int; // half-open [0, 100)
let s = random_string; // base36 keyboard-order alphabet
// Session and user IDs (Node-compatible format)
set_session_prefix;
let sid = generate_session_id; // "myapp1735088400123x9y8z"
let user = make_user_id;
// "connect.app1.user42.client99"
let parsed = parse_user_id.unwrap;
assert_eq!;
let db = make_db_key; // "connect.user42"
let post = make_uid_postfix; // hex of (ms*1000 + rand)
// Rust-side conveniences (no Node equivalent)
let n1 = nanoid; // 21-char URL-safe
let n2 = nanoid_with_len;
time
TimeUse is a multi-stage profiler. Every method takes
tag: Option<&str> — None operates on the global timer started at
TimeUse::new, Some("foo") on a per-tag span tracked in a HashMap.
use TimeUse;
let mut t = new;
t.start;
// ... establish connection ...
let connect = t.stop;
t.start;
// ... run query ...
let query = t.stop;
let total = t.stop;
Two semantic points worth knowing:
- Idempotent stop. Once a span has been stopped, subsequent
stop/elapsedcalls return the cached duration. You won't accidentally double-charge a region. - Tag fallback.
stop(Some("foo"))on a tag that was never started uses the global start as the origin, matching the Node behavior.
Timestamp helpers:
use ;
let ms = now_millis;
let iso = format_now_utc;
let s = format_timestamp_utc.unwrap;
mask
use mask;
mask_phone; // "138****5678"
mask_email; // "a***@example.com"
mask_id_card; // "110101********1234"
mask_bank_card; // "6225********7890"
mask_token; // "sk-1***********cdef"
mask_name; // "张*丰"
mask_middle; // "cu*********ng"
// Direct port of du-node-utils.maskSecret:
mask_secret; // "vau***XYZ"
mask_secret; // "ab" (too short to mask)
mask_secret; // "<none>"
mask_secret; // "<empty>"
mask_secret mirrors the Node version exactly: when the input is long
enough, it always uses three stars as the divider regardless of the
masked length, and returns sentinel strings for None / "".
Examples
Quality
CI runs the full test matrix on Linux, macOS, and Windows against both stable Rust and the declared MSRV (1.74), plus a feature matrix that exercises each feature in isolation.
MSRV
Rust 1.74. Bumping the MSRV is treated as a minor version change.
Releasing
Releases are tag-driven. Pushing a tag of the form v1.2.3 triggers
the release workflow, which:
- Validates the tag is a valid semver string.
- Stamps
1.2.3intoCargo.tomlviacargo set-version. - Runs the full test suite.
- Calls
cargo publishwith theCARGO_REGISTRY_TOKENsecret.
You do not need to manually bump the version in Cargo.toml before
tagging — the local value is a development placeholder.
License
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.