use anyhow::Result;
use async_trait::async_trait;
use capnweb_core::{CapId, RpcError};
use capnweb_server::{RpcTarget, Server, ServerConfig};
use serde_json::{json, Value};
use std::sync::Arc;
use tracing::{error, info};
#[derive(Debug)]
struct CalculatorService;
#[async_trait]
impl RpcTarget for CalculatorService {
async fn call(&self, method: &str, args: Vec<Value>) -> Result<Value, RpcError> {
match method {
"add" => {
if args.len() != 2 {
return Err(RpcError::bad_request("add requires exactly 2 arguments"));
}
Ok(json!({
"result": "sum of inputs",
"method": "add",
"args_count": args.len()
}))
}
"multiply" => {
if args.len() != 2 {
return Err(RpcError::bad_request(
"multiply requires exactly 2 arguments",
));
}
Ok(json!({
"result": "product of inputs",
"method": "multiply",
"args_count": args.len()
}))
}
"echo" => Ok(json!({
"echoed": args,
"method": "echo"
})),
_ => Err(RpcError::not_found(format!("Unknown method: {}", method))),
}
}
}
#[derive(Debug)]
struct BootstrapService;
#[async_trait]
impl RpcTarget for BootstrapService {
async fn call(&self, method: &str, args: Vec<Value>) -> Result<Value, RpcError> {
match method {
"getCapability" => {
let id_value = args.first().ok_or_else(|| {
RpcError::bad_request("getCapability requires a capability ID argument")
})?;
let id_number = id_value
.as_number()
.ok_or_else(|| RpcError::bad_request("Capability ID must be a number"))?;
if !id_number.is_i64() && !id_number.is_u64() {
return Err(RpcError::bad_request("Capability ID must be an integer"));
}
if let Some(i64_val) = id_number.as_i64() {
if i64_val < 0 {
return Err(RpcError::bad_request("Capability ID must be non-negative"));
}
let cap_id = i64_val as u64;
match cap_id {
0..=2 => {
Ok(json!({
"$capnweb": {
"import_id": cap_id
}
}))
}
_ => Err(RpcError::not_found(format!(
"Capability {} not found",
cap_id
))),
}
} else if let Some(cap_id) = id_number.as_u64() {
match cap_id {
0..=2 => Ok(json!({
"$capnweb": {
"import_id": cap_id
}
})),
_ => Err(RpcError::not_found(format!(
"Capability {} not found",
cap_id
))),
}
} else {
Err(RpcError::bad_request(
"Capability ID value is out of valid range",
))
}
}
"echo" => {
Ok(json!({
"echoed": args,
"method": "echo",
"source": "bootstrap"
}))
}
_ => Err(RpcError::not_found(format!(
"Unknown bootstrap method: {}",
method
))),
}
}
}
#[derive(Debug)]
struct EchoService;
#[async_trait]
impl RpcTarget for EchoService {
async fn call(&self, method: &str, args: Vec<Value>) -> Result<Value, RpcError> {
Ok(json!({
"service": "echo",
"method": method,
"args": args,
"timestamp": chrono::Utc::now().to_rfc3339()
}))
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.init();
info!("Starting Cap'n Web Server");
let config = ServerConfig {
port: 8080,
host: "127.0.0.1".to_string(),
max_batch_size: 100,
};
let server = Server::new(config);
server.register_capability(CapId::new(0), Arc::new(BootstrapService));
server.register_capability(CapId::new(1), Arc::new(CalculatorService));
server.register_capability(CapId::new(2), Arc::new(EchoService));
info!("Server configured with capabilities:");
info!(" - CapId(0): Bootstrap Service (main interface)");
info!(" - CapId(1): Calculator Service");
info!(" - CapId(2): Echo Service");
info!("Starting server on http://127.0.0.1:8080");
info!("Endpoints:");
info!(" - HTTP Batch: http://127.0.0.1:8080/rpc/batch");
info!(" - WebSocket: ws://127.0.0.1:8080/rpc/ws");
if let Err(e) = server.run().await {
error!("Server error: {}", e);
std::process::exit(1);
}
Ok(())
}