use clap::Subcommand;
use graphforge_api::{GfError, GraphForge, SavedQuery, SavedQuerySource};
use serde_json::Value;
use std::{collections::HashMap, io::Write, path::PathBuf};
#[derive(Subcommand)]
pub(crate) enum SavedQueryCommand {
Save {
#[arg(long)]
file: PathBuf,
},
List {
#[arg(long)]
version_uuid: Option<String>,
},
Show {
query_uuid: String,
#[arg(long)]
version_uuid: Option<String>,
},
Update {
#[arg(long)]
file: PathBuf,
},
Delete { query_uuid: String },
Run {
query_uuid: String,
#[arg(long)]
params: Option<PathBuf>,
#[arg(long)]
version_uuid: Option<String>,
},
}
fn source(version: Option<String>) -> Result<SavedQuerySource, GfError> {
version.map_or(Ok(SavedQuerySource::Current), |text| {
crate::canonical_uuid(&text).map(|version_uuid| SavedQuerySource::Version { version_uuid })
})
}
fn json(output: &mut dyn Write, value: impl serde::Serialize) -> Result<(), GfError> {
serde_json::to_writer(&mut *output, &value).map_err(|e| GfError::Execution(e.to_string()))?;
writeln!(output).map_err(|e| GfError::Execution(e.to_string()))
}
fn load<T: serde::de::DeserializeOwned>(path: &std::path::Path) -> Result<T, GfError> {
let bytes = crate::read_bounded_file(path, 1024 * 1024, "saved query JSON exceeds byte bound")?;
serde_json::from_slice(&bytes)
.map_err(|_| GfError::Validation("invalid saved query JSON contract".into()))
}
pub(crate) fn run(
graph: &mut GraphForge,
command: SavedQueryCommand,
json_output: bool,
output: &mut dyn Write,
) -> Result<(), GfError> {
match command {
SavedQueryCommand::Save { file } => {
let definition: SavedQuery = load(&file)?;
json(output, graph.create_saved_query(definition)?)
}
SavedQueryCommand::Update { file } => {
let definition: SavedQuery = load(&file)?;
json(output, graph.update_saved_query(definition)?)
}
SavedQueryCommand::Delete { query_uuid } => {
graph.delete_saved_query(crate::canonical_uuid(&query_uuid)?)?;
json(output, serde_json::json!({"deleted": query_uuid}))
}
SavedQueryCommand::Show {
query_uuid,
version_uuid,
} => json(
output,
graph.saved_query_at(crate::canonical_uuid(&query_uuid)?, &source(version_uuid)?)?,
),
SavedQueryCommand::List { version_uuid } => {
json(output, graph.saved_queries_at(&source(version_uuid)?)?)
}
SavedQueryCommand::Run {
query_uuid,
params,
version_uuid,
} => {
let params: HashMap<String, Value> = params
.map(|path| load(&path))
.transpose()?
.unwrap_or_default();
let result = graph.execute_saved_query_json(
crate::canonical_uuid(&query_uuid)?,
¶ms,
&source(version_uuid)?,
None,
)?;
crate::write_execution_result(&result, json_output, output)
}
}
}