Skip to main content

pitchfork_cli/
deprecated.rs

1//! Deprecation warnings that are tied to calendar dates.
2//!
3//! [`deprecated_at!`] warns once per id from the date a feature is deprecated,
4//! and trips a debug assertion once the date it was meant to be removed has
5//! passed, so deprecated code cannot linger unnoticed.
6
7use std::collections::HashSet;
8use std::sync::{LazyLock, Mutex};
9
10/// Today's date in UTC.
11pub fn today() -> chrono::NaiveDate {
12    chrono::Utc::now().date_naive()
13}
14
15/// Parse a `YYYY-MM-DD` date; panics on malformed input (a programmer error).
16pub fn date(s: &str) -> chrono::NaiveDate {
17    chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
18        .unwrap_or_else(|_| panic!("invalid date {s:?} in deprecated_at!, expected YYYY-MM-DD"))
19}
20
21/// Ids that have already warned in this process.
22pub static WARNED: LazyLock<Mutex<HashSet<String>>> = LazyLock::new(Default::default);
23
24/// Warn that something is deprecated, once per `$id`.
25///
26/// ```ignore
27/// deprecated_at!("2026-10-03", "2027-10-03", format!("port-fields:{name}"), "use `port` instead.");
28/// ```
29///
30/// Dates are `YYYY-MM-DD`, compared against today's date in UTC.
31///
32/// - Before `$warn_at` nothing is printed.
33/// - From `$warn_at` the message is logged once per process for each `$id`.
34/// - From `$remove_at` a debug build panics, as a reminder to delete the
35///   deprecated code.
36///
37/// The remaining arguments are a `format!` message.
38#[macro_export]
39macro_rules! deprecated_at {
40    ($warn_at:expr, $remove_at:expr, $id:expr, $($arg:tt)*) => {{
41        let warn_date = $crate::deprecated::date($warn_at);
42        let remove_date = $crate::deprecated::date($remove_at);
43        let today = $crate::deprecated::today();
44        debug_assert!(
45            today < remove_date,
46            "Deprecated code [{}] should have been removed on {}. Please remove this deprecated functionality.",
47            $id, $remove_at
48        );
49        if today >= warn_date
50            && $crate::deprecated::WARNED.lock().unwrap().insert(($id).to_string())
51        {
52            ::log::warn!(
53                "deprecated [{}]: {} This will be removed after {}.",
54                $id, format!($($arg)*), $remove_at
55            );
56        }
57    }};
58}
59
60#[cfg(test)]
61mod tests {
62    #[test]
63    fn warns_once_per_id() {
64        deprecated_at!("2000-01-01", "9999-01-01", "test-once", "old thing.");
65        deprecated_at!("2000-01-01", "9999-01-01", "test-once", "old thing.");
66        assert!(super::WARNED.lock().unwrap().contains("test-once"));
67    }
68
69    #[test]
70    fn silent_before_warn_version() {
71        deprecated_at!("9998-01-01", "9999-01-01", "test-future", "not yet.");
72        assert!(!super::WARNED.lock().unwrap().contains("test-future"));
73    }
74}