toolkit_zero/socket/mod.rs
1//! HTTP server and client utilities, selectively compiled via Cargo features.
2//!
3//! This module exposes two sub-modules behind feature flags:
4//!
5//! | Sub-module | Feature flag(s) |
6//! |---|---|
7//! | [`server`] | `socket` or `socket-server` |
8//! | [`client`] | `socket` or `socket-client` |
9//!
10//! Enable `socket` to get both, or opt into only one side with `socket-server` / `socket-client`.
11//!
12//! # Server
13//!
14//! Build typed HTTP routes using a fluent builder chain starting from [`server::ServerMechanism`].
15//! Routes are registered on a [`server::Server`] and served with a single `.await`.
16//!
17//! The chain supports:
18//! - **No extras** — handler receives no arguments
19//! - **JSON body** — via `.json::<T>()`, handler receives `T: DeserializeOwned`
20//! - **Query params** — via `.query::<T>()`, handler receives `T: DeserializeOwned`
21//! - **VEIL-encrypted body** — via `.encryption::<T>(key)`, body is decrypted before delivery
22//! - **VEIL-encrypted query** — via `.encrypted_query::<T>(key)`, query is decrypted before delivery
23//! - **Shared state** — via `.state(s)`, handler receives a clone of `S`
24//! - **State + body / State + query** — any of the above combined with `.state(s)`
25//!
26//! Each branch finalises with `.onconnect(async handler)` or the unsafe `.onconnect_sync(sync handler)`.
27//! Use the [`reply!`] macro (or standalone helpers) to construct the response.
28//!
29//! The [`server::mechanism`] attribute macro is an ergonomic shorthand for the full builder call:
30//! `#[mechanism(server, POST, "/items", json)]` on an `async fn` is exactly equivalent to
31//! writing `server.mechanism(ServerMechanism::post("/items").json::<T>().onconnect(handler))`.
32//!
33//! ```rust,no_run
34//! # use toolkit_zero::socket::server::*;
35//! # use serde::{Deserialize, Serialize};
36//! # #[derive(Deserialize, Serialize)] struct Item { id: u32, name: String }
37//! # #[derive(Deserialize)] struct NewItem { name: String }
38//! # #[derive(Deserialize)] struct Filter { page: u32 }
39//! # use std::sync::{Arc, Mutex};
40//!
41//! let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
42//!
43//! let mut server = Server::default();
44//! server
45//! .mechanism(
46//! ServerMechanism::get("/health")
47//! .onconnect(|| async { reply!() })
48//! )
49//! .mechanism(
50//! ServerMechanism::post("/items")
51//! .json::<NewItem>()
52//! .onconnect(|body| async move {
53//! let item = Item { id: 1, name: body.name };
54//! reply!(json => item, status => Status::Created)
55//! })
56//! )
57//! .mechanism(
58//! ServerMechanism::get("/items")
59//! .state(store.clone())
60//! .onconnect(|state| async move {
61//! let items = state.lock().unwrap().clone();
62//! reply!(json => items)
63//! })
64//! )
65//! .mechanism(
66//! ServerMechanism::get("/items/search")
67//! .query::<Filter>()
68//! .onconnect(|filter| async move {
69//! let _ = filter.page;
70//! reply!()
71//! })
72//! );
73//!
74//! // server.serve(([0, 0, 0, 0], 8080)).await;
75//! ```
76//!
77//! # Client
78//!
79//! Make typed HTTP requests using a fluent builder chain starting from [`client::Client`].
80//! The full URL is constructed automatically from a [`client::Target`] (localhost port or remote URL)
81//! and the endpoint string. Both async (`.send()`) and sync (`.send_sync()`) are supported.
82//!
83//! The chain supports:
84//! - **Plain** — no body, no query
85//! - **JSON body** — via `.json(value)`, serialises with `serde`
86//! - **Query params** — via `.query(value)`, serialised into the URL query string
87//!
88//! All seven HTTP methods are available: `get`, `post`, `put`, `delete`, `patch`, `head`, `options`.
89//!
90//! ```rust,no_run
91//! # use toolkit_zero::socket::client::*;
92//! # use toolkit_zero::socket::server::*;
93//! # use serde::{Deserialize, Serialize};
94//! # #[derive(Deserialize, Serialize, Clone)] struct Item { id: u32, name: String }
95//! # #[derive(Deserialize, Serialize)] struct NewItem { name: String }
96//! # #[derive(Deserialize, Serialize)] struct Filter { page: u32 }
97//! # async fn example() -> Result<(), reqwest::Error> {
98//!
99//! let client = Client::new_async(Target::Localhost(8080));
100//!
101//! // ── GET /items ───────────────────────────────────────────────────────────
102//! // Server:
103//! // ServerMechanism::get("/items")
104//! // .onconnect(|| async {
105//! // let items: Vec<Item> = vec![];
106//! // reply!(json => items)
107//! // })
108//! let items: Vec<Item> = client.get("/items").send().await?;
109//!
110//! // ── POST /items (JSON body) ──────────────────────────────────────────────
111//! // Server:
112//! // ServerMechanism::post("/items")
113//! // .json::<NewItem>()
114//! // .onconnect(|body| async move {
115//! // let item = Item { id: 1, name: body.name };
116//! // reply!(json => item, status => Status::Created)
117//! // })
118//! let created: Item = client
119//! .post("/items")
120//! .json(NewItem { name: "widget".to_string() })
121//! .send()
122//! .await?;
123//!
124//! // ── GET /items (query params) ────────────────────────────────────────────
125//! // Server:
126//! // ServerMechanism::get("/items")
127//! // .query::<Filter>()
128//! // .onconnect(|filter| async move {
129//! // let _ = filter.page;
130//! // reply!(json => Vec::<Item>::new())
131//! // })
132//! let page: Vec<Item> = client
133//! .get("/items")
134//! .query(Filter { page: 2 })
135//! .send()
136//! .await?;
137//!
138//! // ── DELETE /items/1 (sync) ───────────────────────────────────────────────
139//! // Server:
140//! // ServerMechanism::delete("/items/1")
141//! // .onconnect(|| async {
142//! // reply!(message => warp::reply(), status => Status::NoContent)
143//! // })
144//! let _: Item = client.delete("/items/1").send_sync()?;
145//!
146//! # Ok(())
147//! # }
148//! ```
149
150// ─── Serialization key (shared by server and client) ────────────────────────
151
152/// Controls which VEIL key is used when the body or query parameters are sealed.
153///
154/// Pass this to [`server::ServerMechanism::encryption`], [`server::ServerMechanism::encrypted_query`],
155/// [`client::RequestBuilder::encryption`], and [`client::RequestBuilder::encrypted_query`].
156/// For plain-JSON routes use the existing `.json()` / `.query()` builder methods instead.
157///
158/// | Variant | Wire format | Trait requirements on `T` |
159/// |---|---|---|
160/// | `Default` | VEIL-sealed bytes (`application/octet-stream`) | `bincode::Encode` / `Decode<()>` |
161/// | `Value(key)` | VEIL-sealed bytes with a custom key | `bincode::Encode` / `Decode<()>` |
162#[derive(Clone)]
163pub enum SerializationKey {
164 /// Use the built-in default VEIL key (`"serialization/deserialization"`).
165 Default,
166 /// Use a custom VEIL key shared by both client and server.
167 Value(String),
168}
169
170impl SerializationKey {
171 #[doc(hidden)]
172 pub fn veil_key(&self) -> Option<String> {
173 match self {
174 Self::Default => None,
175 Self::Value(k) => Some(k.clone()),
176 }
177 }
178}
179
180#[cfg(feature = "socket-server")]
181pub mod server;
182
183#[cfg(feature = "socket-client")]
184pub mod client;
185
186#[cfg(any(feature = "socket-server", feature = "socket-client"))]
187pub mod prelude;
188
189#[cfg(feature = "backend-deps")]
190pub mod backend_deps;