relay_core_api/
modification.rs1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize)]
7pub struct FlowQuery {
8 pub host: Option<String>,
10 pub path_contains: Option<String>,
12 pub method: Option<String>,
14 pub status_min: Option<u16>,
16 pub status_max: Option<u16>,
18 pub has_error: Option<bool>,
20 pub is_websocket: Option<bool>,
22 pub limit: Option<usize>,
24 pub offset: Option<usize>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct FlowSummary {
31 pub id: String,
32 pub method: String,
33 pub url: String,
34 pub host: String,
35 pub path: String,
36 pub status: Option<u16>,
37 pub duration_ms: Option<u64>,
38 pub tags: Vec<String>,
39 pub start_time_ms: i64,
40 pub has_error: bool,
41 pub is_websocket: bool,
42}
43
44#[derive(Debug, Clone, Default, Serialize, Deserialize)]
48pub struct FlowModification {
49 pub method: Option<String>,
51 pub url: Option<String>,
52 pub request_headers: Option<HashMap<String, String>>,
53 pub request_body: Option<String>,
54
55 pub status_code: Option<u16>,
57 pub response_headers: Option<HashMap<String, String>>,
58 pub response_body: Option<String>,
59
60 pub message_content: Option<String>,
62}
63
64impl FlowModification {
65 pub fn is_empty(&self) -> bool {
66 self.method.is_none()
67 && self.url.is_none()
68 && self.request_headers.is_none()
69 && self.request_body.is_none()
70 && self.status_code.is_none()
71 && self.response_headers.is_none()
72 && self.response_body.is_none()
73 && self.message_content.is_none()
74 }
75
76 pub fn into_option(self) -> Option<Self> {
77 if self.is_empty() { None } else { Some(self) }
78 }
79
80 pub fn from_json_value(value: &Value) -> Self {
81 Self {
82 method: value
83 .get("method")
84 .and_then(Value::as_str)
85 .map(str::to_string),
86 url: value.get("url").and_then(Value::as_str).map(str::to_string),
87 request_headers: string_map_from_json(value.get("request_headers")),
88 request_body: value
89 .get("request_body")
90 .and_then(Value::as_str)
91 .map(str::to_string),
92 status_code: value
93 .get("status_code")
94 .and_then(Value::as_u64)
95 .map(|code| code as u16),
96 response_headers: string_map_from_json(value.get("response_headers")),
97 response_body: value
98 .get("response_body")
99 .and_then(Value::as_str)
100 .map(str::to_string),
101 message_content: value
102 .get("message_content")
103 .and_then(Value::as_str)
104 .map(str::to_string),
105 }
106 }
107}
108
109fn string_map_from_json(value: Option<&Value>) -> Option<HashMap<String, String>> {
110 value?.as_object().map(|entries| {
111 entries
112 .iter()
113 .filter_map(|(key, value)| value.as_str().map(|value| (key.clone(), value.to_string())))
114 .collect()
115 })
116}
117
118#[cfg(test)]
119mod tests {
120 use super::FlowModification;
121 use serde_json::json;
122 use std::collections::HashMap;
123
124 #[test]
125 fn flow_modification_into_option_returns_none_when_empty() {
126 assert!(FlowModification::default().into_option().is_none());
127 }
128
129 #[test]
130 fn flow_modification_into_option_preserves_non_empty_payload() {
131 let modification = FlowModification {
132 request_body: Some("patched".to_string()),
133 ..Default::default()
134 };
135
136 assert_eq!(
137 modification.clone().into_option().unwrap().request_body,
138 modification.request_body
139 );
140 }
141
142 #[test]
143 fn flow_modification_from_json_value_reads_supported_fields() {
144 let modification = FlowModification::from_json_value(&json!({
145 "method": "PATCH",
146 "url": "http://example.com/new",
147 "request_headers": {
148 "X-Test": "1",
149 "X-Ignore": 2
150 },
151 "response_headers": {
152 "Content-Type": "application/json"
153 },
154 "request_body": "body",
155 "status_code": 202,
156 "response_body": "ok",
157 "message_content": "ws"
158 }));
159
160 assert_eq!(modification.method.as_deref(), Some("PATCH"));
161 assert_eq!(modification.url.as_deref(), Some("http://example.com/new"));
162 assert_eq!(
163 modification.request_headers,
164 Some(HashMap::from([("X-Test".to_string(), "1".to_string())]))
165 );
166 assert_eq!(
167 modification.response_headers,
168 Some(HashMap::from([(
169 "Content-Type".to_string(),
170 "application/json".to_string()
171 )]))
172 );
173 assert_eq!(modification.request_body.as_deref(), Some("body"));
174 assert_eq!(modification.status_code, Some(202));
175 assert_eq!(modification.response_body.as_deref(), Some("ok"));
176 assert_eq!(modification.message_content.as_deref(), Some("ws"));
177 }
178}