Skip to main content

slack_rs/
debug.rs

1//! Debug logging helpers.
2//!
3//! This crate primarily prints user-facing progress messages to stdout.
4//! Any verbose diagnostics should be gated behind an environment variable
5//! and must never leak secrets (tokens, client secrets, etc.).
6
7use serde_json::Value;
8
9/// Returns true when debug logging is enabled.
10///
11/// Enable with `SLACK_RS_DEBUG=1` (also accepts: true/yes/on).
12pub fn enabled() -> bool {
13    match std::env::var("SLACK_RS_DEBUG") {
14        Ok(v) => {
15            let v = v.trim().to_ascii_lowercase();
16            matches!(v.as_str(), "1" | "true" | "yes" | "on")
17        }
18        Err(_) => false,
19    }
20}
21
22/// Print a debug line to stderr when enabled.
23pub fn log(msg: impl AsRef<str>) {
24    if enabled() {
25        eprintln!("DEBUG: {}", msg.as_ref());
26    }
27}
28
29/// Returns a safe, non-reversible hint for a token.
30///
31/// Never returns any part of the token value.
32pub fn token_hint(token: &str) -> String {
33    let kind = if token.starts_with("xoxb-") {
34        "xoxb"
35    } else if token.starts_with("xoxp-") {
36        "xoxp"
37    } else if token.starts_with("xoxa-") {
38        "xoxa"
39    } else if token.starts_with("xoxr-") {
40        "xoxr"
41    } else if token.starts_with("xoxs-") {
42        "xoxs"
43    } else {
44        "token"
45    };
46
47    format!("{} (len={})", kind, token.len())
48}
49
50/// Redact token-like values from a JSON string.
51///
52/// This is intentionally conservative: any string that looks like a Slack token
53/// (starts with "xox") is replaced.
54pub fn redact_json_secrets(json: &str) -> String {
55    let Ok(mut v) = serde_json::from_str::<Value>(json) else {
56        return "<non-json body>".to_string();
57    };
58
59    redact_value_in_place(&mut v);
60    serde_json::to_string(&v).unwrap_or_else(|_| "<unserializable json>".to_string())
61}
62
63fn redact_value_in_place(v: &mut Value) {
64    match v {
65        Value::Object(map) => {
66            for (_k, child) in map.iter_mut() {
67                redact_value_in_place(child);
68            }
69        }
70        Value::Array(items) => {
71            for child in items.iter_mut() {
72                redact_value_in_place(child);
73            }
74        }
75        Value::String(s) => {
76            let trimmed = s.trim();
77            if trimmed.starts_with("xox") {
78                *s = "<redacted>".to_string();
79            }
80        }
81        _ => {}
82    }
83}