use crate::core::{AuditResult, ProjectConfig, Severity};
use crate::notify::{self, AuditNotification, NotificationKind};
use super::NotifyArgs;
use anyhow::{bail, Result};
use serde_json::json;
pub fn run(args: &NotifyArgs) -> Result<()> {
let config = ProjectConfig::from_default_location();
let notification = if let Some(path) = &args.findings {
let content = std::fs::read_to_string(path)
.map_err(|e| anyhow::anyhow!("Could not read {}: {}", path.display(), e))?;
let result: AuditResult = serde_json::from_str(&content).map_err(|e| {
anyhow::anyhow!(
"{} is not a valid forge-guard audit result: {}",
path.display(),
e
)
})?;
AuditNotification {
title: args.title.clone(),
project: result.project_name,
chain: result.chain,
overall_score: Some(result.overall_score),
risk: Some(result.risk_level.to_string()),
success: result.deployment_approved,
findings: result.findings,
}
} else {
AuditNotification {
title: args.title.clone(),
project: config.project_root.to_string_lossy().to_string(),
chain: config.chain.clone(),
overall_score: None,
risk: None,
success: true,
findings: Vec::new(),
}
};
let kind = match &args.kind {
Some(k) => NotificationKind::parse(k)?,
None => {
let probe = args
.webhook
.as_deref()
.or(config.notifications.slack.webhook.as_deref())
.or(config.notifications.discord.webhook.as_deref());
match probe {
Some(url) => NotificationKind::detect(url),
None => NotificationKind::Slack,
}
}
};
let webhook = match &args.webhook {
Some(url) => Some(url.clone()),
None => notify::resolve_webhook(kind, None, &config),
};
let Some(webhook) = webhook else {
eprintln!(
"⚠️ No {} webhook configured. Use --webhook <URL> or set [notifications.{}] in forge-guard.toml.",
kind.as_str(),
kind.as_str()
);
return Ok(());
};
let mut min_severity = notify::configured_min_severity(kind, &config);
if let Some(sev) = &args.severity {
min_severity = notify::severity_from_str(sev).ok_or_else(|| {
anyhow::anyhow!(
"Invalid severity '{}'. Use informational|low|medium|high|critical.",
sev
)
})?;
}
if args.on_critical {
if notification.max_severity() != Severity::Critical {
eprintln!("🔕 No critical findings — skipping notification");
return Ok(());
}
} else if args.on_high {
if notification.max_severity() < Severity::High {
eprintln!("🔕 No high-or-worse findings — skipping notification");
return Ok(());
}
} else if !notify::meets_severity_gate(¬ification.findings, min_severity) {
eprintln!(
"🔕 Findings below {} threshold — skipping notification",
min_severity.label()
);
return Ok(());
}
let mut payload = match kind {
NotificationKind::Slack => notify::build_slack_payload(¬ification),
NotificationKind::Discord => notify::build_discord_payload(¬ification),
};
if let Some(msg) = &args.message {
match kind {
NotificationKind::Slack => payload["blocks"]
.as_array_mut()
.expect("slack payload has blocks")
.push(json!({ "type": "section", "text": { "type": "mrkdwn", "text": msg } })),
NotificationKind::Discord => {
payload["content"] = json!(format!("{}\n{}", args.title, msg));
}
}
}
if args.dry_run {
println!("{}", serde_json::to_string_pretty(&payload)?);
return Ok(());
}
match notify::send(&webhook, &payload) {
Ok(()) => {
eprintln!("📢 {} notification sent", kind.as_str());
Ok(())
}
Err(e) => bail!("Notification failed: {}", e),
}
}