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