organix 0.1.3

Building organic application in rust, leveraging tokio and defining inter communication layer mechanism
Documentation
use async_trait::async_trait;
use organix::{
    service, IntercomMsg, Organix, Service, ServiceIdentifier, ServiceState, WatchdogBuilder,
};
use tokio::{
    io::{stdin, stdout, AsyncBufReadExt as _, AsyncWriteExt as _, BufReader},
    stream::StreamExt as _,
};
use tracing_subscriber::fmt::Subscriber;

struct StdinReader {
    state: ServiceState<Self>,
}

struct StdoutWriter {
    state: ServiceState<Self>,
}

#[derive(Debug, IntercomMsg)]
struct WriteMsg(String);

#[async_trait]
impl Service for StdinReader {
    const SERVICE_IDENTIFIER: ServiceIdentifier = "stdin";

    type IntercomMsg = service::NoIntercom;

    fn prepare(state: ServiceState<Self>) -> Self {
        Self { state }
    }

    async fn start(mut self) {
        let mut stdout = self.state.intercom_with::<StdoutWriter>();
        let mut stdin = BufReader::new(stdin()).lines();

        while let Some(msg) = stdin.next().await {
            match msg {
                Err(err) => {
                    tracing::error!(%err);
                    break;
                }
                Ok(line) if line == "quit" => {
                    self.state.watchdog_controller().clone().shutdown().await;
                    break;
                }
                Ok(line) => {
                    tracing::debug!(%line, "read from stdin");
                    if let Err(err) = stdout.send(WriteMsg(line)).await {
                        tracing::error!(%err);
                        break;
                    }
                }
            }
        }
    }
}

#[async_trait]
impl Service for StdoutWriter {
    const SERVICE_IDENTIFIER: ServiceIdentifier = "stdout";

    type IntercomMsg = WriteMsg;

    fn prepare(state: ServiceState<Self>) -> Self {
        Self { state }
    }

    async fn start(mut self) {
        let mut stdout = stdout();

        while let Some(WriteMsg(msg)) = self.state.intercom_mut().recv().await {
            if let Err(err) = stdout.write_all(msg.as_bytes()).await {
                tracing::error!(%err);
                break;
            }
            stdout.write_all(b"\n").await.unwrap();
            stdout.flush().await.unwrap();
        }
    }
}

#[derive(Organix)]
struct StdEcho {
    #[runtime(io)]
    stdin: service::ServiceManager<StdinReader>,
    #[runtime(io)]
    stdout: service::ServiceManager<StdoutWriter>,
}

fn main() {
    let subscriber = Subscriber::builder()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .finish();

    tracing::subscriber::set_global_default(subscriber).expect("setting tracing default failed");

    let watchdog = WatchdogBuilder::<StdEcho>::new().build();

    let mut controller = watchdog.control();
    watchdog.spawn(async move {
        controller.start::<StdoutWriter>().await.unwrap();
        controller.start::<StdinReader>().await.unwrap();
    });
    watchdog.wait_finished();
}