use serde::{Deserialize, Serialize};
use zlink::{
Server,
introspect::{self, CustomType},
unix::{bind, connect},
};
#[test_log::test(tokio::test(flavor = "multi_thread"))]
async fn service_macro_basic() -> Result<(), Box<dyn std::error::Error>> {
let socket_path = "/tmp/zlink-service-macro-test.sock";
if let Err(e) = tokio::fs::remove_file(socket_path).await {
if e.kind() != std::io::ErrorKind::NotFound {
return Err(e.into());
}
}
let listener = bind(socket_path).unwrap();
let service = BankAccount::new(1000, false);
let server = Server::new(listener, service);
tokio::select! {
res = server.run() => res?,
res = run_client(socket_path) => res?,
}
Ok(())
}
async fn run_client(socket_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut conn = connect(socket_path).await?;
let reply = conn.get_balance().await?.unwrap();
assert_eq!(reply.amount, 1000);
let reply = conn.deposit(500).await?.unwrap();
assert_eq!(reply.amount, 1500);
let reply = conn.get_balance().await?.unwrap();
assert_eq!(reply.amount, 1500);
let reply = conn.withdraw(200).await?.unwrap();
assert_eq!(reply.amount, 1300);
let err = conn.withdraw(5000).await?.unwrap_err();
assert_eq!(
err,
BankError::InsufficientFunds {
available: 1300,
requested: 5000,
}
);
let reply = conn.get_balance().await?.unwrap();
assert_eq!(reply.amount, 1300);
let err = conn.deposit(-100).await?.unwrap_err();
assert_eq!(err, BankError::InvalidAmount { amount: -100 });
conn.lock_account().await?.unwrap();
let err = conn.deposit(100).await?.unwrap_err();
assert_eq!(err, BankError::AccountLocked);
let err = conn.withdraw(100).await?.unwrap_err();
assert_eq!(err, BankError::AccountLocked);
let reply = conn.get_balance().await?.unwrap();
assert_eq!(reply.amount, 1300);
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
pub(crate) struct Balance {
pub amount: i64,
}
#[derive(Debug, Clone, PartialEq, zlink::ReplyError, introspect::ReplyError)]
#[zlink(interface = "org.example.bank")]
pub(crate) enum BankError {
InsufficientFunds { available: i64, requested: i64 },
InvalidAmount { amount: i64 },
AccountLocked,
}
pub(crate) struct BankAccount {
balance: i64,
locked: bool,
}
impl BankAccount {
pub fn new(initial_balance: i64, locked: bool) -> Self {
Self {
balance: initial_balance,
locked,
}
}
}
#[zlink::service(types = [Balance])]
impl BankAccount {
#[zlink(interface = "org.example.bank")]
async fn get_balance(&self) -> Balance {
Balance {
amount: self.balance,
}
}
async fn deposit(&mut self, amount: i64) -> Result<Balance, BankError> {
if self.locked {
return Err(BankError::AccountLocked);
}
if amount <= 0 {
return Err(BankError::InvalidAmount { amount });
}
self.balance += amount;
Ok(Balance {
amount: self.balance,
})
}
async fn withdraw(&mut self, amount: i64) -> Result<Balance, BankError> {
if self.locked {
return Err(BankError::AccountLocked);
}
if amount <= 0 {
return Err(BankError::InvalidAmount { amount });
}
if amount > self.balance {
return Err(BankError::InsufficientFunds {
available: self.balance,
requested: amount,
});
}
self.balance -= amount;
Ok(Balance {
amount: self.balance,
})
}
async fn lock_account(&mut self) -> Result<(), BankError> {
if self.locked {
return Err(BankError::AccountLocked);
}
self.locked = true;
Ok(())
}
}
#[zlink::proxy("org.example.bank")]
trait BankProxy {
async fn get_balance(&mut self) -> zlink::Result<Result<Balance, BankError>>;
async fn deposit(&mut self, amount: i64) -> zlink::Result<Result<Balance, BankError>>;
async fn withdraw(&mut self, amount: i64) -> zlink::Result<Result<Balance, BankError>>;
async fn lock_account(&mut self) -> zlink::Result<Result<(), BankError>>;
}