use std::io::Read;
use std::sync::Arc;
use std::{fs, io};
use brane_ast::{CompileResult, Workflow};
use brane_dsl::{Language, ParserOptions};
use console::style;
use error_trace::trace;
use log::{debug, info};
use specifications::data::DataIndex;
use specifications::driving::{CheckReply, CheckRequest, DriverServiceClient};
use specifications::package::PackageIndex;
use specifications::profiling::{self};
pub use crate::errors::CheckError as Error;
use crate::instance::InstanceInfo;
async fn compile(instance: &InstanceInfo, input: &str, source: String, language: Language, user: Option<String>) -> Result<Workflow, Error> {
let url: String = format!("{}/graphql", instance.api);
debug!("Retrieving package index from '{url}'");
let pindex: PackageIndex = match brane_tsk::api::get_package_index(&url).await {
Ok(pindex) => pindex,
Err(err) => {
return Err(Error::PackageIndexRetrieve { url, err });
},
};
let url: String = format!("{}/data/info", instance.api);
debug!("Retrieving data index from '{url}'");
let dindex: DataIndex = match brane_tsk::api::get_data_index(&url).await {
Ok(dindex) => dindex,
Err(err) => {
return Err(Error::DataIndexRetrieve { url, err });
},
};
match brane_ast::compile_program(source.as_bytes(), &pindex, &dindex, &ParserOptions::new(language)) {
CompileResult::Workflow(mut wf, warns) => {
for warn in warns {
warn.prettyprint(input, &source);
}
wf.user = Arc::new(Some(user.unwrap_or_else(|| instance.user.clone())));
Ok(wf)
},
CompileResult::Err(errs) => {
for err in errs {
err.prettyprint(input, &source);
}
Err(Error::AstCompile { input: input.into() })
},
CompileResult::Eof(err) => {
err.prettyprint(input, source);
Err(Error::AstCompile { input: input.into() })
},
CompileResult::Program(_, _) | CompileResult::Unresolved(_, _) => unreachable!(),
}
}
pub async fn handle(file: String, language: Language, user: Option<String>, profile: bool) -> Result<(), Error> {
info!("Handling 'brane check {}'", if file == "-" { "<stdin>" } else { file.as_str() });
let prof: profiling::ProfileScope = profiling::ProfileScope::new("Local preparation");
debug!("Loading input from '{file}'...");
let load = prof.time("Input loading");
let (input, source): (String, String) = if file == "-" {
let mut source: String = String::new();
if let Err(err) = io::stdin().read_to_string(&mut source) {
return Err(Error::InputStdinRead { err });
}
("<stdin>".into(), source)
} else {
match fs::read_to_string(&file) {
Ok(source) => (file, source),
Err(err) => return Err(Error::InputFileRead { path: file.into(), err }),
}
};
load.stop();
debug!("Retrieving active instance info...");
let instance: InstanceInfo = match prof.time_func("Instance resolution", InstanceInfo::from_active_path) {
Ok(config) => config,
Err(err) => {
return Err(Error::ActiveInstanceInfoLoad { err });
},
};
debug!("Compiling source text to Brane WIR...");
let workflow: Workflow = match prof.time_fut("Workflow compilation", compile(&instance, &input, source, language, user)).await {
Ok(wf) => wf,
Err(err) => return Err(Error::WorkflowCompile { input, err: Box::new(err) }),
};
let sworkflow: String = match prof.time_func("Workflow serialization", || serde_json::to_string(&workflow)) {
Ok(swf) => swf,
Err(err) => return Err(Error::WorkflowSerialize { input, err }),
};
debug!("Connecting to driver '{}'...", instance.drv);
let rem = prof.time("Driver time");
let mut client: DriverServiceClient = match DriverServiceClient::connect(instance.drv.to_string()).await {
Ok(client) => client,
Err(err) => {
return Err(Error::DriverConnect { address: instance.drv, err });
},
};
debug!("Sending check request to driver '{}' and awaiting response...", instance.drv);
let res: CheckReply = match client.check(CheckRequest { workflow: sworkflow }).await {
Ok(res) => res.into_inner(),
Err(err) => return Err(Error::DriverCheck { address: instance.drv, err }),
};
rem.stop();
if profile {
println!();
println!("{}", (0..80).map(|_| '-').collect::<String>());
println!("LOCAL PROFILE RESULTS:");
println!("{}", prof.display());
if let Some(prof) = res.profile {
match serde_json::from_str::<profiling::ProfileScope>(&prof) {
Ok(prof) => {
println!();
println!("REMOTE PROFILE RESULTS:");
println!("{}", prof.display());
},
Err(err) => warn!("{}", trace!(("Failed to deserialize profile information in CheckReply"), err)),
}
}
println!("{}", (0..80).map(|_| '-').collect::<String>());
println!();
std::mem::forget(prof);
}
if res.verdict {
println!("Workflow {} was {} by all domains", style(&workflow.id).bold().cyan(), style("accepted").bold().green());
} else {
println!("Workflow {} was {} by at least one domain", style("").bold().cyan(), style("rejected").bold().red());
if let Some(who) = res.who {
println!(" > Checker of domain {} rejected workflow", style(who).bold().cyan());
if !res.reasons.is_empty() {
println!(" Reasons for denial:");
for reason in res.reasons {
println!(" - {}", style(reason).bold());
}
}
}
}
println!();
Ok(())
}