1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use crate::prelude::full;
use crate::{Handler, Request, Response, SilentError, StatusCode};
use async_trait::async_trait;

struct HandlerWrapperStatic {
    path: String,
}

impl Default for HandlerWrapperStatic {
    fn default() -> Self {
        Self::new(".")
    }
}

impl HandlerWrapperStatic {
    fn new(path: &str) -> Self {
        let mut path = path;
        if path.ends_with('/') {
            path = &path[..path.len() - 1];
        }
        if !std::path::Path::new(path).is_dir() {
            panic!("Path not exists: {}", path);
        }
        Self {
            path: path.to_string(),
        }
    }
}

#[async_trait]
impl Handler for HandlerWrapperStatic {
    async fn call(&self, req: Request) -> Result<Response, SilentError> {
        if let Ok(file_path) = req.get_path_params::<String>("path") {
            let mut path = format!("{}/{}", self.path, file_path);
            if path.ends_with('/') {
                path.push_str("index.html");
            }
            if let Ok(contents) = tokio::fs::read(path).await {
                let mut res = Response::empty();
                res.set_body(full(contents));
                return Ok(res);
            }
        }
        Err(SilentError::BusinessError {
            code: StatusCode::NOT_FOUND,
            msg: "Not Found".to_string(),
        })
    }
}

pub fn static_handler(path: &str) -> impl Handler {
    HandlerWrapperStatic::new(path)
}

#[cfg(test)]
mod tests {
    use super::HandlerWrapperStatic;
    use crate::prelude::*;
    use crate::Handler;
    use crate::Request;
    use crate::SilentError;
    use crate::StatusCode;
    use bytes::Bytes;
    use http_body_util::BodyExt;

    static CONTENT: &str = r#"<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Silent</title>
</head>
<body>

<h1>我的第一个标题</h1>

<p>我的第一个段落。</p>

</body>
</html>"#;

    fn create_static(path: &str) {
        if !std::path::Path::new(path).is_dir() {
            std::fs::create_dir(path).unwrap();
            std::fs::write(format!("./{}/index.html", path), CONTENT).unwrap();
        }
    }

    fn clean_static(path: &str) {
        if std::path::Path::new(path).is_dir() {
            std::fs::remove_file(format!("./{}/index.html", path)).unwrap();
            std::fs::remove_dir(path).unwrap();
        }
    }

    #[tokio::test]
    async fn test_static() {
        let path = "test_static";
        create_static(path);
        let handler = HandlerWrapperStatic::new(path);
        let mut req = Request::default();
        req.set_path_params("path".to_owned(), PathParam::Path("index.html".to_string()));
        let mut res = handler.call(req).await.unwrap();
        clean_static(path);
        assert_eq!(res.status_code, StatusCode::OK);
        assert_eq!(
            res.body.frame().await.unwrap().unwrap().data_ref().unwrap(),
            &Bytes::from(CONTENT)
        );
    }

    #[tokio::test]
    async fn test_static_default() {
        let path = "test_static_default";
        create_static(path);
        let handler = HandlerWrapperStatic::new(path);
        let mut req = Request::default();
        req.set_path_params("path".to_owned(), PathParam::Path("".to_string()));
        let mut res = handler.call(req).await.unwrap();
        clean_static(path);
        assert_eq!(res.status_code, StatusCode::OK);
        assert_eq!(
            res.body.frame().await.unwrap().unwrap().data_ref().unwrap(),
            &Bytes::from(CONTENT)
        );
    }

    #[tokio::test]
    async fn test_static_not_found() {
        let path = "test_static_not_found";
        create_static(path);
        let handler = HandlerWrapperStatic::new(path);
        let mut req = Request::default();
        req.set_path_params(
            "path".to_owned(),
            PathParam::Path("not_found.html".to_string()),
        );
        let res = handler.call(req).await.unwrap_err();
        clean_static(path);
        if let SilentError::BusinessError { code, .. } = res {
            assert_eq!(code, StatusCode::NOT_FOUND);
        } else {
            panic!();
        }
    }
}