use serde::{Deserialize, Serialize};
use thiserror::Error;
use time::OffsetDateTime;
use uuid::Uuid;
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(test, derive(strum::EnumIter))]
#[serde(rename_all = "snake_case")]
pub enum UpdateStatus {
Queued,
Pending,
InProgress,
AwaitingRestart,
Completed,
Failed,
}
impl UpdateStatus {
pub fn as_str(&self) -> &'static str {
match self {
Self::Queued => "queued",
Self::Pending => "pending",
Self::InProgress => "in_progress",
Self::AwaitingRestart => "awaiting_restart",
Self::Completed => "completed",
Self::Failed => "failed",
}
}
}
impl std::fmt::Display for UpdateStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Error)]
#[error("invalid update status value")]
pub struct ParseUpdateStatusError;
impl std::str::FromStr for UpdateStatus {
type Err = ParseUpdateStatusError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"queued" => Ok(Self::Queued),
"pending" => Ok(Self::Pending),
"in_progress" => Ok(Self::InProgress),
"awaiting_restart" => Ok(Self::AwaitingRestart),
"completed" => Ok(Self::Completed),
"failed" => Ok(Self::Failed),
_ => Err(ParseUpdateStatusError),
}
}
}
#[non_exhaustive]
#[derive(Default, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema, utoipa::IntoParams))]
pub struct UpdateHistoryQuery {
pub host_id: Option<Uuid>,
pub software_item_id: Option<Uuid>,
pub status: Option<UpdateStatus>,
pub page: Option<u64>,
pub per_page: Option<u64>,
}
impl UpdateHistoryQuery {
pub fn new(
host_id: Option<Uuid>,
software_item_id: Option<Uuid>,
status: Option<UpdateStatus>,
page: Option<u64>,
per_page: Option<u64>,
) -> Self {
Self {
host_id,
software_item_id,
status,
page,
per_page,
}
}
pub fn pagination(&self) -> crate::pagination::PaginationParams {
crate::pagination::PaginationParams {
page: self.page,
per_page: self.per_page,
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct UpdateHistoryResponse {
pub id: Uuid,
pub host_id: Uuid,
pub host_name: String,
pub software_item_id: Uuid,
pub software_item_name: String,
pub from_version: Option<String>,
pub to_version: String,
pub status: UpdateStatus,
pub output: String,
pub actor_type: String,
pub actor_id: String,
pub actor_name: Option<String>,
#[serde(with = "time::serde::rfc3339")]
#[cfg_attr(
feature = "openapi",
schema(value_type = String, format = DateTime)
)]
pub started_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
#[cfg_attr(
feature = "openapi",
schema(value_type = Option<String>, format = DateTime)
)]
pub completed_at: Option<OffsetDateTime>,
#[serde(with = "time::serde::rfc3339")]
#[cfg_attr(
feature = "openapi",
schema(value_type = String, format = DateTime)
)]
pub created_at: OffsetDateTime,
pub update_category: String,
pub interactive: bool,
pub output_truncated: bool,
pub pre_update_protection_status: Option<String>,
pub pre_update_protection_summary: Option<String>,
pub recovery_hint: Option<String>,
}
impl UpdateHistoryResponse {
#[expect(
clippy::too_many_arguments,
reason = "all fields are required for a fully-typed constructor; splitting would obscure the call-site semantics"
)]
pub fn new(
id: Uuid,
host_id: Uuid,
host_name: String,
software_item_id: Uuid,
software_item_name: String,
from_version: Option<String>,
to_version: String,
status: UpdateStatus,
output: String,
actor_type: String,
actor_id: String,
actor_name: Option<String>,
started_at: OffsetDateTime,
completed_at: Option<OffsetDateTime>,
created_at: OffsetDateTime,
update_category: String,
interactive: bool,
output_truncated: bool,
pre_update_protection_status: Option<String>,
pre_update_protection_summary: Option<String>,
recovery_hint: Option<String>,
) -> Self {
Self {
id,
host_id,
host_name,
software_item_id,
software_item_name,
from_version,
to_version,
status,
output,
actor_type,
actor_id,
actor_name,
started_at,
completed_at,
created_at,
update_category,
interactive,
output_truncated,
pre_update_protection_status,
pre_update_protection_summary,
recovery_hint,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OutputLineSSE {
pub id: Uuid,
pub text: String,
pub stream: String,
#[serde(with = "time::serde::rfc3339")]
pub timestamp: OffsetDateTime,
pub seq: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UpdateCompletedSSE {
pub status: String,
pub error: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StdinAttentionSSE {
pub hint: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_awaiting_restart_serde() {
let s = UpdateStatus::AwaitingRestart;
let json = serde_json::to_string(&s).unwrap();
assert_eq!(json, r#""awaiting_restart""#);
let back: UpdateStatus = serde_json::from_str(&json).unwrap();
assert_eq!(back, UpdateStatus::AwaitingRestart);
}
}