use crate::error::{Result, TemplateError};
use crate::template::SimpleBoolean;
use crate::template::SimpleCollection;
use crate::template::SimpleDate;
use crate::template::SimpleHash;
use crate::template::SimpleScalar;
use crate::template::SimpleSequence;
use crate::template::{
NodeHashModel, TemplateApiSupport, TemplateBooleanModel, TemplateCollectionModel,
TemplateDateModel, TemplateDirectiveModel, TemplateHashModel, TemplateHashModelEx,
TemplateMethodModelEx, TemplateNodeModel, TemplateNumberModel, TemplateScalarModel,
TemplateSequenceModel, TemplateTransformModel,
};
use crate::value::{DateValue, TNumber};
use indexmap::IndexMap;
use std::fmt;
use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelKind {
Nothing,
Scalar,
Number,
Boolean,
Date,
Sequence,
Collection,
Hash,
Method,
Directive,
Node,
Markup,
Wrapped,
Macro,
Lambda,
}
#[derive(Clone)]
pub enum ModelNumber {
Inline(TNumber),
Dyn(Rc<dyn TemplateNumberModel>),
}
impl ModelNumber {
pub fn as_number(&self) -> Result<TNumber> {
match self {
ModelNumber::Inline(n) => Ok(n.clone()),
ModelNumber::Dyn(d) => d.as_number(),
}
}
}
#[derive(Clone)]
pub struct TModel {
pub scalar: Option<Rc<dyn TemplateScalarModel>>,
pub number: Option<ModelNumber>,
pub boolean: Option<Rc<dyn TemplateBooleanModel>>,
pub date: Option<Rc<dyn TemplateDateModel>>,
pub sequence: Option<Rc<dyn TemplateSequenceModel>>,
pub collection: Option<Rc<dyn TemplateCollectionModel>>,
pub hash: Option<Rc<dyn TemplateHashModel>>,
pub hash_ex: Option<Rc<dyn TemplateHashModelEx>>,
pub method: Option<Rc<dyn TemplateMethodModelEx>>,
pub method_indexable: bool,
pub collection_ex: bool,
pub directive: Option<Rc<dyn TemplateDirectiveModel>>,
pub transform: Option<Rc<dyn TemplateTransformModel>>,
pub range: Option<Rc<crate::core::RangeSpec>>,
pub node: Option<Rc<dyn TemplateNodeModel>>,
pub node_hash: Option<Rc<dyn NodeHashModel>>,
pub api: Option<Rc<dyn TemplateApiSupport>>,
pub internal: Option<Rc<dyn std::any::Any>>,
pub markup_format: Option<crate::core::OutputFormatKind>,
pub markup_plain: Option<String>,
pub type_name: &'static str,
pub kind: ModelKind,
}
impl fmt::Debug for TModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TModel")
.field("type_name", &self.type_name)
.finish()
}
}
impl TModel {
pub fn nothing() -> TModel {
TModel {
scalar: None,
number: None,
boolean: None,
date: None,
sequence: None,
collection: None,
hash: None,
hash_ex: None,
method: None,
method_indexable: false,
collection_ex: false,
directive: None,
transform: None,
range: None,
node: None,
node_hash: None,
api: None,
internal: None,
markup_format: None,
markup_plain: None,
type_name: "nothing",
kind: ModelKind::Nothing,
}
}
pub fn gpn() -> TModel {
let empty_seq = Rc::new(SimpleSequence(Vec::new()));
let empty_hash = Rc::new(SimpleHash(IndexMap::with_hasher(
crate::template::utility::FnvBuildHasher::default(),
)));
TModel {
scalar: Some(Rc::new(SimpleScalar(String::new()))),
boolean: Some(Rc::new(SimpleBoolean(false))),
sequence: Some(empty_seq),
hash: Some(empty_hash.clone()),
hash_ex: Some(empty_hash),
type_name: "nothing",
kind: ModelKind::Scalar,
..Self::nothing()
}
}
pub fn from_scalar(v: String) -> TModel {
TModel {
scalar: Some(Rc::new(SimpleScalar(v))),
type_name: "string",
kind: ModelKind::Scalar,
..Self::nothing()
}
}
pub fn from_number(v: TNumber) -> TModel {
TModel {
number: Some(ModelNumber::Inline(v)),
type_name: "number",
kind: ModelKind::Number,
..Self::nothing()
}
}
pub fn from_boolean(b: bool) -> TModel {
TModel {
boolean: Some(Rc::new(SimpleBoolean(b))),
type_name: "boolean",
kind: ModelKind::Boolean,
..Self::nothing()
}
}
pub fn from_date(d: DateValue) -> TModel {
TModel {
date: Some(Rc::new(SimpleDate(d))),
type_name: "date",
kind: ModelKind::Date,
..Self::nothing()
}
}
pub fn from_sequence(v: Vec<TModel>) -> TModel {
let s = Rc::new(SimpleSequence(v));
TModel {
sequence: Some(s),
type_name: "sequence",
kind: ModelKind::Sequence,
..Self::nothing()
}
}
pub fn from_collection(v: Vec<TModel>) -> TModel {
TModel {
collection: Some(Rc::new(SimpleCollection(v))),
type_name: "collection",
kind: ModelKind::Collection,
..Self::nothing()
}
}
pub fn from_hash(v: IndexMap<String, TModel>) -> TModel {
let v: IndexMap<String, TModel, crate::template::utility::FnvBuildHasher> =
v.into_iter().collect();
let h = Rc::new(SimpleHash(v));
let ex = h.clone();
TModel {
hash: Some(h),
hash_ex: Some(ex),
type_name: "extended_hash",
kind: ModelKind::Hash,
..Self::nothing()
}
}
pub fn from_method(m: impl TemplateMethodModelEx + 'static) -> TModel {
TModel {
method: Some(Rc::new(m)),
method_indexable: false,
type_name: "method",
kind: ModelKind::Method,
..Self::nothing()
}
}
pub fn from_directive(d: impl TemplateDirectiveModel + 'static) -> TModel {
TModel {
directive: Some(Rc::new(d)),
type_name: "directive",
kind: ModelKind::Directive,
..Self::nothing()
}
}
pub fn from_transform(t: impl TemplateTransformModel + 'static) -> TModel {
TModel {
transform: Some(Rc::new(t)),
type_name: "transform",
kind: ModelKind::Directive,
..Self::nothing()
}
}
pub fn from_xml_str(s: &str) -> Result<TModel> {
crate::xml::parse_xml(s)
}
pub fn is_scalar(&self) -> bool {
self.scalar.is_some()
}
pub fn is_number(&self) -> bool {
self.number.is_some()
}
pub fn is_boolean(&self) -> bool {
self.boolean.is_some()
}
pub fn is_date(&self) -> bool {
self.date.is_some()
}
pub fn is_sequence(&self) -> bool {
self.sequence.is_some()
}
pub fn is_collection(&self) -> bool {
self.collection.is_some()
}
pub fn is_hash(&self) -> bool {
self.hash.is_some()
}
pub fn is_hash_ex(&self) -> bool {
self.hash_ex.is_some()
}
pub fn is_method(&self) -> bool {
self.method.is_some()
}
pub fn is_directive(&self) -> bool {
self.directive.is_some() || self.transform.is_some() || self.is_macro()
}
pub fn is_node(&self) -> bool {
self.node.is_some()
}
pub fn is_nothing(&self) -> bool {
self.kind == ModelKind::Nothing
}
pub fn is_enumerable(&self) -> bool {
self.sequence.is_some() || self.collection.is_some()
}
pub fn is_collection_ex(&self) -> bool {
self.collection_ex
}
pub fn has_api(&self) -> bool {
false
}
pub fn is_transform(&self) -> bool {
self.transform.is_some()
}
pub fn is_markup_output(&self) -> bool {
self.kind == ModelKind::Markup
}
pub fn is_macro(&self) -> bool {
self.kind == ModelKind::Macro
}
pub fn is_lambda(&self) -> bool {
self.kind == ModelKind::Lambda
}
pub fn internal<T: 'static>(&self) -> Option<Rc<T>> {
self.internal
.as_ref()
.and_then(|any| any.clone().downcast::<T>().ok())
}
pub fn get_scalar(&self) -> Result<String> {
self.scalar
.as_ref()
.ok_or_else(|| TemplateError::type_mismatch("string", self.type_name))?
.as_string()
}
pub fn get_number(&self) -> Result<TNumber> {
self.number
.as_ref()
.ok_or_else(|| TemplateError::type_mismatch("number", self.type_name))?
.as_number()
}
pub fn get_boolean(&self) -> Result<bool> {
self.boolean
.as_ref()
.ok_or_else(|| TemplateError::type_mismatch("boolean", self.type_name))?
.as_boolean()
}
pub fn get_date(&self) -> Result<DateValue> {
self.date
.as_ref()
.ok_or_else(|| TemplateError::type_mismatch("date", self.type_name))?
.as_date()
}
pub fn get_sequence(&self) -> Result<Rc<dyn TemplateSequenceModel>> {
self.sequence
.clone()
.ok_or_else(|| TemplateError::type_mismatch("sequence", self.type_name))
}
pub fn get_hash(&self) -> Result<Rc<dyn TemplateHashModel>> {
self.hash
.clone()
.ok_or_else(|| TemplateError::type_mismatch("hash", self.type_name))
}
pub fn get_method(&self) -> Result<Rc<dyn TemplateMethodModelEx>> {
self.method
.clone()
.ok_or_else(|| TemplateError::type_mismatch("method", self.type_name))
}
pub fn get_directive(&self) -> Result<Rc<dyn TemplateDirectiveModel>> {
self.directive
.clone()
.ok_or_else(|| TemplateError::type_mismatch("directive", self.type_name))
}
pub fn get_transform(&self) -> Result<Rc<dyn TemplateTransformModel>> {
self.transform
.clone()
.ok_or_else(|| TemplateError::type_mismatch("transform", self.type_name))
}
pub fn is_null_or_missing(&self) -> bool {
self.kind == ModelKind::Nothing
}
pub fn has_content(&self) -> Result<bool> {
if self.is_null_or_missing() {
return Ok(false);
}
if let Some(s) = &self.scalar {
return Ok(!s.as_string()?.is_empty());
}
if let Some(seq) = &self.sequence {
return Ok(seq.size()? > 0);
}
if let Some(h) = &self.hash {
return Ok(!h.is_empty()?);
}
if let Some(c) = &self.collection {
let mut it = c.iterator()?;
return Ok(it.next().is_some());
}
Ok(true)
}
pub fn eval_boolean(&self) -> Result<bool> {
if let Some(b) = &self.boolean {
return b.as_boolean();
}
if self.scalar.is_some() {
return Err(TemplateError::type_mismatch("boolean", self.type_name));
}
Err(TemplateError::type_mismatch("boolean", self.type_name))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Environment;
use crate::template::TemplateDirectiveBody;
use std::collections::HashMap;
#[test]
fn constructor_type_names_match_ftl_types() {
assert_eq!(TModel::nothing().type_name, "nothing");
assert_eq!(TModel::from_scalar("s".into()).type_name, "string");
assert_eq!(TModel::from_number(TNumber::Int(1)).type_name, "number");
assert_eq!(TModel::from_boolean(true).type_name, "boolean");
assert_eq!(
TModel::from_date(DateValue {
dt: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z").unwrap(),
kind: crate::value::DateType::DateTime,
is_sql: false,
})
.type_name,
"date"
);
assert_eq!(TModel::from_sequence(vec![]).type_name, "sequence");
assert_eq!(TModel::from_collection(vec![]).type_name, "collection");
assert_eq!(
TModel::from_hash(IndexMap::new()).type_name,
"extended_hash"
);
assert_eq!(TModel::from_method(MethodStub).type_name, "method");
assert_eq!(TModel::from_directive(DirectiveStub).type_name, "directive");
}
#[test]
fn collection_is_enumerable_but_not_sequence() {
let m = TModel::from_collection(vec![
TModel::from_scalar("a".into()),
TModel::from_scalar("b".into()),
]);
assert!(m.is_collection());
assert!(!m.is_sequence());
assert!(m.is_enumerable());
assert_eq!(m.kind, ModelKind::Collection);
let items: Vec<_> = m
.collection
.as_ref()
.unwrap()
.iterator()
.unwrap()
.map(|r| r.unwrap().get_scalar().unwrap())
.collect();
assert_eq!(items, vec!["a", "b"]);
}
struct MethodStub;
impl TemplateMethodModelEx for MethodStub {
fn exec(&self, _env: &mut Environment, _args: Vec<TModel>) -> Result<TModel> {
Ok(TModel::nothing())
}
}
struct DirectiveStub;
impl TemplateDirectiveModel for DirectiveStub {
fn execute(
&self,
_env: &mut crate::core::Environment,
_params: &HashMap<String, TModel>,
_loop_vars: &mut [TModel],
_body: Option<&dyn TemplateDirectiveBody>,
) -> Result<()> {
Ok(())
}
}
}