1use crate::error::{Result, TemplateError};
6use crate::template::SimpleBoolean;
7use crate::template::SimpleCollection;
8use crate::template::SimpleDate;
9use crate::template::SimpleHash;
10use crate::template::SimpleScalar;
11use crate::template::SimpleSequence;
12use crate::template::{
13 NodeHashModel, TemplateApiSupport, TemplateBooleanModel, TemplateCollectionModel,
14 TemplateDateModel, TemplateDirectiveModel, TemplateHashModel, TemplateHashModelEx,
15 TemplateMethodModelEx, TemplateNodeModel, TemplateNumberModel, TemplateScalarModel,
16 TemplateSequenceModel, TemplateTransformModel,
17};
18use crate::value::{DateValue, TNumber};
19use indexmap::IndexMap;
20use std::fmt;
21use std::rc::Rc;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ModelKind {
25 Nothing,
26 Scalar,
27 Number,
28 Boolean,
29 Date,
30 Sequence,
31 Collection,
32 Hash,
33 Method,
34 Directive,
35 Node,
36 Markup,
37 Wrapped,
38 Macro,
40 Lambda,
42}
43
44#[derive(Clone)]
47pub enum ModelNumber {
48 Inline(TNumber),
50 Dyn(Rc<dyn TemplateNumberModel>),
52}
53
54impl ModelNumber {
55 pub fn as_number(&self) -> Result<TNumber> {
57 match self {
58 ModelNumber::Inline(n) => Ok(n.clone()),
59 ModelNumber::Dyn(d) => d.as_number(),
60 }
61 }
62}
63
64#[derive(Clone)]
67pub struct TModel {
68 pub scalar: Option<Rc<dyn TemplateScalarModel>>,
69 pub number: Option<ModelNumber>,
70 pub boolean: Option<Rc<dyn TemplateBooleanModel>>,
71 pub date: Option<Rc<dyn TemplateDateModel>>,
72 pub sequence: Option<Rc<dyn TemplateSequenceModel>>,
73 pub collection: Option<Rc<dyn TemplateCollectionModel>>,
74 pub hash: Option<Rc<dyn TemplateHashModel>>,
75 pub hash_ex: Option<Rc<dyn TemplateHashModelEx>>,
76 pub method: Option<Rc<dyn TemplateMethodModelEx>>,
77 pub method_indexable: bool,
82 pub collection_ex: bool,
85 pub directive: Option<Rc<dyn TemplateDirectiveModel>>,
86 pub transform: Option<Rc<dyn TemplateTransformModel>>,
88 pub range: Option<Rc<crate::core::RangeSpec>>,
90 pub node: Option<Rc<dyn TemplateNodeModel>>,
91 pub node_hash: Option<Rc<dyn NodeHashModel>>,
95 pub api: Option<Rc<dyn TemplateApiSupport>>,
98 pub internal: Option<Rc<dyn std::any::Any>>,
101 pub markup_format: Option<crate::core::OutputFormatKind>,
104 pub markup_plain: Option<String>,
108 pub type_name: &'static str,
110 pub kind: ModelKind,
111}
112
113impl fmt::Debug for TModel {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.debug_struct("TModel")
116 .field("type_name", &self.type_name)
117 .finish()
118 }
119}
120
121impl TModel {
122 pub fn nothing() -> TModel {
123 TModel {
124 scalar: None,
125 number: None,
126 boolean: None,
127 date: None,
128 sequence: None,
129 collection: None,
130 hash: None,
131 hash_ex: None,
132 method: None,
133 method_indexable: false,
134 collection_ex: false,
135 directive: None,
136 transform: None,
137 range: None,
138 node: None,
139 node_hash: None,
140 api: None,
141 internal: None,
142 markup_format: None,
143 markup_plain: None,
144 type_name: "nothing",
145 kind: ModelKind::Nothing,
146 }
147 }
148
149 pub fn gpn() -> TModel {
155 let empty_seq = Rc::new(SimpleSequence(Vec::new()));
156 let empty_hash = Rc::new(SimpleHash(IndexMap::with_hasher(
157 crate::template::utility::FnvBuildHasher::default(),
158 )));
159 TModel {
160 scalar: Some(Rc::new(SimpleScalar(String::new()))),
161 boolean: Some(Rc::new(SimpleBoolean(false))),
162 sequence: Some(empty_seq),
165 hash: Some(empty_hash.clone()),
166 hash_ex: Some(empty_hash),
167 type_name: "nothing",
168 kind: ModelKind::Scalar,
169 ..Self::nothing()
170 }
171 }
172
173 pub fn from_scalar(v: String) -> TModel {
174 TModel {
175 scalar: Some(Rc::new(SimpleScalar(v))),
176 type_name: "string",
177 kind: ModelKind::Scalar,
178 ..Self::nothing()
179 }
180 }
181
182 pub fn from_number(v: TNumber) -> TModel {
183 TModel {
185 number: Some(ModelNumber::Inline(v)),
186 type_name: "number",
187 kind: ModelKind::Number,
188 ..Self::nothing()
189 }
190 }
191
192 pub fn from_boolean(b: bool) -> TModel {
193 TModel {
194 boolean: Some(Rc::new(SimpleBoolean(b))),
195 type_name: "boolean",
196 kind: ModelKind::Boolean,
197 ..Self::nothing()
198 }
199 }
200
201 pub fn from_date(d: DateValue) -> TModel {
202 TModel {
203 date: Some(Rc::new(SimpleDate(d))),
204 type_name: "date",
205 kind: ModelKind::Date,
206 ..Self::nothing()
207 }
208 }
209
210 pub fn from_sequence(v: Vec<TModel>) -> TModel {
211 let s = Rc::new(SimpleSequence(v));
214 TModel {
215 sequence: Some(s),
216 type_name: "sequence",
217 kind: ModelKind::Sequence,
218 ..Self::nothing()
219 }
220 }
221
222 pub fn from_collection(v: Vec<TModel>) -> TModel {
226 TModel {
227 collection: Some(Rc::new(SimpleCollection(v))),
228 type_name: "collection",
229 kind: ModelKind::Collection,
230 ..Self::nothing()
231 }
232 }
233
234 pub fn from_hash(v: IndexMap<String, TModel>) -> TModel {
235 let v: IndexMap<String, TModel, crate::template::utility::FnvBuildHasher> =
238 v.into_iter().collect();
239 let h = Rc::new(SimpleHash(v));
240 let ex = h.clone();
241 TModel {
242 hash: Some(h),
243 hash_ex: Some(ex),
244 type_name: "extended_hash",
248 kind: ModelKind::Hash,
249 ..Self::nothing()
250 }
251 }
252
253 pub fn from_method(m: impl TemplateMethodModelEx + 'static) -> TModel {
254 TModel {
255 method: Some(Rc::new(m)),
256 method_indexable: false,
257 type_name: "method",
258 kind: ModelKind::Method,
259 ..Self::nothing()
260 }
261 }
262
263 pub fn from_directive(d: impl TemplateDirectiveModel + 'static) -> TModel {
264 TModel {
265 directive: Some(Rc::new(d)),
266 type_name: "directive",
267 kind: ModelKind::Directive,
268 ..Self::nothing()
269 }
270 }
271
272 pub fn from_transform(t: impl TemplateTransformModel + 'static) -> TModel {
273 TModel {
274 transform: Some(Rc::new(t)),
275 type_name: "transform",
276 kind: ModelKind::Directive,
277 ..Self::nothing()
278 }
279 }
280
281 pub fn from_xml_str(s: &str) -> Result<TModel> {
285 crate::xml::parse_xml(s)
286 }
287
288 pub fn is_scalar(&self) -> bool {
290 self.scalar.is_some()
291 }
292 pub fn is_number(&self) -> bool {
293 self.number.is_some()
294 }
295 pub fn is_boolean(&self) -> bool {
296 self.boolean.is_some()
297 }
298 pub fn is_date(&self) -> bool {
299 self.date.is_some()
300 }
301 pub fn is_sequence(&self) -> bool {
302 self.sequence.is_some()
303 }
304 pub fn is_collection(&self) -> bool {
305 self.collection.is_some()
306 }
307 pub fn is_hash(&self) -> bool {
308 self.hash.is_some()
309 }
310 pub fn is_hash_ex(&self) -> bool {
311 self.hash_ex.is_some()
312 }
313 pub fn is_method(&self) -> bool {
314 self.method.is_some()
315 }
316 pub fn is_directive(&self) -> bool {
317 self.directive.is_some() || self.transform.is_some() || self.is_macro()
320 }
321 pub fn is_node(&self) -> bool {
322 self.node.is_some()
323 }
324 pub fn is_nothing(&self) -> bool {
325 self.kind == ModelKind::Nothing
326 }
327
328 pub fn is_enumerable(&self) -> bool {
330 self.sequence.is_some() || self.collection.is_some()
331 }
332
333 pub fn is_collection_ex(&self) -> bool {
336 self.collection_ex
337 }
338
339 pub fn has_api(&self) -> bool {
341 false
342 }
343
344 pub fn is_transform(&self) -> bool {
346 self.transform.is_some()
347 }
348
349 pub fn is_markup_output(&self) -> bool {
351 self.kind == ModelKind::Markup
352 }
353
354 pub fn is_macro(&self) -> bool {
357 self.kind == ModelKind::Macro
358 }
359
360 pub fn is_lambda(&self) -> bool {
362 self.kind == ModelKind::Lambda
363 }
364
365 pub fn internal<T: 'static>(&self) -> Option<Rc<T>> {
367 self.internal
368 .as_ref()
369 .and_then(|any| any.clone().downcast::<T>().ok())
370 }
371
372 pub fn get_scalar(&self) -> Result<String> {
374 self.scalar
375 .as_ref()
376 .ok_or_else(|| TemplateError::type_mismatch("string", self.type_name))?
377 .as_string()
378 }
379 pub fn get_number(&self) -> Result<TNumber> {
380 self.number
381 .as_ref()
382 .ok_or_else(|| TemplateError::type_mismatch("number", self.type_name))?
383 .as_number()
384 }
385 pub fn get_boolean(&self) -> Result<bool> {
386 self.boolean
387 .as_ref()
388 .ok_or_else(|| TemplateError::type_mismatch("boolean", self.type_name))?
389 .as_boolean()
390 }
391 pub fn get_date(&self) -> Result<DateValue> {
392 self.date
393 .as_ref()
394 .ok_or_else(|| TemplateError::type_mismatch("date", self.type_name))?
395 .as_date()
396 }
397 pub fn get_sequence(&self) -> Result<Rc<dyn TemplateSequenceModel>> {
398 self.sequence
399 .clone()
400 .ok_or_else(|| TemplateError::type_mismatch("sequence", self.type_name))
401 }
402 pub fn get_hash(&self) -> Result<Rc<dyn TemplateHashModel>> {
403 self.hash
404 .clone()
405 .ok_or_else(|| TemplateError::type_mismatch("hash", self.type_name))
406 }
407 pub fn get_method(&self) -> Result<Rc<dyn TemplateMethodModelEx>> {
408 self.method
409 .clone()
410 .ok_or_else(|| TemplateError::type_mismatch("method", self.type_name))
411 }
412 pub fn get_directive(&self) -> Result<Rc<dyn TemplateDirectiveModel>> {
413 self.directive
414 .clone()
415 .ok_or_else(|| TemplateError::type_mismatch("directive", self.type_name))
416 }
417 pub fn get_transform(&self) -> Result<Rc<dyn TemplateTransformModel>> {
418 self.transform
419 .clone()
420 .ok_or_else(|| TemplateError::type_mismatch("transform", self.type_name))
421 }
422
423 pub fn is_null_or_missing(&self) -> bool {
425 self.kind == ModelKind::Nothing
426 }
427
428 pub fn has_content(&self) -> Result<bool> {
430 if self.is_null_or_missing() {
431 return Ok(false);
432 }
433 if let Some(s) = &self.scalar {
434 return Ok(!s.as_string()?.is_empty());
435 }
436 if let Some(seq) = &self.sequence {
437 return Ok(seq.size()? > 0);
438 }
439 if let Some(h) = &self.hash {
440 return Ok(!h.is_empty()?);
441 }
442 if let Some(c) = &self.collection {
443 let mut it = c.iterator()?;
444 return Ok(it.next().is_some());
445 }
446 Ok(true)
447 }
448
449 pub fn eval_boolean(&self) -> Result<bool> {
451 if let Some(b) = &self.boolean {
452 return b.as_boolean();
453 }
454 if self.scalar.is_some() {
455 return Err(TemplateError::type_mismatch("boolean", self.type_name));
457 }
458 Err(TemplateError::type_mismatch("boolean", self.type_name))
459 }
460}
461
462#[cfg(test)]
467mod tests {
468 use super::*;
469 use crate::core::Environment;
470 use crate::template::TemplateDirectiveBody;
471 use std::collections::HashMap;
472
473 #[test]
477 fn constructor_type_names_match_ftl_types() {
478 assert_eq!(TModel::nothing().type_name, "nothing");
479 assert_eq!(TModel::from_scalar("s".into()).type_name, "string");
480 assert_eq!(TModel::from_number(TNumber::Int(1)).type_name, "number");
481 assert_eq!(TModel::from_boolean(true).type_name, "boolean");
482 assert_eq!(
483 TModel::from_date(DateValue {
484 dt: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z").unwrap(),
485 kind: crate::value::DateType::DateTime,
486 is_sql: false,
487 })
488 .type_name,
489 "date"
490 );
491 assert_eq!(TModel::from_sequence(vec![]).type_name, "sequence");
492 assert_eq!(TModel::from_collection(vec![]).type_name, "collection");
493 assert_eq!(
496 TModel::from_hash(IndexMap::new()).type_name,
497 "extended_hash"
498 );
499 assert_eq!(TModel::from_method(MethodStub).type_name, "method");
500 assert_eq!(TModel::from_directive(DirectiveStub).type_name, "directive");
501 }
502
503 #[test]
505 fn collection_is_enumerable_but_not_sequence() {
506 let m = TModel::from_collection(vec![
507 TModel::from_scalar("a".into()),
508 TModel::from_scalar("b".into()),
509 ]);
510 assert!(m.is_collection());
511 assert!(!m.is_sequence());
512 assert!(m.is_enumerable());
513 assert_eq!(m.kind, ModelKind::Collection);
514 let items: Vec<_> = m
515 .collection
516 .as_ref()
517 .unwrap()
518 .iterator()
519 .unwrap()
520 .map(|r| r.unwrap().get_scalar().unwrap())
521 .collect();
522 assert_eq!(items, vec!["a", "b"]);
523 }
524
525 struct MethodStub;
526 impl TemplateMethodModelEx for MethodStub {
527 fn exec(&self, _env: &mut Environment, _args: Vec<TModel>) -> Result<TModel> {
528 Ok(TModel::nothing())
529 }
530 }
531
532 struct DirectiveStub;
533 impl TemplateDirectiveModel for DirectiveStub {
534 fn execute(
535 &self,
536 _env: &mut crate::core::Environment,
537 _params: &HashMap<String, TModel>,
538 _loop_vars: &mut [TModel],
539 _body: Option<&dyn TemplateDirectiveBody>,
540 ) -> Result<()> {
541 Ok(())
542 }
543 }
544}