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