1mod cereal;
7mod display;
8mod error;
9mod expression;
10mod parse;
11#[cfg(test)]
12mod test_util;
13
14pub use error::{
15 Expected, RenderError, TemplateParseError, ValueError, WithValue,
16};
17pub use expression::{Expression, FunctionCall, Identifier, Literal};
18
19use crate::{
20 error::RenderErrorContext,
21 parse::{FALSE, NULL, TRUE},
22};
23use bytes::{Bytes, BytesMut};
24use derive_more::From;
25use futures::future;
26use indexmap::IndexMap;
27use itertools::Itertools;
28#[cfg(test)]
29use proptest::{arbitrary::any, strategy::Strategy};
30use serde::{Deserialize, Serialize};
31use slumber_util::NEW_ISSUE_LINK;
32use std::{collections::VecDeque, fmt::Debug, sync::Arc};
33
34pub trait Context: Sized + Send + Sync {
37 fn get(
43 &self,
44 identifier: &Identifier,
45 ) -> impl Future<Output = Result<Value, RenderError>> + Send;
46
47 fn call(
49 &self,
50 function_name: &Identifier,
51 arguments: Arguments<'_, Self>,
52 ) -> impl Future<Output = Result<Value, RenderError>> + Send;
53}
54
55#[derive(Clone, Debug, Default, PartialEq)]
68#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
69pub struct Template {
70 #[cfg_attr(
75 test,
76 proptest(
77 strategy = "any::<Vec<TemplateChunk>>().prop_map(test_util::join_raw)"
78 )
79 )]
80 chunks: Vec<TemplateChunk>,
81}
82
83impl Template {
84 pub fn from_chunks(chunks: Vec<TemplateChunk>) -> Self {
96 assert!(
99 !chunks.iter().any(
101 |chunk| matches!(chunk, TemplateChunk::Raw(s) if s.is_empty())
102 )
103 && !chunks.iter().tuple_windows().any(|pair| matches!(
105 pair,
106 (TemplateChunk::Raw(_), TemplateChunk::Raw(_))
107 )),
108 "Invalid chunks in generated template {chunks:?} This is a bug! \
109 Please report it. {NEW_ISSUE_LINK}"
110 );
111 Self { chunks }
112 }
113
114 pub fn raw(template: String) -> Template {
118 let chunks = if template.is_empty() {
119 vec![]
120 } else {
121 vec![TemplateChunk::Raw(template.into())]
125 };
126 Self { chunks }
127 }
128
129 pub fn file(path: String) -> Template {
138 Self::function_call("file", [path.into()], [])
139 }
140
141 pub fn function_call(
155 name: &'static str,
156 position: impl IntoIterator<Item = Expression>,
157 keyword: impl IntoIterator<Item = (&'static str, Option<Expression>)>,
158 ) -> Self {
159 let chunks = vec![TemplateChunk::Expression(Expression::call(
160 name, position, keyword,
161 ))];
162 Self { chunks }
163 }
164
165 pub fn is_empty(&self) -> bool {
166 self.chunks.is_empty()
167 }
168
169 pub async fn render_value<Ctx: Context>(
183 &self,
184 context: &Ctx,
185 ) -> Result<Value, RenderError> {
186 let mut chunks = self.render_chunks(context).await;
187
188 if let &[RenderedChunk::Rendered(_)] = chunks.as_slice() {
191 let Some(RenderedChunk::Rendered(value)) = chunks.pop() else {
192 unreachable!()
194 };
195 return Ok(value);
196 }
197
198 let bytes = chunks_to_bytes(chunks)?;
201 match String::from_utf8(bytes.into()) {
202 Ok(s) => Ok(Value::String(s)),
203 Err(error) => Ok(Value::Bytes(error.into_bytes().into())),
204 }
205 }
206
207 pub async fn render_bytes<Ctx: Context>(
212 &self,
213 context: &Ctx,
214 ) -> Result<Bytes, RenderError> {
215 let chunks = self.render_chunks(context).await;
216 chunks_to_bytes(chunks)
217 }
218
219 pub async fn render_string<Ctx: Context>(
223 &self,
224 context: &Ctx,
225 ) -> Result<String, RenderError> {
226 let bytes = self.render_bytes(context).await?;
227 String::from_utf8(bytes.into()).map_err(RenderError::other)
228 }
229
230 pub async fn render_chunks<Ctx: Context>(
236 &self,
237 context: &Ctx,
238 ) -> Vec<RenderedChunk> {
239 let futures = self.chunks.iter().map(|chunk| async move {
243 match chunk {
244 TemplateChunk::Raw(text) => {
245 RenderedChunk::Raw(Arc::clone(text))
246 }
247 TemplateChunk::Expression(expression) => expression
248 .render(context)
249 .await
250 .map_or_else(RenderedChunk::Error, RenderedChunk::Rendered),
251 }
252 });
253
254 future::join_all(futures).await
256 }
257}
258
259#[cfg(any(test, feature = "test"))]
260impl From<&str> for Template {
261 fn from(value: &str) -> Self {
262 value.parse().unwrap()
263 }
264}
265
266#[cfg(any(test, feature = "test"))]
267impl From<String> for Template {
268 fn from(value: String) -> Self {
269 value.as_str().into()
270 }
271}
272
273#[cfg(any(test, feature = "test"))]
274impl<const N: usize> From<[TemplateChunk; N]> for Template {
275 fn from(chunks: [TemplateChunk; N]) -> Self {
276 Self {
277 chunks: chunks.into(),
278 }
279 }
280}
281
282#[derive(Clone, Debug, PartialEq)]
285#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
286pub enum TemplateChunk {
287 Raw(
293 #[cfg_attr(test, proptest(strategy = "\".+\".prop_map(String::into)"))]
294 Arc<str>,
295 ),
296 Expression(
298 #[cfg_attr(
299 test,
300 proptest(strategy = "test_util::expression_arbitrary()")
301 )]
302 Expression,
303 ),
304}
305
306#[cfg(test)]
307impl From<Expression> for TemplateChunk {
308 fn from(expression: Expression) -> Self {
309 Self::Expression(expression)
310 }
311}
312
313#[derive(Clone, Debug, From, PartialEq, Serialize, Deserialize)]
317#[serde(untagged)]
318pub enum Value {
319 Null,
320 Boolean(bool),
321 Integer(i64),
322 Float(f64),
323 String(String),
324 #[from(skip)] Array(Vec<Self>),
326 Object(IndexMap<String, Self>),
327 Bytes(Bytes),
329}
330
331impl Value {
332 pub fn to_bool(&self) -> bool {
345 match self {
346 Self::Null => false,
347 Self::Boolean(b) => *b,
348 Self::Integer(i) => *i != 0,
349 Self::Float(f) => *f != 0.0,
350 Self::String(s) => !s.is_empty(),
351 Self::Bytes(bytes) => !bytes.is_empty(),
352 Self::Array(array) => !array.is_empty(),
353 Self::Object(object) => !object.is_empty(),
354 }
355 }
356
357 pub fn try_into_string(self) -> Result<String, WithValue<ValueError>> {
361 match self {
362 Self::Null => Ok(NULL.into()),
363 Self::Boolean(false) => Ok(FALSE.into()),
364 Self::Boolean(true) => Ok(TRUE.into()),
365 Self::Integer(i) => Ok(i.to_string()),
366 Self::Float(f) => Ok(f.to_string()),
367 Self::String(s) => Ok(s),
368 Self::Bytes(bytes) => String::from_utf8(bytes.into())
369 .map_err(|error| {
372 WithValue::new(
373 Self::Bytes(error.as_bytes().to_owned().into()),
374 error.utf8_error(),
375 )
376 }),
377 Self::Array(_) | Self::Object(_) => Ok(self.to_string()),
379 }
380 }
381
382 pub fn into_bytes(self) -> Bytes {
385 match self {
386 Self::Null => NULL.into(),
387 Self::Boolean(false) => FALSE.into(),
388 Self::Boolean(true) => TRUE.into(),
389 Self::Integer(i) => i.to_string().into(),
390 Self::Float(f) => f.to_string().into(),
391 Self::String(s) => s.into(),
392 Self::Bytes(bytes) => bytes,
393 Self::Array(_) | Self::Object(_) => self.to_string().into(),
395 }
396 }
397
398 pub fn from_json(json: serde_json::Value) -> Self {
401 serde_json::from_value(json).unwrap()
402 }
403}
404
405impl From<&Literal> for Value {
406 fn from(literal: &Literal) -> Self {
407 match literal {
408 Literal::Null => Value::Null,
409 Literal::Boolean(b) => Value::Boolean(*b),
410 Literal::Integer(i) => Value::Integer(*i),
411 Literal::Float(f) => Value::Float(*f),
412 Literal::String(s) => Value::String(s.clone()),
413 Literal::Bytes(bytes) => Value::Bytes(bytes.clone()),
414 }
415 }
416}
417
418impl From<&str> for Value {
419 fn from(value: &str) -> Self {
420 Self::String(value.into())
421 }
422}
423
424impl<T> From<Vec<T>> for Value
425where
426 Value: From<T>,
427{
428 fn from(value: Vec<T>) -> Self {
429 Self::Array(value.into_iter().map(Self::from).collect())
430 }
431}
432
433impl<K, V> From<Vec<(K, V)>> for Value
434where
435 String: From<K>,
436 Value: From<V>,
437{
438 fn from(value: Vec<(K, V)>) -> Self {
439 Self::Object(
440 value
441 .into_iter()
442 .map(|(key, value)| (key.into(), value.into()))
443 .collect(),
444 )
445 }
446}
447
448#[derive(Debug)]
451pub enum RenderedChunk {
452 Raw(Arc<str>),
456 Rendered(Value),
458 Error(RenderError),
460}
461
462#[cfg(test)]
463impl PartialEq for RenderedChunk {
464 fn eq(&self, other: &Self) -> bool {
465 match (self, other) {
466 (Self::Raw(raw1), Self::Raw(raw2)) => raw1 == raw2,
467 (Self::Rendered(value1), Self::Rendered(value2)) => {
468 value1 == value2
469 }
470 (Self::Error(error1), Self::Error(error2)) => {
471 error1.to_string() == error2.to_string()
474 }
475 _ => false,
476 }
477 }
478}
479
480#[derive(Debug)]
487pub struct Arguments<'ctx, Ctx> {
488 context: &'ctx Ctx,
491 position: VecDeque<Value>,
495 num_popped: usize,
498 keyword: IndexMap<String, Value>,
502}
503
504impl<'ctx, Ctx> Arguments<'ctx, Ctx> {
505 pub fn new(
506 context: &'ctx Ctx,
507 position: VecDeque<Value>,
508 keyword: IndexMap<String, Value>,
509 ) -> Self {
510 Self {
511 context,
512 position,
513 num_popped: 0,
514 keyword,
515 }
516 }
517
518 pub fn context(&self) -> &'ctx Ctx {
520 self.context
521 }
522
523 pub fn pop_position<T: TryFromValue>(&mut self) -> Result<T, RenderError> {
527 let value = self
528 .position
529 .pop_front()
530 .ok_or(RenderError::TooFewArguments)?;
531 let arg_index = self.num_popped;
532 self.num_popped += 1;
533 T::try_from_value(value).map_err(|error| {
534 RenderError::Value(error.error).context(
535 RenderErrorContext::ArgumentConvert {
536 argument: arg_index.to_string(),
537 value: error.value,
538 },
539 )
540 })
541 }
542
543 pub fn pop_keyword<T: Default + TryFromValue>(
547 &mut self,
548 name: &str,
549 ) -> Result<T, RenderError> {
550 match self.keyword.shift_remove(name) {
551 Some(value) => T::try_from_value(value).map_err(|error| {
552 RenderError::Value(error.error).context(
553 RenderErrorContext::ArgumentConvert {
554 argument: name.to_owned(),
555 value: error.value,
556 },
557 )
558 }),
559 None => Ok(T::default()),
561 }
562 }
563
564 pub fn ensure_consumed(self) -> Result<(), RenderError> {
568 if self.position.is_empty() && self.keyword.is_empty() {
569 Ok(())
570 } else {
571 Err(RenderError::TooManyArguments {
572 position: self.position.into(),
573 keyword: self.keyword,
574 })
575 }
576 }
577}
578
579pub trait TryFromValue: Sized {
584 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>>;
585}
586
587impl TryFromValue for Value {
588 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
589 Ok(value)
590 }
591}
592
593impl TryFromValue for bool {
594 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
595 Ok(value.to_bool())
596 }
597}
598
599impl TryFromValue for f64 {
600 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
601 match value {
602 Value::Float(f) => Ok(f),
603 _ => Err(WithValue::new(
604 value,
605 ValueError::Type {
606 expected: Expected::Float,
607 },
608 )),
609 }
610 }
611}
612
613impl TryFromValue for i64 {
614 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
615 match value {
616 Value::Integer(i) => Ok(i),
617 _ => Err(WithValue::new(
618 value,
619 ValueError::Type {
620 expected: Expected::Integer,
621 },
622 )),
623 }
624 }
625}
626
627impl TryFromValue for String {
628 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
629 value.try_into_string()
631 }
632}
633
634impl TryFromValue for Bytes {
635 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
636 Ok(value.into_bytes())
637 }
638}
639
640impl<T> TryFromValue for Option<T>
641where
642 T: TryFromValue,
643{
644 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
645 if let Value::Null = value {
646 Ok(None)
647 } else {
648 T::try_from_value(value).map(Some)
649 }
650 }
651}
652
653impl<T> TryFromValue for Vec<T>
655where
656 T: TryFromValue,
657{
658 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
659 if let Value::Array(array) = value {
660 array.into_iter().map(T::try_from_value).collect()
661 } else {
662 Err(WithValue::new(
663 value,
664 ValueError::Type {
665 expected: Expected::Array,
666 },
667 ))
668 }
669 }
670}
671
672impl From<serde_json::Value> for Value {
673 fn from(value: serde_json::Value) -> Self {
674 Self::from_json(value)
675 }
676}
677
678impl TryFromValue for serde_json::Value {
683 fn try_from_value(value: Value) -> Result<Self, WithValue<ValueError>> {
684 match value {
685 Value::Null => Ok(serde_json::Value::Null),
686 Value::Boolean(b) => Ok(b.into()),
687 Value::Integer(i) => Ok(i.into()),
688 Value::Float(f) => Ok(f.into()),
689 Value::String(s) => Ok(s.into()),
690 Value::Array(array) => array
691 .into_iter()
692 .map(serde_json::Value::try_from_value)
693 .collect(),
694 Value::Object(map) => map
695 .into_iter()
696 .map(|(k, v)| Ok((k, serde_json::Value::try_from_value(v)?)))
697 .collect(),
698 Value::Bytes(_) => {
699 value.try_into_string().map(serde_json::Value::String)
702 }
703 }
704 }
705}
706
707#[macro_export]
712macro_rules! impl_try_from_value_str {
713 ($type:ty) => {
714 impl TryFromValue for $type {
715 fn try_from_value(
716 value: $crate::Value,
717 ) -> Result<Self, $crate::WithValue<$crate::ValueError>> {
718 let s = String::try_from_value(value)?;
719 s.parse().map_err(|error| {
720 $crate::WithValue::new(
721 s.into(),
722 $crate::ValueError::other(error),
723 )
724 })
725 }
726 }
727 };
728}
729
730pub trait FunctionOutput {
734 fn into_result(self) -> Result<Value, RenderError>;
735}
736
737impl<T> FunctionOutput for T
738where
739 Value: From<T>,
740{
741 fn into_result(self) -> Result<Value, RenderError> {
742 Ok(self.into())
743 }
744}
745
746impl<T, E> FunctionOutput for Result<T, E>
747where
748 T: Into<Value> + Send + Sync,
749 E: Into<RenderError> + Send + Sync,
750{
751 fn into_result(self) -> Result<Value, RenderError> {
752 self.map(T::into).map_err(E::into)
753 }
754}
755
756impl<T: FunctionOutput> FunctionOutput for Option<T> {
757 fn into_result(self) -> Result<Value, RenderError> {
758 self.map(T::into_result).unwrap_or(Ok(Value::Null))
759 }
760}
761
762fn chunks_to_bytes(chunks: Vec<RenderedChunk>) -> Result<Bytes, RenderError> {
765 let capacity = chunks
767 .iter()
768 .map(|chunk| match chunk {
769 RenderedChunk::Raw(s) => s.len(),
770 RenderedChunk::Rendered(Value::Bytes(bytes)) => bytes.len(),
771 RenderedChunk::Rendered(Value::String(s)) => s.len(),
772 RenderedChunk::Rendered(_) => 5,
774 RenderedChunk::Error(_) => 0,
775 })
776 .sum();
777 chunks
778 .into_iter()
779 .try_fold(BytesMut::with_capacity(capacity), |mut acc, chunk| {
780 match chunk {
781 RenderedChunk::Raw(s) => acc.extend(s.as_bytes()),
782 RenderedChunk::Rendered(value) => {
783 acc.extend(value.into_bytes());
784 }
785 RenderedChunk::Error(error) => return Err(error),
786 }
787 Ok(acc)
788 })
789 .map(Bytes::from)
790}
791
792#[cfg(test)]
793mod tests {
794 use super::*;
795 use indexmap::indexmap;
796 use rstest::rstest;
797 use slumber_util::assert_err;
798
799 #[rstest]
801 #[case::object(
802 "{{ {'a': 1, 1: 2, ['a',1]: ['b',2]} }}",
803 vec![
804 ("a", Value::from(1)),
805 ("1", 2.into()),
806 ("['a', 1]", vec![Value::from("b"), 2.into()].into()),
808 ].into(),
809 )]
810 #[case::object_dupe_key(
811 "{{ {'Mike': 1, name: 2, 10: 3, '10': 4} }}",
813 vec![("Mike", 2), ("10", 4)].into(),
814 )]
815 #[tokio::test]
816 async fn test_expression(
817 #[case] template: Template,
818 #[case] expected: Value,
819 ) {
820 assert_eq!(
821 template.render_value(&TestContext).await.unwrap(),
822 expected
823 );
824 }
825
826 #[rstest]
830 #[case::unpack("{{ array }}", vec!["a", "b", "c"].into())]
831 #[case::string("my name is {{ name }}", "my name is Mike".into())]
832 #[case::bytes(
833 "my name is {{ invalid_utf8 }}",
834 Value::Bytes(b"my name is \xc3\x28".as_slice().into(),
835 ))]
836 #[tokio::test]
837 async fn test_render_value(
838 #[case] template: Template,
839 #[case] expected: Value,
840 ) {
841 assert_eq!(
842 template.render_value(&TestContext).await.unwrap(),
843 expected
844 );
845 }
846
847 #[rstest]
849 #[case::null(serde_json::Value::Null, Value::Null)]
850 #[case::bool_true(serde_json::Value::Bool(true), Value::Boolean(true))]
851 #[case::bool_false(serde_json::Value::Bool(false), Value::Boolean(false))]
852 #[case::number_positive_int(serde_json::json!(42), Value::Integer(42))]
853 #[case::number_negative_int(serde_json::json!(-17), Value::Integer(-17))]
854 #[case::number_zero(serde_json::json!(0), Value::Integer(0))]
855 #[case::number_float(serde_json::json!(1.23), Value::Float(1.23))]
856 #[case::number_negative_float(serde_json::json!(-2.5), Value::Float(-2.5))]
857 #[case::number_zero_float(serde_json::json!(0.0), Value::Float(0.0))]
858 #[case::string_empty(serde_json::json!(""), "".into())]
859 #[case::string_simple(serde_json::json!("hello"), "hello".into())]
860 #[case::string_with_spaces(serde_json::json!("hello world"), "hello world".into())]
861 #[case::string_with_unicode(serde_json::json!("héllo 🌍"), "héllo 🌍".into())]
862 #[case::string_with_escapes(serde_json::json!("line1\nline2\ttab"), "line1\nline2\ttab".into())]
863 #[case::array(
864 serde_json::json!([null, true, 42, "hello"]),
865 Value::Array(vec![
866 Value::Null,
867 Value::Boolean(true),
868 Value::Integer(42),
869 "hello".into(),
870 ])
871 )]
872 #[case::array_numbers(serde_json::json!([1, 2, 3]), vec![1, 2, 3].into())]
874 #[case::array_nested(
875 serde_json::json!([[1, 2], [3, 4]]),
876 vec![Value::from(vec![1, 2]), Value::from(vec![3, 4])].into()
877 )]
878 #[case::object(
879 serde_json::json!({"name": "John", "age": 30, "active": true}),
880 Value::Object(indexmap! {
881 "name".into() => "John".into(),
882 "age".into() => Value::Integer(30),
883 "active".into() => Value::Boolean(true),
884 })
885 )]
886 #[case::object_nested(
887 serde_json::json!({"user": {"name": "Alice", "scores": [95, 87]}}),
888 Value::Object(indexmap! {
889 "user".into() => Value::Object(indexmap! {
890 "name".into() => "Alice".into(),
891 "scores".into() =>
892 Value::Array(vec![Value::Integer(95), Value::Integer(87)]),
893 })
894 })
895 )]
896 fn test_from_json(
897 #[case] json: serde_json::Value,
898 #[case] expected: Value,
899 ) {
900 let actual = Value::from_json(json);
901 assert_eq!(actual, expected);
902 }
903
904 #[rstest]
905 #[case::one_arg("{{ 1 | identity() }}", "1")]
906 #[case::multiple_args("{{ 'cd' | concat('ab') }}", "abcd")]
907 #[case::kwargs("{{ 'cd' | concat('ab', reverse=true) }}", "dcba")]
909 #[tokio::test]
910 async fn test_pipe(#[case] template: Template, #[case] expected: &str) {
911 assert_eq!(
912 template.render_string(&TestContext).await.unwrap(),
913 expected
914 );
915 }
916
917 #[rstest]
919 #[case::unknown_function("{{ fake() }}", "fake(): Unknown function")]
920 #[case::extra_arg(
921 "{{ identity('a', 'b') }}",
922 "identity(): Extra arguments 'b'"
923 )]
924 #[case::missing_arg("{{ add(1) }}", "add(): Not enough arguments")]
925 #[case::arg_render(
926 "{{ add(f(), 2) }}",
928 "add(): argument 0=f(): f(): Unknown function"
929 )]
930 #[case::arg_convert(
931 "{{ add(1, 'b') }}",
933 "add(): argument 1='b': Expected integer"
934 )]
935 #[tokio::test]
936 async fn test_function_error(
937 #[case] template: Template,
938 #[case] expected_error: &str,
939 ) {
940 assert_err!(
941 template
943 .render_string(&TestContext)
944 .await
945 .map_err(anyhow::Error::from),
946 expected_error
947 );
948 }
949
950 struct TestContext;
951
952 impl Context for TestContext {
953 async fn get(
954 &self,
955 identifier: &Identifier,
956 ) -> Result<Value, RenderError> {
957 match identifier.as_str() {
958 "name" => Ok("Mike".into()),
959 "array" => Ok(vec!["a", "b", "c"].into()),
960 "invalid_utf8" => {
961 Ok(Value::Bytes(b"\xc3\x28".as_slice().into()))
962 }
963 _ => Err(RenderError::FieldUnknown {
964 field: identifier.clone(),
965 }),
966 }
967 }
968
969 async fn call(
970 &self,
971 function_name: &Identifier,
972 mut arguments: Arguments<'_, Self>,
973 ) -> Result<Value, RenderError> {
974 match function_name.as_str() {
975 "identity" => {
976 let value: Value = arguments.pop_position()?;
977 arguments.ensure_consumed()?;
978 Ok(value)
979 }
980 "add" => {
981 let a: i64 = arguments.pop_position()?;
982 let b: i64 = arguments.pop_position()?;
983 arguments.ensure_consumed()?;
984 Ok((a + b).into())
985 }
986 "concat" => {
987 let mut a: String = arguments.pop_position()?;
988 let b: String = arguments.pop_position()?;
989 let reverse: bool = arguments.pop_keyword("reverse")?;
990 arguments.ensure_consumed()?;
991 a.push_str(&b);
992 if reverse {
993 Ok(a.chars().rev().collect::<String>().into())
994 } else {
995 Ok(a.into())
996 }
997 }
998 _ => Err(RenderError::FunctionUnknown),
999 }
1000 }
1001 }
1002}