livereload_server/
serve.rs1use std::path::Path;
2
3use crate::inject::inject_before_the_end_of_body;
4
5pub async fn serve_file(
6 target_dir: impl AsRef<Path>,
7 path_in_request: &str,
8 payload: &str,
9) -> anyhow::Result<Option<Vec<u8>>> {
10 let target_dir = target_dir.as_ref();
11
12 let mut path = path_in_request;
13
14 if path == "/" || path.is_empty() {
15 path = "/index.html";
16 }
17
18 let path = path.strip_prefix('/').unwrap_or(path);
19
20 let path_in_target_dir = target_dir.join(path);
21
22 if !path_in_target_dir.exists() {
23 return Ok(None);
24 }
25
26 let body = if path.ends_with(".html") {
27 log::info!(
28 "Serving HTML requested at {path} with {path_in_target_dir:?}"
29 );
30
31 let content = tokio::fs::read_to_string(path_in_target_dir).await?;
32
33 let data = inject_before_the_end_of_body(content.as_str(), payload)?;
34
35 data.bytes().collect()
36 } else {
37 log::info!("Serving a non HTML file requested as {path_in_request} with {path_in_target_dir:?}");
38
39 tokio::fs::read(path_in_target_dir).await?
40 };
41
42 Ok(Some(body))
43}
44
45#[cfg(test)]
46mod tests {
47 use indoc::indoc;
48 use tempfile::TempDir;
49
50 use super::*;
51
52 const INDEX: &str = indoc! {r#"
53 <!DOCTYPE html>
54 <html>
55 <body>
56 <h1>My First Heading</h1>
57 <p>My first paragraph.</p>
58 </body>
59 </html>
60 "#};
61
62 const INDEX_CSS: &str = indoc! {r#"
63 body {
64 color: red;
65 }
66 "#};
67
68 async fn prepare_directory() -> anyhow::Result<TempDir> {
69 let temp_dir = TempDir::new()?;
70
71 tokio::fs::write(temp_dir.path().join("index.html"), INDEX).await?;
72 tokio::fs::write(temp_dir.path().join("index.css"), INDEX_CSS).await?;
73
74 Ok(temp_dir)
75 }
76
77 #[tokio::test]
78 async fn serving_a_missing_file_returns_none() -> anyhow::Result<()> {
79 let temp_dir = prepare_directory().await?;
80
81 let served = serve_file(temp_dir.path(), "favicon.ico", "").await?;
82
83 assert!(served.is_none());
84
85 Ok(())
86 }
87
88 #[tokio::test]
89 async fn serving_at_root_returns_index_html() -> anyhow::Result<()> {
90 let temp_dir = prepare_directory().await?;
91
92 let expected: Vec<u8> = INDEX.bytes().collect();
93
94 let served = serve_file(temp_dir.path(), "index.html", "")
95 .await?
96 .unwrap();
97 assert_eq!(served, expected);
98
99 let served = serve_file(temp_dir.path(), "/", "").await?.unwrap();
100 assert_eq!(served, expected);
101
102 let served = serve_file(temp_dir.path(), "", "").await?.unwrap();
103 assert_eq!(served, expected);
104
105 Ok(())
106 }
107
108 #[tokio::test]
109 async fn serving_an_existing_file_returns_it() -> anyhow::Result<()> {
110 let temp_dir = prepare_directory().await?;
111
112 let served =
113 serve_file(temp_dir.path(), "index.css", "").await?.unwrap();
114
115 let expected: Vec<u8> = INDEX_CSS.bytes().collect();
116
117 assert_eq!(served, expected);
118
119 Ok(())
120 }
121}