1use std::collections::HashMap;
31use std::fmt;
32use std::sync::{Arc, Mutex, OnceLock};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct Version {
37 pub major: u16,
38 pub minor: u16,
39 pub patch: u16,
40}
41
42impl Version {
43 pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
45 Self {
46 major,
47 minor,
48 patch,
49 }
50 }
51
52 pub fn parse(s: &str) -> Option<Self> {
54 let parts: Vec<&str> = s.split('.').collect();
55 if parts.len() != 3 {
56 return None;
57 }
58
59 let major = parts[0].parse().ok()?;
60 let minor = parts[1].parse().ok()?;
61 let patch = parts[2].parse().ok()?;
62
63 Some(Self::new(major, minor, patch))
64 }
65
66 pub fn is_compatible_with(&self, other: &Version) -> bool {
69 if self.major != other.major {
71 return false;
72 }
73
74 if self.major == 0 && self.minor != other.minor {
76 return false;
77 }
78
79 true
80 }
81}
82
83impl fmt::Display for Version {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
86 }
87}
88
89pub const TORSH_VERSION: Version = Version::new(0, 1, 0);
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
94pub enum DeprecationSeverity {
95 Soft,
97 Hard,
99 Critical,
101}
102
103#[derive(Debug, Clone)]
105pub struct DeprecationInfo {
106 pub api_name: String,
108 pub deprecated_in: Version,
110 pub removed_in: Version,
112 pub replacement: Option<String>,
114 pub reason: Option<String>,
116 pub migration_guide: Option<String>,
118 pub severity: DeprecationSeverity,
120}
121
122impl DeprecationInfo {
123 pub fn new(api_name: impl Into<String>, deprecated_in: Version, removed_in: Version) -> Self {
125 Self {
126 api_name: api_name.into(),
127 deprecated_in,
128 removed_in,
129 replacement: None,
130 reason: None,
131 migration_guide: None,
132 severity: DeprecationSeverity::Soft,
133 }
134 }
135
136 pub fn with_replacement(mut self, replacement: impl Into<String>) -> Self {
138 self.replacement = Some(replacement.into());
139 self
140 }
141
142 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
144 self.reason = Some(reason.into());
145 self
146 }
147
148 pub fn with_migration_guide(mut self, guide: impl Into<String>) -> Self {
150 self.migration_guide = Some(guide.into());
151 self
152 }
153
154 pub fn with_severity(mut self, severity: DeprecationSeverity) -> Self {
156 self.severity = severity;
157 self
158 }
159
160 pub fn is_removed(&self) -> bool {
162 TORSH_VERSION >= self.removed_in
163 }
164
165 pub fn should_warn(&self) -> bool {
167 TORSH_VERSION >= self.deprecated_in && TORSH_VERSION < self.removed_in
168 }
169
170 pub fn format_warning(&self) -> String {
172 let mut msg = format!(
173 "API '{}' is deprecated since version {} and will be removed in version {}",
174 self.api_name, self.deprecated_in, self.removed_in
175 );
176
177 if let Some(ref replacement) = self.replacement {
178 msg.push_str(&format!(". Use '{}' instead", replacement));
179 }
180
181 if let Some(ref reason) = self.reason {
182 msg.push_str(&format!(". Reason: {}", reason));
183 }
184
185 if let Some(ref guide) = self.migration_guide {
186 msg.push_str(&format!(". Migration guide: {}", guide));
187 }
188
189 msg
190 }
191}
192
193static DEPRECATION_TRACKER: OnceLock<Arc<Mutex<DeprecationTracker>>> = OnceLock::new();
195
196struct DeprecationTracker {
198 deprecations: HashMap<String, DeprecationInfo>,
200 warning_counts: HashMap<String, usize>,
202 max_warnings_per_api: usize,
204 emit_warnings: bool,
206}
207
208impl Default for DeprecationTracker {
209 fn default() -> Self {
210 Self {
211 deprecations: HashMap::new(),
212 warning_counts: HashMap::new(),
213 max_warnings_per_api: 10, emit_warnings: !cfg!(test), }
216 }
217}
218
219impl DeprecationTracker {
220 fn register(&mut self, info: DeprecationInfo) {
222 self.deprecations.insert(info.api_name.clone(), info);
223 }
224
225 fn emit_warning(&mut self, api_name: &str) -> bool {
227 let info = match self.deprecations.get(api_name) {
229 Some(info) => info,
230 None => return false,
231 };
232
233 if !info.should_warn() {
235 return false;
236 }
237
238 let count = self.warning_counts.entry(api_name.to_string()).or_insert(0);
240 if *count >= self.max_warnings_per_api {
241 return false;
242 }
243 *count += 1;
244
245 if self.emit_warnings {
247 eprintln!("⚠️ DEPRECATION WARNING: {}", info.format_warning());
248
249 if *count == self.max_warnings_per_api {
250 eprintln!(
251 "⚠️ (Further warnings for '{}' will be suppressed)",
252 api_name
253 );
254 }
255 }
256
257 true
258 }
259
260 fn get_info(&self, api_name: &str) -> Option<&DeprecationInfo> {
262 self.deprecations.get(api_name)
263 }
264
265 fn get_all_deprecations(&self) -> Vec<DeprecationInfo> {
267 self.deprecations.values().cloned().collect()
268 }
269
270 fn get_warning_stats(&self) -> HashMap<String, usize> {
272 self.warning_counts.clone()
273 }
274
275 fn clear_warning_counts(&mut self) {
277 self.warning_counts.clear();
278 }
279
280 fn set_emit_warnings(&mut self, emit: bool) {
282 self.emit_warnings = emit;
283 }
284
285 fn set_max_warnings_per_api(&mut self, max: usize) {
287 self.max_warnings_per_api = max;
288 }
289}
290
291fn get_tracker() -> Arc<Mutex<DeprecationTracker>> {
293 DEPRECATION_TRACKER
294 .get_or_init(|| Arc::new(Mutex::new(DeprecationTracker::default())))
295 .clone()
296}
297
298pub fn register_deprecation(info: DeprecationInfo) {
312 get_tracker()
313 .lock()
314 .expect("lock should not be poisoned")
315 .register(info);
316}
317
318pub fn deprecation_warning(api_name: &str) -> bool {
328 get_tracker()
329 .lock()
330 .expect("lock should not be poisoned")
331 .emit_warning(api_name)
332}
333
334pub fn deprecation_warning_inline(
349 api_name: &str,
350 deprecated_in: Version,
351 removed_in: Version,
352 replacement: Option<&str>,
353) {
354 let mut info = DeprecationInfo::new(api_name, deprecated_in, removed_in);
355 if let Some(repl) = replacement {
356 info = info.with_replacement(repl);
357 }
358 register_deprecation(info);
359 deprecation_warning(api_name);
360}
361
362pub fn get_deprecation_info(api_name: &str) -> Option<DeprecationInfo> {
364 get_tracker()
365 .lock()
366 .expect("lock should not be poisoned")
367 .get_info(api_name)
368 .cloned()
369}
370
371pub fn get_all_deprecations() -> Vec<DeprecationInfo> {
373 get_tracker()
374 .lock()
375 .expect("lock should not be poisoned")
376 .get_all_deprecations()
377}
378
379pub fn get_deprecation_stats() -> HashMap<String, usize> {
381 get_tracker()
382 .lock()
383 .expect("lock should not be poisoned")
384 .get_warning_stats()
385}
386
387pub fn clear_deprecation_counts() {
389 get_tracker()
390 .lock()
391 .expect("lock should not be poisoned")
392 .clear_warning_counts();
393}
394
395pub fn configure_deprecation_warnings(emit: bool, max_per_api: usize) {
397 let binding = get_tracker();
398 let mut tracker = binding.lock().expect("lock should not be poisoned");
399 tracker.set_emit_warnings(emit);
400 tracker.set_max_warnings_per_api(max_per_api);
401}
402
403#[cfg(test)]
406pub fn reset_deprecation_tracker() {
407 let binding = get_tracker();
408 let mut tracker = binding.lock().expect("lock should not be poisoned");
409 tracker.deprecations.clear();
410 tracker.warning_counts.clear();
411 tracker.max_warnings_per_api = 10;
412 tracker.emit_warnings = false; }
414
415pub struct DeprecationReport {
417 pub active: Vec<DeprecationInfo>,
419 pub removed: Vec<DeprecationInfo>,
421 pub pending: Vec<DeprecationInfo>,
423 pub warning_stats: HashMap<String, usize>,
425}
426
427impl DeprecationReport {
428 pub fn generate() -> Self {
430 let binding = get_tracker();
431 let tracker = binding.lock().expect("lock should not be poisoned");
432 let all_deprecations = tracker.get_all_deprecations();
433
434 let mut active = Vec::new();
435 let mut removed = Vec::new();
436 let mut pending = Vec::new();
437
438 for info in all_deprecations {
439 if info.is_removed() {
440 removed.push(info);
441 } else if info.should_warn() {
442 active.push(info);
443 } else {
444 pending.push(info);
445 }
446 }
447
448 Self {
449 active,
450 removed,
451 pending,
452 warning_stats: tracker.get_warning_stats(),
453 }
454 }
455
456 pub fn format(&self) -> String {
458 let mut report = String::from("ToRSh API Deprecation Report\n");
459 report.push_str("==============================\n\n");
460
461 report.push_str(&format!("Current Version: {}\n\n", TORSH_VERSION));
462
463 if !self.active.is_empty() {
464 report.push_str(&format!("Active Deprecations ({})\n", self.active.len()));
465 report.push_str("-------------------------\n");
466 for info in &self.active {
467 report.push_str(&format!(
468 " • {} (deprecated in {}, removed in {})\n",
469 info.api_name, info.deprecated_in, info.removed_in
470 ));
471 if let Some(ref repl) = info.replacement {
472 report.push_str(&format!(" Replacement: {}\n", repl));
473 }
474 if let Some(count) = self.warning_stats.get(&info.api_name) {
475 report.push_str(&format!(" Warnings emitted: {}\n", count));
476 }
477 }
478 report.push('\n');
479 }
480
481 if !self.removed.is_empty() {
482 report.push_str(&format!("Removed APIs ({})\n", self.removed.len()));
483 report.push_str("-----------------\n");
484 for info in &self.removed {
485 report.push_str(&format!(
486 " • {} (removed in {})\n",
487 info.api_name, info.removed_in
488 ));
489 }
490 report.push('\n');
491 }
492
493 if !self.pending.is_empty() {
494 report.push_str(&format!("Pending Deprecations ({})\n", self.pending.len()));
495 report.push_str("-------------------------\n");
496 for info in &self.pending {
497 report.push_str(&format!(
498 " • {} (will be deprecated in {})\n",
499 info.api_name, info.deprecated_in
500 ));
501 }
502 report.push('\n');
503 }
504
505 report
506 }
507}
508
509#[macro_export]
511macro_rules! deprecated {
512 ($name:expr, $deprecated_in:expr, $removed_in:expr) => {
513 $crate::api_compat::deprecation_warning_inline($name, $deprecated_in, $removed_in, None);
514 };
515 ($name:expr, $deprecated_in:expr, $removed_in:expr, $replacement:expr) => {
516 $crate::api_compat::deprecation_warning_inline(
517 $name,
518 $deprecated_in,
519 $removed_in,
520 Some($replacement),
521 );
522 };
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_version_parsing() {
531 let v = Version::parse("1.2.3").expect("parse should succeed");
532 assert_eq!(v, Version::new(1, 2, 3));
533
534 assert!(Version::parse("invalid").is_none());
535 assert!(Version::parse("1.2").is_none());
536 }
537
538 #[test]
539 fn test_version_compatibility() {
540 let v1 = Version::new(1, 2, 3);
541 let v2 = Version::new(1, 3, 0);
542 let v3 = Version::new(2, 0, 0);
543
544 assert!(v1.is_compatible_with(&v2));
545 assert!(!v1.is_compatible_with(&v3));
546
547 let v4 = Version::new(0, 1, 0);
549 let v5 = Version::new(0, 1, 5);
550 let v6 = Version::new(0, 2, 0);
551
552 assert!(v4.is_compatible_with(&v5));
553 assert!(!v4.is_compatible_with(&v6));
554 }
555
556 #[test]
557 fn test_deprecation_info() {
558 let info = DeprecationInfo::new("old_func", Version::new(0, 1, 0), Version::new(0, 2, 0))
559 .with_replacement("new_func")
560 .with_reason("Better performance");
561
562 assert_eq!(info.api_name, "old_func");
563 assert_eq!(info.replacement, Some("new_func".to_string()));
564 assert_eq!(info.reason, Some("Better performance".to_string()));
565 }
566
567 #[test]
568 fn test_deprecation_registration() {
569 clear_deprecation_counts();
570
571 let info = DeprecationInfo::new("test_api", Version::new(0, 0, 1), Version::new(1, 0, 0))
572 .with_replacement("new_test_api");
573
574 register_deprecation(info);
575
576 let retrieved =
577 get_deprecation_info("test_api").expect("get_deprecation_info should succeed");
578 assert_eq!(retrieved.api_name, "test_api");
579 assert_eq!(retrieved.replacement, Some("new_test_api".to_string()));
580 }
581
582 #[test]
583 fn test_deprecation_warning() {
584 configure_deprecation_warnings(true, 10);
586 clear_deprecation_counts();
587
588 let info = DeprecationInfo::new(
589 "test_warning_api",
590 Version::new(0, 0, 1),
591 Version::new(1, 0, 0),
592 );
593
594 register_deprecation(info);
595 deprecation_warning("test_warning_api");
596
597 let stats = get_deprecation_stats();
598 let count = stats.get("test_warning_api").copied().unwrap_or(0);
600 assert!(count >= 1, "Expected at least 1 warning, got {}", count);
601 }
602
603 #[test]
604 fn test_deprecation_report() {
605 clear_deprecation_counts();
606
607 register_deprecation(DeprecationInfo::new(
609 "active_api",
610 Version::new(0, 0, 1),
611 Version::new(1, 0, 0),
612 ));
613
614 let report = DeprecationReport::generate();
615 let formatted = report.format();
616
617 assert!(formatted.contains("ToRSh API Deprecation Report"));
618 }
619
620 #[test]
621 fn test_max_warnings_limit() {
622 let unique_api = "limited_api_max_warnings_test_unique_v2";
624
625 reset_deprecation_tracker();
627
628 configure_deprecation_warnings(false, 3);
630
631 let info = DeprecationInfo::new(unique_api, Version::new(0, 0, 1), Version::new(1, 0, 0));
632
633 register_deprecation(info);
634
635 let mut actual_emitted = 0;
637 for _ in 0..5 {
638 if deprecation_warning(unique_api) {
639 actual_emitted += 1;
640 }
641 }
642
643 assert_eq!(
645 actual_emitted, 3,
646 "Should have returned true for exactly 3 warnings"
647 );
648
649 let stats = get_deprecation_stats();
650 assert_eq!(stats.get(unique_api), Some(&3)); }
652}