use crate::{ServerOptions, new_router};
use bevy_app;
use clap::Parser;
use crossflow::{
CrossflowExecutorApp, Diagram, DiagramError, Outcome, RequestExt, RunCommandsOnWorldExt,
};
use std::thread;
use std::{fs::File, str::FromStr};
pub use crossflow::DiagramElementRegistry;
pub use std::error::Error;
pub mod prelude {
pub use crossflow::prelude::*;
}
#[derive(Parser, Debug)]
#[clap(
name = "Basic Diagram Editor / Workflow Executor",
version = "0.1.0",
about = "Basic program for running workflow diagrams headlessly (run) or serving a web-based diagram editor (serve)."
)]
pub struct Args {
#[clap(subcommand)]
pub command: Commands,
}
#[derive(Parser, Debug)]
pub enum Commands {
Run(RunArgs),
Serve(ServeArgs),
}
#[derive(Parser, Debug)]
pub struct RunArgs {
#[arg(help = "path to the diagram to run")]
diagram: String,
#[arg(help = "json containing the request to the diagram")]
request: String,
}
#[derive(Parser, Debug)]
pub struct ServeArgs {
#[arg(short, long, default_value_t = 3000)]
port: u16,
}
pub fn headless(args: RunArgs, registry: DiagramElementRegistry) -> Result<(), Box<dyn Error>> {
let mut app = bevy_app::App::new();
app.add_plugins(CrossflowExecutorApp::default());
let file = File::open(args.diagram).unwrap();
let diagram = Diagram::from_reader(file)?;
let request = serde_json::Value::from_str(&args.request)?;
let mut outcome =
app.world_mut()
.command(|cmds| -> Result<Outcome<serde_json::Value>, DiagramError> {
let workflow = diagram.spawn_io_workflow(cmds, ®istry)?;
Ok(cmds.request(request, workflow).outcome())
})?;
while outcome.is_pending() {
app.update();
}
match outcome.try_recv().unwrap() {
Ok(response) => println!("response: {response}"),
Err(err) => println!("error: {err}"),
}
Ok(())
}
pub async fn serve(
args: ServeArgs,
registry: DiagramElementRegistry,
) -> Result<(), Box<dyn Error>> {
println!("Serving diagram editor at http://localhost:{}", args.port);
let (router_sender, router_receiver) = tokio::sync::oneshot::channel();
thread::spawn(move || {
let mut app = bevy_app::App::new();
app.add_plugins(CrossflowExecutorApp::default());
let router = new_router(&mut app, registry, ServerOptions::default());
let _ = router_sender.send(router);
app.run()
});
let router = router_receiver.await?;
let listener = tokio::net::TcpListener::bind(("localhost", args.port))
.await
.unwrap();
axum::serve(listener, router).await?;
Ok(())
}
pub fn run(registry: DiagramElementRegistry) -> Result<(), Box<dyn Error>> {
run_with_args(Args::parse(), registry)
}
pub fn run_with_args(args: Args, registry: DiagramElementRegistry) -> Result<(), Box<dyn Error>> {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(run_async_with_args(args, registry))
}
pub async fn run_async(registry: DiagramElementRegistry) -> Result<(), Box<dyn Error>> {
run_async_with_args(Args::parse(), registry).await
}
pub async fn run_async_with_args(
args: Args,
registry: DiagramElementRegistry,
) -> Result<(), Box<dyn Error>> {
tracing_subscriber::fmt::init();
match args.command {
Commands::Run(args) => headless(args, registry),
Commands::Serve(args) => serve(args, registry).await,
}
}