Skip to main content

tiny_proxy/api/
endpoints.rs

1//! API endpoints for proxy management
2
3use anyhow::Result;
4use bytes::Bytes;
5use http_body::Body;
6use http_body_util::{BodyExt, Full};
7use hyper::{Request, Response};
8use std::sync::Arc;
9use tracing::info;
10
11use crate::config::Config;
12
13/// Handle GET /config
14pub async fn handle_get_config<B>(
15    _req: Request<B>,
16    config: Arc<tokio::sync::RwLock<Config>>,
17) -> Result<Response<Full<Bytes>>>
18where
19    B: Body,
20{
21    let config = config.read().await;
22
23    let json = serde_json::to_string_pretty(&*config)
24        .unwrap_or_else(|_| r#"{"error": "Failed to serialize config"}"#.to_string());
25
26    info!("GET /config - Returning configuration");
27
28    let response = Response::builder()
29        .status(200)
30        .header("Content-Type", "application/json")
31        .body(Full::new(Bytes::from(json)))
32        .unwrap();
33
34    Ok(response)
35}
36
37/// Handle POST /config
38pub async fn handle_post_config<B>(
39    req: Request<B>,
40    _config: Arc<tokio::sync::RwLock<Config>>,
41) -> Result<Response<Full<Bytes>>>
42where
43    B: Body,
44    B::Error: std::fmt::Display,
45{
46    let body_bytes = match BodyExt::collect(req.into_body()).await {
47        Ok(collected) => collected.to_bytes(),
48        Err(e) => {
49            let response = Response::builder()
50                .status(400)
51                .body(Full::new(Bytes::from(format!(
52                    "Failed to read request body: {}",
53                    e
54                ))))
55                .unwrap();
56            return Ok(response);
57        }
58    };
59
60    let body_str = match std::str::from_utf8(&body_bytes) {
61        Ok(s) => s,
62        Err(_) => {
63            let response = Response::builder()
64                .status(400)
65                .body(Full::new(Bytes::from(
66                    "Invalid UTF-8 in request body".to_string(),
67                )))
68                .unwrap();
69            return Ok(response);
70        }
71    };
72
73    info!("POST /config - Updating configuration");
74    info!("New config: {}", body_str);
75
76    let response = Response::builder()
77        .status(200)
78        .header("Content-Type", "application/json")
79        .body(Full::new(Bytes::from(
80            r#"{"status": "success", "message": "Configuration updated"}"#.to_string(),
81        )))
82        .unwrap();
83
84    Ok(response)
85}
86
87/// Handle GET /health
88pub async fn handle_health_check<B>(_req: Request<B>) -> Result<Response<Full<Bytes>>>
89where
90    B: Body,
91{
92    info!("GET /health - Health check");
93
94    let health = serde_json::json!({
95        "status": "healthy",
96        "service": "tiny-proxy",
97        "version": env!("CARGO_PKG_VERSION")
98    });
99
100    let response = Response::builder()
101        .status(200)
102        .header("Content-Type", "application/json")
103        .body(Full::new(Bytes::from(
104            serde_json::to_string(&health).unwrap(),
105        )))
106        .unwrap();
107
108    Ok(response)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use http_body_util::Empty;
115    use std::collections::HashMap;
116    use std::sync::Arc;
117
118    #[tokio::test]
119    async fn test_handle_health_check() {
120        let req: Request<Empty<Bytes>> = Request::builder().body(Empty::new()).unwrap();
121
122        let response = handle_health_check(req).await.unwrap();
123        assert_eq!(response.status(), 200);
124    }
125
126    #[tokio::test]
127    async fn test_handle_get_config() {
128        let config = Arc::new(tokio::sync::RwLock::new(Config {
129            sites: HashMap::new(),
130        }));
131
132        let req: Request<Empty<Bytes>> = Request::builder().body(Empty::new()).unwrap();
133
134        let response = handle_get_config(req, config).await.unwrap();
135        assert_eq!(response.status(), 200);
136    }
137
138    #[tokio::test]
139    async fn test_handle_post_config() {
140        let config = Arc::new(tokio::sync::RwLock::new(Config {
141            sites: HashMap::new(),
142        }));
143
144        let req: Request<Empty<Bytes>> = Request::builder().body(Empty::new()).unwrap();
145
146        let response = handle_post_config(req, config).await.unwrap();
147        assert_eq!(response.status(), 200);
148    }
149}