use std::sync::{Arc, Mutex};
use std::time::Duration;
use serde::{Deserialize, Serialize};
use bincode::{Encode, Decode};
use toolkit_zero::socket::SerializationKey;
use toolkit_zero::socket::server::{Server, Status, reply, mechanism};
use toolkit_zero::socket::client::{ClientBuilder, Target, request};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode)]
struct Item {
id: u32,
name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
struct NewItem {
name: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct Filter {
prefix: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct SearchResult {
matches: Vec<Item>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Health {
ok: bool,
}
const PORT: u16 = 19_877;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store: Arc<Mutex<Vec<Item>>> = Arc::new(Mutex::new(vec![]));
let mut server = Server::default();
#[mechanism(server, GET, "/health")]
async fn health_handler() {
reply!(json => Health { ok: true })
}
#[mechanism(server, POST, "/items", json)]
async fn create_item(body: NewItem) {
reply!(json => Item { id: 1, name: body.name }, status => Status::Created)
}
#[mechanism(server, GET, "/items/search", state(store.clone()), query)]
async fn search_items(state: Arc<Mutex<Vec<Item>>>, f: Filter) {
let items = state.lock().unwrap();
let matches: Vec<Item> = items
.iter()
.filter(|i| i.name.starts_with(&f.prefix))
.cloned()
.collect();
reply!(json => SearchResult { matches })
}
#[mechanism(server, POST, "/items/store", state(store.clone()), json)]
async fn store_item(state: Arc<Mutex<Vec<Item>>>, body: NewItem) {
let mut s = state.lock().unwrap();
let id = s.len() as u32 + 1;
let item = Item { id, name: body.name };
s.push(item.clone());
reply!(json => item, status => Status::Created)
}
#[mechanism(server, POST, "/items/secure", encrypted(SerializationKey::Default))]
async fn secure_create(body: NewItem) {
let item = Item { id: 99, name: body.name };
reply!(sealed => item, key => SerializationKey::Default)
}
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let server_handle = server
.serve_with_graceful_shutdown(
([127, 0, 0, 1], PORT),
async { rx.await.ok(); },
)
.background();
tokio::time::sleep(Duration::from_millis(200)).await;
println!("Server started on port {PORT}");
let client = ClientBuilder::new(Target::Localhost(PORT))
.timeout(Duration::from_secs(5))
.build_async()?;
#[request(client, GET, "/health", async)]
async fn health_resp() -> Health {}
assert!(health_resp.ok);
println!("GET /health → ok={}", health_resp.ok);
#[request(client, POST, "/items", json(NewItem { name: "widget".to_string() }), async)]
async fn created() -> Item {}
assert_eq!(created.name, "widget");
println!("POST /items → {:?}", created);
#[request(client, POST, "/items/store", json(NewItem { name: "gadget".to_string() }), async)]
async fn stored1() -> Item {}
println!("POST /items/store → {:?}", stored1);
#[request(client, POST, "/items/store", json(NewItem { name: "gizmo".to_string() }), async)]
async fn stored2() -> Item {}
println!("POST /items/store → {:?}", stored2);
#[request(client, GET, "/items/search", query(Filter { prefix: "ga".to_string() }), async)]
async fn results() -> SearchResult {}
assert!(results.matches.iter().all(|i| i.name.starts_with("ga")));
println!(
"GET /items/search?prefix=ga → {} match(es): {:?}",
results.matches.len(),
results.matches
);
#[request(client, POST, "/items/secure", encrypted(NewItem { name: "secret".to_string() }, SerializationKey::Default), async)]
async fn secure_item() -> Item {}
assert_eq!(secure_item.name, "secret");
println!("POST /items/secure → {:?}", secure_item);
tx.send(()).ok();
server_handle.await?;
println!("\nAll requests successful ✓");
Ok(())
}