1use alloc::{
4 string::{String, ToString},
5 sync::Arc,
6 vec::Vec,
7};
8use core::fmt;
9
10use crate::compat::HashMap;
11
12#[derive(Debug, Clone)]
14pub enum Value {
15 Str(String),
17 Bool(bool),
19 Int(i64),
21 Float(f64),
23 List(Arc<Vec<Value>>),
25 Struct(Arc<HashMap<String, Value>>),
27 Tmpl(Arc<crate::template::Template>),
29 None,
31}
32
33impl PartialEq for Value {
34 fn eq(&self, other: &Self) -> bool {
35 match (self, other) {
36 (Self::Str(a), Self::Str(b)) => a == b,
37 (Self::Bool(a), Self::Bool(b)) => a == b,
38 (Self::Int(a), Self::Int(b)) => a == b,
39 (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
40 (Self::List(a), Self::List(b)) => a == b,
41 (Self::Struct(a), Self::Struct(b)) => a == b,
42 (Self::Tmpl(a), Self::Tmpl(b)) => Arc::ptr_eq(a, b),
43 (Self::None, Self::None) => true,
44 _ => false,
45 }
46 }
47}
48
49impl Eq for Value {}
50
51impl fmt::Display for Value {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::Str(s) => f.write_str(s),
55 Self::Bool(b) => write!(f, "{b}"),
56 Self::Int(i) => {
57 let mut buf = itoa::Buffer::new();
58 f.write_str(buf.format(*i))
59 }
60 Self::Float(v) => write!(f, "{v}"),
61 Self::List(items) => write!(f, "[<list of {}>]", items.len()),
62 Self::Struct(map) => write!(f, "{{<struct of {}>}}", map.len()),
63 Self::Tmpl(_) => write!(f, "<template>"),
64 Self::None => Ok(()),
65 }
66 }
67}
68
69impl Value {
70 #[must_use]
72 pub fn is_truthy(&self) -> bool {
73 match self {
74 Self::Str(s) => !s.is_empty(),
75 Self::Bool(b) => *b,
76 Self::Int(i) => *i != 0,
77 Self::Float(f) => *f != 0.0,
78 Self::List(v) => !v.is_empty(),
79 Self::Struct(m) => !m.is_empty(),
80 Self::Tmpl(_) => true,
81 Self::None => false,
82 }
83 }
84 #[must_use]
86 pub fn type_name(&self) -> &'static str {
87 match self {
88 Self::Str(_) => crate::consts::TYPE_STR,
89 Self::Bool(_) => crate::consts::TYPE_BOOL,
90 Self::Int(_) => crate::consts::TYPE_INT,
91 Self::Float(_) => crate::consts::TYPE_FLOAT,
92 Self::List(_) => crate::consts::TYPE_LIST,
93 Self::Struct(_) => crate::consts::TYPE_STRUCT,
94 Self::Tmpl(_) => crate::consts::TYPE_TMPL,
95 Self::None => crate::consts::TYPE_NONE,
96 }
97 }
98
99 #[must_use]
105 pub(crate) fn field_names_hint(&self) -> Vec<&str> {
106 match self {
107 Self::Struct(m) => m
108 .keys()
109 .filter(|k| k.as_str() != crate::consts::ENUM_TAG_KEY)
110 .map(String::as_str)
111 .collect(),
112 _ => Vec::new(),
113 }
114 }
115 #[inline]
120 #[must_use]
121 pub fn get_field(&self, key: &str) -> Option<&Value> {
122 match self {
123 Self::Struct(m) => {
124 if key == crate::consts::ENUM_TAG_KEY {
126 return None;
127 }
128 m.get(key)
129 }
130 _ => None,
131 }
132 }
133
134 #[inline]
140 #[must_use]
141 pub(crate) fn get_field_unchecked(&self, key: &str) -> Option<&Value> {
142 debug_assert!(
143 key != crate::consts::ENUM_TAG_KEY,
144 "get_field_unchecked called with internal ENUM_TAG_KEY '{key}' — \
145 this should have been rejected at compile time",
146 );
147 match self {
148 Self::Struct(m) => m.get(key),
149 _ => None,
150 }
151 }
152
153 #[must_use]
155 pub fn is_str(&self) -> bool {
156 matches!(self, Self::Str(_))
157 }
158
159 #[must_use]
161 pub fn is_int(&self) -> bool {
162 matches!(self, Self::Int(_))
163 }
164
165 #[must_use]
167 pub fn is_float(&self) -> bool {
168 matches!(self, Self::Float(_))
169 }
170
171 #[must_use]
173 pub fn is_bool(&self) -> bool {
174 matches!(self, Self::Bool(_))
175 }
176
177 #[must_use]
179 pub fn is_list(&self) -> bool {
180 matches!(self, Self::List(_))
181 }
182
183 #[must_use]
185 pub fn is_struct(&self) -> bool {
186 matches!(self, Self::Struct(_))
187 }
188
189 #[must_use]
191 pub fn as_str(&self) -> Option<&str> {
192 match self {
193 Self::Str(s) => Some(s),
194 _ => None,
195 }
196 }
197
198 #[must_use]
200 pub fn as_int(&self) -> Option<i64> {
201 match self {
202 Self::Int(i) => Some(*i),
203 _ => None,
204 }
205 }
206
207 #[must_use]
209 pub fn as_float(&self) -> Option<f64> {
210 match self {
211 Self::Float(f) => Some(*f),
212 _ => None,
213 }
214 }
215
216 #[must_use]
218 pub fn as_bool(&self) -> Option<bool> {
219 match self {
220 Self::Bool(b) => Some(*b),
221 _ => None,
222 }
223 }
224
225 #[must_use]
227 pub fn as_list(&self) -> Option<&[Value]> {
228 match self {
229 Self::List(v) => Some(v),
230 _ => None,
231 }
232 }
233
234 #[must_use]
236 pub fn as_struct(&self) -> Option<&HashMap<String, Value>> {
237 match self {
238 Self::Struct(m) => Some(m),
239 _ => None,
240 }
241 }
242
243 #[must_use]
245 pub fn as_tmpl(&self) -> Option<&Arc<crate::template::Template>> {
246 match self {
247 Self::Tmpl(t) => Some(t),
248 _ => None,
249 }
250 }
251
252 #[must_use]
265 pub fn new_struct<I, K, V>(pairs: I) -> Self
266 where
267 I: IntoIterator<Item = (K, V)>,
268 K: Into<String>,
269 V: Into<Value>,
270 {
271 Self::Struct(Arc::new(
272 pairs
273 .into_iter()
274 .map(|(k, v)| (k.into(), v.into()))
275 .collect(),
276 ))
277 }
278
279 #[must_use]
295 pub fn list<I, V>(items: I) -> Self
296 where
297 I: IntoIterator<Item = V>,
298 V: Into<Value>,
299 {
300 Self::List(Arc::new(items.into_iter().map(Into::into).collect()))
301 }
302}
303
304#[cfg(feature = "serde")]
305impl Value {
306 pub fn from_serialize<T: serde::Serialize>(
333 value: &T,
334 ) -> Result<Self, crate::serde_support::SerError> {
335 crate::serde_support::to_value(value)
336 }
337
338 pub fn deserialize_into<'de, T: serde::Deserialize<'de>>(
368 &'de self,
369 ) -> Result<T, crate::serde_support::DeError> {
370 crate::serde_support::from_value(self)
371 }
372}
373
374#[cfg(feature = "flexbuffers")]
377impl Value {
378 pub fn from_flexbuffers(data: &[u8]) -> Result<Self, crate::error::TemplateError> {
384 let r = flexbuffers::Reader::get_root(data).map_err(|e| {
385 crate::error::TemplateError::syntax(format!("flexbuffers root error: {e}"))
386 })?;
387 serde::Deserialize::deserialize(r).map_err(|e| {
388 crate::error::TemplateError::syntax(format!("flexbuffers deserialization failed: {e}"))
389 })
390 }
391}
392
393impl From<&str> for Value {
398 fn from(s: &str) -> Self {
399 Self::Str(s.to_string())
400 }
401}
402
403impl From<String> for Value {
404 fn from(s: String) -> Self {
405 Self::Str(s)
406 }
407}
408
409impl From<bool> for Value {
410 fn from(b: bool) -> Self {
411 Self::Bool(b)
412 }
413}
414
415impl From<i64> for Value {
416 fn from(i: i64) -> Self {
417 Self::Int(i)
418 }
419}
420
421impl From<i32> for Value {
422 fn from(i: i32) -> Self {
423 Self::Int(i64::from(i))
424 }
425}
426
427impl From<u32> for Value {
428 fn from(i: u32) -> Self {
429 Self::Int(i64::from(i))
430 }
431}
432
433impl TryFrom<u64> for Value {
434 type Error = core::num::TryFromIntError;
435 fn try_from(i: u64) -> Result<Self, Self::Error> {
436 Ok(Self::Int(i64::try_from(i)?))
437 }
438}
439
440impl TryFrom<usize> for Value {
441 type Error = core::num::TryFromIntError;
442 fn try_from(i: usize) -> Result<Self, Self::Error> {
443 Ok(Self::Int(i64::try_from(i)?))
444 }
445}
446
447impl From<f64> for Value {
448 fn from(f: f64) -> Self {
449 Self::Float(f)
450 }
451}
452
453impl From<f32> for Value {
454 fn from(f: f32) -> Self {
455 Self::Float(f64::from(f))
456 }
457}
458
459impl From<Vec<Value>> for Value {
460 fn from(v: Vec<Value>) -> Self {
461 Self::List(Arc::new(v))
462 }
463}
464
465impl From<HashMap<String, Value>> for Value {
466 fn from(m: HashMap<String, Value>) -> Self {
467 Self::Struct(Arc::new(m))
468 }
469}
470
471impl From<crate::template::Template> for Value {
472 fn from(t: crate::template::Template) -> Self {
473 Self::Tmpl(Arc::new(t))
474 }
475}
476
477impl From<Arc<crate::template::Template>> for Value {
478 fn from(t: Arc<crate::template::Template>) -> Self {
479 Self::Tmpl(t)
480 }
481}
482
483impl From<&crate::template::Template> for Value {
484 fn from(t: &crate::template::Template) -> Self {
485 Self::Tmpl(Arc::new(t.clone()))
486 }
487}
488
489#[derive(Debug, Clone, PartialEq, Eq)]
495pub struct ValueTypeError {
496 pub expected: &'static str,
498 pub actual: &'static str,
500}
501
502impl fmt::Display for ValueTypeError {
503 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504 write!(f, "expected {}, got {}", self.expected, self.actual)
505 }
506}
507
508impl core::error::Error for ValueTypeError {}
509
510impl TryFrom<Value> for String {
511 type Error = ValueTypeError;
512 fn try_from(v: Value) -> Result<Self, Self::Error> {
513 match v {
514 Value::Str(s) => Ok(s),
515 other => Err(ValueTypeError {
516 expected: crate::consts::TYPE_STR,
517 actual: other.type_name(),
518 }),
519 }
520 }
521}
522
523impl TryFrom<Value> for i64 {
524 type Error = ValueTypeError;
525 fn try_from(v: Value) -> Result<Self, Self::Error> {
526 match v {
527 Value::Int(i) => Ok(i),
528 other => Err(ValueTypeError {
529 expected: crate::consts::TYPE_INT,
530 actual: other.type_name(),
531 }),
532 }
533 }
534}
535
536impl TryFrom<Value> for f64 {
537 type Error = ValueTypeError;
538 fn try_from(v: Value) -> Result<Self, Self::Error> {
539 match v {
540 Value::Float(f) => Ok(f),
541 other => Err(ValueTypeError {
542 expected: crate::consts::TYPE_FLOAT,
543 actual: other.type_name(),
544 }),
545 }
546 }
547}
548
549impl TryFrom<Value> for bool {
550 type Error = ValueTypeError;
551 fn try_from(v: Value) -> Result<Self, Self::Error> {
552 match v {
553 Value::Bool(b) => Ok(b),
554 other => Err(ValueTypeError {
555 expected: crate::consts::TYPE_BOOL,
556 actual: other.type_name(),
557 }),
558 }
559 }
560}
561
562impl TryFrom<Value> for Vec<Value> {
563 type Error = ValueTypeError;
564 fn try_from(v: Value) -> Result<Self, Self::Error> {
565 match v {
566 Value::List(l) => Ok(Arc::try_unwrap(l).unwrap_or_else(|arc| (*arc).clone())),
567 other => Err(ValueTypeError {
568 expected: crate::consts::TYPE_LIST,
569 actual: other.type_name(),
570 }),
571 }
572 }
573}
574
575impl<S: core::hash::BuildHasher + Default> TryFrom<Value> for HashMap<String, Value, S> {
576 type Error = ValueTypeError;
577 fn try_from(v: Value) -> Result<Self, Self::Error> {
578 match v {
579 Value::Struct(m) => {
580 let owned = Arc::try_unwrap(m).unwrap_or_else(|arc| (*arc).clone());
581 Ok(owned.into_iter().collect())
582 }
583 other => Err(ValueTypeError {
584 expected: crate::consts::TYPE_STRUCT,
585 actual: other.type_name(),
586 }),
587 }
588 }
589}
590
591#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
602 fn display_str() {
603 assert_eq!(Value::Str("hello".into()).to_string(), "hello");
604 }
605
606 #[test]
607 fn display_bool() {
608 assert_eq!(Value::Bool(true).to_string(), "true");
609 assert_eq!(Value::Bool(false).to_string(), "false");
610 }
611
612 #[test]
613 fn display_int() {
614 assert_eq!(Value::Int(42).to_string(), "42");
615 assert_eq!(Value::Int(-7).to_string(), "-7");
616 }
617
618 #[test]
619 fn display_float() {
620 assert_eq!(Value::Float(3.25).to_string(), "3.25");
621 }
622
623 #[test]
624 fn display_list() {
625 let list = Value::List(Arc::new(vec![Value::Int(1)]));
626 assert_eq!(list.to_string(), "[<list of 1>]");
627 assert_eq!(Value::List(Arc::new(vec![])).to_string(), "[<list of 0>]");
628 }
629
630 #[test]
631 fn display_dict() {
632 let dict = Value::Struct(Arc::new(HashMap::from([("k".into(), Value::Int(1))])));
633 assert_eq!(dict.to_string(), "{<struct of 1>}");
634 assert_eq!(
635 Value::Struct(Arc::new(HashMap::new())).to_string(),
636 "{<struct of 0>}"
637 );
638 }
639
640 #[cfg(feature = "flexbuffers")]
643 #[test]
644 fn from_flexbuffers_roundtrip() {
645 use serde::Serialize;
646
647 let source = alloc::collections::BTreeMap::from([("name", "Alice"), ("role", "admin")]);
648 let mut ser = flexbuffers::FlexbufferSerializer::new();
649 source.serialize(&mut ser).expect("flexbuffers encode");
650
651 match Value::from_flexbuffers(ser.view()).expect("from_flexbuffers") {
652 Value::Struct(map) => {
653 assert_eq!(map.get("name"), Some(&Value::Str("Alice".into())));
654 assert_eq!(map.get("role"), Some(&Value::Str("admin".into())));
655 }
656 _ => panic!("expected a struct value"),
657 }
658 }
659
660 #[cfg(feature = "flexbuffers")]
661 #[test]
662 fn from_flexbuffers_rejects_garbage() {
663 Value::from_flexbuffers(&[0xde, 0xad, 0xbe, 0xef])
664 .expect_err("garbage flexbuffer must error without panicking");
665 }
666
667 #[test]
670 fn truthy_str() {
671 assert!(Value::Str("hello".into()).is_truthy());
672 assert!(!Value::Str(String::new()).is_truthy());
673 }
674
675 #[test]
676 fn truthy_bool() {
677 assert!(Value::Bool(true).is_truthy());
678 assert!(!Value::Bool(false).is_truthy());
679 }
680
681 #[test]
682 fn truthy_int() {
683 assert!(Value::Int(1).is_truthy());
684 assert!(Value::Int(-1).is_truthy());
685 assert!(!Value::Int(0).is_truthy());
686 }
687
688 #[test]
689 fn truthy_float() {
690 assert!(Value::Float(0.1).is_truthy());
691 assert!(!Value::Float(0.0).is_truthy());
692 }
693
694 #[test]
695 fn truthy_list() {
696 assert!(Value::List(Arc::new(vec![Value::Int(1)])).is_truthy());
697 assert!(!Value::List(Arc::new(vec![])).is_truthy());
698 }
699
700 #[test]
701 fn truthy_dict() {
702 let populated = Value::Struct(Arc::new(HashMap::from([("k".into(), Value::Int(1))])));
703 assert!(populated.is_truthy());
704 assert!(!Value::Struct(Arc::new(HashMap::new())).is_truthy());
705 }
706
707 #[test]
710 fn type_names() {
711 assert_eq!(Value::Str("x".into()).type_name(), "str");
712 assert_eq!(Value::Bool(true).type_name(), "bool");
713 assert_eq!(Value::Int(0).type_name(), "int");
714 assert_eq!(Value::Float(0.0).type_name(), "float");
715 assert_eq!(Value::List(Arc::new(vec![])).type_name(), "list");
716 assert_eq!(
717 Value::Struct(Arc::new(HashMap::new())).type_name(),
718 "struct"
719 );
720 }
721
722 #[test]
725 fn get_field_on_dict() {
726 let dict = Value::Struct(Arc::new(HashMap::from([
727 ("name".into(), Value::Str("Alice".into())),
728 ("score".into(), Value::Int(95)),
729 ])));
730 assert_eq!(dict.get_field("name"), Some(&Value::Str("Alice".into())));
731 assert_eq!(dict.get_field("score"), Some(&Value::Int(95)));
732 assert_eq!(dict.get_field("missing"), None);
733 }
734
735 #[test]
736 fn get_field_on_non_dict_returns_none() {
737 assert_eq!(Value::Str("x".into()).get_field("any"), None);
738 assert_eq!(Value::Int(1).get_field("any"), None);
739 assert_eq!(Value::List(Arc::new(vec![])).get_field("any"), None);
740 }
741
742 #[test]
745 fn from_str_ref() {
746 let v: Value = "hello".into();
747 assert_eq!(v, Value::Str("hello".into()));
748 }
749
750 #[test]
751 fn from_string() {
752 let v: Value = String::from("world").into();
753 assert_eq!(v, Value::Str("world".into()));
754 }
755
756 #[test]
757 fn from_bool() {
758 let v: Value = true.into();
759 assert_eq!(v, Value::Bool(true));
760 }
761
762 #[test]
763 fn from_i64() {
764 let v: Value = 42_i64.into();
765 assert_eq!(v, Value::Int(42));
766 }
767
768 #[test]
769 fn from_i32() {
770 let v: Value = 7_i32.into();
771 assert_eq!(v, Value::Int(7));
772 }
773
774 #[test]
775 fn from_u32() {
776 let v: Value = 100_u32.into();
777 assert_eq!(v, Value::Int(100));
778 }
779
780 #[test]
781 fn try_from_u64() {
782 let v = Value::try_from(999_u64).unwrap();
783 assert_eq!(v, Value::Int(999));
784 }
785
786 #[test]
787 fn try_from_u64_overflow() {
788 let result = Value::try_from(u64::MAX);
789 assert!(result.is_err(), "u64::MAX should not fit in i64");
790 }
791
792 #[test]
793 fn try_from_usize() {
794 let v = Value::try_from(5_usize).unwrap();
795 assert_eq!(v, Value::Int(5));
796 }
797
798 #[test]
799 fn from_f64() {
800 let v: Value = 2.5_f64.into();
801 assert_eq!(v, Value::Float(2.5));
802 }
803
804 #[test]
805 fn from_f32() {
806 let v: Value = 1.5_f32.into();
807 assert!(matches!(v, Value::Float(f) if (f - 1.5).abs() < f64::EPSILON));
809 }
810
811 #[test]
812 fn from_vec_value() {
813 let items = vec![Value::Int(1), Value::Str("two".into())];
814 let v: Value = items.into();
815 assert!(matches!(v, Value::List(ref l) if l.len() == 2));
816 }
817
818 #[test]
819 fn from_hashmap_value() {
820 let map = HashMap::from([("k".into(), Value::Bool(true))]);
821 let v: Value = map.into();
822 assert_eq!(v.get_field("k"), Some(&Value::Bool(true)));
823 }
824
825 #[test]
828 fn as_str_returns_some_for_str() {
829 assert_eq!(Value::Str("hello".into()).as_str(), Some("hello"));
830 }
831
832 #[test]
833 fn as_str_returns_none_for_non_str() {
834 assert_eq!(Value::Int(42).as_str(), None);
835 }
836
837 #[test]
838 fn as_int_returns_some_for_int() {
839 assert_eq!(Value::Int(42).as_int(), Some(42));
840 }
841
842 #[test]
843 fn as_int_returns_none_for_non_int() {
844 assert_eq!(Value::Str("42".into()).as_int(), None);
845 }
846
847 #[test]
848 fn as_float_returns_some_for_float() {
849 assert_eq!(Value::Float(3.25).as_float(), Some(3.25));
850 }
851
852 #[test]
853 fn as_float_returns_none_for_non_float() {
854 assert_eq!(Value::Int(3).as_float(), None);
855 }
856
857 #[test]
858 fn as_bool_returns_some_for_bool() {
859 assert_eq!(Value::Bool(true).as_bool(), Some(true));
860 }
861
862 #[test]
863 fn as_bool_returns_none_for_non_bool() {
864 assert_eq!(Value::Str("true".into()).as_bool(), None);
865 }
866
867 #[test]
868 fn as_list_returns_some_for_list() {
869 let items = vec![Value::Int(1), Value::Int(2)];
870 let v = Value::List(Arc::new(items.clone()));
871 assert_eq!(v.as_list(), Some(items.as_slice()));
872 }
873
874 #[test]
875 fn as_list_returns_none_for_non_list() {
876 assert_eq!(Value::Int(1).as_list(), None);
877 }
878
879 #[test]
880 fn as_struct_returns_some_for_dict() {
881 let map = HashMap::from([("k".into(), Value::Int(1))]);
882 let v = Value::Struct(Arc::new(map.clone()));
883 assert_eq!(v.as_struct(), Some(&map));
884 }
885
886 #[test]
887 fn as_struct_returns_none_for_non_dict() {
888 assert_eq!(Value::Int(1).as_struct(), None);
889 }
890
891 #[test]
894 fn try_from_str_success() {
895 let v = Value::Str("hello".into());
896 assert_eq!(String::try_from(v).unwrap(), "hello");
897 }
898
899 #[test]
900 fn try_from_str_failure_has_message() {
901 let v = Value::Int(42);
902 let err = String::try_from(v).unwrap_err();
903 assert_eq!(err.expected, "str");
904 assert_eq!(err.actual, "int");
905 assert_eq!(err.to_string(), "expected str, got int");
906 }
907
908 #[test]
909 fn try_from_i64_success() {
910 let v = Value::Int(99);
911 assert_eq!(i64::try_from(v).unwrap(), 99);
912 }
913
914 #[test]
915 fn try_from_i64_failure() {
916 let v = Value::Str("99".into());
917 let err = i64::try_from(v).expect_err("Str should not convert to i64");
918 assert_eq!(err.expected, "int");
919 assert_eq!(err.actual, "str");
920 }
921
922 #[test]
923 fn try_from_f64_success() {
924 let v = Value::Float(2.5);
925 assert!((f64::try_from(v).unwrap() - 2.5).abs() < f64::EPSILON);
926 }
927
928 #[test]
929 fn try_from_bool_success() {
930 let v = Value::Bool(false);
931 assert!(!bool::try_from(v).unwrap());
932 }
933
934 #[test]
935 fn try_from_vec_success() {
936 let v = Value::List(Arc::new(vec![Value::Int(1)]));
937 let list = Vec::<Value>::try_from(v).unwrap();
938 assert_eq!(list.len(), 1);
939 }
940
941 #[test]
942 fn try_from_hashmap_success() {
943 let v = Value::Struct(Arc::new(HashMap::from([("k".into(), Value::Int(1))])));
944 let map = HashMap::<String, Value>::try_from(v).unwrap();
945 assert_eq!(map.len(), 1);
946 }
947
948 #[test]
949 fn from_template_owned() {
950 let tmpl = crate::Template::from_source(
951 r"---
952params: [x = str]
953---
954{{ x }}",
955 )
956 .unwrap();
957 let val = Value::from(tmpl);
958 assert!(matches!(val, Value::Tmpl(_)));
959 assert_eq!(val.type_name(), "tmpl");
960 }
961
962 #[test]
963 fn from_template_ref() {
964 let tmpl = crate::Template::from_source(
965 r"---
966params: [x = str]
967---
968{{ x }}",
969 )
970 .unwrap();
971 let val = Value::from(&tmpl);
972 assert!(matches!(val, Value::Tmpl(_)));
973 }
974
975 #[test]
976 fn from_template_arc() {
977 let tmpl = crate::Template::from_source(
978 r"---
979params: [x = str]
980---
981{{ x }}",
982 )
983 .unwrap();
984 let arc = Arc::new(tmpl);
985 let val = Value::from(arc);
986 assert!(matches!(val, Value::Tmpl(_)));
987 }
988
989 #[test]
990 fn context_set_with_template() {
991 let tmpl = crate::Template::from_source(
992 r"---
993params: [x = str]
994---
995{{ x }}",
996 )
997 .unwrap();
998 let mut ctx = crate::Context::new();
999 ctx.set("widget", tmpl);
1001 assert!(ctx.get("widget").unwrap().as_tmpl().is_some());
1002 }
1003
1004 #[test]
1005 fn context_set_with_template_ref() {
1006 let tmpl = crate::Template::from_source(
1007 r"---
1008params: [x = str]
1009---
1010{{ x }}",
1011 )
1012 .unwrap();
1013 let mut ctx = crate::Context::new();
1014 ctx.set("widget", &tmpl);
1016 assert!(ctx.get("widget").unwrap().as_tmpl().is_some());
1017 }
1018}