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//!
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(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<&str> {
173        match self {
174            Self::Default => None,
175            Self::Value(k) => Some(k.as_str()),
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;