1use std::borrow::Cow;
17use std::sync::Arc;
18use rust_embed::EmbeddedFile;
19use warp::filters::path::{FullPath, Tail};
20use warp::http::Uri;
21use warp::{reject::Rejection, reply::Reply, reply::Response, Filter};
22
23#[derive(Debug, Clone)]
25pub struct EmbedConfig {
26 pub directory_index: Vec<String>,
30}
31
32impl Default for EmbedConfig {
33 fn default() -> Self {
34 EmbedConfig {
35 directory_index: vec!["index.html".to_string(), "index.htm".to_string()],
36 }
37 }
38}
39
40struct EmbedFile {
41 data: Cow<'static, [u8]>,
42}
43
44impl Reply for EmbedFile {
45 fn into_response(self) -> Response {
46 Response::new(self.data.into())
47 }
48}
49
50fn append_filename(path: &str, filename: &str) -> String {
51 if path.is_empty() {
52 filename.to_string()
53 } else {
54 format!("{}/{}", path, filename)
55 }
56}
57
58pub fn embed_one<A: rust_embed::RustEmbed>(
60 _: &A,
61 filename: &str,
62) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
63 let filename = Arc::new(filename.to_string());
64 warp::any()
65 .map(move || filename.clone())
66 .and_then(|filename: Arc<String>| async move {
67 if let Some(x) = A::get(&filename) {
68 Ok(create_reply(x, &filename))
69 } else {
70 Err(warp::reject::not_found())
71 }
72 })
73}
74
75pub fn embed<A: rust_embed::RustEmbed>(
77 x: &A,
78) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
79 embed_with_config(x, EmbedConfig::default())
80}
81
82#[allow(dead_code)]
83#[derive(Debug)]
84struct NotFound {
85 config: Arc<EmbedConfig>,
86 tail: Tail,
87 full: FullPath,
88}
89
90impl warp::reject::Reject for NotFound {}
91
92pub fn embed_with_config<A: rust_embed::RustEmbed>(
95 _: &A,
96 config: EmbedConfig,
97) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
98 let config = Arc::new(config);
99 let config2 = config.clone();
100 let direct_serve = warp::path::tail().and_then(|tail: Tail| async move {
101 if let Some(x) = A::get(tail.as_str()) {
102 Ok(create_reply(x, tail.as_str()))
103 } else {
104 Err(warp::reject::not_found())
105 }
106 });
107
108 let directory_index = warp::any()
109 .map(move || config.clone())
110 .and(warp::path::tail())
111 .and(warp::path::full())
112 .and_then(
113 |config: Arc<EmbedConfig>, tail: Tail, full: FullPath| async move {
114 for one in config.directory_index.iter() {
116 if full.as_str().ends_with('/') {
117 let filepath = format!("{}{}", tail.as_str(), one);
118 if let Some(x) = A::get(&filepath) {
119 return Ok(create_reply(x, one));
120 }
121 }
122 }
123
124 Err(warp::reject::not_found())
125 },
126 );
127
128 let redirect = warp::any()
129 .map(move || config2.clone())
130 .and(warp::path::tail())
131 .and(warp::path::full())
132 .and_then(
133 |config: Arc<EmbedConfig>, tail: Tail, full: FullPath| async move {
134 for one in config.directory_index.iter() {
135 if A::get(&append_filename(tail.as_str(), one)).is_some()
136 && !full.as_str().ends_with('/')
137 {
138 let new_path = format!("{}/", full.as_str());
139 return Ok(warp::redirect(
140 Uri::builder()
141 .path_and_query(new_path.as_str())
142 .build()
143 .unwrap(),
144 ));
145 }
146 }
147
148 Err(warp::reject::not_found())
149 },
150 );
151
152 warp::any()
153 .and(direct_serve)
154 .or(directory_index)
155 .or(redirect)
156}
157
158fn create_reply(file: EmbeddedFile, actual_name: &str) -> impl Reply {
159 let suggest = mime_guess::from_path(actual_name).first_or_octet_stream();
160 warp::reply::with_header(EmbedFile { data: file.data }, "Content-Type", suggest.to_string())
161}
162
163#[cfg(test)]
164mod test;