Skip to main content

ironflow_api/entities/
run.rs

1//! Run-related DTOs and query parameters.
2
3use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6use ironflow_store::models::{Run, RunStatus, TriggerKind};
7use rust_decimal::Decimal;
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11use super::StepResponse;
12
13/// Run response DTO — public API representation of a run.
14///
15/// Maps from the internal [`Run`] model, exposing only necessary fields.
16///
17/// # Examples
18///
19/// ```
20/// use ironflow_store::models::{Run, RunStatus, TriggerKind};
21/// use ironflow_api::entities::RunResponse;
22/// ```
23#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
24#[derive(Debug, Serialize, Deserialize)]
25pub struct RunResponse {
26    /// Unique run identifier.
27    pub id: Uuid,
28    /// Workflow name.
29    pub workflow_name: String,
30    /// Current status.
31    pub status: RunStatus,
32    /// How the run was triggered.
33    pub trigger: TriggerKind,
34    /// Optional error message.
35    pub error: Option<String>,
36    /// Number of times retried.
37    pub retry_count: u32,
38    /// Maximum allowed retries.
39    pub max_retries: u32,
40    /// Aggregated cost in USD.
41    #[cfg_attr(feature = "openapi", schema(value_type = f64))]
42    pub cost_usd: Decimal,
43    /// Total duration in milliseconds.
44    pub duration_ms: u64,
45    /// When created.
46    pub created_at: DateTime<Utc>,
47    /// When last updated.
48    pub updated_at: DateTime<Utc>,
49    /// When execution started.
50    pub started_at: Option<DateTime<Utc>>,
51    /// When execution completed.
52    pub completed_at: Option<DateTime<Utc>>,
53    /// Version of the handler that created this run.
54    pub handler_version: Option<String>,
55    /// User-defined key-value labels.
56    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
57    pub labels: HashMap<String, String>,
58    /// Scheduled execution time. `None` means the run executed immediately.
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub scheduled_at: Option<DateTime<Utc>>,
61}
62
63impl From<Run> for RunResponse {
64    fn from(run: Run) -> Self {
65        RunResponse {
66            id: run.id,
67            workflow_name: run.workflow_name,
68            status: run.status.state,
69            trigger: run.trigger,
70            error: run.error,
71            retry_count: run.retry_count,
72            max_retries: run.max_retries,
73            cost_usd: run.cost_usd,
74            duration_ms: run.duration_ms,
75            created_at: run.created_at,
76            updated_at: run.updated_at,
77            started_at: run.started_at,
78            completed_at: run.completed_at,
79            handler_version: run.handler_version,
80            labels: run.labels,
81            scheduled_at: run.scheduled_at,
82        }
83    }
84}
85
86/// Run detail response — includes steps and payload.
87#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
88#[derive(Debug, Serialize)]
89pub struct RunDetailResponse {
90    /// The run.
91    pub run: RunResponse,
92    /// Associated steps, ordered by position.
93    pub steps: Vec<StepResponse>,
94    /// Input payload that triggered this run.
95    pub payload: serde_json::Value,
96}
97
98/// Query parameters for listing runs.
99#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams, utoipa::ToSchema))]
100#[derive(Debug, Deserialize)]
101pub struct ListRunsQuery {
102    /// Filter by workflow name.
103    pub workflow: Option<String>,
104    /// Filter by run status.
105    pub status: Option<RunStatus>,
106    /// When `true`, only return runs with at least one step.
107    /// When `false`, only return runs with no steps.
108    pub has_steps: Option<bool>,
109    /// Filter by labels. Comma-separated `key:value` pairs.
110    pub label: Option<String>,
111    /// Page number (1-based).
112    pub page: Option<u32>,
113    /// Items per page.
114    pub per_page: Option<u32>,
115}
116
117impl ListRunsQuery {
118    /// Parse the comma-separated `label` param into a `HashMap`.
119    pub fn parse_labels(&self) -> Option<HashMap<String, String>> {
120        self.label.as_ref().and_then(|raw| {
121            let mut map = HashMap::new();
122            for entry in raw.split(',') {
123                let entry = entry.trim();
124                if let Some((k, v)) = entry.split_once(':') {
125                    map.insert(k.to_string(), v.to_string());
126                }
127            }
128            if map.is_empty() { None } else { Some(map) }
129        })
130    }
131}