1use crate::{PluginCapabilities, PluginContext, PluginError, PluginResult, Result};
9use axum::http::{HeaderMap, Method, StatusCode};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::HashMap;
13
14#[async_trait::async_trait]
20pub trait ResponsePlugin: Send + Sync {
21 fn capabilities(&self) -> PluginCapabilities;
23
24 async fn initialize(&self, config: &ResponsePluginConfig) -> Result<()>;
26
27 async fn can_handle(
40 &self,
41 context: &PluginContext,
42 request: &ResponseRequest,
43 config: &ResponsePluginConfig,
44 ) -> Result<PluginResult<bool>>;
45
46 async fn generate_response(
59 &self,
60 context: &PluginContext,
61 request: &ResponseRequest,
62 config: &ResponsePluginConfig,
63 ) -> Result<PluginResult<ResponseData>>;
64
65 fn priority(&self) -> i32;
67
68 fn validate_config(&self, config: &ResponsePluginConfig) -> Result<()>;
70
71 fn supported_content_types(&self) -> Vec<String>;
73
74 async fn cleanup(&self) -> Result<()>;
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ResponsePluginConfig {
81 pub config: HashMap<String, serde_json::Value>,
83 pub enabled: bool,
85 pub priority: i32,
87 pub content_types: Vec<String>,
89 pub url_patterns: Vec<String>,
91 pub methods: Vec<String>,
93 pub settings: HashMap<String, serde_json::Value>,
95}
96
97impl Default for ResponsePluginConfig {
98 fn default() -> Self {
99 Self {
100 config: HashMap::new(),
101 enabled: true,
102 priority: 100,
103 content_types: vec!["application/json".to_string()],
104 url_patterns: vec!["*".to_string()],
105 methods: vec!["GET".to_string(), "POST".to_string()],
106 settings: HashMap::new(),
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct ResponseRequest {
114 pub method: Method,
116 pub uri: String,
118 pub path: String,
120 pub query_params: HashMap<String, String>,
122 pub headers: HeaderMap,
124 pub body: Option<Vec<u8>>,
126 pub path_params: HashMap<String, String>,
128 pub client_ip: Option<String>,
130 pub user_agent: Option<String>,
132 pub timestamp: chrono::DateTime<chrono::Utc>,
134 pub auth_context: Option<HashMap<String, Value>>,
136 pub custom: HashMap<String, Value>,
138}
139
140impl ResponseRequest {
141 pub fn from_axum(
143 method: Method,
144 uri: axum::http::Uri,
145 headers: HeaderMap,
146 body: Option<Vec<u8>>,
147 path_params: HashMap<String, String>,
148 ) -> Self {
149 let query_params = uri
150 .query()
151 .map(|q| url::form_urlencoded::parse(q.as_bytes()).into_owned().collect())
152 .unwrap_or_default();
153
154 let client_ip = headers
155 .get("x-forwarded-for")
156 .or_else(|| headers.get("x-real-ip"))
157 .and_then(|h| h.to_str().ok())
158 .map(|s| s.to_string());
159
160 let user_agent =
161 headers.get("user-agent").and_then(|h| h.to_str().ok()).map(|s| s.to_string());
162
163 Self {
164 method,
165 uri: uri.to_string(),
166 path: uri.path().to_string(),
167 query_params,
168 headers,
169 body,
170 path_params,
171 client_ip,
172 user_agent,
173 timestamp: chrono::Utc::now(),
174 auth_context: None,
175 custom: HashMap::new(),
176 }
177 }
178
179 pub fn header(&self, name: &str) -> Option<&str> {
181 self.headers.get(name).and_then(|h| h.to_str().ok())
182 }
183
184 pub fn query_param(&self, name: &str) -> Option<&str> {
186 self.query_params.get(name).map(|s| s.as_str())
187 }
188
189 pub fn path_param(&self, name: &str) -> Option<&str> {
191 self.path_params.get(name).map(|s| s.as_str())
192 }
193
194 pub fn auth_value(&self, key: &str) -> Option<&Value> {
196 self.auth_context.as_ref()?.get(key)
197 }
198
199 pub fn custom_value(&self, key: &str) -> Option<&Value> {
201 self.custom.get(key)
202 }
203
204 pub fn matches_url_pattern(&self, pattern: &str) -> bool {
206 if pattern == "*" {
207 return true;
208 }
209
210 if pattern.contains('*') {
212 let regex_pattern = pattern.replace('.', r"\.").replace('*', ".*");
213 regex::Regex::new(&format!("^{}$", regex_pattern))
214 .map(|re| re.is_match(&self.path))
215 .unwrap_or(false)
216 } else {
217 self.path == pattern
218 }
219 }
220
221 pub fn matches_method(&self, methods: &[String]) -> bool {
223 methods.iter().any(|m| m == "*" || m == &self.method.to_string())
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct ResponseData {
230 pub status_code: u16,
232 pub headers: HashMap<String, String>,
234 pub body: Vec<u8>,
236 pub content_type: String,
238 pub metadata: HashMap<String, Value>,
240 pub cache_control: Option<String>,
242 pub custom: HashMap<String, Value>,
244}
245
246impl ResponseData {
247 pub fn new(status_code: u16, content_type: String, body: Vec<u8>) -> Self {
249 Self {
250 status_code,
251 headers: HashMap::new(),
252 body,
253 content_type,
254 metadata: HashMap::new(),
255 cache_control: None,
256 custom: HashMap::new(),
257 }
258 }
259
260 pub fn json<T: Serialize>(status_code: u16, data: &T) -> Result<Self> {
262 let body = serde_json::to_vec(data)
263 .map_err(|e| PluginError::execution(format!("JSON serialization error: {}", e)))?;
264
265 Ok(Self::new(status_code, "application/json".to_string(), body))
266 }
267
268 pub fn text<S: Into<String>>(status_code: u16, text: S) -> Self {
270 Self::new(status_code, "text/plain".to_string(), text.into().into_bytes())
271 }
272
273 pub fn html<S: Into<String>>(status_code: u16, html: S) -> Self {
275 Self::new(status_code, "text/html".to_string(), html.into().into_bytes())
276 }
277
278 pub fn xml<S: Into<String>>(status_code: u16, xml: S) -> Self {
280 Self::new(status_code, "application/xml".to_string(), xml.into().into_bytes())
281 }
282
283 pub fn with_header<S: Into<String>>(mut self, key: S, value: S) -> Self {
285 self.headers.insert(key.into(), value.into());
286 self
287 }
288
289 pub fn with_headers(mut self, headers: HashMap<String, String>) -> Self {
291 self.headers.extend(headers);
292 self
293 }
294
295 pub fn with_metadata<S: Into<String>>(mut self, key: S, value: Value) -> Self {
297 self.metadata.insert(key.into(), value);
298 self
299 }
300
301 pub fn with_cache_control<S: Into<String>>(mut self, cache_control: S) -> Self {
303 self.cache_control = Some(cache_control.into());
304 self
305 }
306
307 pub fn with_custom<S: Into<String>>(mut self, key: S, value: Value) -> Self {
309 self.custom.insert(key.into(), value);
310 self
311 }
312
313 pub fn to_axum_response(self) -> Result<axum::response::Response> {
315 use axum::http::HeaderValue;
316 use axum::response::Response;
317
318 let mut response = Response::new(axum::body::Body::from(self.body));
319 *response.status_mut() = StatusCode::from_u16(self.status_code)
320 .map_err(|_| PluginError::execution("Invalid status code"))?;
321
322 for (key, value) in self.headers {
324 if let (Ok(header_name), Ok(header_value)) =
325 (key.parse::<axum::http::HeaderName>(), value.parse::<HeaderValue>())
326 {
327 response.headers_mut().insert(header_name, header_value);
328 }
329 }
330
331 if !response.headers().contains_key("content-type") {
333 if let Ok(header_value) = self.content_type.parse::<HeaderValue>() {
334 response.headers_mut().insert("content-type", header_value);
335 }
336 }
337
338 if let Some(cache_control) = self.cache_control {
340 if let Ok(header_value) = cache_control.parse::<HeaderValue>() {
341 response.headers_mut().insert("cache-control", header_value);
342 }
343 }
344
345 Ok(response)
346 }
347
348 pub fn body_as_string(&self) -> Option<String> {
350 String::from_utf8(self.body.clone()).ok()
351 }
352
353 pub fn body_as_json(&self) -> Option<Value> {
355 serde_json::from_slice(&self.body).ok()
356 }
357}
358
359pub struct ResponsePluginEntry {
361 pub plugin_id: crate::PluginId,
363 pub plugin: std::sync::Arc<dyn ResponsePlugin>,
365 pub config: ResponsePluginConfig,
367 pub capabilities: PluginCapabilities,
369}
370
371impl ResponsePluginEntry {
372 pub fn new(
374 plugin_id: crate::PluginId,
375 plugin: std::sync::Arc<dyn ResponsePlugin>,
376 config: ResponsePluginConfig,
377 ) -> Self {
378 let capabilities = plugin.capabilities();
379 Self {
380 plugin_id,
381 plugin,
382 config,
383 capabilities,
384 }
385 }
386
387 pub fn is_enabled(&self) -> bool {
389 self.config.enabled
390 }
391
392 pub fn priority(&self) -> i32 {
394 self.config.priority
395 }
396
397 pub fn can_handle_request(&self, request: &ResponseRequest) -> bool {
399 self.is_enabled()
400 && request.matches_method(&self.config.methods)
401 && self
402 .config
403 .url_patterns
404 .iter()
405 .any(|pattern| request.matches_url_pattern(pattern))
406 }
407}
408
409#[async_trait::async_trait]
423pub trait ResponseModifierPlugin: Send + Sync {
424 fn capabilities(&self) -> PluginCapabilities;
426
427 async fn initialize(&self, config: &ResponseModifierConfig) -> Result<()>;
429
430 async fn should_modify(
441 &self,
442 context: &PluginContext,
443 request: &ResponseRequest,
444 response: &ResponseData,
445 config: &ResponseModifierConfig,
446 ) -> Result<PluginResult<bool>>;
447
448 async fn modify_response(
462 &self,
463 context: &PluginContext,
464 request: &ResponseRequest,
465 response: ResponseData,
466 config: &ResponseModifierConfig,
467 ) -> Result<PluginResult<ResponseData>>;
468
469 fn priority(&self) -> i32;
471
472 fn validate_config(&self, config: &ResponseModifierConfig) -> Result<()>;
474
475 async fn cleanup(&self) -> Result<()>;
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct ResponseModifierConfig {
482 pub config: HashMap<String, serde_json::Value>,
484 pub enabled: bool,
486 pub priority: i32,
488 pub content_types: Vec<String>,
490 pub url_patterns: Vec<String>,
492 pub methods: Vec<String>,
494 pub status_codes: Vec<u16>,
496 pub settings: HashMap<String, serde_json::Value>,
498}
499
500impl Default for ResponseModifierConfig {
501 fn default() -> Self {
502 Self {
503 config: HashMap::new(),
504 enabled: true,
505 priority: 100,
506 content_types: vec!["application/json".to_string()],
507 url_patterns: vec!["*".to_string()],
508 methods: vec!["GET".to_string(), "POST".to_string()],
509 status_codes: vec![], settings: HashMap::new(),
511 }
512 }
513}
514
515pub trait ResponsePluginFactory: Send + Sync {
517 fn create_plugin(&self) -> Result<Box<dyn ResponsePlugin>>;
519}
520
521pub trait ResponseModifierPluginFactory: Send + Sync {
523 fn create_plugin(&self) -> Result<Box<dyn ResponseModifierPlugin>>;
525}
526
527pub mod helpers {
529 use super::*;
530
531 pub fn error_response(status_code: u16, message: &str) -> ResponseData {
533 let error_data = serde_json::json!({
534 "error": {
535 "message": message,
536 "timestamp": chrono::Utc::now().to_rfc3339(),
537 "status_code": status_code
538 }
539 });
540
541 ResponseData::json(status_code, &error_data)
542 .unwrap_or_else(|_| ResponseData::text(status_code, format!("Error: {}", message)))
543 }
544
545 pub fn success_response<T: Serialize>(data: &T) -> Result<ResponseData> {
547 ResponseData::json(200, data)
548 }
549
550 pub fn redirect_response(location: &str, permanent: bool) -> ResponseData {
552 let status_code = if permanent { 301 } else { 302 };
553 ResponseData::new(
554 status_code,
555 "text/plain".to_string(),
556 format!("Redirecting to: {}", location).into_bytes(),
557 )
558 .with_header("location", location)
559 }
560
561 pub fn not_found_response(message: Option<&str>) -> ResponseData {
563 let message = message.unwrap_or("Resource not found");
564 error_response(404, message)
565 }
566
567 pub fn unauthorized_response(message: Option<&str>) -> ResponseData {
569 let message = message.unwrap_or("Unauthorized");
570 error_response(401, message)
571 }
572
573 pub fn forbidden_response(message: Option<&str>) -> ResponseData {
575 let message = message.unwrap_or("Forbidden");
576 error_response(403, message)
577 }
578}
579
580#[cfg(test)]
581mod tests {
582
583 #[test]
584 fn test_module_compiles() {
585 }
587}