Skip to main content

ironflow_engine/config/
http.rs

1//! [`HttpConfig`] — serializable configuration for an HTTP step.
2
3use ironflow_core::retry::RetryPolicy;
4use ironflow_core::trace_context::WorkflowTraceContext;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// Serializable configuration for an HTTP step.
9///
10/// # Examples
11///
12/// ```
13/// use ironflow_engine::config::HttpConfig;
14///
15/// let config = HttpConfig::get("https://api.example.com/health");
16/// ```
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct HttpConfig {
19    /// HTTP method (GET, POST, PUT, PATCH, DELETE).
20    pub method: String,
21    /// Request URL.
22    pub url: String,
23    /// Request headers.
24    pub headers: Vec<(String, String)>,
25    /// Request body as JSON.
26    pub body: Option<Value>,
27    /// Timeout in seconds (default: 30).
28    pub timeout_secs: Option<u64>,
29    /// When `true`, a failure of this step does not fail the run.
30    #[serde(default)]
31    pub allow_failure: bool,
32    /// Optional step-level retry policy.
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub retry: Option<RetryPolicy>,
35    /// Optional W3C trace context for distributed tracing propagation.
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub trace_context: Option<WorkflowTraceContext>,
38}
39
40impl HttpConfig {
41    /// Create a GET request config.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// use ironflow_engine::config::HttpConfig;
47    ///
48    /// let config = HttpConfig::get("https://example.com");
49    /// assert_eq!(config.method, "GET");
50    /// ```
51    pub fn get(url: &str) -> Self {
52        Self::new("GET", url)
53    }
54
55    /// Create a POST request config.
56    pub fn post(url: &str) -> Self {
57        Self::new("POST", url)
58    }
59
60    /// Create a PUT request config.
61    pub fn put(url: &str) -> Self {
62        Self::new("PUT", url)
63    }
64
65    /// Create a PATCH request config.
66    pub fn patch(url: &str) -> Self {
67        Self::new("PATCH", url)
68    }
69
70    /// Create a DELETE request config.
71    pub fn delete(url: &str) -> Self {
72        Self::new("DELETE", url)
73    }
74
75    fn new(method: &str, url: &str) -> Self {
76        Self {
77            method: method.to_string(),
78            url: url.to_string(),
79            headers: Vec::new(),
80            body: None,
81            timeout_secs: None,
82            allow_failure: false,
83            retry: None,
84            trace_context: None,
85        }
86    }
87
88    /// Add a request header.
89    pub fn header(mut self, name: &str, value: &str) -> Self {
90        self.headers.push((name.to_string(), value.to_string()));
91        self
92    }
93
94    /// Set the request body as JSON.
95    pub fn json(mut self, body: Value) -> Self {
96        self.body = Some(body);
97        self
98    }
99
100    /// Set the timeout in seconds.
101    pub fn timeout_secs(mut self, secs: u64) -> Self {
102        self.timeout_secs = Some(secs);
103        self
104    }
105
106    /// Mark this step as allowed to fail without stopping the run.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use ironflow_engine::config::HttpConfig;
112    ///
113    /// let config = HttpConfig::get("https://example.com").allow_failure();
114    /// assert!(config.allow_failure);
115    /// ```
116    pub fn allow_failure(mut self) -> Self {
117        self.allow_failure = true;
118        self
119    }
120
121    /// Set a step-level retry policy.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// use ironflow_core::retry::RetryPolicy;
127    /// use ironflow_engine::config::HttpConfig;
128    ///
129    /// let config = HttpConfig::get("https://api.example.com")
130    ///     .retry_policy(RetryPolicy::new(5));
131    /// assert!(config.retry.is_some());
132    /// ```
133    pub fn retry_policy(mut self, policy: RetryPolicy) -> Self {
134        self.retry = Some(policy);
135        self
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use serde_json::json;
143
144    #[test]
145    fn methods() {
146        assert_eq!(HttpConfig::get("http://x").method, "GET");
147        assert_eq!(HttpConfig::post("http://x").method, "POST");
148        assert_eq!(HttpConfig::put("http://x").method, "PUT");
149        assert_eq!(HttpConfig::patch("http://x").method, "PATCH");
150        assert_eq!(HttpConfig::delete("http://x").method, "DELETE");
151    }
152
153    #[test]
154    fn builder() {
155        let config = HttpConfig::post("http://api.example.com")
156            .header("Authorization", "Bearer token")
157            .json(json!({"key": "value"}))
158            .timeout_secs(10);
159
160        assert_eq!(config.headers.len(), 1);
161        assert!(config.body.is_some());
162        assert_eq!(config.timeout_secs, Some(10));
163    }
164
165    #[test]
166    fn a_config_predating_retry_still_deserializes() {
167        let config: HttpConfig = serde_json::from_str(
168            r#"{"url":"http://x","method":"GET","headers":[],"body":null,"timeout_secs":null}"#,
169        )
170        .expect("deserialize");
171        assert!(config.retry.is_none());
172    }
173
174    #[test]
175    fn retry_policy_roundtrip() {
176        use ironflow_core::retry::RetryPolicy;
177
178        let config = HttpConfig::get("http://api").retry_policy(RetryPolicy::new(3));
179        let json = serde_json::to_string(&config).expect("serialize");
180        let back: HttpConfig = serde_json::from_str(&json).expect("deserialize");
181        assert_eq!(back.retry.as_ref().unwrap().max_retries(), 3);
182    }
183}