ironflow_api/entities/
run.rs1use 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#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
24#[derive(Debug, Serialize, Deserialize)]
25pub struct RunResponse {
26 pub id: Uuid,
28 pub workflow_name: String,
30 pub status: RunStatus,
32 pub trigger: TriggerKind,
34 pub error: Option<String>,
36 pub retry_count: u32,
38 pub max_retries: u32,
40 #[cfg_attr(feature = "openapi", schema(value_type = f64))]
42 pub cost_usd: Decimal,
43 pub duration_ms: u64,
45 pub created_at: DateTime<Utc>,
47 pub updated_at: DateTime<Utc>,
49 pub started_at: Option<DateTime<Utc>>,
51 pub completed_at: Option<DateTime<Utc>>,
53 pub handler_version: Option<String>,
55 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
57 pub labels: HashMap<String, String>,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub scheduled_at: Option<DateTime<Utc>>,
61 pub created_by: CreatedBy,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub idempotency_key: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub concurrency_key: Option<String>,
70 #[cfg_attr(feature = "openapi", schema(value_type = Option<f64>))]
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub max_cost_usd: Option<Decimal>,
74}
75
76impl From<Run> for RunResponse {
77 fn from(run: Run) -> Self {
78 let created_by = CreatedBy::from(&run);
79 RunResponse {
80 id: run.id,
81 workflow_name: run.workflow_name,
82 status: run.status.state,
83 trigger: run.trigger,
84 error: run.error,
85 retry_count: run.retry_count,
86 max_retries: run.max_retries,
87 cost_usd: run.cost_usd,
88 duration_ms: run.duration_ms,
89 created_at: run.created_at,
90 updated_at: run.updated_at,
91 started_at: run.started_at,
92 completed_at: run.completed_at,
93 handler_version: run.handler_version,
94 labels: run.labels,
95 scheduled_at: run.scheduled_at,
96 created_by,
97 idempotency_key: run.idempotency_key,
98 concurrency_key: run.concurrency_key,
99 max_cost_usd: run.max_cost_usd,
100 }
101 }
102}
103
104#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
106#[derive(Debug, Serialize)]
107pub struct RunDetailResponse {
108 pub run: RunResponse,
110 pub steps: Vec<StepResponse>,
112 pub payload: serde_json::Value,
114}
115
116#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams, utoipa::ToSchema))]
118#[derive(Debug, Deserialize)]
119pub struct ListRunsQuery {
120 pub workflow: Option<String>,
122 pub status: Option<RunStatus>,
124 pub has_steps: Option<bool>,
129 pub label: Option<String>,
131 pub created_by: Option<Uuid>,
135 pub page: Option<u32>,
137 pub per_page: Option<u32>,
139}
140
141impl ListRunsQuery {
142 pub fn parse_labels(&self) -> Option<HashMap<String, String>> {
144 parse_label_param(&self.label)
145 }
146}
147
148pub(crate) fn parse_label_param(raw: &Option<String>) -> Option<HashMap<String, String>> {
153 raw.as_ref().and_then(|raw| {
154 let mut map = HashMap::new();
155 for entry in raw.split(',') {
156 let entry = entry.trim();
157 if let Some((k, v)) = entry.split_once(':') {
158 map.insert(k.to_string(), v.to_string());
159 }
160 }
161 if map.is_empty() { None } else { Some(map) }
162 })
163}