use std::io::Write;
use std::path::Path;
use clap::Args;
use graphforge_api::GraphForge;
use crate::open_cli_graph;
#[derive(Args)]
pub(super) struct DiagnosticsArgs {
#[arg(long = "diagnostics", global = true)]
enabled: bool,
#[arg(long, global = true, hide = true)]
pub(super) allocation_diagnostics: bool,
}
pub(super) fn capture(
cli: &super::Cli,
) -> Result<Option<graphforge_api::LifecycleIoCapture>, graphforge_api::GfError> {
if cli.diagnostics.enabled && !cli.json {
return Err(graphforge_api::GfError::Validation(
"diagnostics require --json".into(),
));
}
let requested = cli.diagnostics.enabled
|| cli.diagnostics.allocation_diagnostics
|| matches!(
cli.command.as_ref(),
Some(super::Command::StorageAttribution(_))
);
Ok(requested.then(graphforge_api::LifecycleIoCapture::install))
}
#[derive(Args)]
pub(crate) struct StorageAttributionArgs {
#[arg(long)]
recovery: bool,
}
#[derive(serde::Serialize)]
#[serde(deny_unknown_fields)]
struct StorageAttributionCommandReceipt {
contract: &'static str,
storage: graphforge_api::StorageAttributionReceipt,
reopen_agrees: bool,
application_io: Option<graphforge_api::LifecyclePhaseAttribution>,
}
pub(crate) fn lifecycle_application_io()
-> Result<Option<graphforge_api::LifecyclePhaseAttribution>, graphforge_api::GfError> {
let attribution = graphforge_api::lifecycle_io_snapshot();
if let Some(attribution) = &attribution {
attribution.validate_for_qualification()?;
}
Ok(attribution)
}
pub(crate) fn run_storage_attribution(
graph: GraphForge,
path: &Path,
args: &StorageAttributionArgs,
json: bool,
output: &mut dyn Write,
allocation: Option<&graphforge_api::StorageAllocationDiagnostics>,
) -> Result<(), graphforge_api::GfError> {
if args.recovery {
crate::maintenance_cli::run_recovery(&graph, json, output)?;
}
let storage = graph.storage_attribution_receipt()?;
storage.validate_reconciliation()?;
drop(graph);
let path_text = path.to_str().ok_or_else(|| {
graphforge_api::GfError::Validation("--project must be valid UTF-8".into())
})?;
let reopened = open_cli_graph(Path::new(path_text), allocation)?;
let reopened_storage = reopened.storage_attribution_receipt()?;
reopened_storage.validate_reconciliation()?;
if reopened_storage != storage {
return Err(graphforge_api::GfError::Validation(
"storage attribution changed across reopen".into(),
));
}
if json {
serde_json::to_writer(
&mut *output,
&StorageAttributionCommandReceipt {
contract: "graphforge-storage-attribution-command/1",
storage,
reopen_agrees: true,
application_io: lifecycle_application_io()?,
},
)
.map_err(|error| graphforge_api::GfError::Execution(error.to_string()))?;
writeln!(output).map_err(|error| graphforge_api::GfError::Execution(error.to_string()))?;
} else {
writeln!(
output,
"retained_logical_eof_bytes={} allocated_physical_bytes={} reopen_agrees=true",
storage.retained_logical_eof_bytes, storage.allocated_physical_bytes
)
.map_err(|error| graphforge_api::GfError::Execution(error.to_string()))?;
}
Ok(())
}