Crates.io | starbase |
lib.rs | starbase |
version | 0.9.3 |
source | src |
created_at | 2023-04-06 00:57:23.750599 |
updated_at | 2024-11-03 01:52:17.913912 |
description | Framework for building performant command line applications and developer tools. |
homepage | |
repository | https://github.com/moonrepo/starbase |
max_upload_size | |
id | 831818 |
size | 31,533 |
Application framework for building performant command line applications and developer tools.
An application uses a session based approach, where a session object contains data required for the entire application lifecycle.
Create an App
, optionally setup diagnostics (miette
) and tracing (tracing
), and then run the
application with the provided session. A mutable session is required, as the session can be mutated
for each phase.
use starbase::{App, MainResult};
use std::process::ExitCode;
use crate::CustomSession;
#[tokio::main]
async fn main() -> MainResult {
let app = App::default();
app.setup_diagnostics();
let exit_code = app.run(CustomSession::default(), |session| async {
// Run CLI
Ok(None)
}).await?;
Ok(ExitCode::from(exit_code))
}
A session must implement the AppSession
trait. This trait provides 4 optional methods, each
representing a different phase in the application life cycle.
use starbase::{AppSession, AppResult};
use std::path::PathBuf;
use async_trait::async_trait;
#[derive(Clone)]
pub struct CustomSession {
pub workspace_root: PathBuf,
}
#[async_trait]
impl AppSession for CustomSession {
async fn startup(&mut self) -> AppResult {
self.workspace_root = detect_workspace_root()?;
Ok(None)
}
}
Sessions must be cloneable and be
Send + Sync
compatible. We clone the session when spawning tokio tasks. If you want to persist data across threads, wrap session properties inArc
,RwLock
, and other mechanisms.
An application is divided into phases, where each phase will be processed and completed before moving onto the next phase. The following phases are available:
App#run
).
If a session implements the
AppSession#execute
trait method, it will run in parallel with theApp#run
method.
Errors and diagnostics are provided by the miette
crate. All
layers of the application return the miette::Result
type (via AppResult
). This allows for errors
to be easily converted to diagnostics, and for miette to automatically render to the terminal for
errors and panics.
To benefit from this, update your main
function to return MainResult
.
use starbase::{App, MainResult};
#[tokio::main]
async fn main() -> MainResult {
let app = App::default();
app.setup_diagnostics();
app.setup_tracing_with_defaults();
// ...
Ok(())
}
To make the most out of errors, and in turn diagnostics, it's best (also suggested) to use the
thiserror
crate.
use miette::Diagnostic;
use thiserror::Error;
#[derive(Debug, Diagnostic, Error)]
pub enum AppError {
#[error(transparent)]
#[diagnostic(code(app::io_error))]
IoError(#[from] std::io::Error),
#[error("Systems offline!")]
#[diagnostic(code(app::bad_code))]
SystemsOffline,
}
A returned Err
must be converted to a diagnostic first. There are 2 approaches to achieve this:
#[system]
async fn could_fail() {
// Convert error using into()
Err(AppError::SystemsOffline.into())
// OR use ? operator on Err()
Err(AppError::SystemsOffline)?
}