rok-utils — Comprehensive Implementation Plan
A Laravel/AdonisJS-Inspired Utility Crate for Rust
Version: 0.1.0-plan
Edition: Rust 2021
Inspiration: Laravel 13.x Strings & Helpers, AdonisJS v6 Helpers & Exceptions
Table of Contents
- Overview & Philosophy
- Crate Configuration —
Cargo.toml - Module Structure
- String Utilities —
str.rs - Array / Collection Utilities —
arr.rs - Error Handling —
errors.rs - Data Utilities —
data.rs - Functional Patterns —
fp.rs - Fluent Builder API —
fluent.rs - Type Helpers —
types.rs - Testing Strategy
- CI/CD Pipeline
- Milestone Roadmap
1. Overview & Philosophy
rok-utils is a zero-bloat, modular utility crate for the Rok ecosystem. It follows the same
design philosophy as Laravel's Illuminate\Support\Str and AdonisJS's @adonisjs/core/helpers:
utilities are ergonomic, predictable, and composable.
Design Principles
- Fluent API first — chainable builder pattern mirroring Laravel's
Str::of()/ AdonisJS'sstring.camelCase().truncate()pipeline - No runtime panics — all functions return
Result<T, RokError>orOption<T>; neverunwrap()in library code - Feature-flagged — heavy dependencies (crypto, chrono, uuid) are opt-in via Cargo features
- UTF-8 native — all string operations use
chars(), never raw bytes, mirroring AdonisJS's unicode-safeescapeHTML/encodeSymbolsapproach < 400 linesper file — every module is a single focused concern; no "god files"- Fully tested — unit tests +
proptestfor invariant testing + doctests as living documentation
Comparison Table
| Feature | Laravel (Str::) |
AdonisJS (string.) |
rok-utils |
|---|---|---|---|
| Case conversion | snake(), camel() |
snakeCase(), camelCase() |
to_snake_case() etc. |
| Fluent chaining | Str::of('x')->slug() |
N/A | Str::of("x").slug() |
| Slug generation | Str::slug() |
string.slug() |
str::slug() |
| Pluralize | Str::plural() |
string.plural() |
str::plural() (feature) |
| Error types | HttpException |
E_HTTP_EXCEPTION |
RokError enum |
| Byte/time parsing | N/A | string.bytes.parse('1MB') |
parse_bytes() etc. |
| Random string | Str::random(32) |
string.random(32) |
str::random(32) |
2. Crate Configuration — Cargo.toml
[]
= "rok-utils"
= "0.1.0"
= "2021"
= "Laravel/AdonisJS-inspired utility helpers for the Rok ecosystem"
= "MIT"
= "https://github.com/ateeq1999/rok-utils"
= ["utilities", "string", "helpers", "rok"]
= ["text-processing", "data-structures"]
# ── Core (always available) ──────────────────────────────────────────
[]
= "1.0" # Derive-based error types
= "1.19" # Lazy statics / memoization
# ── Optional features ────────────────────────────────────────────────
= { = "0.4", = true } # Date/time
= { = "1.0", = true, = ["derive"] }
= { = "1.0", = true }
= { = "1.6", = true, = ["v4", "v7"] }
= { = "0.10", = true } # Hashing
= { = "0.8", = true } # Random strings
= { = "1.10", = true }
# ── Dev / Test ────────────────────────────────────────────────────────
[]
= "1.4"
= { = "0.5", = ["html_reports"] }
# ── Feature flags ────────────────────────────────────────────────────
[]
= []
= ["dates", "crypto", "json", "ids", "random", "unicode"]
= ["dep:chrono"]
= ["dep:sha2"]
= ["dep:serde", "dep:serde_json"]
= ["dep:uuid"]
= ["dep:rand"]
= ["dep:unicode-segmentation"]
[[]]
= "string_bench"
= false
Feature Flag Usage
// Consumer opts in to only what they need:
// rok-utils = { version = "0.1", features = ["dates", "crypto"] }
pub use crate*;
pub use crate*;
3. Module Structure
rok-utils/
├── Cargo.toml
├── src/
│ ├── lib.rs ← Public re-exports, feature gates
│ ├── str/
│ │ ├── mod.rs ← str::* top-level re-exports
│ │ ├── case.rs ← Case conversions (snake, camel, pascal…)
│ │ ├── transform.rs ← slug, truncate, excerpt, squish, wrap…
│ │ ├── inspect.rs ← is_empty, starts_with, contains, word_count…
│ │ ├── fluent.rs ← Str::of() builder (chainable API)
│ │ └── random.rs ← random() — requires "random" feature
│ ├── arr/
│ │ ├── mod.rs
│ │ ├── ops.rs ← map, filter, reduce, chunk, flatten…
│ │ ├── query.rs ← first, last, find, where_in, pluck…
│ │ └── set.rs ← unique, diff, intersect, merge…
│ ├── errors/
│ │ ├── mod.rs
│ │ ├── kinds.rs ← RokError enum (all variants)
│ │ ├── context.rs ← wrap_error, add_context, ResultExt trait
│ │ └── http.rs ← HttpError with status codes (AdonisJS-style)
│ ├── data/
│ │ ├── mod.rs
│ │ ├── numbers.rs ← format_number, format_currency, round…
│ │ ├── dates.rs ← now, today, format, diff (needs "dates")
│ │ ├── hashing.rs ← hash, verify, generate_token (needs "crypto")
│ │ └── ids.rs ← uuid_v4, uuid_v7, ulid (needs "ids")
│ ├── fp/
│ │ ├── mod.rs
│ │ ├── compose.rs ← pipe, compose, partial, tap
│ │ ├── lazy.rs ← Lazy<T>, memoize, once
│ │ └── macros.rs ← macro_rules! helpers
│ └── types/
│ ├── mod.rs
│ └── guards.rs ← is_json, is_uuid, is_ulid, is_url…
├── tests/
│ ├── str_tests.rs
│ ├── arr_tests.rs
│ ├── error_tests.rs
│ └── proptest_suite.rs
└── benches/
└── string_bench.rs
4. String Utilities — str.rs
Modeled after Laravel's Illuminate\Support\Str and AdonisJS's string helper module.
All functions are free functions in rok_utils::str and also available on the fluent Str builder.
4.1 Case Conversion — str/case.rs
Mirrors AdonisJS camelCase, snakeCase, pascalCase, dashCase, dotCase, noCase, sentenceCase, titleCase, and Laravel Str::camel(), Str::snake(), Str::studly(), Str::title(), Str::headline().
Implementation Notes
// to_snake_case: handles "TestV2" → "test_v2", "XMLParser" → "xml_parser"
// Uses char-by-char state machine, never regex, to stay no_std-compatible.
Case Conversion Examples (AdonisJS reference table, adapted for Rust):
| Input | snake |
camel |
pascal |
kebab |
|---|---|---|---|---|
"test string" |
test_string |
testString |
TestString |
test-string |
"TestV2" |
test_v2 |
testV2 |
TestV2 |
test-v2 |
"XMLParser" |
xml_parser |
xmlParser |
XmlParser |
xml-parser |
"version 1.2" |
version_1_2 |
version12 |
Version12 |
version-12 |
4.2 Transform Functions — str/transform.rs
/// Laravel: Str::slug() / AdonisJS: string.slug()
4.3 Inspection Functions — str/inspect.rs
4.4 Random Strings — str/random.rs (feature = "random")
/// Cryptographically secure random string, URL-safe base64
/// Laravel: Str::random() / AdonisJS: string.random()
///
/// string::random(32) → "8mejfWWbXbry8Rh7u8MW3o-6dxd80Thk"
4.5 Pluralization — str/plural.rs (feature = "unicode")
/// Laravel: Str::plural() / Str::singular()
/// AdonisJS: string.plural(), string.singular(), string.pluralize()
4.6 Fluent Builder — str/fluent.rs
Mirrors Laravel's Str::of() — every method returns Self for chaining.
Unlike Laravel's PHP fluent strings, this uses Rust's move semantics and zero-copy slices where possible.
Usage:
use Str;
let result = of
.trim
.to_snake_case
.slug
.truncate
.when_empty
.value;
// "hello-world"
let banner = of
.title
.wrap
.value;
// "=== Welcome To Rok ==="
5. Array / Collection Utilities — arr.rs
Inspired by Laravel's Collection API and AdonisJS array helpers. All functions are generic
over T and use Rust's Iterator trait internally.
5.1 Transformation — arr/ops.rs
/// Map over a slice, collecting into Vec<U>
5.2 Query — arr/query.rs
5.3 Set Operations — arr/set.rs
/// Remove duplicates (preserves order) — Laravel/AdonisJS: unique()
6. Error Handling — errors.rs
Inspired by AdonisJS's typed exception system (E_ROUTE_NOT_FOUND, E_UNAUTHORIZED_ACCESS,
E_HTTP_EXCEPTION, etc.) and Laravel's HttpException. Every error variant carries a
machine-readable code string matching the AdonisJS convention.
6.1 Core Error Enum — errors/kinds.rs
use Error;
/// All rok-utils errors. Each variant maps to an AdonisJS-style error code.
6.2 Context & Wrapping — errors/context.rs
/// Add context to any error (AdonisJS: error.help / error.cause pattern)
Usage:
use ;
// Error codes match AdonisJS convention:
let err = NotFound;
assert_eq!;
assert_eq!;
assert!;
6.3 Error Macros — errors/macros.rs
/// Quickly build a validation error
/// Bail early from a function with a RokError
7. Data Utilities — data.rs
7.1 Numbers — data/numbers.rs
/// Format with thousand separators: 1_234_567.89 → "1,234,567.89"
7.2 Dates — data/dates.rs (feature = "dates")
use ;
7.3 IDs — data/ids.rs (feature = "ids")
/// UUID v4 (random) — Laravel: Str::uuid()
7.4 Hashing — data/hashing.rs (feature = "crypto")
/// SHA-256 hex digest
8. Functional Patterns — fp.rs
Inspired by Laravel's pipeline, AdonisJS middleware chaining, and standard functional programming utilities.
8.1 Core Combinators — fp/compose.rs
/// Thread a value through a sequence of transformations
/// Mirrors Laravel's pipeline / AdonisJS middleware chain
8.2 Lazy & Memoization — fp/lazy.rs
use OnceCell;
/// Lazily initialized value — computed only on first access
/// Memoize a single-argument function using a HashMap cache
8.3 Helper Macros — fp/macros.rs
/// Build a Vec of boxed closures for use with pipe()
/// Unwrap or return Err with a RokError variant
/// Conditional expression returning a value
9. Fluent Builder API — fluent.rs
The top-level Str builder (see §4.6) is the primary ergonomic API, but rok-utils also provides
collection-level fluency via Collect<T>:
10. Type Helpers — types.rs
AdonisJS ships @adonisjs/core/helpers/is for runtime type guards. We mirror this:
11. Testing Strategy
11.1 Unit Tests (inline per module)
Every public function has at least:
- A happy-path test
- An edge case (empty string, zero, negative numbers, Unicode input)
- A
#[should_panic]orErrassertion where appropriate
11.2 Property-Based Tests (proptest)
use *;
proptest!
11.3 Benchmarks (Criterion)
// benches/string_bench.rs
use ;
use *;
criterion_group!;
criterion_main!;
11.4 Doctests as Living Docs
Every public function has a /// # Examples block that doubles as a doctest:
/// Convert a string to snake_case.
///
/// # Examples
///
/// ```rust
/// use rok_utils::str::to_snake_case;
///
/// assert_eq!(to_snake_case("HelloWorld"), "hello_world");
/// assert_eq!(to_snake_case("XMLParser"), "xml_parser");
/// assert_eq!(to_snake_case(""), "");
/// ```
12. CI/CD Pipeline
# .github/workflows/ci.yml
name: CI
on:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Format check
run: cargo fmt --all -- --check
- name: Clippy (no warnings allowed)
run: cargo clippy --all-features -- -D warnings
- name: Tests (all features)
run: cargo test --all-features
- name: Tests (no default features)
run: cargo test --no-default-features
- name: Doctests
run: cargo test --doc --all-features
- name: Proptest suite
run: cargo test --test proptest_suite --all-features
bench:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo bench --all-features
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo install cargo-tarpaulin
- run: cargo tarpaulin --all-features --out Xml
- uses: codecov/codecov-action@v3
13. Milestone Roadmap
Milestone 1 — Foundation (Publishing 0.1.0)
Goal: Core string, array, and error modules are complete and published to crates.io.
-
str/case.rs— all 12 case conversion functions with doctests -
str/transform.rs— 25+ transform functions (slug, truncate, excerpt, squish, wrap…) -
str/inspect.rs— 15+ inspection functions -
str/fluent.rs—Str::of()builder with 30+ chainable methods andwhen*conditionals -
arr/ops.rs,arr/query.rs,arr/set.rs— full collection API -
errors/kinds.rs—RokErrorenum with AdonisJS-style codes + HTTP status -
errors/context.rs—ResultExttrait andwrap_error -
fp/compose.rs—pipe,tap,compose,retry - CI green: fmt + clippy + tests + doctests
- Code coverage ≥ 90%
- All files ≤ 400 lines
Milestone 2 — Feature Modules (0.2.0)
Goal: Optional modules enabled via feature flags are ready for production.
-
data/numbers.rs—format_number,format_currency,format_percentage -
data/dates.rs— chrono-backed date helpers (feature = "dates") -
data/ids.rs— UUID v4/v7, ULID (feature = "ids") -
data/hashing.rs— SHA-256, tokens (feature = "crypto") -
str/random.rs— cryptographically securerandom()andpassword()(feature = "random") -
str/plural.rs— pluralize, singular, is_plural (feature = "unicode") -
fp/lazy.rs—Lazy<T>,memoize,once -
types/guards.rs— JSON type guards and dot-path access
Milestone 3 — CLI Integration (rok-cli uses rok-utils)
Goal: Replace all ad-hoc string handling in rok-cli with rok-utils.
- Replace internal case conversion with
rok_utils::str::to_snake_caseetc. - Use
Str::of()fluent builder in code generation templates - Wire
RokErrorinto CLI error reporting pipeline -
rok generate model <Name>usesto_pascal_case+to_snake_caseconsistently - Benchmark: no regression vs. previous hand-rolled string code
Milestone 4 — SQLx / ORM Integration
Goal: rok-orm macro system uses rok-utils for identifier generation.
-
model!macro usesto_snake_casefor table names -
migrate!usesuuid_v7for migration timestamps -
query!builder usesCollect<T>for result mapping - Error propagation: database errors wrap to
RokError::Internal
Milestone 5 — Docs Site & Stable API (1.0.0)
Goal: Public API is frozen and fully documented.
-
cargo doc --all-features --no-depsgenerates complete API reference - Deploy to GitHub Pages via CI
- Add
#[non_exhaustive]toRokErrorenum - Write migration guide from
0.xto1.0 - All public types implement
Debug + Clone + Send + Syncwhere applicable - Minimum Supported Rust Version (MSRV) pinned to stable - 2 releases
Summary
This plan delivers a rok-utils crate that:
- Mirrors battle-tested APIs — Laravel's
Strand AdonisJS'sstringhelpers are the benchmark; every function name and behavior maps to a known analogue - Ergonomic by default — the fluent
Str::of()builder makes complex transformations readable and composable without intermediate variables - Error-first design — AdonisJS-style typed error codes (
E_NOT_FOUND,E_VALIDATION_FAILURE) with HTTP status semantics baked in from day one - Zero-bloat — feature flags ensure consumers pay only for what they use
- Production-ready — proptest invariants, criterion benchmarks, and ≥90% coverage before any milestone ships