vertigo_cli/serve/
mount_path.rs

1#![allow(clippy::question_mark)]
2use std::{collections::HashMap, path::Path, sync::Arc};
3use vertigo::dev::VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER;
4
5use crate::commons::{ErrorCode, models::IndexModel};
6
7pub struct MountConfigBuilder {
8    pub mount_point: String,
9    pub dest_dir: String,
10    pub env: Vec<(String, String)>,
11    pub wasm_preload: bool,
12    pub disable_hydration: bool,
13}
14
15impl MountConfigBuilder {
16    pub fn new(mount_point: impl Into<String>, dest_dir: impl Into<String>) -> MountConfigBuilder {
17        MountConfigBuilder {
18            mount_point: mount_point.into(),
19            dest_dir: dest_dir.into(),
20            env: vec![],
21            wasm_preload: false,
22            disable_hydration: false,
23        }
24    }
25
26    pub fn envs(mut self, envs: Vec<(String, String)>) -> Self {
27        self.env = envs;
28        self
29    }
30
31    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
32        self.env.push((key.into(), value.into()));
33        self
34    }
35
36    pub fn wasm_preload(mut self, wasm_preload: bool) -> Self {
37        self.wasm_preload = wasm_preload;
38        self
39    }
40
41    pub fn disable_hydration(mut self, disable_hydration: bool) -> Self {
42        self.disable_hydration = disable_hydration;
43        self
44    }
45
46    pub fn build(self) -> Result<MountConfig, ErrorCode> {
47        MountConfig::new(
48            self.mount_point,
49            self.dest_dir,
50            self.env,
51            self.wasm_preload,
52            self.disable_hydration,
53        )
54    }
55}
56
57#[derive(Clone, Debug)]
58pub struct MountConfig {
59    /// Mount point in URL
60    mount_point: String,
61    /// Build destination directory (where wasm file and wasm_run.js are stored)
62    dest_dir: String,
63    /// waasm_run.js taken from index.json
64    run_js: String,
65    /// path to wasm-file taken from index.json
66    wasm_path: String,
67    /// Environment variables passed to WASM runtime
68    pub env: Arc<HashMap<String, String>>,
69    /// Whether to preload wasm script using <link rel="preload">
70    pub wasm_preload: bool,
71    /// Whether to disable hydration
72    pub disable_hydration: bool,
73}
74
75impl MountConfig {
76    pub fn new(
77        public_mount_point: impl Into<String>,
78        dest_dir: impl Into<String>,
79        env: Vec<(String, String)>,
80        wasm_preload: bool,
81        disable_hydration: bool,
82    ) -> Result<MountConfig, ErrorCode> {
83        let dest_dir = dest_dir.into();
84        let index_model = read_index(&dest_dir)?;
85
86        Ok(MountConfig {
87            dest_dir,
88            mount_point: public_mount_point.into(),
89            run_js: index_model.run_js,
90            wasm_path: index_model.wasm,
91            env: Arc::new(env.into_iter().collect()),
92            wasm_preload,
93            disable_hydration,
94        })
95    }
96
97    pub fn mount_point(&self) -> &str {
98        self.mount_point.as_str()
99    }
100
101    pub fn dest_dir(&self) -> &str {
102        self.dest_dir.trim_start_matches("./")
103    }
104
105    pub fn dest_http_root(&self) -> String {
106        Path::new(&self.mount_point)
107            .join(self.dest_dir())
108            .components()
109            .as_path()
110            .to_string_lossy()
111            .into_owned()
112    }
113
114    pub fn get_wasm_http_path(&self) -> String {
115        self.translate_to_http(&self.wasm_path)
116    }
117
118    pub fn get_run_js_http_path(&self) -> String {
119        self.translate_to_http(&self.run_js)
120    }
121
122    pub fn get_wasm_fs_path(&self) -> String {
123        self.translate_to_fs(&self.wasm_path)
124    }
125
126    fn translate_to_http(&self, fs_path: impl Into<String>) -> String {
127        let fs_path = fs_path.into();
128        fs_path.replace(
129            VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER,
130            &self.dest_http_root(),
131        )
132    }
133
134    fn translate_to_fs(&self, http_path: impl Into<String>) -> String {
135        let http_path = http_path.into();
136        replace_prefix(&self.dest_dir, &http_path)
137    }
138}
139
140fn read_index(dest_dir: &str) -> Result<IndexModel, ErrorCode> {
141    let index_path = Path::new(dest_dir).join("index.json");
142    let index_html = match std::fs::read_to_string(&index_path) {
143        Ok(data) => data,
144        Err(err) => {
145            log::error!("File read error: file={index_path:?}, error={err}, dest_dir={dest_dir}");
146            return Err(ErrorCode::ServeCantReadIndexFile);
147        }
148    };
149
150    serde_json::from_str::<IndexModel>(&index_html).map_err(|err| {
151        log::error!("File read error 2: file={index_path:?}, error={err}, dest_dir={dest_dir}");
152        ErrorCode::ServeCantReadIndexFile
153    })
154}
155
156fn replace_prefix(dest_dir: &str, path: &str) -> String {
157    if path.starts_with(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER) {
158        // Dynamic path resolution
159        path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, dest_dir)
160    } else {
161        // Static path resolution
162        path.to_string()
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use vertigo::dev::VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER;
169
170    use super::replace_prefix;
171
172    #[test]
173    fn test_replace_prefix() {
174        assert_eq!(
175            replace_prefix("demo_build", "build/vertigo_demo.33.wasm"),
176            "build/vertigo_demo.33.wasm".to_string()
177        );
178
179        assert_eq!(
180            replace_prefix("demo_build", "build/vertigo_demo.33.wasm"),
181            "build/vertigo_demo.33.wasm".to_string()
182        );
183
184        assert_eq!(
185            replace_prefix(
186                "demo_build",
187                &format!("{VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER}/vertigo_demo.33.wasm")
188            ),
189            "demo_build/vertigo_demo.33.wasm".to_string()
190        );
191    }
192}