relay_core_api/
modification.rs1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
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() {
78 None
79 } else {
80 Some(self)
81 }
82 }
83
84 pub fn from_json_value(value: &Value) -> Self {
85 Self {
86 method: value.get("method").and_then(Value::as_str).map(str::to_string),
87 url: value.get("url").and_then(Value::as_str).map(str::to_string),
88 request_headers: string_map_from_json(value.get("request_headers")),
89 request_body: value.get("request_body").and_then(Value::as_str).map(str::to_string),
90 status_code: value.get("status_code").and_then(Value::as_u64).map(|code| code as u16),
91 response_headers: string_map_from_json(value.get("response_headers")),
92 response_body: value.get("response_body").and_then(Value::as_str).map(str::to_string),
93 message_content: value.get("message_content").and_then(Value::as_str).map(str::to_string),
94 }
95 }
96}
97
98fn string_map_from_json(value: Option<&Value>) -> Option<HashMap<String, String>> {
99 value?.as_object().map(|entries| {
100 entries
101 .iter()
102 .filter_map(|(key, value)| value.as_str().map(|value| (key.clone(), value.to_string())))
103 .collect()
104 })
105}
106
107#[cfg(test)]
108mod tests {
109 use super::FlowModification;
110 use serde_json::json;
111 use std::collections::HashMap;
112
113 #[test]
114 fn flow_modification_into_option_returns_none_when_empty() {
115 assert!(FlowModification::default().into_option().is_none());
116 }
117
118 #[test]
119 fn flow_modification_into_option_preserves_non_empty_payload() {
120 let modification = FlowModification {
121 request_body: Some("patched".to_string()),
122 ..Default::default()
123 };
124
125 assert_eq!(
126 modification.clone().into_option().unwrap().request_body,
127 modification.request_body
128 );
129 }
130
131 #[test]
132 fn flow_modification_from_json_value_reads_supported_fields() {
133 let modification = FlowModification::from_json_value(&json!({
134 "method": "PATCH",
135 "url": "http://example.com/new",
136 "request_headers": {
137 "X-Test": "1",
138 "X-Ignore": 2
139 },
140 "response_headers": {
141 "Content-Type": "application/json"
142 },
143 "request_body": "body",
144 "status_code": 202,
145 "response_body": "ok",
146 "message_content": "ws"
147 }));
148
149 assert_eq!(modification.method.as_deref(), Some("PATCH"));
150 assert_eq!(modification.url.as_deref(), Some("http://example.com/new"));
151 assert_eq!(
152 modification.request_headers,
153 Some(HashMap::from([("X-Test".to_string(), "1".to_string())]))
154 );
155 assert_eq!(
156 modification.response_headers,
157 Some(HashMap::from([(
158 "Content-Type".to_string(),
159 "application/json".to_string()
160 )]))
161 );
162 assert_eq!(modification.request_body.as_deref(), Some("body"));
163 assert_eq!(modification.status_code, Some(202));
164 assert_eq!(modification.response_body.as_deref(), Some("ok"));
165 assert_eq!(modification.message_content.as_deref(), Some("ws"));
166 }
167}