1use std::borrow::Cow;
2use std::sync::Arc;
3
4use actix_files::Files;
5use actix_web::dev::HttpServiceFactory;
6use path_tree::PathTree;
7use tracing::warn;
8
9use crate::spa_service::SpaService;
10use crate::utils::{find_and_parse_build_manifest, serve_index};
11
12#[derive(Debug, Clone)]
30pub struct Spa {
31    index_file: Cow<'static, str>,
32    static_resources_mount: Cow<'static, str>,
33    static_resources_location: Cow<'static, str>,
34}
35
36impl Spa {
37    pub fn new() -> Self {
39        Self::default()
40    }
41
42    pub fn index_file(mut self, index_file: impl Into<String>) -> Self {
44        self.index_file = Cow::Owned(index_file.into());
45        self
46    }
47
48    pub fn static_resources_mount(mut self, static_resources_mount: impl Into<String>) -> Self {
50        self.static_resources_mount = Cow::Owned(static_resources_mount.into());
51        self
52    }
53
54    pub fn static_resources_location(mut self, static_resources_location: impl Into<String>) -> Self {
56        self.static_resources_location = Cow::Owned(static_resources_location.into());
57        self
58    }
59
60    pub fn finish(self) -> impl HttpServiceFactory {
62        let index_file = Arc::new(self.index_file.into_owned());
63        let static_resources_location = Arc::new(self.static_resources_location.into_owned());
64        let static_resources_mount = self.static_resources_mount.into_owned();
65
66        let path_tree = match find_and_parse_build_manifest(&static_resources_location) {
67            Ok(tree) => Arc::new(tree),
68            Err(e) => {
69                warn!("Failed to parse build manifest: {}. Using default path tree.", e);
70                Arc::new(PathTree::default())
71            }
72        };
73
74        let files = Files::new(&static_resources_mount, static_resources_location.as_str())
75            .index_file("extremely-unlikely-to-exist-!@$%^&*.txt")
76            .default_handler({
77                let index_file = Arc::clone(&index_file);
78                let static_resources_location = Arc::clone(&static_resources_location);
79                let path_tree = Arc::clone(&path_tree);
80
81                move |req| {
82                    let index_file = Arc::clone(&index_file);
83                    let static_resources_location = Arc::clone(&static_resources_location);
84                    let path_tree = Arc::clone(&path_tree);
85
86                    async move {
87                        serve_index(req, &index_file, &static_resources_location, &path_tree)
88                            .await
89                            .map_err(actix_web::Error::from)
90                    }
91                }
92            });
93
94        SpaService {
95            index_file,
96            static_resources_location,
97            files,
98            path_tree,
99        }
100    }
101}
102
103impl Default for Spa {
105    fn default() -> Self {
106        Self {
107            index_file: Cow::Borrowed("./index.html"),
108            static_resources_mount: Cow::Borrowed("/"),
109            static_resources_location: Cow::Borrowed("./"),
110        }
111    }
112}
113
114pub fn spa() -> Spa {
116    Spa::default()
117}