use std::any::Any;
use std::sync::Arc;
use crate::cache::ICacheEntryValidity;
use crate::expression::{TemplateObject, TemplateObjectPropertyError, TemplateValue};
use crate::templatemode::TemplateMode;
use crate::templateresource::ITemplateResource;
use crate::util::Utf16String;
#[derive(Clone)]
pub struct TemplateData {
template: Option<Utf16String>,
template_selectors: Option<Vec<Utf16String>>,
template_resource: Option<Arc<dyn ITemplateResource>>,
template_mode: Option<TemplateMode>,
cache_validity: Option<Arc<dyn ICacheEntryValidity>>,
}
impl TemplateData {
#[must_use]
pub fn new(
template: Option<Utf16String>,
template_selectors: Option<Vec<Utf16String>>,
template_resource: Option<Arc<dyn ITemplateResource>>,
template_mode: Option<TemplateMode>,
cache_validity: Option<Arc<dyn ICacheEntryValidity>>,
) -> Self {
Self {
template,
template_selectors,
template_resource,
template_mode,
cache_validity,
}
}
#[must_use]
pub const fn get_template(&self) -> Option<&Utf16String> {
self.template.as_ref()
}
#[must_use]
pub const fn has_template_selectors(&self) -> bool {
self.template_selectors.is_some()
}
#[must_use]
pub fn get_template_selectors(&self) -> Option<&[Utf16String]> {
self.template_selectors.as_deref()
}
#[must_use]
pub fn get_template_resource(&self) -> Option<&dyn ITemplateResource> {
self.template_resource.as_deref()
}
#[must_use]
pub(crate) fn get_template_resource_arc(&self) -> Option<Arc<dyn ITemplateResource>> {
self.template_resource.clone()
}
#[must_use]
pub const fn get_template_mode(&self) -> Option<TemplateMode> {
self.template_mode
}
#[must_use]
pub fn get_validity(&self) -> Option<&dyn ICacheEntryValidity> {
self.cache_validity.as_deref()
}
#[must_use]
pub(crate) fn get_validity_arc(&self) -> Option<Arc<dyn ICacheEntryValidity>> {
self.cache_validity.clone()
}
}
impl TemplateObject for TemplateData {
fn class_name(&self) -> &str {
"org.thymeleaf.engine.TemplateData"
}
fn to_utf16_string(&self) -> Utf16String {
Utf16String::from_rust_str("org.thymeleaf.engine.TemplateData")
}
fn as_any(&self) -> &dyn Any {
self
}
fn get_property(
&self,
property_name: &Utf16String,
) -> Option<Result<Option<Arc<TemplateValue>>, TemplateObjectPropertyError>> {
let value = match property_name.to_string_lossy().as_str() {
"template" => self
.get_template()
.cloned()
.map(TemplateValue::string)
.map(Arc::new),
"templateSelectors" => self.get_template_selectors().map(|selectors| {
Arc::new(TemplateValue::List(Arc::new(
selectors
.iter()
.cloned()
.map(TemplateValue::string)
.map(Arc::new)
.collect(),
)))
}),
"templateMode" => self
.get_template_mode()
.map(|mode| Arc::new(TemplateValue::Object(Arc::new(mode)))),
"templateResource" => self.get_template_resource_arc().map(|resource| {
Arc::new(TemplateValue::Object(Arc::new(
TemplateResourceExpressionObject { resource },
)))
}),
"validity" => self.get_validity_arc().map(|validity| {
Arc::new(TemplateValue::Object(Arc::new(
CacheEntryValidityExpressionObject { validity },
)))
}),
_ => return None,
};
Some(Ok(value))
}
}
struct TemplateResourceExpressionObject {
resource: Arc<dyn ITemplateResource>,
}
impl TemplateObject for TemplateResourceExpressionObject {
fn class_name(&self) -> &str {
"org.thymeleaf.templateresource.ITemplateResource"
}
fn to_utf16_string(&self) -> Utf16String {
Utf16String::from_rust_str(&self.resource.get_description())
}
fn as_any(&self) -> &dyn Any {
self
}
fn get_property(
&self,
property_name: &Utf16String,
) -> Option<Result<Option<Arc<TemplateValue>>, TemplateObjectPropertyError>> {
let value = match property_name.to_string_lossy().as_str() {
"description" => Some(Arc::new(TemplateValue::string(Utf16String::from_rust_str(
&self.resource.get_description(),
)))),
"baseName" => self.resource.get_base_name().map(|base_name| {
Arc::new(TemplateValue::string(Utf16String::from_rust_str(
&base_name,
)))
}),
"exists" => Some(Arc::new(TemplateValue::Boolean(self.resource.exists()))),
_ => return None,
};
Some(Ok(value))
}
}
struct CacheEntryValidityExpressionObject {
validity: Arc<dyn ICacheEntryValidity>,
}
impl TemplateObject for CacheEntryValidityExpressionObject {
fn class_name(&self) -> &str {
"org.thymeleaf.cache.ICacheEntryValidity"
}
fn to_utf16_string(&self) -> Utf16String {
Utf16String::from_rust_str("org.thymeleaf.cache.ICacheEntryValidity")
}
fn as_any(&self) -> &dyn Any {
self
}
fn get_property(
&self,
property_name: &Utf16String,
) -> Option<Result<Option<Arc<TemplateValue>>, TemplateObjectPropertyError>> {
let value = match property_name.to_string_lossy().as_str() {
"cacheable" => Some(Arc::new(TemplateValue::Boolean(
self.validity.is_cacheable(),
))),
"cacheStillValid" => Some(Arc::new(TemplateValue::Boolean(
self.validity.is_cache_still_valid(),
))),
_ => return None,
};
Some(Ok(value))
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::TemplateData;
use crate::TemplateMode;
use crate::cache::AlwaysValidCacheEntryValidity;
use crate::expression::{TemplateObject, TemplateValue};
use crate::templateresource::{ITemplateResource, StringTemplateResource};
use crate::util::Utf16String;
#[test]
fn constructor_preserves_nullable_metadata_like_java() {
let data = TemplateData::new(
Some(Utf16String::from_rust_str("template")),
None,
None,
Some(TemplateMode::HTML),
None,
);
let nullable = |value: Option<String>| value.unwrap_or_else(|| "null".to_owned());
let output = format!(
"{},{},{},{},{},{}",
data.get_template()
.map(Utf16String::to_string_lossy)
.expect("template"),
data.has_template_selectors(),
nullable(
data.get_template_selectors()
.map(|value| format!("{value:?}"))
),
nullable(
data.get_template_resource()
.map(|value| value.get_description())
),
data.get_template_mode().expect("mode"),
nullable(
data.get_validity()
.map(|value| value.is_cacheable().to_string())
),
);
let expected = include_str!("../../tests/fixtures/model_golden.txt")
.lines()
.find_map(|line| line.strip_prefix("templateData="))
.expect("Java Golden record");
assert_eq!(output, expected);
let resource: Arc<dyn ITemplateResource> =
Arc::new(StringTemplateResource::new(Some("contents")).expect("resource"));
let validity: Arc<dyn crate::cache::ICacheEntryValidity> =
Arc::new(AlwaysValidCacheEntryValidity::new());
let full = TemplateData::new(
Some(Utf16String::from_rust_str("full")),
Some(vec![
Utf16String::from_rust_str("second"),
Utf16String::from_rust_str("first"),
]),
Some(Arc::clone(&resource)),
Some(TemplateMode::XML),
Some(Arc::clone(&validity)),
);
let full_output = format!(
"{},[{}, {}],{},{},{},{}",
full.get_template().expect("template").to_string_lossy(),
full.get_template_selectors().expect("selectors")[0].to_string_lossy(),
full.get_template_selectors().expect("selectors")[1].to_string_lossy(),
std::ptr::eq(
full.get_template_resource().expect("resource"),
resource.as_ref()
),
full.get_template_resource()
.expect("resource")
.get_description(),
full.get_template_mode().expect("mode"),
std::ptr::eq(full.get_validity().expect("validity"), validity.as_ref()),
);
let full_expected = include_str!("../../tests/fixtures/model_golden.txt")
.lines()
.find_map(|line| line.strip_prefix("templateDataFull="))
.expect("Java Golden record");
assert_eq!(full_output, full_expected);
assert_property(&full, "template", Some("full"));
assert_property(&full, "templateSelectors", Some("[second, first]"));
assert_property(&full, "templateMode", Some("XML"));
let template_resource = object_property(&full, "templateResource");
assert_object_property(template_resource.as_ref(), "description", Some("contents"));
assert_object_property(template_resource.as_ref(), "baseName", None);
assert_object_property(template_resource.as_ref(), "exists", Some("true"));
let validity = object_property(&full, "validity");
assert_object_property(validity.as_ref(), "cacheable", Some("true"));
assert_object_property(validity.as_ref(), "cacheStillValid", Some("true"));
}
fn assert_property(data: &TemplateData, property: &str, expected: Option<&str>) {
let actual = data
.get_property(&Utf16String::from_rust_str(property))
.expect("TemplateData JavaBean property")
.expect("TemplateData getter must not fail")
.and_then(|value| value.to_utf16_string())
.map(|value| value.to_string_lossy());
assert_eq!(actual.as_deref(), expected, "property {property}");
}
fn object_property(data: &TemplateData, property: &str) -> Arc<dyn TemplateObject> {
let value = data
.get_property(&Utf16String::from_rust_str(property))
.expect("TemplateData JavaBean property")
.expect("TemplateData getter must not fail")
.expect("non-null Java object");
let TemplateValue::Object(value) = value.as_ref() else {
panic!("property {property} must preserve a Java object");
};
Arc::clone(value)
}
fn assert_object_property(object: &dyn TemplateObject, property: &str, expected: Option<&str>) {
let actual = object
.get_property(&Utf16String::from_rust_str(property))
.expect("nested JavaBean property")
.expect("nested getter must not fail")
.and_then(|value| value.to_utf16_string())
.map(|value| value.to_string_lossy());
assert_eq!(actual.as_deref(), expected, "nested property {property}");
}
}