use std::future::Future;
use std::net::SocketAddr;
use crate::error::{Error, Result};
use crate::resources::Resources;
use crate::router::BoxFuture;
use crate::state::StateMap;
pub trait Lifespan: Resources + Sized {
fn startup(ctx: LifespanContext) -> impl Future<Output = Result<Self>> + Send;
fn shutdown(self) -> impl Future<Output = Result<()>> + Send {
async move {
let _ = self;
Ok(())
}
}
}
pub struct LifespanContext {
_private: (),
}
impl LifespanContext {
pub fn new() -> Self {
Self { _private: () }
}
pub fn env(&self, key: &str) -> Result<String> {
std::env::var(key).map_err(|_| {
Error::internal(format!("required environment variable `{key}` is not set"))
.with_code("MISSING_ENV")
})
}
}
impl Default for LifespanContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct ReadyContext {
addr: SocketAddr,
}
impl ReadyContext {
pub fn new(addr: SocketAddr) -> Self {
Self { addr }
}
pub fn addr(&self) -> SocketAddr {
self.addr
}
}
pub(crate) trait ErasedLifespan: Send + Sync {
fn startup<'a>(
&'a mut self,
ctx: LifespanContext,
registry: &'a mut StateMap,
) -> BoxFuture<'a, Result<()>>;
fn shutdown(&mut self) -> BoxFuture<'_, Result<()>>;
}
pub(crate) struct LifespanCell<L: Lifespan> {
container: Option<L>,
}
impl<L: Lifespan> LifespanCell<L> {
pub(crate) fn new() -> Self {
Self { container: None }
}
}
impl<L: Lifespan> ErasedLifespan for LifespanCell<L> {
fn startup<'a>(
&'a mut self,
ctx: LifespanContext,
registry: &'a mut StateMap,
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let container = L::startup(ctx).await?;
container.register(registry);
self.container = Some(container);
Ok(())
})
}
fn shutdown(&mut self) -> BoxFuture<'_, Result<()>> {
Box::pin(async move {
match self.container.take() {
Some(container) => container.shutdown().await,
None => Ok(()),
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_env_is_an_error() {
let ctx = LifespanContext::new();
let error = ctx.env("TORK_DEFINITELY_MISSING_VARIABLE_XYZ").unwrap_err();
assert_eq!(error.code(), "MISSING_ENV");
}
#[derive(Clone)]
struct Probe {
value: i64,
}
impl Resources for Probe {
fn register(&self, registry: &mut StateMap) {
registry.insert(self.value);
}
}
impl Lifespan for Probe {
async fn startup(_ctx: LifespanContext) -> Result<Self> {
Ok(Probe { value: 7 })
}
}
#[tokio::test]
async fn cell_registers_resources_and_shuts_down() {
let mut cell = LifespanCell::<Probe>::new();
let mut registry = StateMap::new();
cell.startup(LifespanContext::new(), &mut registry)
.await
.unwrap();
assert_eq!(registry.get::<i64>().map(|value| *value), Some(7));
cell.shutdown().await.unwrap();
cell.shutdown().await.unwrap();
}
}