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::{CreatedBy, 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    /// Who triggered the run. Always present.
62    pub created_by: CreatedBy,
63    /// Idempotency key that produced this run, when one was supplied.
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub idempotency_key: Option<String>,
66    /// Cumulative cost cap for this run, in USD. `None` means no cap.
67    #[cfg_attr(feature = "openapi", schema(value_type = Option<f64>))]
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub max_cost_usd: Option<Decimal>,
70}
71
72impl From<Run> for RunResponse {
73    fn from(run: Run) -> Self {
74        let created_by = CreatedBy::from(&run);
75        RunResponse {
76            id: run.id,
77            workflow_name: run.workflow_name,
78            status: run.status.state,
79            trigger: run.trigger,
80            error: run.error,
81            retry_count: run.retry_count,
82            max_retries: run.max_retries,
83            cost_usd: run.cost_usd,
84            duration_ms: run.duration_ms,
85            created_at: run.created_at,
86            updated_at: run.updated_at,
87            started_at: run.started_at,
88            completed_at: run.completed_at,
89            handler_version: run.handler_version,
90            labels: run.labels,
91            scheduled_at: run.scheduled_at,
92            created_by,
93            idempotency_key: run.idempotency_key,
94            max_cost_usd: run.max_cost_usd,
95        }
96    }
97}
98
99/// Run detail response — includes steps and payload.
100#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
101#[derive(Debug, Serialize)]
102pub struct RunDetailResponse {
103    /// The run.
104    pub run: RunResponse,
105    /// Associated steps, ordered by position.
106    pub steps: Vec<StepResponse>,
107    /// Input payload that triggered this run.
108    pub payload: serde_json::Value,
109}
110
111/// Query parameters for listing runs.
112#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams, utoipa::ToSchema))]
113#[derive(Debug, Deserialize)]
114pub struct ListRunsQuery {
115    /// Filter by workflow name.
116    pub workflow: Option<String>,
117    /// Filter by run status.
118    pub status: Option<RunStatus>,
119    /// Filter by step presence (only applies to completed/cancelled runs).
120    /// Non-terminal runs (pending, running, etc.) are always included.
121    /// When `true`, only return completed/cancelled runs that have steps.
122    /// When `false`, only return completed/cancelled runs without steps.
123    pub has_steps: Option<bool>,
124    /// Filter by labels. Comma-separated `key:value` pairs.
125    pub label: Option<String>,
126    /// Filter by author: the user ID that triggered the run.
127    ///
128    /// Also matches runs triggered by one of that user's API keys.
129    pub created_by: Option<Uuid>,
130    /// Page number (1-based).
131    pub page: Option<u32>,
132    /// Items per page.
133    pub per_page: Option<u32>,
134}
135
136impl ListRunsQuery {
137    /// Parse the comma-separated `label` param into a `HashMap`.
138    pub fn parse_labels(&self) -> Option<HashMap<String, String>> {
139        self.label.as_ref().and_then(|raw| {
140            let mut map = HashMap::new();
141            for entry in raw.split(',') {
142                let entry = entry.trim();
143                if let Some((k, v)) = entry.split_once(':') {
144                    map.insert(k.to_string(), v.to_string());
145                }
146            }
147            if map.is_empty() { None } else { Some(map) }
148        })
149    }
150}