openzeppelin_relayer/models/
plugin.rs1use std::{collections::HashMap, time::Duration};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Map;
5use utoipa::ToSchema;
6
7use crate::constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS;
8
9#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
10pub struct PluginModel {
11 pub id: String,
13 pub path: String,
15 #[schema(value_type = u64)]
17 pub timeout: Duration,
18 #[serde(default)]
20 pub emit_logs: bool,
21 #[serde(default)]
23 pub emit_traces: bool,
24 #[serde(default)]
26 pub raw_response: bool,
27 #[serde(default)]
29 pub allow_get_invocation: bool,
30 #[serde(default)]
32 pub config: Option<Map<String, serde_json::Value>>,
33 #[serde(default)]
35 pub forward_logs: bool,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
39pub struct PluginCallRequest {
40 #[serde(default)]
42 pub params: serde_json::Value,
43 #[serde(default, skip_deserializing)]
45 pub headers: Option<HashMap<String, Vec<String>>>,
46 #[serde(default, skip_deserializing)]
48 pub route: Option<String>,
49 #[serde(default, skip_deserializing)]
51 pub method: Option<String>,
52 #[serde(default, skip_deserializing)]
54 pub query: Option<HashMap<String, Vec<String>>>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, Default, ToSchema)]
61#[serde(deny_unknown_fields)]
62pub struct UpdatePluginRequest {
63 #[schema(value_type = Option<u64>)]
65 pub timeout: Option<u64>,
66 pub emit_logs: Option<bool>,
68 pub emit_traces: Option<bool>,
70 pub raw_response: Option<bool>,
72 pub allow_get_invocation: Option<bool>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub config: Option<Option<Map<String, serde_json::Value>>>,
78 pub forward_logs: Option<bool>,
80}
81
82#[derive(Debug, thiserror::Error)]
84pub enum PluginValidationError {
85 #[error("Invalid timeout: {0}")]
86 InvalidTimeout(String),
87}
88
89impl PluginModel {
90 pub fn apply_update(&self, update: UpdatePluginRequest) -> Result<Self, PluginValidationError> {
93 let mut updated = self.clone();
94
95 if let Some(timeout_secs) = update.timeout {
96 if timeout_secs == 0 {
97 return Err(PluginValidationError::InvalidTimeout(
98 "Timeout must be greater than 0".to_string(),
99 ));
100 }
101 updated.timeout = Duration::from_secs(timeout_secs);
102 }
103
104 if let Some(emit_logs) = update.emit_logs {
105 updated.emit_logs = emit_logs;
106 }
107
108 if let Some(emit_traces) = update.emit_traces {
109 updated.emit_traces = emit_traces;
110 }
111
112 if let Some(raw_response) = update.raw_response {
113 updated.raw_response = raw_response;
114 }
115
116 if let Some(allow_get_invocation) = update.allow_get_invocation {
117 updated.allow_get_invocation = allow_get_invocation;
118 }
119
120 if let Some(config) = update.config {
125 updated.config = config;
126 }
127
128 if let Some(forward_logs) = update.forward_logs {
129 updated.forward_logs = forward_logs;
130 }
131
132 Ok(updated)
133 }
134}
135
136impl Default for PluginModel {
137 fn default() -> Self {
138 Self {
139 id: String::new(),
140 path: String::new(),
141 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
142 emit_logs: false,
143 emit_traces: false,
144 raw_response: false,
145 allow_get_invocation: false,
146 config: None,
147 forward_logs: false,
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 fn create_test_plugin() -> PluginModel {
157 PluginModel {
158 id: "test-plugin".to_string(),
159 path: "plugins/test.ts".to_string(),
160 timeout: Duration::from_secs(30),
161 emit_logs: false,
162 emit_traces: false,
163 raw_response: false,
164 allow_get_invocation: false,
165 config: None,
166 forward_logs: false,
167 }
168 }
169
170 #[test]
171 fn test_apply_update_timeout() {
172 let plugin = create_test_plugin();
173 let update = UpdatePluginRequest {
174 timeout: Some(60),
175 ..Default::default()
176 };
177
178 let updated = plugin.apply_update(update).unwrap();
179 assert_eq!(updated.timeout, Duration::from_secs(60));
180 assert!(!updated.emit_logs);
182 }
183
184 #[test]
185 fn test_apply_update_timeout_zero_fails() {
186 let plugin = create_test_plugin();
187 let update = UpdatePluginRequest {
188 timeout: Some(0),
189 ..Default::default()
190 };
191
192 let result = plugin.apply_update(update);
193 assert!(result.is_err());
194 }
195
196 #[test]
197 fn test_apply_update_all_fields() {
198 let plugin = create_test_plugin();
199 let mut config_map = Map::new();
200 config_map.insert("key".to_string(), serde_json::json!("value"));
201
202 let update = UpdatePluginRequest {
203 timeout: Some(120),
204 emit_logs: Some(true),
205 emit_traces: Some(true),
206 raw_response: Some(true),
207 allow_get_invocation: Some(true),
208 config: Some(Some(config_map.clone())),
209 forward_logs: Some(true),
210 };
211
212 let updated = plugin.apply_update(update).unwrap();
213 assert_eq!(updated.timeout, Duration::from_secs(120));
214 assert!(updated.emit_logs);
215 assert!(updated.emit_traces);
216 assert!(updated.raw_response);
217 assert!(updated.allow_get_invocation);
218 assert_eq!(updated.config, Some(config_map));
219 assert!(updated.forward_logs);
220 }
221
222 #[test]
223 fn test_apply_update_clear_config() {
224 let mut plugin = create_test_plugin();
225 let mut config_map = Map::new();
226 config_map.insert("key".to_string(), serde_json::json!("value"));
227 plugin.config = Some(config_map);
228
229 let update = UpdatePluginRequest {
231 config: Some(None),
232 ..Default::default()
233 };
234
235 let updated = plugin.apply_update(update).unwrap();
236 assert!(updated.config.is_none());
237 }
238
239 #[test]
240 fn test_apply_update_no_changes() {
241 let plugin = create_test_plugin();
242 let update = UpdatePluginRequest::default();
243
244 let updated = plugin.apply_update(update).unwrap();
245 assert_eq!(updated.id, plugin.id);
246 assert_eq!(updated.path, plugin.path);
247 assert_eq!(updated.timeout, plugin.timeout);
248 }
249}