thymeleaf 0.1.0-beta.0

A framework-neutral Thymeleaf-compatible dynamic template engine for Rust
Documentation
use std::io;
use std::sync::{Arc, RwLock, RwLockReadGuard};

use indexmap::IndexMap;

use crate::expression::TemplateValue;
use crate::model::IModel;
use crate::util::{FastStringWriter, Utf16String, ValidateError};

/// Fragment 调用参数的有序动态 Map;键和值都保留 Java null。
pub type FragmentParameterMap = IndexMap<Option<Utf16String>, Option<Arc<TemplateValue>>>;

/// Fragment Expression 的执行结果。
///
/// 对应 Java: `org.thymeleaf.standard.expression.Fragment`。
pub struct Fragment {
    template_model: Option<Arc<dyn IModel>>,
    parameters: Option<Arc<RwLock<FragmentParameterMap>>>,
    synthetic_parameters: bool,
}

impl Fragment {
    /// 不包含模型或参数的空 Fragment。
    pub const EMPTY_FRAGMENT: Self = Self {
        template_model: None,
        parameters: None,
        synthetic_parameters: false,
    };

    /// 创建 Fragment;参数 Map 使用只读包装但保留原 backing map 身份。
    /// 对应 Java 语义:`Fragment` 的 `new` 行为(Rust 侧辅助/私有路径)。
    pub fn new(
        template_model: Option<Arc<dyn IModel>>,
        parameters: Option<Arc<RwLock<FragmentParameterMap>>>,
        synthetic_parameters: bool,
    ) -> Result<Self, ValidateError> {
        let template_model = template_model.ok_or_else(|| ValidateError::IllegalArgument {
            message: Some("Template model cannot be null".to_owned()),
        })?;
        let synthetic_parameters = parameters.as_ref().is_some_and(|values| {
            !read_recovering_poison(values).is_empty() && synthetic_parameters
        });
        Ok(Self {
            template_model: Some(template_model),
            parameters,
            synthetic_parameters,
        })
    }

    /// 返回全局 EMPTY_FRAGMENT 单例。
    /// 对应 Java 语义:`Fragment` 的 `empty_fragment` 行为(Rust 侧辅助/私有路径)。
    pub fn empty_fragment() -> &'static Self {
        &Self::EMPTY_FRAGMENT
    }

    /// 返回可空模板模型。
    /// 对应 Java: `Fragment#getTemplateModel()`。
    pub fn get_template_model(&self) -> Option<&dyn IModel> {
        self.template_model.as_deref()
    }

    /// 返回共享模板模型身份,供结构处理器直接插入模型而不序列化为字符串。
    #[must_use]
    /// 对应 Java 语义:`Fragment` 的 `get_template_model_arc` 行为(Rust 侧辅助/私有路径)。
    pub fn get_template_model_arc(&self) -> Option<Arc<dyn IModel>> {
        self.template_model.clone()
    }

    /// 返回原参数 Map 的实时只读视图。
    /// 对应 Java: `Fragment#getParameters()`。
    pub fn get_parameters(&self) -> Option<RwLockReadGuard<'_, FragmentParameterMap>> {
        self.parameters
            .as_ref()
            .map(|parameters| read_recovering_poison(parameters))
    }

    /// 返回参数 Map 的共享身份,供 Fragment 签名重整与局部变量注入。
    #[must_use]
    /// 对应 Java 语义:`Fragment` 的 `get_parameters_arc` 行为(Rust 侧辅助/私有路径)。
    pub fn get_parameters_arc(&self) -> Option<Arc<RwLock<FragmentParameterMap>>> {
        self.parameters.clone()
    }

    /// 判断构造瞬间非空参数是否为合成位置参数。
    /// 对应 Java: `Fragment#hasSyntheticParameters()`。
    pub fn has_synthetic_parameters(&self) -> bool {
        self.synthetic_parameters
    }

    /// 将 Fragment 模型写入 Java Writer;EMPTY_FRAGMENT 不写任何内容。
    /// 对应 Java: `Fragment#write()`。
    pub fn write(&self, writer: &mut dyn crate::util::TemplateWriter) -> io::Result<()> {
        if let Some(template_model) = &self.template_model {
            template_model.write(writer)?;
        }
        Ok(())
    }

    /// 返回模型序列化文本。
    /// 对应 Java 语义:`Fragment` 的 `to_utf16_string` 行为(Rust 侧辅助/私有路径)。
    pub fn to_utf16_string(&self) -> io::Result<Utf16String> {
        let mut writer = FastStringWriter::new();
        self.write(&mut writer)?;
        Ok(writer.to_string())
    }
}

impl super::TemplateObject for Fragment {
    fn class_name(&self) -> &str {
        "org.thymeleaf.standard.expression.Fragment"
    }

    fn to_utf16_string(&self) -> Utf16String {
        self.to_utf16_string()
            .unwrap_or_else(|_| Utf16String::from_rust_str(""))
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

fn read_recovering_poison<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
    lock.read()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}