use core::{cmp::Ordering, fmt};
use crate::action_polling::ActionUpdate;
use super::{
AsyncResourceId, AsyncResourceLink, AsyncResourceText, AsyncResourceTimestamp,
AsyncResourceValidationError, MAX_ASYNC_ERRORS, MAX_ASYNC_EVENTS, MAX_ASYNC_PROGRESS_STEPS,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AsyncResourceStatus {
Pending,
Scheduled,
Running,
WaitingForInput,
Succeeded,
Failed,
}
#[derive(Debug, Eq, PartialEq)]
pub enum AsyncPollDisposition<'a> {
Update(ActionUpdate<&'a [AsyncTaskError<'a>]>),
WaitingForInput,
ContradictorySuccess(&'a [AsyncTaskError<'a>]),
}
impl AsyncResourceStatus {
#[must_use]
pub const fn is_terminal(self) -> bool {
matches!(self, Self::Succeeded | Self::Failed)
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct AsyncProgressStep<'a> {
name: AsyncResourceText<'a>,
status: AsyncResourceStatus,
}
impl<'a> AsyncProgressStep<'a> {
#[must_use]
pub const fn new(name: AsyncResourceText<'a>, status: AsyncResourceStatus) -> Self {
Self { name, status }
}
#[must_use]
pub const fn name(self) -> AsyncResourceText<'a> {
self.name
}
#[must_use]
pub const fn status(self) -> AsyncResourceStatus {
self.status
}
}
impl fmt::Debug for AsyncProgressStep<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AsyncProgressStep")
.field("name", &"[redacted]")
.field("status", &self.status)
.finish()
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct AsyncTaskError<'a> {
message: AsyncResourceText<'a>,
}
impl<'a> AsyncTaskError<'a> {
#[must_use]
pub const fn new(message: AsyncResourceText<'a>) -> Self {
Self { message }
}
#[must_use]
pub const fn message(self) -> AsyncResourceText<'a> {
self.message
}
}
impl fmt::Debug for AsyncTaskError<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("AsyncTaskError([redacted])")
}
}
#[derive(Clone, Copy)]
pub struct AsyncTaskParts<'a> {
pub id: AsyncResourceId<'a>,
pub kind: AsyncResourceText<'a>,
pub status: AsyncResourceStatus,
pub link: Option<AsyncResourceLink<'a>>,
pub message: Option<AsyncResourceText<'a>>,
pub created_at: AsyncResourceTimestamp<'a>,
pub updated_at: AsyncResourceTimestamp<'a>,
pub started_at: Option<AsyncResourceTimestamp<'a>>,
pub finished_at: Option<AsyncResourceTimestamp<'a>>,
pub progress: &'a [AsyncProgressStep<'a>],
pub errors: &'a [AsyncTaskError<'a>],
}
pub struct AsyncTask<'a> {
parts: AsyncTaskParts<'a>,
}
impl<'a> AsyncTask<'a> {
pub fn new(parts: AsyncTaskParts<'a>) -> Result<Self, AsyncResourceValidationError> {
if parts.progress.len() > MAX_ASYNC_PROGRESS_STEPS {
return Err(AsyncResourceValidationError::TooManyProgressSteps);
}
if parts.errors.len() > MAX_ASYNC_ERRORS {
return Err(AsyncResourceValidationError::TooManyErrors);
}
if parts.updated_at.compare(parts.created_at) == Ordering::Less
|| parts
.started_at
.is_some_and(|value| value.compare(parts.created_at) == Ordering::Less)
|| parts
.started_at
.is_some_and(|value| value.compare(parts.updated_at) == Ordering::Greater)
|| parts
.finished_at
.is_some_and(|value| value.compare(parts.created_at) == Ordering::Less)
|| parts
.finished_at
.is_some_and(|value| value.compare(parts.updated_at) == Ordering::Greater)
|| matches!((parts.started_at, parts.finished_at), (Some(started), Some(finished))
if finished.compare(started) == Ordering::Less)
{
return Err(AsyncResourceValidationError::TimestampOrder);
}
if parts.status.is_terminal() != parts.finished_at.is_some() {
return Err(AsyncResourceValidationError::TerminalTimeMismatch);
}
Ok(Self { parts })
}
#[must_use]
pub const fn id(&self) -> AsyncResourceId<'a> {
self.parts.id
}
#[must_use]
pub const fn kind(&self) -> AsyncResourceText<'a> {
self.parts.kind
}
#[must_use]
pub const fn status(&self) -> AsyncResourceStatus {
self.parts.status
}
#[must_use]
pub const fn link(&self) -> Option<AsyncResourceLink<'a>> {
self.parts.link
}
#[must_use]
pub const fn message(&self) -> Option<AsyncResourceText<'a>> {
self.parts.message
}
#[must_use]
pub const fn created_at(&self) -> AsyncResourceTimestamp<'a> {
self.parts.created_at
}
#[must_use]
pub const fn updated_at(&self) -> AsyncResourceTimestamp<'a> {
self.parts.updated_at
}
#[must_use]
pub const fn started_at(&self) -> Option<AsyncResourceTimestamp<'a>> {
self.parts.started_at
}
#[must_use]
pub const fn finished_at(&self) -> Option<AsyncResourceTimestamp<'a>> {
self.parts.finished_at
}
#[must_use]
pub const fn progress(&self) -> &'a [AsyncProgressStep<'a>] {
self.parts.progress
}
#[must_use]
pub const fn errors(&self) -> &'a [AsyncTaskError<'a>] {
self.parts.errors
}
#[must_use]
pub fn poll_disposition(&self) -> AsyncPollDisposition<'a> {
match self.parts.status {
AsyncResourceStatus::Succeeded if !self.parts.errors.is_empty() => {
AsyncPollDisposition::ContradictorySuccess(self.parts.errors)
}
AsyncResourceStatus::Succeeded => AsyncPollDisposition::Update(ActionUpdate::Success),
AsyncResourceStatus::Failed => {
AsyncPollDisposition::Update(ActionUpdate::Failed(self.parts.errors))
}
AsyncResourceStatus::WaitingForInput => AsyncPollDisposition::WaitingForInput,
_ => AsyncPollDisposition::Update(ActionUpdate::Running),
}
}
}
impl fmt::Debug for AsyncTask<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AsyncTask")
.field("id", &"[redacted]")
.field("kind", &"[redacted]")
.field("status", &self.parts.status)
.field("link", &"[redacted]")
.field("message", &"[redacted]")
.field("timestamps", &"[redacted]")
.field("progress_steps", &self.parts.progress.len())
.field("errors", &self.parts.errors.len())
.finish()
}
}
#[derive(Clone, Copy)]
pub struct AsyncEventParts<'a> {
pub id: AsyncResourceId<'a>,
pub kind: AsyncResourceText<'a>,
pub observed_at: AsyncResourceTimestamp<'a>,
pub link: Option<AsyncResourceLink<'a>>,
pub message: Option<AsyncResourceText<'a>>,
}
#[derive(Clone, Copy)]
pub struct AsyncEvent<'a> {
parts: AsyncEventParts<'a>,
}
impl<'a> AsyncEvent<'a> {
#[must_use]
pub const fn new(parts: AsyncEventParts<'a>) -> Self {
Self { parts }
}
#[must_use]
pub const fn id(&self) -> AsyncResourceId<'a> {
self.parts.id
}
#[must_use]
pub const fn kind(&self) -> AsyncResourceText<'a> {
self.parts.kind
}
#[must_use]
pub const fn observed_at(&self) -> AsyncResourceTimestamp<'a> {
self.parts.observed_at
}
#[must_use]
pub const fn link(&self) -> Option<AsyncResourceLink<'a>> {
self.parts.link
}
#[must_use]
pub const fn message(&self) -> Option<AsyncResourceText<'a>> {
self.parts.message
}
}
impl fmt::Debug for AsyncEvent<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("AsyncEvent([redacted])")
}
}
pub struct AsyncEventBatch<'a> {
events: &'a [AsyncEvent<'a>],
}
impl<'a> AsyncEventBatch<'a> {
pub fn new(events: &'a [AsyncEvent<'a>]) -> Result<Self, AsyncResourceValidationError> {
if events.len() > MAX_ASYNC_EVENTS {
return Err(AsyncResourceValidationError::TooManyEvents);
}
Ok(Self { events })
}
#[must_use]
pub const fn events(&self) -> &'a [AsyncEvent<'a>] {
self.events
}
}
impl fmt::Debug for AsyncEventBatch<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AsyncEventBatch")
.field("events", &self.events.len())
.finish()
}
}