wora 0.0.21

A framework for building applications (daemons, etc) that run in different environments (Linux, Kubernetes, etc).
Documentation
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use tokio::sync::mpsc::Sender;
use tracing::Level;
use tracing_subscriber::prelude::*;
use wora::prelude::*;

#[derive(Clone, Debug)]
enum OnboardingEvent {
    Tick,
}

#[derive(Default, Debug)]
struct OnboardingConfig {
    greeting: String,
}

impl Config for OnboardingConfig {
    type ConfigT = OnboardingConfig;

    fn parse_main_config_file(data: String) -> Result<Self::ConfigT, Box<dyn std::error::Error>> {
        Ok(Self {
            greeting: data.trim().to_string(),
        })
    }
}

struct OnboardingApp {
    greeting: String,
}

#[async_trait]
impl App<OnboardingEvent, ()> for OnboardingApp {
    type AppConfig = OnboardingConfig;
    type AppSecrets = NoSecrets;
    type Setup = ();

    fn name(&self) -> &'static str {
        "onboarding"
    }

    async fn reload_config(&mut self, reload: ConfigReload<OnboardingConfig>) -> Result<(), Box<dyn std::error::Error>> {
        if let Some(config) = reload.main {
            self.greeting = config.greeting;
        }
        Ok(())
    }

    async fn setup(
        &mut self,
        wora: &Wora<OnboardingEvent, ()>,
        _exec: impl AsyncExecutor<OnboardingEvent, ()>,
        _fs: impl WFS + 'static,
        _metrics: Sender<O11yEvent<()>>,
        _is_first_boot: bool,
    ) -> Result<Self::Setup, Box<dyn std::error::Error>> {
        wora.report_readiness(ReadinessState::Ready);

        let sender = wora.sender.clone();
        tokio::spawn(async move {
            tokio::time::sleep(Duration::from_millis(25)).await;
            let _ = sender.send(Event::App(OnboardingEvent::Tick)).await;
            tokio::time::sleep(Duration::from_millis(25)).await;
            let _ = sender.send(Event::Control(ControlEvent::Shutdown(None))).await;
        });

        Ok(())
    }

    async fn main(
        &mut self,
        wora: &mut Wora<OnboardingEvent, ()>,
        _exec: impl AsyncExecutor<OnboardingEvent, ()>,
        fs: impl WFS + 'static,
        _metrics: Sender<O11yEvent<()>>,
    ) -> MainRetryAction {
        wora.run_event_loop(self, fs, |app, _wora, event| match event {
            Event::App(OnboardingEvent::Tick) => {
                println!("{}", app.greeting);
                EventLoopAction::Continue
            }
            Event::Control(ControlEvent::Shutdown(_)) => EventLoopAction::Exit(MainRetryAction::Success),
            _ => EventLoopAction::Continue,
        })
        .await
        .unwrap_or_else(|err| {
            eprintln!("reload error: {}", err);
            MainRetryAction::UseExitCode(78)
        })
    }

    async fn end(
        &mut self,
        _wora: &Wora<OnboardingEvent, ()>,
        _exec: impl AsyncExecutor<OnboardingEvent, ()>,
        _fs: impl WFS + 'static,
        _metrics: Sender<O11yEvent<()>>,
    ) {
    }
}

#[tokio::main]
async fn main() -> Result<(), MainEarlyReturn> {
    let entries = Arc::new(Mutex::new(Vec::new()));
    let log_path = std::env::temp_dir().join("wora-onboarding.jsonl");
    let pipeline = O11yPipeline::builder()
        .capacity(64)
        .sink("memory", O11yMemorySink::new(entries.clone()))
        .sink("json-lines", O11yJsonLinesSink::new(log_path.clone()).with_name("onboarding"))
        .flush_interval(Duration::from_millis(25))
        .status_interval(Duration::from_millis(25))
        .host_stats_interval(Duration::from_millis(25))
        .build()
        .map_err(|err| MainEarlyReturn::WoraSetup(WoraSetupError::Str(err.to_string())))?;

    tracing_subscriber::registry().with(pipeline.tracing_layer(Level::INFO)).init();

    let fs = PhysicalVFS::new();

    let app_name = "onboarding";
    let exec = UnixLikeUser::new(app_name, fs.clone()).await.map_err(MainEarlyReturn::Vfs)?;
    let config_path: PathBuf = <UnixLikeUser as AsyncExecutor<OnboardingEvent, ()>>::dirs(&exec)
        .metadata_root_dir
        .join("onboarding.toml");
    if let Some(parent) = config_path.parent() {
        tokio::fs::create_dir_all(parent).await?;
    }
    tokio::fs::write(&config_path, "hello from wora").await?;

    let app = OnboardingApp {
        greeting: "hello from onboarding".to_string(),
    };

    exec_async_runner(exec, app, fs, pipeline).await?;

    let entries = entries.lock().map_err(|err| MainEarlyReturn::WoraSetup(WoraSetupError::Str(err.to_string())))?;
    println!("captured {} observability events", entries.len());
    println!("json-lines sink path: {}", log_path.display());
    Ok(())
}