1use std::fmt;
10use std::sync::atomic::{AtomicBool, Ordering};
11
12use super::internal::is_caller_internal;
13
14#[derive(Debug, Clone)]
16pub struct AgentChainDeprecationWarning {
17 message: String,
18}
19
20impl AgentChainDeprecationWarning {
21 pub fn new(message: impl Into<String>) -> Self {
23 Self {
24 message: message.into(),
25 }
26 }
27
28 pub fn message(&self) -> &str {
30 &self.message
31 }
32}
33
34impl fmt::Display for AgentChainDeprecationWarning {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(f, "{}", self.message)
37 }
38}
39
40impl std::error::Error for AgentChainDeprecationWarning {}
41
42#[derive(Debug, Clone)]
44pub struct AgentChainPendingDeprecationWarning {
45 message: String,
46}
47
48impl AgentChainPendingDeprecationWarning {
49 pub fn new(message: impl Into<String>) -> Self {
51 Self {
52 message: message.into(),
53 }
54 }
55
56 pub fn message(&self) -> &str {
58 &self.message
59 }
60}
61
62impl fmt::Display for AgentChainPendingDeprecationWarning {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 write!(f, "{}", self.message)
65 }
66}
67
68impl std::error::Error for AgentChainPendingDeprecationWarning {}
69
70static SUPPRESS_DEPRECATION_WARNINGS: AtomicBool = AtomicBool::new(false);
72
73#[derive(Debug, Clone, Default)]
75pub struct DeprecationParams {
76 pub since: String,
78 pub message: Option<String>,
80 pub name: Option<String>,
82 pub alternative: Option<String>,
84 pub alternative_import: Option<String>,
86 pub pending: bool,
88 pub obj_type: Option<String>,
90 pub addendum: Option<String>,
92 pub removal: Option<String>,
94 pub package: Option<String>,
96}
97
98impl DeprecationParams {
99 pub fn new(since: impl Into<String>) -> Self {
101 Self {
102 since: since.into(),
103 ..Default::default()
104 }
105 }
106
107 pub fn with_name(mut self, name: impl Into<String>) -> Self {
109 self.name = Some(name.into());
110 self
111 }
112
113 pub fn with_message(mut self, message: impl Into<String>) -> Self {
115 self.message = Some(message.into());
116 self
117 }
118
119 pub fn with_alternative(mut self, alternative: impl Into<String>) -> Self {
121 self.alternative = Some(alternative.into());
122 self
123 }
124
125 pub fn with_alternative_import(mut self, alternative_import: impl Into<String>) -> Self {
127 self.alternative_import = Some(alternative_import.into());
128 self
129 }
130
131 pub fn with_pending(mut self, pending: bool) -> Self {
133 self.pending = pending;
134 self
135 }
136
137 pub fn with_obj_type(mut self, obj_type: impl Into<String>) -> Self {
139 self.obj_type = Some(obj_type.into());
140 self
141 }
142
143 pub fn with_addendum(mut self, addendum: impl Into<String>) -> Self {
145 self.addendum = Some(addendum.into());
146 self
147 }
148
149 pub fn with_removal(mut self, removal: impl Into<String>) -> Self {
151 self.removal = Some(removal.into());
152 self
153 }
154
155 pub fn with_package(mut self, package: impl Into<String>) -> Self {
157 self.package = Some(package.into());
158 self
159 }
160
161 pub fn validate(&self) -> Result<(), String> {
165 if self.pending && self.removal.is_some() {
166 return Err("A pending deprecation cannot have a scheduled removal".to_string());
167 }
168 if !self.pending && self.removal.is_none() && self.message.is_none() {
171 return Err(
172 "Need to determine which default deprecation schedule to use. \
173 Non-pending deprecations must specify a removal version."
174 .to_string(),
175 );
176 }
177 if self.alternative.is_some() && self.alternative_import.is_some() {
178 return Err("Cannot specify both alternative and alternative_import".to_string());
179 }
180 if let Some(ref alt_import) = self.alternative_import
181 && !alt_import.contains("::")
182 {
183 return Err(format!(
184 "alternative_import must be a fully qualified module path. Got {}",
185 alt_import
186 ));
187 }
188 Ok(())
189 }
190}
191
192#[derive(Debug, Clone)]
194pub struct RenameParameterParams {
195 pub since: String,
197 pub removal: String,
199 pub old: String,
201 pub new: String,
203}
204
205impl RenameParameterParams {
206 pub fn new(
208 since: impl Into<String>,
209 removal: impl Into<String>,
210 old: impl Into<String>,
211 new: impl Into<String>,
212 ) -> Self {
213 Self {
214 since: since.into(),
215 removal: removal.into(),
216 old: old.into(),
217 new: new.into(),
218 }
219 }
220}
221
222pub fn handle_renamed_parameter<T>(
260 params: &RenameParameterParams,
261 old_value: Option<T>,
262 new_value: Option<T>,
263 func_name: &str,
264 caller_module: &str,
265) -> Result<Option<T>, String> {
266 match (old_value, new_value) {
267 (Some(_), Some(_)) => Err(format!(
268 "{}() got multiple values for argument '{}'",
269 func_name, params.new
270 )),
271 (Some(old), None) => {
272 warn_deprecated(
274 DeprecationParams::new(¶ms.since)
275 .with_message(format!(
276 "The parameter `{}` of `{}` was deprecated in {} and will be removed in {}. Use `{}` instead.",
277 params.old, func_name, params.since, params.removal, params.new
278 ))
279 .with_removal(¶ms.removal),
280 caller_module,
281 );
282 Ok(Some(old))
283 }
284 (None, new) => Ok(new),
285 }
286}
287
288pub fn warn_deprecated(params: DeprecationParams, caller_module: &str) {
319 if SUPPRESS_DEPRECATION_WARNINGS.load(Ordering::Relaxed) {
321 return;
322 }
323
324 if is_caller_internal(caller_module) {
326 return;
327 }
328
329 if let Err(err) = params.validate() {
331 eprintln!("Invalid deprecation parameters: {}", err);
332 return;
333 }
334
335 let message = if let Some(msg) = params.message {
336 msg
337 } else {
338 let name = params.name.unwrap_or_else(|| "unknown".to_string());
339 let package = params.package.unwrap_or_else(|| "agent-chain".to_string());
340
341 let mut msg = if let Some(ref obj_type) = params.obj_type {
342 format!("The {} `{}`", obj_type, name)
343 } else {
344 format!("`{}`", name)
345 };
346
347 if params.pending {
348 msg.push_str(" will be deprecated in a future version");
349 } else {
350 msg.push_str(&format!(" was deprecated in {} {}", package, params.since));
351
352 if let Some(ref removal) = params.removal {
353 msg.push_str(&format!(" and will be removed in {}", removal));
354 }
355 }
356
357 if let Some(ref alternative_import) = params.alternative_import {
358 let alt_package = alternative_import
359 .split("::")
360 .next()
361 .unwrap_or(alternative_import)
362 .replace('_', "-");
363
364 if alt_package == package {
365 msg.push_str(&format!(". Use {} instead.", alternative_import));
366 } else {
367 let parts: Vec<&str> = alternative_import.rsplitn(2, "::").collect();
368 if parts.len() == 2 {
369 let alt_name = parts[0];
370 let alt_module = parts[1];
371 msg.push_str(&format!(
372 ". An updated version of the {} exists in the {} package and should be used instead. \
373 To use it add `{}` to your dependencies and import as `use {}::{};`.",
374 params.obj_type.as_deref().unwrap_or("item"),
375 alt_package,
376 alt_package,
377 alt_module,
378 alt_name
379 ));
380 }
381 }
382 } else if let Some(ref alternative) = params.alternative {
383 msg.push_str(&format!(". Use {} instead.", alternative));
384 }
385
386 if let Some(ref addendum) = params.addendum {
387 msg.push(' ');
388 msg.push_str(addendum);
389 }
390
391 msg
392 };
393
394 if params.pending {
395 let warning = AgentChainPendingDeprecationWarning::new(message);
396 eprintln!("AgentChainPendingDeprecationWarning: {}", warning);
397 } else {
398 let warning = AgentChainDeprecationWarning::new(message);
399 eprintln!("AgentChainDeprecationWarning: {}", warning);
400 }
401}
402
403pub struct SuppressDeprecationWarnings {
405 previous_state: bool,
406}
407
408impl SuppressDeprecationWarnings {
409 pub fn new() -> Self {
411 let previous_state = SUPPRESS_DEPRECATION_WARNINGS.swap(true, Ordering::Relaxed);
412 Self { previous_state }
413 }
414}
415
416impl Default for SuppressDeprecationWarnings {
417 fn default() -> Self {
418 Self::new()
419 }
420}
421
422impl Drop for SuppressDeprecationWarnings {
423 fn drop(&mut self) {
424 SUPPRESS_DEPRECATION_WARNINGS.store(self.previous_state, Ordering::Relaxed);
425 }
426}
427
428pub fn suppress_deprecation_warnings() -> SuppressDeprecationWarnings {
442 SuppressDeprecationWarnings::new()
443}
444
445pub fn surface_deprecation_warnings() {
449 SUPPRESS_DEPRECATION_WARNINGS.store(false, Ordering::Relaxed);
450}
451
452#[macro_export]
474macro_rules! renamed_parameter {
475 (
476 since = $since:expr,
477 removal = $removal:expr,
478 old = $old_name:expr => $old_value:expr,
479 new = $new_name:expr => $new_value:expr,
480 func = $func_name:expr
481 ) => {{
482 let params =
483 $crate::api::RenameParameterParams::new($since, $removal, $old_name, $new_name);
484 $crate::api::handle_renamed_parameter(
485 ¶ms,
486 $old_value,
487 $new_value,
488 $func_name,
489 module_path!(),
490 )
491 }};
492}
493
494#[macro_export]
512macro_rules! deprecated {
513 ($since:expr, $name:expr $(, $key:ident = $value:expr)* $(,)?) => {{
514 let mut params = $crate::api::DeprecationParams::new($since).with_name($name);
515 $(
516 params = $crate::deprecated!(@set params, $key, $value);
517 )*
518 $crate::api::warn_deprecated(params, module_path!())
519 }};
520 (@set $params:expr, message, $value:expr) => {
521 $params.with_message($value)
522 };
523 (@set $params:expr, alternative, $value:expr) => {
524 $params.with_alternative($value)
525 };
526 (@set $params:expr, alternative_import, $value:expr) => {
527 $params.with_alternative_import($value)
528 };
529 (@set $params:expr, pending, $value:expr) => {
530 $params.with_pending($value)
531 };
532 (@set $params:expr, obj_type, $value:expr) => {
533 $params.with_obj_type($value)
534 };
535 (@set $params:expr, addendum, $value:expr) => {
536 $params.with_addendum($value)
537 };
538 (@set $params:expr, removal, $value:expr) => {
539 $params.with_removal($value)
540 };
541 (@set $params:expr, package, $value:expr) => {
542 $params.with_package($value)
543 };
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn test_deprecation_warning_creation() {
552 let warning = AgentChainDeprecationWarning::new("Test warning");
553 assert_eq!(warning.message(), "Test warning");
554 assert_eq!(format!("{}", warning), "Test warning");
555 }
556
557 #[test]
558 fn test_pending_deprecation_warning_creation() {
559 let warning = AgentChainPendingDeprecationWarning::new("Test pending warning");
560 assert_eq!(warning.message(), "Test pending warning");
561 assert_eq!(format!("{}", warning), "Test pending warning");
562 }
563
564 #[test]
565 fn test_deprecation_params_builder() {
566 let params = DeprecationParams::new("0.1.0")
567 .with_name("test_function")
568 .with_obj_type("function")
569 .with_alternative("new_function")
570 .with_removal("0.2.0");
571
572 assert_eq!(params.since, "0.1.0");
573 assert_eq!(params.name, Some("test_function".to_string()));
574 assert_eq!(params.obj_type, Some("function".to_string()));
575 assert_eq!(params.alternative, Some("new_function".to_string()));
576 assert_eq!(params.removal, Some("0.2.0".to_string()));
577 }
578
579 #[test]
580 fn test_deprecation_params_validation() {
581 let params = DeprecationParams::new("0.1.0")
583 .with_name("test")
584 .with_removal("0.2.0");
585 assert!(params.validate().is_ok());
586
587 let params = DeprecationParams::new("0.1.0")
589 .with_name("test")
590 .with_pending(true);
591 assert!(params.validate().is_ok());
592
593 let params = DeprecationParams::new("0.1.0")
595 .with_name("test")
596 .with_message("Custom deprecation message");
597 assert!(params.validate().is_ok());
598
599 let params = DeprecationParams::new("0.1.0")
601 .with_pending(true)
602 .with_removal("0.2.0");
603 assert!(params.validate().is_err());
604
605 let params = DeprecationParams::new("0.1.0").with_name("test");
607 assert!(params.validate().is_err());
608
609 let params = DeprecationParams::new("0.1.0")
611 .with_alternative("new_thing")
612 .with_alternative_import("some::path::NewThing")
613 .with_removal("0.2.0");
614 assert!(params.validate().is_err());
615
616 let params = DeprecationParams::new("0.1.0")
618 .with_alternative_import("InvalidPath")
619 .with_removal("0.2.0");
620 assert!(params.validate().is_err());
621 }
622
623 #[test]
624 fn test_suppress_deprecation_warnings() {
625 surface_deprecation_warnings();
627 assert!(!SUPPRESS_DEPRECATION_WARNINGS.load(Ordering::Relaxed));
628
629 {
630 let _guard = suppress_deprecation_warnings();
631 assert!(SUPPRESS_DEPRECATION_WARNINGS.load(Ordering::Relaxed));
632 }
633
634 assert!(!SUPPRESS_DEPRECATION_WARNINGS.load(Ordering::Relaxed));
635 }
636
637 #[test]
638 fn test_rename_parameter_params() {
639 let params = RenameParameterParams::new("0.1.0", "0.2.0", "old_name", "new_name");
640 assert_eq!(params.since, "0.1.0");
641 assert_eq!(params.removal, "0.2.0");
642 assert_eq!(params.old, "old_name");
643 assert_eq!(params.new, "new_name");
644 }
645
646 #[test]
647 fn test_handle_renamed_parameter_new_only() {
648 surface_deprecation_warnings();
649 let params = RenameParameterParams::new("0.1.0", "0.2.0", "old_param", "new_param");
650
651 let result = handle_renamed_parameter(
653 ¶ms,
654 None::<String>,
655 Some("new_value".to_string()),
656 "test_func",
657 "external_crate::module",
658 );
659 assert!(result.is_ok());
660 assert_eq!(result.unwrap(), Some("new_value".to_string()));
661 }
662
663 #[test]
664 fn test_handle_renamed_parameter_old_only() {
665 surface_deprecation_warnings();
666 let params = RenameParameterParams::new("0.1.0", "0.2.0", "old_param", "new_param");
667
668 let result = handle_renamed_parameter(
670 ¶ms,
671 Some("old_value".to_string()),
672 None,
673 "test_func",
674 "external_crate::module",
675 );
676 assert!(result.is_ok());
677 assert_eq!(result.unwrap(), Some("old_value".to_string()));
678 }
679
680 #[test]
681 fn test_handle_renamed_parameter_both_provided() {
682 let params = RenameParameterParams::new("0.1.0", "0.2.0", "old_param", "new_param");
683
684 let result = handle_renamed_parameter(
686 ¶ms,
687 Some("old_value".to_string()),
688 Some("new_value".to_string()),
689 "test_func",
690 "external_crate::module",
691 );
692 assert!(result.is_err());
693 assert!(
694 result
695 .unwrap_err()
696 .contains("got multiple values for argument")
697 );
698 }
699
700 #[test]
701 fn test_handle_renamed_parameter_none() {
702 let params = RenameParameterParams::new("0.1.0", "0.2.0", "old_param", "new_param");
703
704 let result = handle_renamed_parameter(
706 ¶ms,
707 None::<String>,
708 None,
709 "test_func",
710 "external_crate::module",
711 );
712 assert!(result.is_ok());
713 assert_eq!(result.unwrap(), None);
714 }
715}