anycms_spa/core/
mod.rs

1pub mod path;
2
3
4use rust_embed::RustEmbed;
5use thiserror::Error;
6use std::borrow::Cow;
7
8#[derive(Debug, Error)]
9pub enum SpaError {
10    #[error("Resource not found: {0}")]
11    NotFound(String),
12    #[error("MIME type detection failed")]
13    MimeDetection,
14    #[error("Path error: {0}")]
15    PathError(#[from] crate::core::path::PathError),
16    #[error("Index file not found")]
17    IndexFileNotFound,
18}
19
20/// SPA 配置
21#[derive(Clone)]
22pub struct SpaConfig {
23    pub base_path: String,
24    pub index_files: Vec<String>,
25}
26
27impl Default for SpaConfig {
28    fn default() -> Self {
29        SpaConfig {
30            base_path: "/".to_string(),
31            index_files: vec!["index.html".to_string()],
32        }
33    }
34}
35
36impl SpaConfig {
37    /// 设置基础路径
38    pub fn with_base_path(mut self, base_path: &str) -> Self {
39        self.base_path = base_path.to_string();
40        self
41    }
42    
43    /// 设置索引文件(可多个)
44    pub fn with_index_files(mut self, files: &[&str]) -> Self {
45        self.index_files = files.iter().map(|s| s.to_string()).collect();
46        self
47    }
48    
49    /// 添加索引文件
50    pub fn add_index_file(mut self, file: &str) -> Self {
51        self.index_files.push(file.to_string());
52        self
53    }
54}
55
56/// SPA 处理器
57pub struct SpaHandler<E: RustEmbed> {
58    config: SpaConfig,
59    _marker: std::marker::PhantomData<E>,
60}
61
62impl<E: RustEmbed> SpaHandler<E> {
63    pub fn new(config: SpaConfig) -> Self {
64        Self {
65            config,
66            _marker: std::marker::PhantomData,
67        }
68    }
69    
70    /// 获取嵌入的文件(考虑基础路径)
71    pub fn get_file(&self, request_path: &str) -> Result<(Cow<'static, [u8]>, &'static str), SpaError> {
72        // 规范化请求路径
73        let normalized_path = crate::core::path::normalize_path(request_path)?;
74        // 获取相对于基础路径的资源路径
75        let resource_path = crate::core::path::relative_to_base(&normalized_path, &self.config.base_path);
76        // 尝试获取资源
77        if let Some(content) = E::get(&resource_path) {
78            let mime = mime_guess::from_path(&resource_path)
79                .first_raw()
80                .ok_or(SpaError::MimeDetection)?;
81            return Ok((content.data, mime));
82        }
83        
84        // 尝试获取索引文件
85        for index_file in &self.config.index_files {
86            if let Some(content) = E::get(index_file) {
87                return Ok((content.data, "text/html"));
88            }
89        }
90        
91        // 尝试默认索引文件
92        if let Some(content) = E::get("index.html") {
93            return Ok((content.data, "text/html"));
94        }
95        
96        Err(SpaError::IndexFileNotFound)
97    }
98}