agent_chain_core/api/
beta.rs

1//! Helper functions for marking parts of the Agent Chain API as beta.
2//!
3//! This module was loosely adapted from matplotlib's _api/deprecation.py module:
4//! https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/_api/deprecation.py
5//!
6//! **Warning:** This module is for internal use only. Do not use it in your own code.
7//! We may change the API at any time with no warning.
8
9use std::fmt;
10use std::sync::atomic::{AtomicBool, Ordering};
11
12use super::internal::is_caller_internal;
13
14/// A warning type for beta features in Agent Chain.
15#[derive(Debug, Clone)]
16pub struct AgentChainBetaWarning {
17    message: String,
18}
19
20impl AgentChainBetaWarning {
21    /// Create a new beta warning with the given message.
22    pub fn new(message: impl Into<String>) -> Self {
23        Self {
24            message: message.into(),
25        }
26    }
27
28    /// Get the warning message.
29    pub fn message(&self) -> &str {
30        &self.message
31    }
32}
33
34impl fmt::Display for AgentChainBetaWarning {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(f, "{}", self.message)
37    }
38}
39
40impl std::error::Error for AgentChainBetaWarning {}
41
42/// Global flag to suppress beta warnings.
43static SUPPRESS_BETA_WARNINGS: AtomicBool = AtomicBool::new(false);
44
45/// Parameters for configuring beta warnings.
46#[derive(Debug, Clone, Default)]
47pub struct BetaParams {
48    /// Override the default beta message.
49    pub message: Option<String>,
50    /// The name of the beta object.
51    pub name: Option<String>,
52    /// The object type being marked as beta (e.g., "function", "class", "method").
53    pub obj_type: Option<String>,
54    /// Additional text appended directly to the final message.
55    pub addendum: Option<String>,
56}
57
58impl BetaParams {
59    /// Create new beta parameters with the given name.
60    pub fn new(name: impl Into<String>) -> Self {
61        Self {
62            name: Some(name.into()),
63            ..Default::default()
64        }
65    }
66
67    /// Set the custom message.
68    pub fn with_message(mut self, message: impl Into<String>) -> Self {
69        self.message = Some(message.into());
70        self
71    }
72
73    /// Set the object type.
74    pub fn with_obj_type(mut self, obj_type: impl Into<String>) -> Self {
75        self.obj_type = Some(obj_type.into());
76        self
77    }
78
79    /// Set the addendum.
80    pub fn with_addendum(mut self, addendum: impl Into<String>) -> Self {
81        self.addendum = Some(addendum.into());
82        self
83    }
84}
85
86/// Display a standardized beta warning.
87///
88/// # Arguments
89///
90/// * `params` - Parameters for the beta warning.
91/// * `caller_module` - The module path of the caller (typically from `module_path!()` macro).
92///
93/// # Example
94///
95/// ```
96/// use agent_chain_core::api::{warn_beta, BetaParams};
97///
98/// // Simple warning
99/// warn_beta(BetaParams::new("my_function"), module_path!());
100///
101/// // With additional details
102/// warn_beta(
103///     BetaParams::new("MyClass")
104///         .with_obj_type("class")
105///         .with_addendum("Consider using StableClass instead."),
106///     module_path!()
107/// );
108/// ```
109pub fn warn_beta(params: BetaParams, caller_module: &str) {
110    // Skip if warnings are suppressed
111    if SUPPRESS_BETA_WARNINGS.load(Ordering::Relaxed) {
112        return;
113    }
114
115    // Skip if caller is internal
116    if is_caller_internal(caller_module) {
117        return;
118    }
119
120    let message = if let Some(msg) = params.message {
121        msg
122    } else {
123        let name = params.name.unwrap_or_else(|| "unknown".to_string());
124        let mut msg = if let Some(obj_type) = params.obj_type {
125            format!("The {} `{}`", obj_type, name)
126        } else {
127            format!("`{}`", name)
128        };
129
130        msg.push_str(" is in beta. It is actively being worked on, so the API may change.");
131
132        if let Some(addendum) = params.addendum {
133            msg.push(' ');
134            msg.push_str(&addendum);
135        }
136
137        msg
138    };
139
140    let warning = AgentChainBetaWarning::new(message);
141
142    // In Rust, we use tracing or log for warnings
143    // For now, we use eprintln to match Python's warnings behavior
144    eprintln!("AgentChainBetaWarning: {}", warning);
145}
146
147/// Guard that suppresses beta warnings while it exists.
148pub struct SuppressBetaWarnings {
149    previous_state: bool,
150}
151
152impl SuppressBetaWarnings {
153    /// Create a new guard that suppresses beta warnings.
154    pub fn new() -> Self {
155        let previous_state = SUPPRESS_BETA_WARNINGS.swap(true, Ordering::Relaxed);
156        Self { previous_state }
157    }
158}
159
160impl Default for SuppressBetaWarnings {
161    fn default() -> Self {
162        Self::new()
163    }
164}
165
166impl Drop for SuppressBetaWarnings {
167    fn drop(&mut self) {
168        SUPPRESS_BETA_WARNINGS.store(self.previous_state, Ordering::Relaxed);
169    }
170}
171
172/// Suppress beta warnings within a scope.
173///
174/// # Example
175///
176/// ```
177/// use agent_chain_core::api::suppress_beta_warnings;
178///
179/// {
180///     let _guard = suppress_beta_warnings();
181///     // Beta warnings are suppressed here
182/// }
183/// // Beta warnings are restored here
184/// ```
185pub fn suppress_beta_warnings() -> SuppressBetaWarnings {
186    SuppressBetaWarnings::new()
187}
188
189/// Enable beta warnings (unmute them).
190///
191/// This function enables beta warnings that may have been suppressed.
192pub fn surface_beta_warnings() {
193    SUPPRESS_BETA_WARNINGS.store(false, Ordering::Relaxed);
194}
195
196/// Macro to mark a function or method as beta.
197///
198/// This macro emits a beta warning when the decorated item is called.
199///
200/// # Example
201///
202/// ```ignore
203/// use agent_chain_core::beta;
204///
205/// #[beta]
206/// fn experimental_feature() {
207///     // Implementation
208/// }
209///
210/// #[beta(name = "custom_name", addendum = "Use stable_feature instead.")]
211/// fn another_beta_feature() {
212///     // Implementation
213/// }
214/// ```
215#[macro_export]
216macro_rules! beta {
217    ($name:expr) => {
218        $crate::api::warn_beta($crate::api::BetaParams::new($name), module_path!())
219    };
220    ($name:expr, $($key:ident = $value:expr),+ $(,)?) => {{
221        let mut params = $crate::api::BetaParams::new($name);
222        $(
223            params = $crate::api::beta!(@set params, $key, $value);
224        )+
225        $crate::api::warn_beta(params, module_path!())
226    }};
227    (@set $params:expr, message, $value:expr) => {
228        $params.with_message($value)
229    };
230    (@set $params:expr, obj_type, $value:expr) => {
231        $params.with_obj_type($value)
232    };
233    (@set $params:expr, addendum, $value:expr) => {
234        $params.with_addendum($value)
235    };
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[test]
243    fn test_beta_warning_creation() {
244        let warning = AgentChainBetaWarning::new("Test warning");
245        assert_eq!(warning.message(), "Test warning");
246        assert_eq!(format!("{}", warning), "Test warning");
247    }
248
249    #[test]
250    fn test_beta_params_builder() {
251        let params = BetaParams::new("test_function")
252            .with_obj_type("function")
253            .with_addendum("Consider using other_function.");
254
255        assert_eq!(params.name, Some("test_function".to_string()));
256        assert_eq!(params.obj_type, Some("function".to_string()));
257        assert_eq!(
258            params.addendum,
259            Some("Consider using other_function.".to_string())
260        );
261    }
262
263    #[test]
264    fn test_suppress_beta_warnings() {
265        // Ensure warnings are not suppressed initially
266        surface_beta_warnings();
267        assert!(!SUPPRESS_BETA_WARNINGS.load(Ordering::Relaxed));
268
269        {
270            let _guard = suppress_beta_warnings();
271            assert!(SUPPRESS_BETA_WARNINGS.load(Ordering::Relaxed));
272        }
273
274        assert!(!SUPPRESS_BETA_WARNINGS.load(Ordering::Relaxed));
275    }
276}