use std::sync::atomic::{AtomicBool, Ordering};
static PLAINTEXT_OAUTH_TOKENS: AtomicBool = AtomicBool::new(false);
static OAUTH_CLAIM_VALUES: AtomicBool = AtomicBool::new(false);
static TOOL_CALL_ARGUMENTS: AtomicBool = AtomicBool::new(false);
static UPSTREAM_ERROR_BODIES: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
#[allow(
clippy::struct_excessive_bools,
reason = "each field is an independent operator-facing opt-in switch; grouping them into sub-structs would complicate the public API and the TOML surface for no safety gain"
)]
pub struct DiagnosticExposure {
pub plaintext_oauth_tokens: bool,
pub oauth_claim_values: bool,
pub tool_call_arguments: bool,
pub upstream_error_bodies: bool,
}
pub fn set_diagnostic_exposure(exposure: &DiagnosticExposure) {
PLAINTEXT_OAUTH_TOKENS.store(exposure.plaintext_oauth_tokens, Ordering::Relaxed);
OAUTH_CLAIM_VALUES.store(exposure.oauth_claim_values, Ordering::Relaxed);
TOOL_CALL_ARGUMENTS.store(exposure.tool_call_arguments, Ordering::Relaxed);
UPSTREAM_ERROR_BODIES.store(exposure.upstream_error_bodies, Ordering::Relaxed);
}
pub(crate) fn plaintext_oauth_tokens() -> bool {
PLAINTEXT_OAUTH_TOKENS.load(Ordering::Relaxed)
}
#[cfg_attr(
not(feature = "oauth"),
allow(
dead_code,
reason = "only consumed by the oauth module; kept unconditional so the \
switch set is uniform across feature combinations"
)
)]
pub(crate) fn oauth_claim_values() -> bool {
OAUTH_CLAIM_VALUES.load(Ordering::Relaxed)
}
pub(crate) fn tool_call_arguments() -> bool {
TOOL_CALL_ARGUMENTS.load(Ordering::Relaxed)
}
#[cfg_attr(
not(feature = "oauth"),
allow(
dead_code,
reason = "only consumed by the oauth module; kept unconditional so the \
switch set is uniform across feature combinations"
)
)]
pub(crate) fn upstream_error_bodies() -> bool {
UPSTREAM_ERROR_BODIES.load(Ordering::Relaxed)
}
#[cfg(test)]
pub(crate) struct ExposureTestGuard {
_lock: std::sync::MutexGuard<'static, ()>,
previous: DiagnosticExposure,
}
#[cfg(test)]
impl ExposureTestGuard {
pub(crate) fn acquire() -> Self {
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let lock = TEST_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
Self {
_lock: lock,
previous: DiagnosticExposure {
plaintext_oauth_tokens: plaintext_oauth_tokens(),
oauth_claim_values: oauth_claim_values(),
tool_call_arguments: tool_call_arguments(),
upstream_error_bodies: upstream_error_bodies(),
},
}
}
}
#[cfg(test)]
impl Drop for ExposureTestGuard {
fn drop(&mut self) {
set_diagnostic_exposure(&self.previous);
}
}
#[cfg(test)]
mod tests {
use super::{
DiagnosticExposure, ExposureTestGuard, oauth_claim_values, plaintext_oauth_tokens,
set_diagnostic_exposure, tool_call_arguments,
};
#[test]
fn default_exposure_is_fully_redacted() {
let _guard = ExposureTestGuard::acquire();
set_diagnostic_exposure(&DiagnosticExposure::default());
assert!(!plaintext_oauth_tokens(), "tokens must default to redacted");
assert!(!oauth_claim_values(), "claims must default to redacted");
assert!(!tool_call_arguments(), "arguments must default to redacted");
}
#[test]
fn each_switch_is_independently_settable() {
let _guard = ExposureTestGuard::acquire();
set_diagnostic_exposure(&DiagnosticExposure {
plaintext_oauth_tokens: true,
..DiagnosticExposure::default()
});
assert!(plaintext_oauth_tokens());
assert!(!oauth_claim_values());
assert!(!tool_call_arguments());
set_diagnostic_exposure(&DiagnosticExposure {
oauth_claim_values: true,
..DiagnosticExposure::default()
});
assert!(!plaintext_oauth_tokens());
assert!(oauth_claim_values());
assert!(!tool_call_arguments());
set_diagnostic_exposure(&DiagnosticExposure {
tool_call_arguments: true,
..DiagnosticExposure::default()
});
assert!(!plaintext_oauth_tokens());
assert!(!oauth_claim_values());
assert!(tool_call_arguments());
}
#[test]
fn guard_restores_previous_state_on_drop() {
let guard = ExposureTestGuard::acquire();
set_diagnostic_exposure(&DiagnosticExposure {
plaintext_oauth_tokens: true,
oauth_claim_values: true,
tool_call_arguments: true,
upstream_error_bodies: true,
});
assert!(plaintext_oauth_tokens());
drop(guard);
let _reacquired = ExposureTestGuard::acquire();
assert!(
!plaintext_oauth_tokens() && !oauth_claim_values() && !tool_call_arguments(),
"dropping the guard must restore the pre-acquire state"
);
}
}