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