Skip to main content

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;