Skip to main content

toolkit_zero/
lib.rs

1//! # toolkit-zero
2//!
3//! A feature-selective Rust utility toolkit.  Pull in only what you need via Cargo
4//! feature flags — each feature compiles exactly the modules it requires and nothing
5//! more.
6//!
7//! ---
8//!
9//! ## Table of Contents
10//!
11//! 1. [Feature flags](#feature-flags)
12//! 2. [Serialization](#serialization)
13//! 3. [Socket — server](#socket--server)
14//! 4. [Socket — client](#socket--client)
15//! 5. [Location](#location)
16//! 6. [Backend deps](#backend-deps-1)
17//!
18//! ---
19//!
20//! ## Feature flags
21//!
22//! | Feature | Enables | Exposes |
23//! |---|---||---|
24//! | `serialization` | VEIL cipher (seal / open) | [`serialization`] |
25//! | `socket-server` | VEIL + typed HTTP server builder | [`socket::server`] |
26//! | `socket-client` | VEIL + typed HTTP client builder | [`socket::client`] |
27//! | `socket` | Both `socket-server` and `socket-client` | both |
28//! | `location-native` | Browser-based geolocation | [`location::browser`] |
29//! | `location` | Alias for `location-native` | [`location`] |
30//! | `backend-deps` | Re-exports all third-party deps used by each active module | `*::backend_deps` |
31//!
32//! ```toml
33//! [dependencies]
34//! # Only the VEIL cipher
35//! toolkit-zero = { version = "2", features = ["serialization"] }
36//!
37//! # HTTP server only
38//! toolkit-zero = { version = "2", features = ["socket-server"] }
39//!
40//! # HTTP client only
41//! toolkit-zero = { version = "2", features = ["socket-client"] }
42//!
43//! # Both sides of the socket
44//! toolkit-zero = { version = "2", features = ["socket"] }
45//!
46//! # Geolocation (bundles socket-server automatically)
47//! toolkit-zero = { version = "2", features = ["location"] }
48//!
49//! # Re-export deps alongside socket-server
50//! toolkit-zero = { version = "2", features = ["socket-server", "backend-deps"] }
51//! ```
52//!
53//! ---
54//!
55//! ## Serialization
56//!
57//! The `serialization` feature exposes the **VEIL cipher** — a custom,
58//! key-dependent binary codec that converts any [`bincode`]-encodable value into
59//! an opaque byte sequence and back.
60//!
61//! The two entry points are [`serialization::seal`] and [`serialization::open`].
62//! Every output byte depends on the full message and the key; without the exact
63//! key, the output cannot be inverted.
64//!
65//! ```rust,no_run
66//! use toolkit_zero::serialization::{seal, open, Encode, Decode};
67//!
68//! #[derive(Encode, Decode, Debug, PartialEq)]
69//! struct Point { x: f64, y: f64 }
70//!
71//! let p = Point { x: 1.0, y: -2.0 };
72//! let blob = seal(&p, None).unwrap();                          // default key
73//! let back: Point = open(&blob, None).unwrap();
74//! assert_eq!(p, back);
75//!
76//! let blob2 = seal(&p, Some("my-key")).unwrap();              // custom key
77//! let back2: Point = open(&blob2, Some("my-key")).unwrap();
78//! assert_eq!(p, back2);
79//! ```
80//!
81//! ---
82//!
83//! ## Socket — server
84//!
85//! The `socket-server` feature exposes a fluent builder API for declaring typed
86//! HTTP routes and serving them.  Start every route with [`socket::server::ServerMechanism`],
87//! optionally add a JSON body expectation, URL query parameters, or shared state,
88//! then finalise with `.onconnect(async_handler)`.  Register all routes on a
89//! [`socket::server::Server`] and call `.serve(addr).await`.
90//!
91//! The [`socket::server::reply!`] macro is the primary way to construct responses.
92//!
93//! ```rust,no_run
94//! use toolkit_zero::socket::server::{Server, ServerMechanism, reply, Status};
95//! use serde::{Deserialize, Serialize};
96//! use std::sync::{Arc, Mutex};
97//!
98//! #[derive(Deserialize, Serialize, Clone)]
99//! struct Item { id: u32, name: String }
100//!
101//! #[derive(Deserialize)]
102//! struct NewItem { name: String }
103//!
104//! #[derive(Deserialize)]
105//! struct Filter { page: u32 }
106//!
107//! # async fn run() {
108//! let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
109//!
110//! let mut server = Server::default();
111//! server
112//!     // Plain GET — no body, no state
113//!     .mechanism(
114//!         ServerMechanism::get("/health")
115//!             .onconnect(|| async { reply!() })
116//!     )
117//!     // POST with a JSON body
118//!     .mechanism(
119//!         ServerMechanism::post("/items")
120//!             .json::<NewItem>()
121//!             .onconnect(|body: NewItem| async move {
122//!                 reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
123//!             })
124//!     )
125//!     // GET with shared state
126//!     .mechanism(
127//!         ServerMechanism::get("/items")
128//!             .state(store.clone())
129//!             .onconnect(|state: Arc<Mutex<Vec<Item>>>| async move {
130//!                 let items = state.lock().unwrap().clone();
131//!                 reply!(json => items)
132//!             })
133//!     )
134//!     // GET with URL query parameters
135//!     .mechanism(
136//!         ServerMechanism::get("/items/search")
137//!             .query::<Filter>()
138//!             .onconnect(|f: Filter| async move {
139//!                 let _ = f.page;
140//!                 reply!()
141//!             })
142//!     );
143//!
144//! server.serve(([127, 0, 0, 1], 8080)).await;
145//! # }
146//! ```
147//!
148//! VEIL-encrypted routes are also supported via
149//! [`socket::server::ServerMechanism::encryption`] and
150//! [`socket::server::ServerMechanism::encrypted_query`].
151//! The body or query is decrypted before the handler is called; a wrong key or
152//! corrupt payload returns `403 Forbidden` automatically.
153//!
154//! ---
155//!
156//! ## Socket — client
157//!
158//! The `socket-client` feature exposes a fluent [`socket::client::Client`] for
159//! issuing typed HTTP requests.  Construct a client from a
160//! [`socket::client::Target`] (a `localhost` port or a remote URL), pick an HTTP
161//! method, optionally attach a body or query, and call `.send().await` (async) or
162//! `.send_sync()` (blocking).
163//!
164//! ```rust,no_run
165//! use toolkit_zero::socket::client::{Client, Target};
166//! use serde::{Deserialize, Serialize};
167//!
168//! #[derive(Deserialize, Serialize, Clone)]
169//! struct Item { id: u32, name: String }
170//!
171//! #[derive(Serialize)]
172//! struct NewItem { name: String }
173//!
174//! #[derive(Serialize)]
175//! struct Filter { page: u32 }
176//!
177//! # async fn run() -> Result<(), reqwest::Error> {
178//! // Async-only client — safe inside #[tokio::main]
179//! let client = Client::new_async(Target::Localhost(8080));
180//!
181//! // Plain GET
182//! let items: Vec<Item> = client.get("/items").send().await?;
183//!
184//! // POST with JSON body
185//! let created: Item = client
186//!     .post("/items")
187//!     .json(NewItem { name: "widget".into() })
188//!     .send()
189//!     .await?;
190//!
191//! // GET with query params
192//! let page: Vec<Item> = client
193//!     .get("/items")
194//!     .query(Filter { page: 2 })
195//!     .send()
196//!     .await?;
197//!
198//! // Synchronous DELETE (Client::new_sync must be called outside any async runtime)
199//! let _: Item = client.delete("/items/1").send_sync()?;
200//! # Ok(())
201//! # }
202//! ```
203//!
204//! VEIL-encrypted requests are available via
205//! [`socket::client::RequestBuilder::encryption`] and
206//! [`socket::client::RequestBuilder::encrypted_query`].
207//! The body or query parameters are sealed before the wire send; the response is
208//! opened automatically.
209//!
210//! ---
211//!
212//! ## Location
213//!
214//! The `location` (or `location-native`) feature exposes browser-based geographic
215//! coordinate acquisition.  A temporary local HTTP server is bound on a random
216//! port, the system's default browser is opened to a consent page, and the
217//! standard browser Geolocation API POSTs the coordinates back.  The server shuts
218//! itself down once a result arrives.
219//!
220//! Two entry points are available in [`location::browser`]:
221//!
222//! | Function | Context |
223//! |---|---|
224//! | [`location::browser::__location__`] | Blocking — safe from sync or async |
225//! | [`location::browser::__location_async__`] | Async — preferred inside `#[tokio::main]` |
226//!
227//! ```rust,no_run
228//! use toolkit_zero::location::browser::{__location__, __location_async__, PageTemplate};
229//!
230//! // Blocking — works from sync main or from inside a Tokio runtime
231//! match __location__(PageTemplate::default()) {
232//!     Ok(data) => println!("lat={:.6}  lon={:.6}  ±{:.0}m",
233//!                          data.latitude, data.longitude, data.accuracy),
234//!     Err(e)   => eprintln!("location error: {e}"),
235//! }
236//!
237//! // Async — preferred when already inside #[tokio::main]
238//! # async fn run() {
239//! match __location_async__(PageTemplate::default()).await {
240//!     Ok(data) => println!("lat={:.6}  lon={:.6}", data.latitude, data.longitude),
241//!     Err(e)   => eprintln!("location error: {e}"),
242//! }
243//! # }
244//! ```
245//!
246//! The [`location::browser::PageTemplate`] enum controls what the user sees:
247//! a plain single-button page, a checkbox-gated variant, or a fully custom HTML
248//! document.
249//!
250//! ---
251//!
252//! ## Backend deps
253//!
254//! The `backend-deps` feature adds a `backend_deps` sub-module to every active
255//! module.  Each `backend_deps` module re-exports with `pub use` every
256//! third-party crate that its parent module uses internally.
257//!
258//! This lets downstream crates access those dependencies without declaring them
259//! separately in their own `Cargo.toml`.
260//!
261//! | Module | Path | Re-exports |
262//! |---|---|---|
263//! | serialization | [`serialization::backend_deps`] | `bincode`, `base64` |
264//! | socket (server) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `bytes`, `serde_urlencoded`, `warp` |
265//! | socket (client) | [`socket::backend_deps`] | `bincode`, `base64`, `serde`, `tokio`, `log`, `reqwest` |
266//! | location | [`location::backend_deps`] | `tokio`, `serde`, `webbrowser` |
267//!
268//! `backend-deps` on its own (without any other feature) compiles but exposes
269//! nothing — the re-exports inside each `backend_deps` module are individually
270//! gated on their parent feature.
271
272#[cfg(any(feature = "socket", feature = "socket-server", feature = "socket-client"))]
273pub mod socket;
274
275#[cfg(any(feature = "location", feature = "location-native"))]
276pub mod location;
277
278#[cfg(feature = "serialization")]
279pub mod serialization;