use std::io::Read as _;
use std::process::ExitCode;
use anyhow::{Context, Result};
use crate::commands;
use crate::output::{self, CommandReport, OutputFormat};
use crate::paths;
use crate::state;
struct RadarRenderOptions<'a> {
format: OutputFormat,
repo_path: &'a std::path::Path,
explicit_profile: Option<&'a str>,
fields: Option<Vec<String>>,
memory_depth: Option<output::MemoryDepth>,
commit: bool,
since_session: Option<String>,
}
pub(crate) fn context_check(
args: crate::ContextCheckArgs,
format: OutputFormat,
) -> Result<ExitCode> {
if args.fields.is_some() && format != OutputFormat::Json {
anyhow::bail!("--fields requires --output json");
}
if args.memory_depth.is_some() && format != OutputFormat::Json {
anyhow::bail!("--memory-depth requires --output json");
}
if let Some(fields) = &args.fields {
output::validate_fields(&output::CONTEXT_CHECK_FIELD_FILTER_SPEC, fields)?;
}
let repo_path = paths::cli::resolve(&args.path)?;
let report = state::radar::run_context_check(
&repo_path,
args.profile.as_deref(),
args.trigger.into_trigger(),
)?;
let use_metadata = args.memory_depth == Some(output::MemoryDepth::Metadata);
match &args.fields {
Some(fields) => {
let exit_code = report.exit_code();
let mut value = serde_json::to_value(&report)?;
if use_metadata {
value = output::strip_memory_content(value);
}
let filtered = output::try_filter_json_fields(
value,
fields,
&output::CONTEXT_CHECK_FIELD_FILTER_SPEC,
)?;
println!("{}", serde_json::to_string_pretty(&filtered)?);
Ok(exit_code)
}
None if use_metadata => {
let exit_code = report.exit_code();
let mut value = serde_json::to_value(&report)?;
value = output::strip_memory_content(value);
println!("{}", serde_json::to_string_pretty(&value)?);
Ok(exit_code)
}
None => output::render_report(format, &report),
}
}
pub(crate) fn policy_check(args: crate::PolicyCheckArgs, format: OutputFormat) -> Result<ExitCode> {
let repo_path = paths::cli::resolve(&args.path)?;
let report = commands::policy_check::run(
&repo_path,
args.profile.as_deref(),
args.action_family.into_policy_action_family(),
)?;
output::render_report(format, &report)
}
pub(crate) fn runtime_state(
command: crate::RuntimeStateCommand,
format: OutputFormat,
) -> Result<ExitCode> {
match command {
crate::RuntimeStateCommand::Export(args) => {
let repo_path = paths::cli::resolve(&args.path)?;
let report = state::runtime_export::run(
&repo_path,
args.profile.as_deref(),
args.projection_target
.map(crate::ProjectionTargetArg::into_projection_target),
args.projection_format
.map(crate::ProjectionFormatArg::into_projection_format),
)?;
output::render_report(format, &report)
}
crate::RuntimeStateCommand::ChildBootstrap(args) => {
let repo_path = paths::cli::resolve(&args.path)?;
let report = state::child_bootstrap::run(&repo_path, args.profile.as_deref())?;
output::render_report(format, &report)
}
}
}
pub(crate) fn escalation_state(
command: crate::EscalationStateCommand,
format: OutputFormat,
) -> Result<ExitCode> {
match command {
crate::EscalationStateCommand::Set(args) => {
let repo_path = paths::cli::resolve(&args.base.path)?;
let report = state::escalation::set(
&repo_path,
args.base.profile.as_deref(),
args.id.as_deref(),
args.protected_write.into_state_options(),
Some(args.kind.into_kind()),
&args.reason,
)?;
output::render_report(format, &report)
}
crate::EscalationStateCommand::Clear(args) => {
let repo_path = paths::cli::resolve(&args.base.path)?;
let report = state::escalation::clear(
&repo_path,
args.base.profile.as_deref(),
args.id.as_deref(),
args.protected_write.into_state_options(),
)?;
output::render_report(format, &report)
}
crate::EscalationStateCommand::List(args) => {
let repo_path = paths::cli::resolve(&args.base.path)?;
let report = state::escalation::list(&repo_path, args.base.profile.as_deref())?;
output::render_report(format, &report)
}
}
}
pub(crate) fn radar_state(args: crate::RadarStateArgs, format: OutputFormat) -> Result<ExitCode> {
if args.fields.is_some() && format != OutputFormat::Json {
anyhow::bail!("--fields requires --output json");
}
if args.memory_depth.is_some() && format != OutputFormat::Json {
anyhow::bail!("--memory-depth requires --output json");
}
if args.commit && format != OutputFormat::Json {
anyhow::bail!("--commit requires --output json");
}
if args.since_session.is_some() && format != OutputFormat::Json {
anyhow::bail!("--since-session requires --output json");
}
if let Some(fields) = &args.fields {
output::validate_fields(&output::RADAR_STATE_FIELD_FILTER_SPEC, fields)?;
}
let repo_path = paths::cli::resolve(&args.path)?;
let needs_digests = args.since_session.is_some();
let (report, current_digests) = if needs_digests {
let (report, digests) =
state::radar::run_with_digests(&repo_path, args.profile.as_deref(), false)?;
(report, Some(digests))
} else {
(
state::radar::run(&repo_path, args.profile.as_deref(), false)?,
None,
)
};
render_radar_like_report(
RadarRenderOptions {
format,
repo_path: &repo_path,
explicit_profile: args.profile.as_deref(),
fields: args.fields,
memory_depth: args.memory_depth,
commit: args.commit,
since_session: args.since_session,
},
report,
current_digests,
)
}
pub(crate) fn checkpoint(args: crate::CheckpointArgs, format: OutputFormat) -> Result<ExitCode> {
if args.fields.is_some() && format != OutputFormat::Json {
anyhow::bail!("--fields requires --output json");
}
if args.memory_depth.is_some() && format != OutputFormat::Json {
anyhow::bail!("--memory-depth requires --output json");
}
if args.commit && format != OutputFormat::Json {
anyhow::bail!("--commit requires --output json");
}
if args.since_session.is_some() && format != OutputFormat::Json {
anyhow::bail!("--since-session requires --output json");
}
if let Some(fields) = &args.fields {
output::validate_fields(&output::RADAR_STATE_FIELD_FILTER_SPEC, fields)?;
}
let repo_path = paths::cli::resolve(&args.path)?;
let needs_digests = args.since_session.is_some();
let (report, current_digests) = if needs_digests {
let (report, digests) =
state::radar::run_with_digests(&repo_path, args.profile.as_deref(), true)?;
(report, Some(digests))
} else {
(
state::radar::run(&repo_path, args.profile.as_deref(), true)?,
None,
)
};
render_radar_like_report(
RadarRenderOptions {
format,
repo_path: &repo_path,
explicit_profile: args.profile.as_deref(),
fields: args.fields,
memory_depth: args.memory_depth,
commit: args.commit,
since_session: args.since_session,
},
report,
current_digests,
)
}
fn render_radar_like_report(
options: RadarRenderOptions<'_>,
report: state::radar::RadarStateReport,
current_digests: Option<state::compiled::ProjectionDigests>,
) -> Result<ExitCode> {
let use_metadata = options.memory_depth == Some(output::MemoryDepth::Metadata);
let needs_json = options.fields.is_some()
|| options.commit
|| use_metadata
|| options.since_session.is_some();
if needs_json {
let mut value = serde_json::to_value(&report)?;
if let Some(ref since) = options.since_session {
value = crate::apply_since_session_delta(
value,
options.repo_path,
options.explicit_profile,
since,
current_digests
.as_ref()
.expect("digests available when --since-session is set"),
)?;
}
if options.commit {
let mut buf = String::new();
std::io::stdin()
.read_to_string(&mut buf)
.context("failed to read commit payload from stdin")?;
let payload: state::radar::CommitPayload =
serde_json::from_str(&buf).context("failed to parse commit payload JSON")?;
let commit_result =
state::radar::commit_writes(options.repo_path, options.explicit_profile, payload);
value["commit"] = serde_json::to_value(&commit_result)?;
}
if use_metadata {
value = output::strip_memory_content(value);
}
if let Some(ref fields) = options.fields {
value = output::try_filter_json_fields(
value,
fields,
&output::RADAR_STATE_FIELD_FILTER_SPEC,
)?;
} else {
value = output::apply_default_json_contract(
value,
output::DefaultJsonContract::RadarState,
)?;
}
println!("{}", serde_json::to_string_pretty(&value)?);
Ok(ExitCode::SUCCESS)
} else if options.format == OutputFormat::Json {
let value = serde_json::to_value(&report)?;
let filtered =
output::apply_default_json_contract(value, output::DefaultJsonContract::RadarState)?;
println!("{}", serde_json::to_string_pretty(&filtered)?);
Ok(ExitCode::SUCCESS)
} else {
output::render_report(options.format, &report)
}
}