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 = "std")]
377#[cfg(feature = "serde")]
378impl Value {
379 pub fn from_flexbuffers(data: &[u8]) -> Result<Self, crate::error::TemplateError> {
385 let r = flexbuffers::Reader::get_root(data).map_err(|e| {
386 crate::error::TemplateError::syntax(format!("flexbuffers root error: {e}"))
387 })?;
388 serde::Deserialize::deserialize(r).map_err(|e| {
389 crate::error::TemplateError::syntax(format!("flexbuffers deserialization failed: {e}"))
390 })
391 }
392}
393
394impl From<&str> for Value {
399 fn from(s: &str) -> Self {
400 Self::Str(s.to_string())
401 }
402}
403
404impl From<String> for Value {
405 fn from(s: String) -> Self {
406 Self::Str(s)
407 }
408}
409
410impl From<bool> for Value {
411 fn from(b: bool) -> Self {
412 Self::Bool(b)
413 }
414}
415
416impl From<i64> for Value {
417 fn from(i: i64) -> Self {
418 Self::Int(i)
419 }
420}
421
422impl From<i32> for Value {
423 fn from(i: i32) -> Self {
424 Self::Int(i64::from(i))
425 }
426}
427
428impl From<u32> for Value {
429 fn from(i: u32) -> Self {
430 Self::Int(i64::from(i))
431 }
432}
433
434impl TryFrom<u64> for Value {
435 type Error = core::num::TryFromIntError;
436 fn try_from(i: u64) -> Result<Self, Self::Error> {
437 Ok(Self::Int(i64::try_from(i)?))
438 }
439}
440
441impl TryFrom<usize> for Value {
442 type Error = core::num::TryFromIntError;
443 fn try_from(i: usize) -> Result<Self, Self::Error> {
444 Ok(Self::Int(i64::try_from(i)?))
445 }
446}
447
448impl From<f64> for Value {
449 fn from(f: f64) -> Self {
450 Self::Float(f)
451 }
452}
453
454impl From<f32> for Value {
455 fn from(f: f32) -> Self {
456 Self::Float(f64::from(f))
457 }
458}
459
460impl From<Vec<Value>> for Value {
461 fn from(v: Vec<Value>) -> Self {
462 Self::List(Arc::new(v))
463 }
464}
465
466impl From<HashMap<String, Value>> for Value {
467 fn from(m: HashMap<String, Value>) -> Self {
468 Self::Struct(Arc::new(m))
469 }
470}
471
472impl From<crate::template::Template> for Value {
473 fn from(t: crate::template::Template) -> Self {
474 Self::Tmpl(Arc::new(t))
475 }
476}
477
478impl From<Arc<crate::template::Template>> for Value {
479 fn from(t: Arc<crate::template::Template>) -> Self {
480 Self::Tmpl(t)
481 }
482}
483
484impl From<&crate::template::Template> for Value {
485 fn from(t: &crate::template::Template) -> Self {
486 Self::Tmpl(Arc::new(t.clone()))
487 }
488}
489
490#[derive(Debug, Clone, PartialEq, Eq)]
496pub struct ValueTypeError {
497 pub expected: &'static str,
499 pub actual: &'static str,
501}
502
503impl fmt::Display for ValueTypeError {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 write!(f, "expected {}, got {}", self.expected, self.actual)
506 }
507}
508
509impl core::error::Error for ValueTypeError {}
510
511impl TryFrom<Value> for String {
512 type Error = ValueTypeError;
513 fn try_from(v: Value) -> Result<Self, Self::Error> {
514 match v {
515 Value::Str(s) => Ok(s),
516 other => Err(ValueTypeError {
517 expected: crate::consts::TYPE_STR,
518 actual: other.type_name(),
519 }),
520 }
521 }
522}
523
524impl TryFrom<Value> for i64 {
525 type Error = ValueTypeError;
526 fn try_from(v: Value) -> Result<Self, Self::Error> {
527 match v {
528 Value::Int(i) => Ok(i),
529 other => Err(ValueTypeError {
530 expected: crate::consts::TYPE_INT,
531 actual: other.type_name(),
532 }),
533 }
534 }
535}
536
537impl TryFrom<Value> for f64 {
538 type Error = ValueTypeError;
539 fn try_from(v: Value) -> Result<Self, Self::Error> {
540 match v {
541 Value::Float(f) => Ok(f),
542 other => Err(ValueTypeError {
543 expected: crate::consts::TYPE_FLOAT,
544 actual: other.type_name(),
545 }),
546 }
547 }
548}
549
550impl TryFrom<Value> for bool {
551 type Error = ValueTypeError;
552 fn try_from(v: Value) -> Result<Self, Self::Error> {
553 match v {
554 Value::Bool(b) => Ok(b),
555 other => Err(ValueTypeError {
556 expected: crate::consts::TYPE_BOOL,
557 actual: other.type_name(),
558 }),
559 }
560 }
561}
562
563impl TryFrom<Value> for Vec<Value> {
564 type Error = ValueTypeError;
565 fn try_from(v: Value) -> Result<Self, Self::Error> {
566 match v {
567 Value::List(l) => Ok(Arc::try_unwrap(l).unwrap_or_else(|arc| (*arc).clone())),
568 other => Err(ValueTypeError {
569 expected: crate::consts::TYPE_LIST,
570 actual: other.type_name(),
571 }),
572 }
573 }
574}
575
576impl<S: core::hash::BuildHasher + Default> TryFrom<Value> for HashMap<String, Value, S> {
577 type Error = ValueTypeError;
578 fn try_from(v: Value) -> Result<Self, Self::Error> {
579 match v {
580 Value::Struct(m) => {
581 let owned = Arc::try_unwrap(m).unwrap_or_else(|arc| (*arc).clone());
582 Ok(owned.into_iter().collect())
583 }
584 other => Err(ValueTypeError {
585 expected: crate::consts::TYPE_STRUCT,
586 actual: other.type_name(),
587 }),
588 }
589 }
590}
591
592#[cfg(test)]
597mod tests {
598 use super::*;
599
600 #[test]
603 fn display_str() {
604 assert_eq!(Value::Str("hello".into()).to_string(), "hello");
605 }
606
607 #[test]
608 fn display_bool() {
609 assert_eq!(Value::Bool(true).to_string(), "true");
610 assert_eq!(Value::Bool(false).to_string(), "false");
611 }
612
613 #[test]
614 fn display_int() {
615 assert_eq!(Value::Int(42).to_string(), "42");
616 assert_eq!(Value::Int(-7).to_string(), "-7");
617 }
618
619 #[test]
620 fn display_float() {
621 assert_eq!(Value::Float(3.25).to_string(), "3.25");
622 }
623
624 #[test]
625 fn display_list() {
626 let list = Value::List(Arc::new(vec![Value::Int(1)]));
627 assert_eq!(list.to_string(), "[<list of 1>]");
628 assert_eq!(Value::List(Arc::new(vec![])).to_string(), "[<list of 0>]");
629 }
630
631 #[test]
632 fn display_dict() {
633 let dict = Value::Struct(Arc::new(HashMap::from([("k".into(), Value::Int(1))])));
634 assert_eq!(dict.to_string(), "{<struct of 1>}");
635 assert_eq!(
636 Value::Struct(Arc::new(HashMap::new())).to_string(),
637 "{<struct of 0>}"
638 );
639 }
640
641 #[test]
644 fn truthy_str() {
645 assert!(Value::Str("hello".into()).is_truthy());
646 assert!(!Value::Str(String::new()).is_truthy());
647 }
648
649 #[test]
650 fn truthy_bool() {
651 assert!(Value::Bool(true).is_truthy());
652 assert!(!Value::Bool(false).is_truthy());
653 }
654
655 #[test]
656 fn truthy_int() {
657 assert!(Value::Int(1).is_truthy());
658 assert!(Value::Int(-1).is_truthy());
659 assert!(!Value::Int(0).is_truthy());
660 }
661
662 #[test]
663 fn truthy_float() {
664 assert!(Value::Float(0.1).is_truthy());
665 assert!(!Value::Float(0.0).is_truthy());
666 }
667
668 #[test]
669 fn truthy_list() {
670 assert!(Value::List(Arc::new(vec![Value::Int(1)])).is_truthy());
671 assert!(!Value::List(Arc::new(vec![])).is_truthy());
672 }
673
674 #[test]
675 fn truthy_dict() {
676 let populated = Value::Struct(Arc::new(HashMap::from([("k".into(), Value::Int(1))])));
677 assert!(populated.is_truthy());
678 assert!(!Value::Struct(Arc::new(HashMap::new())).is_truthy());
679 }
680
681 #[test]
684 fn type_names() {
685 assert_eq!(Value::Str("x".into()).type_name(), "str");
686 assert_eq!(Value::Bool(true).type_name(), "bool");
687 assert_eq!(Value::Int(0).type_name(), "int");
688 assert_eq!(Value::Float(0.0).type_name(), "float");
689 assert_eq!(Value::List(Arc::new(vec![])).type_name(), "list");
690 assert_eq!(
691 Value::Struct(Arc::new(HashMap::new())).type_name(),
692 "struct"
693 );
694 }
695
696 #[test]
699 fn get_field_on_dict() {
700 let dict = Value::Struct(Arc::new(HashMap::from([
701 ("name".into(), Value::Str("Alice".into())),
702 ("score".into(), Value::Int(95)),
703 ])));
704 assert_eq!(dict.get_field("name"), Some(&Value::Str("Alice".into())));
705 assert_eq!(dict.get_field("score"), Some(&Value::Int(95)));
706 assert_eq!(dict.get_field("missing"), None);
707 }
708
709 #[test]
710 fn get_field_on_non_dict_returns_none() {
711 assert_eq!(Value::Str("x".into()).get_field("any"), None);
712 assert_eq!(Value::Int(1).get_field("any"), None);
713 assert_eq!(Value::List(Arc::new(vec![])).get_field("any"), None);
714 }
715
716 #[test]
719 fn from_str_ref() {
720 let v: Value = "hello".into();
721 assert_eq!(v, Value::Str("hello".into()));
722 }
723
724 #[test]
725 fn from_string() {
726 let v: Value = String::from("world").into();
727 assert_eq!(v, Value::Str("world".into()));
728 }
729
730 #[test]
731 fn from_bool() {
732 let v: Value = true.into();
733 assert_eq!(v, Value::Bool(true));
734 }
735
736 #[test]
737 fn from_i64() {
738 let v: Value = 42_i64.into();
739 assert_eq!(v, Value::Int(42));
740 }
741
742 #[test]
743 fn from_i32() {
744 let v: Value = 7_i32.into();
745 assert_eq!(v, Value::Int(7));
746 }
747
748 #[test]
749 fn from_u32() {
750 let v: Value = 100_u32.into();
751 assert_eq!(v, Value::Int(100));
752 }
753
754 #[test]
755 fn try_from_u64() {
756 let v = Value::try_from(999_u64).unwrap();
757 assert_eq!(v, Value::Int(999));
758 }
759
760 #[test]
761 fn try_from_u64_overflow() {
762 let result = Value::try_from(u64::MAX);
763 assert!(result.is_err(), "u64::MAX should not fit in i64");
764 }
765
766 #[test]
767 fn try_from_usize() {
768 let v = Value::try_from(5_usize).unwrap();
769 assert_eq!(v, Value::Int(5));
770 }
771
772 #[test]
773 fn from_f64() {
774 let v: Value = 2.5_f64.into();
775 assert_eq!(v, Value::Float(2.5));
776 }
777
778 #[test]
779 fn from_f32() {
780 let v: Value = 1.5_f32.into();
781 assert!(matches!(v, Value::Float(f) if (f - 1.5).abs() < f64::EPSILON));
783 }
784
785 #[test]
786 fn from_vec_value() {
787 let items = vec![Value::Int(1), Value::Str("two".into())];
788 let v: Value = items.into();
789 assert!(matches!(v, Value::List(ref l) if l.len() == 2));
790 }
791
792 #[test]
793 fn from_hashmap_value() {
794 let map = HashMap::from([("k".into(), Value::Bool(true))]);
795 let v: Value = map.into();
796 assert_eq!(v.get_field("k"), Some(&Value::Bool(true)));
797 }
798
799 #[test]
802 fn as_str_returns_some_for_str() {
803 assert_eq!(Value::Str("hello".into()).as_str(), Some("hello"));
804 }
805
806 #[test]
807 fn as_str_returns_none_for_non_str() {
808 assert_eq!(Value::Int(42).as_str(), None);
809 }
810
811 #[test]
812 fn as_int_returns_some_for_int() {
813 assert_eq!(Value::Int(42).as_int(), Some(42));
814 }
815
816 #[test]
817 fn as_int_returns_none_for_non_int() {
818 assert_eq!(Value::Str("42".into()).as_int(), None);
819 }
820
821 #[test]
822 fn as_float_returns_some_for_float() {
823 assert_eq!(Value::Float(3.25).as_float(), Some(3.25));
824 }
825
826 #[test]
827 fn as_float_returns_none_for_non_float() {
828 assert_eq!(Value::Int(3).as_float(), None);
829 }
830
831 #[test]
832 fn as_bool_returns_some_for_bool() {
833 assert_eq!(Value::Bool(true).as_bool(), Some(true));
834 }
835
836 #[test]
837 fn as_bool_returns_none_for_non_bool() {
838 assert_eq!(Value::Str("true".into()).as_bool(), None);
839 }
840
841 #[test]
842 fn as_list_returns_some_for_list() {
843 let items = vec![Value::Int(1), Value::Int(2)];
844 let v = Value::List(Arc::new(items.clone()));
845 assert_eq!(v.as_list(), Some(items.as_slice()));
846 }
847
848 #[test]
849 fn as_list_returns_none_for_non_list() {
850 assert_eq!(Value::Int(1).as_list(), None);
851 }
852
853 #[test]
854 fn as_struct_returns_some_for_dict() {
855 let map = HashMap::from([("k".into(), Value::Int(1))]);
856 let v = Value::Struct(Arc::new(map.clone()));
857 assert_eq!(v.as_struct(), Some(&map));
858 }
859
860 #[test]
861 fn as_struct_returns_none_for_non_dict() {
862 assert_eq!(Value::Int(1).as_struct(), None);
863 }
864
865 #[test]
868 fn try_from_str_success() {
869 let v = Value::Str("hello".into());
870 assert_eq!(String::try_from(v).unwrap(), "hello");
871 }
872
873 #[test]
874 fn try_from_str_failure_has_message() {
875 let v = Value::Int(42);
876 let err = String::try_from(v).unwrap_err();
877 assert_eq!(err.expected, "str");
878 assert_eq!(err.actual, "int");
879 assert_eq!(err.to_string(), "expected str, got int");
880 }
881
882 #[test]
883 fn try_from_i64_success() {
884 let v = Value::Int(99);
885 assert_eq!(i64::try_from(v).unwrap(), 99);
886 }
887
888 #[test]
889 fn try_from_i64_failure() {
890 let v = Value::Str("99".into());
891 let err = i64::try_from(v).expect_err("Str should not convert to i64");
892 assert_eq!(err.expected, "int");
893 assert_eq!(err.actual, "str");
894 }
895
896 #[test]
897 fn try_from_f64_success() {
898 let v = Value::Float(2.5);
899 assert!((f64::try_from(v).unwrap() - 2.5).abs() < f64::EPSILON);
900 }
901
902 #[test]
903 fn try_from_bool_success() {
904 let v = Value::Bool(false);
905 assert!(!bool::try_from(v).unwrap());
906 }
907
908 #[test]
909 fn try_from_vec_success() {
910 let v = Value::List(Arc::new(vec![Value::Int(1)]));
911 let list = Vec::<Value>::try_from(v).unwrap();
912 assert_eq!(list.len(), 1);
913 }
914
915 #[test]
916 fn try_from_hashmap_success() {
917 let v = Value::Struct(Arc::new(HashMap::from([("k".into(), Value::Int(1))])));
918 let map = HashMap::<String, Value>::try_from(v).unwrap();
919 assert_eq!(map.len(), 1);
920 }
921
922 #[test]
923 fn from_template_owned() {
924 let tmpl = crate::Template::from_source(
925 r"---
926params: [x = str]
927---
928{{ x }}",
929 )
930 .unwrap();
931 let val = Value::from(tmpl);
932 assert!(matches!(val, Value::Tmpl(_)));
933 assert_eq!(val.type_name(), "tmpl");
934 }
935
936 #[test]
937 fn from_template_ref() {
938 let tmpl = crate::Template::from_source(
939 r"---
940params: [x = str]
941---
942{{ x }}",
943 )
944 .unwrap();
945 let val = Value::from(&tmpl);
946 assert!(matches!(val, Value::Tmpl(_)));
947 }
948
949 #[test]
950 fn from_template_arc() {
951 let tmpl = crate::Template::from_source(
952 r"---
953params: [x = str]
954---
955{{ x }}",
956 )
957 .unwrap();
958 let arc = Arc::new(tmpl);
959 let val = Value::from(arc);
960 assert!(matches!(val, Value::Tmpl(_)));
961 }
962
963 #[test]
964 fn context_set_with_template() {
965 let tmpl = crate::Template::from_source(
966 r"---
967params: [x = str]
968---
969{{ x }}",
970 )
971 .unwrap();
972 let mut ctx = crate::Context::new();
973 ctx.set("widget", tmpl);
975 assert!(ctx.get("widget").unwrap().as_tmpl().is_some());
976 }
977
978 #[test]
979 fn context_set_with_template_ref() {
980 let tmpl = crate::Template::from_source(
981 r"---
982params: [x = str]
983---
984{{ x }}",
985 )
986 .unwrap();
987 let mut ctx = crate::Context::new();
988 ctx.set("widget", &tmpl);
990 assert!(ctx.get("widget").unwrap().as_tmpl().is_some());
991 }
992}