toolkit-zero
A feature-selective Rust utility toolkit. Pull in only the modules you need via Cargo feature flags — each feature compiles exactly what it requires and nothing more.
Table of Contents
Overview
toolkit-zero is designed to be zero-waste: you declare only the features you want and cargo compiles only what those features require. There is no "kitchen sink" import.
Feature flags
| Feature | What it enables | Module exposed |
|---|---|---|
serialization |
VEIL cipher — seal any struct to opaque bytes and back | toolkit_zero::serialization |
socket-server |
Typed HTTP server builder (includes serialization) |
toolkit_zero::socket::server |
socket-client |
Typed HTTP client builder (includes serialization) |
toolkit_zero::socket::client |
socket |
Both socket-server and socket-client |
both socket sub-modules |
location-native |
Browser-based geolocation (includes socket-server) |
toolkit_zero::location::browser |
location |
Alias for location-native |
toolkit_zero::location |
backend-deps |
Re-exports all third-party deps used by each active module | *::backend_deps |
Add to Cargo.toml:
[]
# VEIL cipher only
= { = "2", = ["serialization"] }
# HTTP server only
= { = "2", = ["socket-server"] }
# HTTP client only
= { = "2", = ["socket-client"] }
# Both sides
= { = "2", = ["socket"] }
# Geolocation (pulls in socket-server automatically)
= { = "2", = ["location"] }
# Re-export deps alongside socket-server
= { = "2", = ["socket-server", "backend-deps"] }
Serialization
Feature: serialization
The VEIL cipher converts any bincode-encodable struct into an opaque, key-dependent byte blob. The output has no recognisable structure and every output byte depends on the full input and the key. Without the exact key the bytes cannot be inverted.
Entry points:
| Function | Direction |
|---|---|
toolkit_zero::serialization::seal(&value, key) |
struct → Vec<u8> |
toolkit_zero::serialization::open::<T>(&bytes, key) |
Vec<u8> → struct |
key is Option<&str>. Pass None to use the built-in default key.
Your types must derive Encode and Decode:
use ;
// With the default key
let cfg = Config ;
let blob = seal.unwrap;
let back: Config = open.unwrap;
assert_eq!;
// With a custom shared key
let blob2 = seal.unwrap;
let back2: Config = open.unwrap;
assert_eq!;
Socket — server
Feature: socket-server
A fluent builder API for declaring typed HTTP routes and serving them. Every route starts from ServerMechanism, is enriched with optional body / query / state expectations, and is finalised with .onconnect(async_handler). Finalised routes are registered on a Server, which is then served with a single .await.
Plain routes
No body and no query. The handler receives nothing.
use ;
let mut server = default;
server.mechanism;
All seven HTTP methods are available: get, post, put, delete, patch, head, options.
JSON body routes
Call .json::<T>() on the mechanism. The JSON body is deserialised before the handler runs; the handler receives a ready-to-use T. T must implement serde::Deserialize. A missing or malformed body returns 400 Bad Request automatically.
use Deserialize;
use ;
let mut server = default;
server.mechanism;
Query parameter routes
Call .query::<T>() on the mechanism. The URL query string is deserialised before the handler runs; the handler receives a ready-to-use T. T must implement serde::Deserialize.
use Deserialize;
use ;
let mut server = default;
server.mechanism;
Shared state
Call .state(value) on the mechanism. A fresh clone of the state is injected into every request. The state must be Clone + Send + Sync + 'static. Wrap mutable state in Arc<Mutex<_>> or Arc<RwLock<_>>.
use ;
use Serialize;
use ;
let store: = new;
let mut server = default;
server.mechanism;
Combining state with body / query
State and a body (or query) can be combined. The order of .state() and .json() / .query() does not matter. The handler receives (state: S, body_or_query: T).
use ;
use ;
use ;
let store: = new;
let mut server = default;
server.mechanism;
VEIL-encrypted routes
Call .encryption::<T>(key) (body) or .encrypted_query::<T>(key) (query) on the mechanism. Provide a SerializationKey::Default (built-in key) or SerializationKey::Value("your-key") (custom key).
Before the handler is called, the body or query is VEIL-decrypted using the supplied key. A wrong key, mismatched secret, or corrupt payload returns 403 Forbidden without ever reaching the handler. The T the closure receives is always a trusted, fully-decrypted value.
T must implement bincode::Decode<()>.
use ;
use SerializationKey;
use ;
let mut server = default;
server.mechanism;
For encrypted query parameters, the client sends ?data=<base64url> where the value is URL-safe base64 of the VEIL-sealed struct bytes.
Serving the server
// Bind to a specific address — runs until the process exits
server.serve.await;
Graceful shutdown
use oneshot;
let = ;
// Shut down later by calling: tx.send(()).ok();
server.serve_with_graceful_shutdown.await;
To use an OS-assigned port (e.g. to know the port before the server starts):
use TcpListener;
use oneshot;
let listener = bind.await?;
let port = listener.local_addr?.port;
let = ;
server.serve_from_listener.await;
Building responses
Use the reply! macro:
| Expression | Result |
|---|---|
reply!() |
200 OK with empty body |
reply!(json => value) |
200 OK with JSON-serialised body |
reply!(json => value, status => Status::Created) |
201 Created with JSON body |
reply!(message => warp::reply(), status => Status::NoContent) |
custom status on any reply |
reply!(sealed => value) |
200 OK with VEIL-sealed body (application/octet-stream) |
reply!(sealed => value, key => SerializationKey::Value("k")) |
sealed with explicit key |
Status re-exports the most common HTTP status codes as named variants (Status::Ok, Status::Created, Status::NoContent, Status::BadRequest, Status::Forbidden, Status::NotFound, Status::InternalServerError).
Socket — client
Feature: socket-client
A fluent builder API for issuing typed HTTP requests. Construct a Client from a Target, pick an HTTP method, optionally attach a body or query parameters, and call .send().await or .send_sync().
Creating a client
use ;
// Async-only — safe to create inside #[tokio::main]
let client = new_async;
// Sync-only — must be created before entering any async runtime
let client = new_sync;
// Both async and blocking — must be created before entering any async runtime
let client = new;
// Remote target
let client = new_async;
Note:
Client::new()andClient::new_sync()panic if called from within an existing Tokio runtime (e.g. inside#[tokio::main]). UseClient::new_async()when your program is async-first.
Plain requests
use Deserialize;
// Async
let item: Item = client.get.send.await?;
// Sync
let item: Item = client.get.send_sync?;
All seven HTTP methods are available: get, post, put, delete, patch, head, options.
JSON body requests
Attach a body with .json(value). value must implement serde::Serialize. The response is deserialised as R: serde::Deserialize.
use ;
let created: Item = client
.post
.json
.send
.await?;
Query parameter requests
Attach query parameters with .query(value). value must implement serde::Serialize. Fields are appended to the URL as ?field=value&....
use ;
let items: = client
.get
.query
.send
.await?;
VEIL-encrypted requests
Attach a VEIL-sealed body with .encryption(value, key). The body is sealed before the wire send and the response bytes are opened automatically. Both value (request) and R (response) use bincode — value: T must implement bincode::Encode, R must implement bincode::Decode<()>.
use ;
use SerializationKey;
use ClientError;
let resp: Resp = client
.post
.encryption
.send
.await?;
For encrypted query parameters, use .encrypted_query(value, key). The params are sealed and sent as ?data=<base64url>.
let resp: Resp = client
.get
.encrypted_query
.send
.await?;
Both .send() and .send_sync() are available on encrypted builders, returning Result<R, ClientError>.
Sync vs async sends
| Method | Blocks | Requires |
|---|---|---|
.send().await |
No | Client::new_async() or Client::new() |
.send_sync() |
Yes | Client::new_sync() or Client::new() |
Location
Feature: location (or location-native)
Acquires the device's geographic coordinates by opening the system's default browser to a locally served consent page. The browser prompts the user for location permission via the standard Web Geolocation API. On success, the coordinates are POSTed back to the local server, which shuts itself down and returns the result to the caller.
No external service is contacted. Everything happens on 127.0.0.1.
Blocking usage
Works from synchronous main and from inside an async Tokio runtime. When called inside an existing runtime, an OS thread is spawned to avoid nesting runtimes.
use ;
match __location__
Async usage
Preferred when already inside a Tokio async context — avoids the extra OS thread spawn.
use ;
async
Page templates
PageTemplate controls what the user sees in the browser.
| Variant | Description |
|---|---|
PageTemplate::Default { title, body_text } |
Clean single-button consent page. Both fields are Option<String> and fall back to built-in text when None. |
PageTemplate::Tickbox { title, body_text, consent_text } |
Same as Default but adds a checkbox the user must tick before the button activates. |
PageTemplate::Custom(html) |
Fully custom HTML string. Place exactly one {} where the capture button should appear; the required JavaScript is injected automatically. |
use ;
// Custom title only
let _data = __location__;
// Tick-box consent
let _data = __location__;
// Fully custom HTML
let html = r#"<!DOCTYPE html>
<html><body>
<h1>Grant access</h1>
{}
</body></html>"#;
let _data = __location__;
LocationData fields
| Field | Type | Description |
|---|---|---|
latitude |
f64 |
Decimal degrees (WGS 84) |
longitude |
f64 |
Decimal degrees (WGS 84) |
accuracy |
f64 |
Horizontal accuracy in metres (95 % confidence) |
altitude |
Option<f64> |
Metres above WGS 84 ellipsoid, if available |
altitude_accuracy |
Option<f64> |
Accuracy of altitude in metres, if available |
heading |
Option<f64> |
Degrees clockwise from true north [0, 360), or None if stationary |
speed |
Option<f64> |
Ground speed in m/s, or None if unavailable |
timestamp_ms |
f64 |
Browser Unix timestamp in milliseconds |
LocationError variants
| Variant | Cause |
|---|---|
PermissionDenied |
User denied the browser's location permission prompt |
PositionUnavailable |
Device cannot determine its position |
Timeout |
No fix within the browser's built-in 30 s timeout |
ServerError |
Failed to start the local HTTP server or Tokio runtime |
Backend deps
Feature: backend-deps
When combined with any other feature, backend-deps adds a backend_deps sub-module to every active module. Each backend_deps module re-exports (via pub use) every third-party crate that its parent uses internally.
This lets downstream crates access those dependencies without declaring them separately in their own Cargo.toml.
| Module | Path | Re-exports |
|---|---|---|
serialization |
toolkit_zero::serialization::backend_deps |
bincode, base64 |
socket (server side) |
toolkit_zero::socket::backend_deps |
bincode, base64, serde, tokio, log, bytes, serde_urlencoded, warp |
socket (client side) |
toolkit_zero::socket::backend_deps |
bincode, base64, serde, tokio, log, reqwest |
location |
toolkit_zero::location::backend_deps |
tokio, serde, webbrowser |
Each re-export inside backend_deps is individually gated on its parent feature, so only the deps that are actually compiled appear. Enabling backend-deps alone (without any other feature) compiles cleanly but exposes nothing.
# Example: socket-server + dep re-exports
= { = "2", = ["socket-server", "backend-deps"] }
Then in your code:
// Access warp directly through toolkit-zero
use warp;
// Access bincode through serialization
use bincode;
License
MIT — see LICENSE.