use std::collections::HashMap;
use std::sync::Arc;
use indexmap::IndexSet;
use crate::cache::{
AlwaysValidCacheEntryValidity, ICacheEntryValidity, NonCacheableCacheEntryValidity,
TTLCacheEntryValidity,
};
use crate::util::{ContentTypeUtils, PatternSpec, PatternSpecError, Utf16String};
use crate::{TemplateMode, TemplateModeParseError};
use super::{AbstractTemplateResolver, TemplateResolverError};
pub struct AbstractConfigurableTemplateResolver {
resolver: AbstractTemplateResolver,
prefix: Option<Utf16String>,
suffix: Option<Utf16String>,
force_suffix: bool,
character_encoding: Option<Utf16String>,
template_mode: TemplateMode,
force_template_mode: bool,
cacheable: bool,
cache_ttl_ms: Option<i64>,
template_aliases: HashMap<Utf16String, Utf16String>,
xml_template_mode_pattern_spec: PatternSpec,
html_template_mode_pattern_spec: PatternSpec,
text_template_mode_pattern_spec: PatternSpec,
java_script_template_mode_pattern_spec: PatternSpec,
css_template_mode_pattern_spec: PatternSpec,
raw_template_mode_pattern_spec: PatternSpec,
cacheable_pattern_spec: PatternSpec,
non_cacheable_pattern_spec: PatternSpec,
}
impl AbstractConfigurableTemplateResolver {
pub const DEFAULT_TEMPLATE_MODE: TemplateMode = TemplateMode::HTML;
pub const DEFAULT_CACHEABLE: bool = true;
pub const DEFAULT_CACHE_TTL_MS: Option<i64> = None;
#[must_use]
pub fn new(class_name: &str) -> Self {
Self {
resolver: AbstractTemplateResolver::new(class_name),
prefix: None,
suffix: None,
force_suffix: false,
character_encoding: None,
template_mode: Self::DEFAULT_TEMPLATE_MODE,
force_template_mode: false,
cacheable: Self::DEFAULT_CACHEABLE,
cache_ttl_ms: Self::DEFAULT_CACHE_TTL_MS,
template_aliases: HashMap::with_capacity(8),
xml_template_mode_pattern_spec: PatternSpec::new(),
html_template_mode_pattern_spec: PatternSpec::new(),
text_template_mode_pattern_spec: PatternSpec::new(),
java_script_template_mode_pattern_spec: PatternSpec::new(),
css_template_mode_pattern_spec: PatternSpec::new(),
raw_template_mode_pattern_spec: PatternSpec::new(),
cacheable_pattern_spec: PatternSpec::new(),
non_cacheable_pattern_spec: PatternSpec::new(),
}
}
#[must_use]
pub const fn resolver(&self) -> &AbstractTemplateResolver {
&self.resolver
}
pub fn resolver_mut(&mut self) -> &mut AbstractTemplateResolver {
&mut self.resolver
}
#[must_use]
pub fn get_prefix(&self) -> Option<&Utf16String> {
self.prefix.as_ref()
}
pub fn set_prefix(&mut self, prefix: Option<Utf16String>) {
self.prefix = prefix;
}
#[must_use]
pub fn get_suffix(&self) -> Option<&Utf16String> {
self.suffix.as_ref()
}
pub fn set_suffix(&mut self, suffix: Option<Utf16String>) {
self.suffix = suffix;
}
#[must_use]
pub const fn get_force_suffix(&self) -> bool {
self.force_suffix
}
pub const fn set_force_suffix(&mut self, force_suffix: bool) {
self.force_suffix = force_suffix;
}
#[must_use]
pub fn get_character_encoding(&self) -> Option<&Utf16String> {
self.character_encoding.as_ref()
}
pub fn set_character_encoding(&mut self, character_encoding: Option<Utf16String>) {
self.character_encoding = character_encoding;
}
#[must_use]
pub const fn get_template_mode(&self) -> TemplateMode {
self.template_mode
}
pub const fn set_template_mode(&mut self, template_mode: TemplateMode) {
self.template_mode = template_mode;
}
pub fn set_template_mode_nullable(
&mut self,
template_mode: Option<TemplateMode>,
) -> Result<(), TemplateResolverError> {
self.template_mode = template_mode.ok_or_else(|| {
TemplateResolverError::InvalidArgument(
"Cannot set a null template mode value".to_owned(),
)
})?;
Ok(())
}
pub fn set_template_mode_name(
&mut self,
template_mode: Option<&str>,
) -> Result<(), TemplateResolverError> {
let template_mode = template_mode.ok_or_else(|| {
TemplateResolverError::InvalidArgument(
"Cannot set a null template mode value".to_owned(),
)
})?;
self.template_mode =
TemplateMode::parse(Some(template_mode)).map_err(|error: TemplateModeParseError| {
TemplateResolverError::InvalidArgument(error.to_string())
})?;
Ok(())
}
#[must_use]
pub const fn get_force_template_mode(&self) -> bool {
self.force_template_mode
}
pub const fn set_force_template_mode(&mut self, force_template_mode: bool) {
self.force_template_mode = force_template_mode;
}
#[must_use]
pub const fn is_cacheable(&self) -> bool {
self.cacheable
}
pub const fn set_cacheable(&mut self, cacheable: bool) {
self.cacheable = cacheable;
}
#[must_use]
pub const fn get_cache_ttl_ms(&self) -> Option<i64> {
self.cache_ttl_ms
}
pub const fn set_cache_ttl_ms(&mut self, cache_ttl_ms: Option<i64>) {
self.cache_ttl_ms = cache_ttl_ms;
}
#[must_use]
pub const fn get_template_aliases(&self) -> &HashMap<Utf16String, Utf16String> {
&self.template_aliases
}
pub fn set_template_aliases(
&mut self,
template_aliases: Option<&HashMap<Utf16String, Utf16String>>,
) {
if let Some(template_aliases) = template_aliases {
self.template_aliases.extend(
template_aliases
.iter()
.map(|(alias, template)| (alias.clone(), template.clone())),
);
}
}
pub fn add_template_alias(&mut self, alias: Utf16String, template_name: Utf16String) {
self.template_aliases.insert(alias, template_name);
}
pub fn add_template_alias_nullable(
&mut self,
alias: Option<Utf16String>,
template_name: Option<Utf16String>,
) -> Result<(), TemplateResolverError> {
let alias = alias.ok_or_else(|| {
TemplateResolverError::InvalidArgument("Alias cannot be null".to_owned())
})?;
let template_name = template_name.ok_or_else(|| {
TemplateResolverError::InvalidArgument("Template name cannot be null".to_owned())
})?;
self.add_template_alias(alias, template_name);
Ok(())
}
pub fn clear_template_aliases(&mut self) {
self.template_aliases.clear();
}
pub const fn get_xml_template_mode_pattern_spec(&self) -> &PatternSpec {
&self.xml_template_mode_pattern_spec
}
pub fn get_xml_template_mode_patterns(&self) -> &IndexSet<Option<String>> {
self.xml_template_mode_pattern_spec.get_patterns()
}
pub fn set_xml_template_mode_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.xml_template_mode_pattern_spec.set_patterns(patterns)
}
pub const fn get_html_template_mode_pattern_spec(&self) -> &PatternSpec {
&self.html_template_mode_pattern_spec
}
pub fn get_html_template_mode_patterns(&self) -> &IndexSet<Option<String>> {
self.html_template_mode_pattern_spec.get_patterns()
}
pub fn set_html_template_mode_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.html_template_mode_pattern_spec.set_patterns(patterns)
}
pub const fn get_text_template_mode_pattern_spec(&self) -> &PatternSpec {
&self.text_template_mode_pattern_spec
}
pub fn get_text_template_mode_patterns(&self) -> &IndexSet<Option<String>> {
self.text_template_mode_pattern_spec.get_patterns()
}
pub fn set_text_template_mode_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.text_template_mode_pattern_spec.set_patterns(patterns)
}
pub const fn get_java_script_template_mode_pattern_spec(&self) -> &PatternSpec {
&self.java_script_template_mode_pattern_spec
}
pub fn get_java_script_template_mode_patterns(&self) -> &IndexSet<Option<String>> {
self.java_script_template_mode_pattern_spec.get_patterns()
}
pub fn set_java_script_template_mode_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.java_script_template_mode_pattern_spec
.set_patterns(patterns)
}
pub const fn get_css_template_mode_pattern_spec(&self) -> &PatternSpec {
&self.css_template_mode_pattern_spec
}
pub fn get_css_template_mode_patterns(&self) -> &IndexSet<Option<String>> {
self.css_template_mode_pattern_spec.get_patterns()
}
pub fn set_css_template_mode_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.css_template_mode_pattern_spec.set_patterns(patterns)
}
pub const fn get_raw_template_mode_pattern_spec(&self) -> &PatternSpec {
&self.raw_template_mode_pattern_spec
}
pub fn get_raw_template_mode_patterns(&self) -> &IndexSet<Option<String>> {
self.raw_template_mode_pattern_spec.get_patterns()
}
pub fn set_raw_template_mode_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.raw_template_mode_pattern_spec.set_patterns(patterns)
}
pub const fn get_cacheable_pattern_spec(&self) -> &PatternSpec {
&self.cacheable_pattern_spec
}
pub fn get_cacheable_patterns(&self) -> &IndexSet<Option<String>> {
self.cacheable_pattern_spec.get_patterns()
}
pub fn set_cacheable_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.cacheable_pattern_spec.set_patterns(patterns)
}
pub const fn get_non_cacheable_pattern_spec(&self) -> &PatternSpec {
&self.non_cacheable_pattern_spec
}
pub fn get_non_cacheable_patterns(&self) -> &IndexSet<Option<String>> {
self.non_cacheable_pattern_spec.get_patterns()
}
pub fn set_non_cacheable_patterns(
&mut self,
patterns: Option<&[Option<&str>]>,
) -> Result<(), PatternSpecError> {
self.non_cacheable_pattern_spec.set_patterns(patterns)
}
#[must_use]
pub fn compute_resource_name(&self, template: &Utf16String) -> Utf16String {
let unaliased_name = self
.template_aliases
.get(template)
.cloned()
.unwrap_or_else(|| template.clone());
let unaliased_text = unaliased_name.to_string_lossy();
let prefix = self
.prefix
.as_ref()
.filter(|value| !is_empty_or_whitespace(value));
let suffix = self
.suffix
.as_ref()
.filter(|value| !is_empty_or_whitespace(value));
let should_apply_suffix = suffix.is_some()
&& (self.force_suffix
|| !ContentTypeUtils::has_recognized_file_extension(Some(&unaliased_text)));
let mut result = Vec::new();
if let Some(prefix) = prefix {
result.extend_from_slice(prefix.as_utf16());
}
result.extend_from_slice(unaliased_name.as_utf16());
if should_apply_suffix {
result.extend_from_slice(suffix.expect("suffix checked").as_utf16());
}
Utf16String::from_utf16(result)
}
#[must_use]
pub fn compute_template_mode(&self, template: &Utf16String) -> TemplateMode {
let text = template.to_string_lossy();
for (spec, template_mode) in [
(&self.xml_template_mode_pattern_spec, TemplateMode::XML),
(&self.html_template_mode_pattern_spec, TemplateMode::HTML),
(&self.text_template_mode_pattern_spec, TemplateMode::TEXT),
(
&self.java_script_template_mode_pattern_spec,
TemplateMode::JAVASCRIPT,
),
(&self.css_template_mode_pattern_spec, TemplateMode::CSS),
(&self.raw_template_mode_pattern_spec, TemplateMode::RAW),
] {
if spec
.matches(Some(&text))
.expect("validated template mode patterns")
{
return template_mode;
}
}
if !self.force_template_mode {
let resource_name = self.compute_resource_name(template);
if let Some(template_mode) = ContentTypeUtils::compute_template_mode_for_template_name(
Some(&resource_name.to_string_lossy()),
) {
return template_mode;
}
}
self.template_mode
}
#[must_use]
pub fn compute_validity(&self, template: &Utf16String) -> Arc<dyn ICacheEntryValidity> {
let text = template.to_string_lossy();
let cacheable = if self
.cacheable_pattern_spec
.matches(Some(&text))
.expect("validated cacheable patterns")
{
true
} else if self
.non_cacheable_pattern_spec
.matches(Some(&text))
.expect("validated non-cacheable patterns")
{
false
} else {
self.cacheable
};
if !cacheable {
return Arc::new(NonCacheableCacheEntryValidity::new());
}
self.cache_ttl_ms.map_or_else(
|| Arc::new(AlwaysValidCacheEntryValidity::new()) as Arc<dyn ICacheEntryValidity>,
|ttl| Arc::new(TTLCacheEntryValidity::new(ttl)),
)
}
}
impl std::ops::Deref for AbstractConfigurableTemplateResolver {
type Target = AbstractTemplateResolver;
fn deref(&self) -> &Self::Target {
&self.resolver
}
}
impl std::ops::DerefMut for AbstractConfigurableTemplateResolver {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.resolver
}
}
fn is_empty_or_whitespace(value: &Utf16String) -> bool {
value.as_utf16().iter().all(|character| {
matches!(
*character,
0x0009..=0x000D | 0x001C..=0x0020 | 0x1680 | 0x2000..=0x2006 | 0x2008..=0x200A
| 0x2028 | 0x2029 | 0x205F | 0x3000
)
})
}