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}