Skip to main content

qubit_budget/time/
time_budget_error.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Errors emitted by continuous monotonic deadline budgets.
9
10use std::time::Duration;
11
12use qubit_clock::MonotonicInstant;
13use qubit_clock::TimeError;
14use thiserror::Error;
15
16/// Failure facts for a continuous deadline check.
17///
18/// # Type Parameters
19///
20/// * `R` - Caller-defined resource value retained for diagnostics.
21///
22/// # Examples
23///
24/// ```
25/// use std::time::Duration;
26/// use qubit_budget::TimeBudget;
27/// use qubit_budget::TimeBudgetError;
28/// use qubit_clock::ManualMonotonicClock;
29///
30/// let clock = ManualMonotonicClock::new_shared();
31/// let budget = TimeBudget::for_duration("request", clock.clone(), Duration::from_secs(1))
32///     .expect("the deadline should be representable");
33/// clock.advance(Duration::from_secs(1)).expect("the clock should advance");
34/// assert!(matches!(budget.check(), Err(TimeBudgetError::Expired { .. })));
35/// ```
36#[must_use]
37#[derive(Debug, Error)]
38pub enum TimeBudgetError<R> {
39    /// The clock rejected a domain or instant operation.
40    #[error("time budget for {resource:?} failed: {source}")]
41    Clock {
42        /// Resource value associated with the deadline.
43        resource: R,
44        /// Underlying clock failure.
45        #[source]
46        source: TimeError,
47    },
48    /// The fixed deadline has already been reached.
49    #[error("time budget for {resource:?} expired at {deadline:?}; now is {now:?}")]
50    Expired {
51        /// Resource value associated with the deadline.
52        resource: R,
53        /// Fixed deadline.
54        deadline: MonotonicInstant,
55        /// Current sampled instant.
56        now: MonotonicInstant,
57    },
58    /// A prospective operation would reach or pass the deadline.
59    #[error("time budget for {resource:?} at {now:?} cannot fit {requested:?} before {deadline:?}")]
60    WouldExpire {
61        /// Resource value associated with the deadline.
62        resource: R,
63        /// Fixed deadline.
64        deadline: MonotonicInstant,
65        /// Current sampled instant.
66        now: MonotonicInstant,
67        /// Prospective operation duration.
68        requested: Duration,
69    },
70}
71
72impl<R> TimeBudgetError<R> {
73    /// Returns the resource by reference.
74    ///
75    /// # Returns
76    ///
77    /// Returns the resource by reference.
78    #[must_use]
79    #[inline(always)]
80    pub const fn resource(&self) -> &R {
81        match self {
82            Self::Clock { resource, .. } | Self::Expired { resource, .. } | Self::WouldExpire { resource, .. } => {
83                resource
84            }
85        }
86    }
87
88    /// Consumes the error and returns its resource.
89    ///
90    /// # Returns
91    ///
92    /// Consumes the error and returns its resource.
93    #[inline(always)]
94    #[must_use]
95    pub fn into_resource(self) -> R {
96        match self {
97            Self::Clock { resource, .. } | Self::Expired { resource, .. } | Self::WouldExpire { resource, .. } => {
98                resource
99            }
100        }
101    }
102
103    /// Returns the underlying clock error, when present.
104    ///
105    /// # Returns
106    ///
107    /// `Some` contains the underlying clock error for [`Self::Clock`]; `None`
108    /// is returned for [`Self::Expired`] and [`Self::WouldExpire`].
109    #[must_use]
110    #[inline(always)]
111    pub const fn clock_error(&self) -> Option<&TimeError> {
112        match self {
113            Self::Clock { source, .. } => Some(source),
114            Self::Expired { .. } | Self::WouldExpire { .. } => None,
115        }
116    }
117
118    /// Returns the deadline for deadline-related errors.
119    ///
120    /// # Returns
121    ///
122    /// `Some` contains the fixed deadline for [`Self::Expired`] and
123    /// [`Self::WouldExpire`]; `None` is returned for [`Self::Clock`].
124    #[must_use]
125    #[inline(always)]
126    pub const fn deadline(&self) -> Option<MonotonicInstant> {
127        match self {
128            Self::Expired { deadline, .. } | Self::WouldExpire { deadline, .. } => Some(*deadline),
129            Self::Clock { .. } => None,
130        }
131    }
132
133    /// Returns the sampled instant for deadline-related errors.
134    ///
135    /// # Returns
136    ///
137    /// `Some` contains the sampled instant for [`Self::Expired`] and
138    /// [`Self::WouldExpire`]; `None` is returned for [`Self::Clock`].
139    #[must_use]
140    #[inline(always)]
141    pub const fn now(&self) -> Option<MonotonicInstant> {
142        match self {
143            Self::Expired { now, .. } | Self::WouldExpire { now, .. } => Some(*now),
144            Self::Clock { .. } => None,
145        }
146    }
147
148    /// Returns the prospective duration for a would-expire error.
149    ///
150    /// # Returns
151    ///
152    /// `Some` contains the requested duration for [`Self::WouldExpire`];
153    /// `None` is returned for [`Self::Clock`] and [`Self::Expired`].
154    #[must_use]
155    #[inline(always)]
156    pub const fn requested(&self) -> Option<Duration> {
157        match self {
158            Self::WouldExpire { requested, .. } => Some(*requested),
159            Self::Clock { .. } | Self::Expired { .. } => None,
160        }
161    }
162}