use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::exceptions::{TemplateEngineException, TemplateProcessingException};
use crate::model::ITemplateEvent;
use crate::processor::IProcessor;
use crate::templatemode::TemplateMode;
use crate::util::{Validate, ValidateError};
pub struct AbstractProcessor {
precedence: i32,
template_mode: TemplateMode,
}
impl AbstractProcessor {
pub fn new(
template_mode: Option<TemplateMode>,
precedence: i32,
) -> Result<Self, ValidateError> {
Validate::not_null(template_mode.as_ref(), Some("Template mode cannot be null"))?;
Ok(Self {
precedence,
template_mode: template_mode.expect("validated template mode"),
})
}
#[must_use]
pub const fn get_template_mode(&self) -> TemplateMode {
self.template_mode
}
#[must_use]
pub const fn get_precedence(&self) -> i32 {
self.precedence
}
}
impl IProcessor for AbstractProcessor {
fn get_template_mode(&self) -> Option<TemplateMode> {
Some(AbstractProcessor::get_template_mode(self))
}
fn get_precedence(&self) -> i32 {
AbstractProcessor::get_precedence(self)
}
}
pub(crate) struct AbstractProcessorAdapter<F> {
processor: AbstractProcessor,
processor_class_name: &'static str,
callback: F,
}
impl<F> AbstractProcessorAdapter<F> {
pub(crate) fn new(
template_mode: Option<TemplateMode>,
precedence: i32,
processor_class_name: &'static str,
callback: F,
) -> Result<Self, ValidateError> {
Ok(Self {
processor: AbstractProcessor::new(template_mode, precedence)?,
processor_class_name,
callback,
})
}
pub(crate) fn execute(
&self,
event: &dyn ITemplateEvent,
operation: impl FnOnce(&F) -> Result<(), Box<dyn TemplateEngineException>>,
) -> Result<(), Box<dyn TemplateEngineException>> {
self.execute_optional(Some(event), operation)
}
pub(crate) fn execute_optional(
&self,
event: Option<&dyn ITemplateEvent>,
operation: impl FnOnce(&F) -> Result<(), Box<dyn TemplateEngineException>>,
) -> Result<(), Box<dyn TemplateEngineException>> {
match operation(&self.callback) {
Ok(()) => Ok(()),
Err(mut error) => {
if let Some(processing) = error.as_processing_exception_mut() {
if let Some(event) = event {
enrich_location(processing, event);
}
return Err(error);
}
let wrapped = TemplateProcessingException::with_location_and_cause(
Some(format!(
"Error during execution of processor '{}'",
self.processor_class_name
)),
event
.and_then(ITemplateEvent::get_template_name)
.map(|name| name.to_string_lossy()),
event.map_or(-1, ITemplateEvent::get_line),
event.map_or(-1, ITemplateEvent::get_col),
ProcessorExecutionCause(error),
);
Err(Box::new(wrapped))
}
}
}
pub(crate) fn template_mode(&self) -> Option<TemplateMode> {
IProcessor::get_template_mode(&self.processor)
}
pub(crate) fn precedence(&self) -> i32 {
IProcessor::get_precedence(&self.processor)
}
pub(crate) const fn processor_class_name(&self) -> &'static str {
self.processor_class_name
}
}
fn enrich_location(processing: &mut TemplateProcessingException, event: &dyn ITemplateEvent) {
if !event.has_location() {
return;
}
if !processing.has_template_name() {
processing.set_template_name(event.get_template_name().map(|name| name.to_string_lossy()));
}
if !processing.has_line_and_col() {
processing.set_line_and_col(event.get_line(), event.get_col());
}
}
struct ProcessorExecutionCause(Box<dyn TemplateEngineException>);
impl Display for ProcessorExecutionCause {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, formatter)
}
}
impl std::fmt::Debug for ProcessorExecutionCause {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("ProcessorExecutionCause")
.field(&self.0.to_string())
.finish()
}
}
impl Error for ProcessorExecutionCause {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(self.0.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::AbstractProcessor;
use crate::processor::IProcessor;
use crate::templatemode::TemplateMode;
use crate::util::ValidateError;
#[test]
fn rejects_null_template_mode_with_exact_validate_error() {
let error = AbstractProcessor::new(None, 123)
.err()
.expect("null template mode must fail");
assert_eq!(
error,
ValidateError::IllegalArgument {
message: Some("Template mode cannot be null".to_owned()),
}
);
assert_eq!(error.class_name(), "java.lang.IllegalArgumentException");
assert_eq!(error.to_string(), "Template mode cannot be null");
}
#[test]
fn preserves_all_modes_precedence_boundaries_and_dynamic_contract() {
let cases = [
(TemplateMode::HTML, i32::MIN),
(TemplateMode::XML, -1),
(TemplateMode::TEXT, 0),
(TemplateMode::JAVASCRIPT, 1),
(TemplateMode::CSS, 1_000),
(TemplateMode::RAW, i32::MAX),
];
for (template_mode, precedence) in cases {
let processor = AbstractProcessor::new(Some(template_mode), precedence)
.expect("non-null mode is valid");
let contract: &dyn IProcessor = &processor;
assert_eq!(processor.get_template_mode(), template_mode);
assert_eq!(processor.get_precedence(), precedence);
assert_eq!(contract.get_template_mode(), Some(template_mode));
assert_eq!(contract.get_precedence(), precedence);
assert_eq!(processor.get_template_mode(), template_mode);
assert_eq!(processor.get_precedence(), precedence);
}
}
}