Skip to main content

cloud_sdk/async_resource/
model.rs

1use core::{cmp::Ordering, fmt};
2
3use crate::action_polling::ActionUpdate;
4
5use super::{
6    AsyncResourceId, AsyncResourceLink, AsyncResourceText, AsyncResourceTimestamp,
7    AsyncResourceValidationError, MAX_ASYNC_ERRORS, MAX_ASYNC_EVENTS, MAX_ASYNC_PROGRESS_STEPS,
8};
9
10/// Provider-neutral lifecycle classification for asynchronous resources.
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub enum AsyncResourceStatus {
13    /// Accepted but not started.
14    Pending,
15    /// Scheduled for later execution.
16    Scheduled,
17    /// Currently executing.
18    Running,
19    /// Waiting for explicit caller input.
20    WaitingForInput,
21    /// Completed successfully.
22    Succeeded,
23    /// Completed with provider errors.
24    Failed,
25}
26
27/// Exhaustive polling disposition for one asynchronous task snapshot.
28#[derive(Debug, Eq, PartialEq)]
29pub enum AsyncPollDisposition<'a> {
30    /// The task can be consumed by the ordinary bounded action-polling driver.
31    Update(ActionUpdate<&'a [AsyncTaskError<'a>]>),
32    /// The provider requires explicit caller intervention before polling resumes.
33    WaitingForInput,
34    /// The provider reported success while retaining contradictory error evidence.
35    ContradictorySuccess(&'a [AsyncTaskError<'a>]),
36}
37
38impl AsyncResourceStatus {
39    /// Reports whether the provider lifecycle is complete.
40    #[must_use]
41    pub const fn is_terminal(self) -> bool {
42        matches!(self, Self::Succeeded | Self::Failed)
43    }
44}
45
46/// One bounded progress step.
47#[derive(Clone, Copy, Eq, PartialEq)]
48pub struct AsyncProgressStep<'a> {
49    name: AsyncResourceText<'a>,
50    status: AsyncResourceStatus,
51}
52
53impl<'a> AsyncProgressStep<'a> {
54    /// Creates one validated progress step.
55    #[must_use]
56    pub const fn new(name: AsyncResourceText<'a>, status: AsyncResourceStatus) -> Self {
57        Self { name, status }
58    }
59
60    /// Returns the sensitive progress-step name.
61    #[must_use]
62    pub const fn name(self) -> AsyncResourceText<'a> {
63        self.name
64    }
65
66    /// Returns the normalized progress status.
67    #[must_use]
68    pub const fn status(self) -> AsyncResourceStatus {
69        self.status
70    }
71}
72
73impl fmt::Debug for AsyncProgressStep<'_> {
74    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
75        formatter
76            .debug_struct("AsyncProgressStep")
77            .field("name", &"[redacted]")
78            .field("status", &self.status)
79            .finish()
80    }
81}
82
83/// One bounded provider task error with redacted diagnostics.
84#[derive(Clone, Copy, Eq, PartialEq)]
85pub struct AsyncTaskError<'a> {
86    message: AsyncResourceText<'a>,
87}
88
89impl<'a> AsyncTaskError<'a> {
90    /// Creates one task error.
91    #[must_use]
92    pub const fn new(message: AsyncResourceText<'a>) -> Self {
93        Self { message }
94    }
95
96    /// Returns the sensitive provider error message.
97    #[must_use]
98    pub const fn message(self) -> AsyncResourceText<'a> {
99        self.message
100    }
101}
102
103impl fmt::Debug for AsyncTaskError<'_> {
104    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105        formatter.write_str("AsyncTaskError([redacted])")
106    }
107}
108
109/// Complete borrowed fields used to validate one task snapshot.
110#[derive(Clone, Copy)]
111pub struct AsyncTaskParts<'a> {
112    /// Opaque task identifier.
113    pub id: AsyncResourceId<'a>,
114    /// Provider task type.
115    pub kind: AsyncResourceText<'a>,
116    /// Current normalized status.
117    pub status: AsyncResourceStatus,
118    /// Optional related-resource link. This is metadata, not an executable target.
119    pub link: Option<AsyncResourceLink<'a>>,
120    /// Optional provider task description.
121    pub message: Option<AsyncResourceText<'a>>,
122    /// Creation timestamp.
123    pub created_at: AsyncResourceTimestamp<'a>,
124    /// Last-update timestamp.
125    pub updated_at: AsyncResourceTimestamp<'a>,
126    /// Optional start timestamp.
127    pub started_at: Option<AsyncResourceTimestamp<'a>>,
128    /// Optional terminal timestamp.
129    pub finished_at: Option<AsyncResourceTimestamp<'a>>,
130    /// Borrowed bounded progress collection.
131    pub progress: &'a [AsyncProgressStep<'a>],
132    /// Borrowed bounded provider errors.
133    pub errors: &'a [AsyncTaskError<'a>],
134}
135
136/// Validated borrowed asynchronous task snapshot.
137pub struct AsyncTask<'a> {
138    parts: AsyncTaskParts<'a>,
139}
140
141impl<'a> AsyncTask<'a> {
142    /// Validates collection limits, timestamp ordering, and lifecycle coherence.
143    pub fn new(parts: AsyncTaskParts<'a>) -> Result<Self, AsyncResourceValidationError> {
144        if parts.progress.len() > MAX_ASYNC_PROGRESS_STEPS {
145            return Err(AsyncResourceValidationError::TooManyProgressSteps);
146        }
147        if parts.errors.len() > MAX_ASYNC_ERRORS {
148            return Err(AsyncResourceValidationError::TooManyErrors);
149        }
150        if parts.updated_at.compare(parts.created_at) == Ordering::Less
151            || parts
152                .started_at
153                .is_some_and(|value| value.compare(parts.created_at) == Ordering::Less)
154            || parts
155                .started_at
156                .is_some_and(|value| value.compare(parts.updated_at) == Ordering::Greater)
157            || parts
158                .finished_at
159                .is_some_and(|value| value.compare(parts.created_at) == Ordering::Less)
160            || parts
161                .finished_at
162                .is_some_and(|value| value.compare(parts.updated_at) == Ordering::Greater)
163            || matches!((parts.started_at, parts.finished_at), (Some(started), Some(finished))
164                if finished.compare(started) == Ordering::Less)
165        {
166            return Err(AsyncResourceValidationError::TimestampOrder);
167        }
168        if parts.status.is_terminal() != parts.finished_at.is_some() {
169            return Err(AsyncResourceValidationError::TerminalTimeMismatch);
170        }
171        Ok(Self { parts })
172    }
173
174    /// Returns the opaque task identifier.
175    #[must_use]
176    pub const fn id(&self) -> AsyncResourceId<'a> {
177        self.parts.id
178    }
179
180    /// Returns the provider task type.
181    #[must_use]
182    pub const fn kind(&self) -> AsyncResourceText<'a> {
183        self.parts.kind
184    }
185
186    /// Returns the normalized lifecycle status.
187    #[must_use]
188    pub const fn status(&self) -> AsyncResourceStatus {
189        self.parts.status
190    }
191
192    /// Returns the non-executable related-resource link.
193    #[must_use]
194    pub const fn link(&self) -> Option<AsyncResourceLink<'a>> {
195        self.parts.link
196    }
197
198    /// Returns the optional sensitive provider task description.
199    #[must_use]
200    pub const fn message(&self) -> Option<AsyncResourceText<'a>> {
201        self.parts.message
202    }
203
204    /// Returns the creation timestamp.
205    #[must_use]
206    pub const fn created_at(&self) -> AsyncResourceTimestamp<'a> {
207        self.parts.created_at
208    }
209
210    /// Returns the last-update timestamp.
211    #[must_use]
212    pub const fn updated_at(&self) -> AsyncResourceTimestamp<'a> {
213        self.parts.updated_at
214    }
215
216    /// Returns the optional start timestamp.
217    #[must_use]
218    pub const fn started_at(&self) -> Option<AsyncResourceTimestamp<'a>> {
219        self.parts.started_at
220    }
221
222    /// Returns the optional completion timestamp.
223    #[must_use]
224    pub const fn finished_at(&self) -> Option<AsyncResourceTimestamp<'a>> {
225        self.parts.finished_at
226    }
227
228    /// Returns the provider progress steps.
229    #[must_use]
230    pub const fn progress(&self) -> &'a [AsyncProgressStep<'a>] {
231        self.parts.progress
232    }
233
234    /// Returns the provider task errors.
235    #[must_use]
236    pub const fn errors(&self) -> &'a [AsyncTaskError<'a>] {
237        self.parts.errors
238    }
239
240    /// Classifies the snapshot without collapsing caller-intervention states.
241    #[must_use]
242    pub fn poll_disposition(&self) -> AsyncPollDisposition<'a> {
243        match self.parts.status {
244            AsyncResourceStatus::Succeeded if !self.parts.errors.is_empty() => {
245                AsyncPollDisposition::ContradictorySuccess(self.parts.errors)
246            }
247            AsyncResourceStatus::Succeeded => AsyncPollDisposition::Update(ActionUpdate::Success),
248            AsyncResourceStatus::Failed => {
249                AsyncPollDisposition::Update(ActionUpdate::Failed(self.parts.errors))
250            }
251            AsyncResourceStatus::WaitingForInput => AsyncPollDisposition::WaitingForInput,
252            _ => AsyncPollDisposition::Update(ActionUpdate::Running),
253        }
254    }
255}
256
257impl fmt::Debug for AsyncTask<'_> {
258    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
259        formatter
260            .debug_struct("AsyncTask")
261            .field("id", &"[redacted]")
262            .field("kind", &"[redacted]")
263            .field("status", &self.parts.status)
264            .field("link", &"[redacted]")
265            .field("message", &"[redacted]")
266            .field("timestamps", &"[redacted]")
267            .field("progress_steps", &self.parts.progress.len())
268            .field("errors", &self.parts.errors.len())
269            .finish()
270    }
271}
272
273/// Complete borrowed fields for one generic asynchronous event fixture.
274#[derive(Clone, Copy)]
275pub struct AsyncEventParts<'a> {
276    /// Opaque event identifier.
277    pub id: AsyncResourceId<'a>,
278    /// Provider event type.
279    pub kind: AsyncResourceText<'a>,
280    /// Event observation timestamp.
281    pub observed_at: AsyncResourceTimestamp<'a>,
282    /// Optional non-executable related-resource link.
283    pub link: Option<AsyncResourceLink<'a>>,
284    /// Optional sensitive event message.
285    pub message: Option<AsyncResourceText<'a>>,
286}
287
288/// Bounded generic event model that makes no provider endpoint claim.
289#[derive(Clone, Copy)]
290pub struct AsyncEvent<'a> {
291    parts: AsyncEventParts<'a>,
292}
293
294impl<'a> AsyncEvent<'a> {
295    /// Creates an event from individually validated bounded fields.
296    #[must_use]
297    pub const fn new(parts: AsyncEventParts<'a>) -> Self {
298        Self { parts }
299    }
300
301    /// Returns the opaque event identifier.
302    #[must_use]
303    pub const fn id(&self) -> AsyncResourceId<'a> {
304        self.parts.id
305    }
306
307    /// Returns the sensitive provider event type.
308    #[must_use]
309    pub const fn kind(&self) -> AsyncResourceText<'a> {
310        self.parts.kind
311    }
312
313    /// Returns the event observation timestamp.
314    #[must_use]
315    pub const fn observed_at(&self) -> AsyncResourceTimestamp<'a> {
316        self.parts.observed_at
317    }
318
319    /// Returns the optional non-executable related-resource link.
320    #[must_use]
321    pub const fn link(&self) -> Option<AsyncResourceLink<'a>> {
322        self.parts.link
323    }
324
325    /// Returns the optional sensitive event message.
326    #[must_use]
327    pub const fn message(&self) -> Option<AsyncResourceText<'a>> {
328        self.parts.message
329    }
330}
331
332impl fmt::Debug for AsyncEvent<'_> {
333    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
334        formatter.write_str("AsyncEvent([redacted])")
335    }
336}
337
338/// Borrowed event batch with an unconditional event-count bound.
339pub struct AsyncEventBatch<'a> {
340    events: &'a [AsyncEvent<'a>],
341}
342
343impl<'a> AsyncEventBatch<'a> {
344    /// Validates the complete event-count bound before exposing the batch.
345    pub fn new(events: &'a [AsyncEvent<'a>]) -> Result<Self, AsyncResourceValidationError> {
346        if events.len() > MAX_ASYNC_EVENTS {
347            return Err(AsyncResourceValidationError::TooManyEvents);
348        }
349        Ok(Self { events })
350    }
351
352    /// Returns the complete bounded event slice.
353    #[must_use]
354    pub const fn events(&self) -> &'a [AsyncEvent<'a>] {
355        self.events
356    }
357}
358
359impl fmt::Debug for AsyncEventBatch<'_> {
360    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
361        formatter
362            .debug_struct("AsyncEventBatch")
363            .field("events", &self.events.len())
364            .finish()
365    }
366}