Skip to main content

qubit_progress/model/
progress_event_builder.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::time::Duration;
11
12use super::{
13    ProgressCounter,
14    ProgressEvent,
15    ProgressPhase,
16    ProgressSchema,
17    ProgressStage,
18};
19
20/// Builder for [`ProgressEvent`].
21///
22/// The builder keeps the common path compact by carrying the event schema and
23/// letting callers append named metric counters with a closure.
24///
25/// # Examples
26///
27/// ```
28/// use std::time::Duration;
29///
30/// use qubit_progress::{
31///     ProgressEvent,
32///     ProgressMetric,
33///     ProgressPhase,
34///     ProgressSchema,
35/// };
36///
37/// let schema = ProgressSchema::new(vec![ProgressMetric::new("bytes", "Bytes")]);
38/// let event = ProgressEvent::builder(schema)
39///     .running()
40///     .counter("bytes", |counter| counter.total(8).completed(3).active(1))
41///     .stage_named("copy", "Copy files")
42///     .elapsed(Duration::from_secs(2))
43///     .build();
44///
45/// assert_eq!(event.phase(), ProgressPhase::Running);
46/// assert_eq!(event.counter("bytes").map(|c| c.completed_count()), Some(3));
47/// assert_eq!(event.stage().map(|stage| stage.id()), Some("copy"));
48/// ```
49#[derive(Debug, Clone, PartialEq)]
50pub struct ProgressEventBuilder {
51    /// Metric schema carried by the event being built.
52    pub(crate) schema: ProgressSchema,
53    /// Lifecycle phase of the event being built.
54    pub(crate) phase: ProgressPhase,
55    /// Metric counters for the event being built.
56    pub(crate) counters: Vec<ProgressCounter>,
57    /// Optional current stage.
58    pub(crate) stage: Option<ProgressStage>,
59    /// Monotonic elapsed duration.
60    pub(crate) elapsed: Duration,
61}
62
63impl ProgressEventBuilder {
64    /// Creates a builder for a schema.
65    ///
66    /// # Parameters
67    ///
68    /// * `schema` - Metric schema carried by the built event.
69    ///
70    /// # Returns
71    ///
72    /// A builder whose phase is [`ProgressPhase::Running`], elapsed duration is
73    /// zero, and counters are empty.
74    #[inline]
75    pub fn new(schema: ProgressSchema) -> Self {
76        Self {
77            schema,
78            phase: ProgressPhase::Running,
79            counters: Vec::new(),
80            stage: None,
81            elapsed: Duration::ZERO,
82        }
83    }
84
85    /// Configures the lifecycle phase.
86    ///
87    /// # Parameters
88    ///
89    /// * `phase` - Lifecycle phase to report.
90    ///
91    /// # Returns
92    ///
93    /// This builder with `phase` recorded.
94    #[inline]
95    #[must_use]
96    pub const fn phase(mut self, phase: ProgressPhase) -> Self {
97        self.phase = phase;
98        self
99    }
100
101    /// Configures the event as started.
102    ///
103    /// # Returns
104    ///
105    /// This builder with [`ProgressPhase::Started`].
106    #[inline]
107    #[must_use]
108    pub const fn started(self) -> Self {
109        self.phase(ProgressPhase::Started)
110    }
111
112    /// Configures the event as running.
113    ///
114    /// # Returns
115    ///
116    /// This builder with [`ProgressPhase::Running`].
117    #[inline]
118    #[must_use]
119    pub const fn running(self) -> Self {
120        self.phase(ProgressPhase::Running)
121    }
122
123    /// Configures the event as finished.
124    ///
125    /// # Returns
126    ///
127    /// This builder with [`ProgressPhase::Finished`].
128    #[inline]
129    #[must_use]
130    pub const fn finished(self) -> Self {
131        self.phase(ProgressPhase::Finished)
132    }
133
134    /// Configures the event as failed.
135    ///
136    /// # Returns
137    ///
138    /// This builder with [`ProgressPhase::Failed`].
139    #[inline]
140    #[must_use]
141    pub const fn failed(self) -> Self {
142        self.phase(ProgressPhase::Failed)
143    }
144
145    /// Configures the event as canceled.
146    ///
147    /// # Returns
148    ///
149    /// This builder with [`ProgressPhase::Canceled`].
150    #[inline]
151    #[must_use]
152    pub const fn canceled(self) -> Self {
153        self.phase(ProgressPhase::Canceled)
154    }
155
156    /// Replaces the current counter list.
157    ///
158    /// # Parameters
159    ///
160    /// * `counters` - Complete counter list to carry in the built event.
161    ///
162    /// # Returns
163    ///
164    /// This builder with `counters` recorded.
165    #[inline]
166    #[must_use]
167    pub fn counters(mut self, counters: Vec<ProgressCounter>) -> Self {
168        self.counters = counters;
169        self
170    }
171
172    /// Appends one configured counter.
173    ///
174    /// # Parameters
175    ///
176    /// * `metric_id` - Metric identifier for the counter.
177    /// * `configure` - Closure that fills the counter values.
178    ///
179    /// # Returns
180    ///
181    /// This builder with the configured counter appended.
182    #[inline]
183    #[must_use]
184    pub fn counter<F>(mut self, metric_id: &str, configure: F) -> Self
185    where
186        F: FnOnce(ProgressCounter) -> ProgressCounter,
187    {
188        self.counters.push(configure(ProgressCounter::new(metric_id)));
189        self
190    }
191
192    /// Appends a prebuilt counter.
193    ///
194    /// # Parameters
195    ///
196    /// * `counter` - Counter to append.
197    ///
198    /// # Returns
199    ///
200    /// This builder with `counter` appended.
201    #[inline]
202    #[must_use]
203    pub fn add_counter(mut self, counter: ProgressCounter) -> Self {
204        self.counters.push(counter);
205        self
206    }
207
208    /// Configures the current stage.
209    ///
210    /// # Parameters
211    ///
212    /// * `stage` - Stage metadata to carry in the built event.
213    ///
214    /// # Returns
215    ///
216    /// This builder with `stage` recorded.
217    #[inline]
218    #[must_use]
219    pub fn stage(mut self, stage: ProgressStage) -> Self {
220        self.stage = Some(stage);
221        self
222    }
223
224    /// Configures the current stage from an id and display name.
225    ///
226    /// # Parameters
227    ///
228    /// * `id` - Stable machine-readable stage identifier.
229    /// * `name` - Human-readable stage name.
230    ///
231    /// # Returns
232    ///
233    /// This builder with a stage created from `id` and `name`.
234    #[inline]
235    #[must_use]
236    pub fn stage_named(self, id: &str, name: &str) -> Self {
237        self.stage(ProgressStage::new(id, name))
238    }
239
240    /// Configures the elapsed duration.
241    ///
242    /// # Parameters
243    ///
244    /// * `elapsed` - Monotonic elapsed duration to carry in the event.
245    ///
246    /// # Returns
247    ///
248    /// This builder with `elapsed` recorded.
249    #[inline]
250    #[must_use]
251    pub const fn elapsed(mut self, elapsed: Duration) -> Self {
252        self.elapsed = elapsed;
253        self
254    }
255
256    /// Builds the progress event.
257    ///
258    /// # Returns
259    ///
260    /// An immutable [`ProgressEvent`] with the configured values.
261    #[inline]
262    pub fn build(self) -> ProgressEvent {
263        ProgressEvent::new(self)
264    }
265}