ironflow_engine/config/
http.rs1use ironflow_core::retry::RetryPolicy;
4use ironflow_core::trace_context::WorkflowTraceContext;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct HttpConfig {
19 pub method: String,
21 pub url: String,
23 pub headers: Vec<(String, String)>,
25 pub body: Option<Value>,
27 pub timeout_secs: Option<u64>,
29 #[serde(default)]
31 pub allow_failure: bool,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub retry: Option<RetryPolicy>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub trace_context: Option<WorkflowTraceContext>,
38 #[serde(default, skip_serializing_if = "Vec::is_empty")]
41 pub allowed_hosts: Vec<String>,
42}
43
44impl HttpConfig {
45 pub fn get(url: &str) -> Self {
56 Self::new("GET", url)
57 }
58
59 pub fn post(url: &str) -> Self {
61 Self::new("POST", url)
62 }
63
64 pub fn put(url: &str) -> Self {
66 Self::new("PUT", url)
67 }
68
69 pub fn patch(url: &str) -> Self {
71 Self::new("PATCH", url)
72 }
73
74 pub fn delete(url: &str) -> Self {
76 Self::new("DELETE", url)
77 }
78
79 fn new(method: &str, url: &str) -> Self {
80 Self {
81 method: method.to_string(),
82 url: url.to_string(),
83 headers: Vec::new(),
84 body: None,
85 timeout_secs: None,
86 allow_failure: false,
87 retry: None,
88 trace_context: None,
89 allowed_hosts: Vec::new(),
90 }
91 }
92
93 pub fn header(mut self, name: &str, value: &str) -> Self {
95 self.headers.push((name.to_string(), value.to_string()));
96 self
97 }
98
99 pub fn json(mut self, body: Value) -> Self {
101 self.body = Some(body);
102 self
103 }
104
105 pub fn timeout_secs(mut self, secs: u64) -> Self {
107 self.timeout_secs = Some(secs);
108 self
109 }
110
111 pub fn allow_failure(mut self) -> Self {
122 self.allow_failure = true;
123 self
124 }
125
126 pub fn retry_policy(mut self, policy: RetryPolicy) -> Self {
139 self.retry = Some(policy);
140 self
141 }
142
143 pub fn allow_host(mut self, host: &str) -> Self {
160 self.allowed_hosts.push(host.to_string());
161 self
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use serde_json::json;
169
170 #[test]
171 fn methods() {
172 assert_eq!(HttpConfig::get("http://x").method, "GET");
173 assert_eq!(HttpConfig::post("http://x").method, "POST");
174 assert_eq!(HttpConfig::put("http://x").method, "PUT");
175 assert_eq!(HttpConfig::patch("http://x").method, "PATCH");
176 assert_eq!(HttpConfig::delete("http://x").method, "DELETE");
177 }
178
179 #[test]
180 fn builder() {
181 let config = HttpConfig::post("http://api.example.com")
182 .header("Authorization", "Bearer token")
183 .json(json!({"key": "value"}))
184 .timeout_secs(10);
185
186 assert_eq!(config.headers.len(), 1);
187 assert!(config.body.is_some());
188 assert_eq!(config.timeout_secs, Some(10));
189 }
190
191 #[test]
192 fn a_config_predating_retry_still_deserializes() {
193 let config: HttpConfig = serde_json::from_str(
194 r#"{"url":"http://x","method":"GET","headers":[],"body":null,"timeout_secs":null}"#,
195 )
196 .expect("deserialize");
197 assert!(config.retry.is_none());
198 }
199
200 #[test]
201 fn retry_policy_roundtrip() {
202 use ironflow_core::retry::RetryPolicy;
203
204 let config = HttpConfig::get("http://api").retry_policy(RetryPolicy::new(3));
205 let json = serde_json::to_string(&config).expect("serialize");
206 let back: HttpConfig = serde_json::from_str(&json).expect("deserialize");
207 assert_eq!(back.retry.as_ref().unwrap().max_retries(), 3);
208 }
209
210 #[test]
211 fn a_config_predating_allowed_hosts_still_deserializes() {
212 let config: HttpConfig = serde_json::from_str(
213 r#"{"url":"http://x","method":"GET","headers":[],"body":null,"timeout_secs":null}"#,
214 )
215 .expect("deserialize");
216 assert!(config.allowed_hosts.is_empty());
217 }
218
219 #[test]
220 fn allowed_hosts_roundtrip_and_stay_out_of_json_when_empty() {
221 let config = HttpConfig::get("http://billing.internal")
222 .allow_host("billing.internal")
223 .allow_host("10.0.0.5");
224 let json = serde_json::to_string(&config).expect("serialize");
225 let back: HttpConfig = serde_json::from_str(&json).expect("deserialize");
226 assert_eq!(back.allowed_hosts, ["billing.internal", "10.0.0.5"]);
227
228 let json = serde_json::to_string(&HttpConfig::get("http://x")).expect("serialize");
229 assert!(!json.contains("allowed_hosts"), "{json}");
230 }
231}