diff --git a/minijinja/src/environment.rs b/minijinja/src/environment.rs index 5ca3570c..9bb2377a 100644 --- a/minijinja/src/environment.rs +++ b/minijinja/src/environment.rs @@ -9,7 +9,7 @@ use crate::error::Error; use crate::instructions::Instructions; use crate::parser::{parse, parse_expr}; use crate::utils::{AutoEscape, BTreeMapKeysDebug, HtmlEscape}; -use crate::value::{ArgType, FunctionArgs, Value}; +use crate::value::{ArgType, FunctionArgs, FunctionResult, Value}; use crate::vm::Vm; use crate::{filters, functions, tests}; @@ -534,12 +534,14 @@ impl<'source> Environment<'source> { /// Adds a new filter function. /// - /// For details about filters have a look at [`filters`]. + /// Filter functions are functions that can be applied to values in + /// templates. For details about filters have a look at + /// [`Filter`](crate::filters::Filter). pub fn add_filter(&mut self, name: &'source str, f: F) where - V: for<'a> ArgType<'a>, - Rv: Into, F: filters::Filter, + V: for<'a> ArgType<'a>, + Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { self.filters.insert(name, filters::BoxedFilter::new(f)); @@ -552,11 +554,14 @@ impl<'source> Environment<'source> { /// Adds a new test function. /// - /// For details about tests have a look at [`tests`]. - pub fn add_test(&mut self, name: &'source str, f: F) + /// Test functions are similar to filters but perform a check on a value + /// where the return value is always true or false. For details about tests + /// have a look at [`Test`](crate::tests::Test). + pub fn add_test(&mut self, name: &'source str, f: F) where V: for<'a> ArgType<'a>, - F: tests::Test, + Rv: tests::TestResult, + F: tests::Test, Args: for<'a> FunctionArgs<'a>, { self.tests.insert(name, tests::BoxedTest::new(f)); @@ -573,7 +578,7 @@ impl<'source> Environment<'source> { /// functions and other global variables share the same namespace. pub fn add_function(&mut self, name: &'source str, f: F) where - Rv: Into, + Rv: FunctionResult, F: functions::Function, Args: for<'a> FunctionArgs<'a>, { diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs index aa1de55d..6cd133be 100644 --- a/minijinja/src/filters.rs +++ b/minijinja/src/filters.rs @@ -28,27 +28,38 @@ //! //! # Custom Filters //! -//! A custom filter is just a simple function which accepts inputs as parameters and then -//! returns a new value. For instance the following shows a filter which takes an input -//! value and replaces whitespace with dashes and converts it to lowercase: +//! A custom filter is just a simple function which accepts [`State`] and inputs +//! as parameters and then returns a new value. For instance the following +//! shows a filter which takes an input value and replaces whitespace with +//! dashes and converts it to lowercase: //! //! ``` -//! # use minijinja::{Environment, State, Error}; +//! # use minijinja::Environment; //! # let mut env = Environment::new(); -//! fn slugify(_state: &State, value: String) -> Result { -//! Ok(value.to_lowercase().split_whitespace().collect::>().join("-")) +//! use minijinja::State; +//! +//! fn slugify(_state: &State, value: String) -> String { +//! value.to_lowercase().split_whitespace().collect::>().join("-") //! } //! //! env.add_filter("slugify", slugify); //! ``` //! -//! MiniJinja will perform the necessary conversions automatically via the -//! [`FunctionArgs`](crate::value::FunctionArgs) and [`Into`] traits. +//! MiniJinja will perform the necessary conversions automatically. For more +//! information see the [`Filter`] trait. +//! +//! # Built-in Filters +//! +//! When the `builtins` feature is enabled a range of built-in filters are +//! automatically added to the environment. These are also all provided in +//! this module. Note though that these functions are not to be +//! called from Rust code as their exact interface (arguments and return types) +//! might change from one MiniJinja version to another. use std::collections::BTreeMap; use std::sync::Arc; use crate::error::Error; -use crate::value::{ArgType, FunctionArgs, Value}; +use crate::value::{ArgType, FunctionArgs, FunctionResult, Value}; use crate::vm::State; use crate::AutoEscape; @@ -58,19 +69,52 @@ type FilterFunc = dyn Fn(&State, &Value, &[Value]) -> Result + Syn pub(crate) struct BoxedFilter(Arc); /// A utility trait that represents filters. +/// +/// This trait is used by the [`add_filter`](crate::Environment::add_filter) method to abstract over +/// different types of functions that implement filters. Filters are functions +/// which at the very least accept the [`State`] by reference as first parameter +/// and the value that that the filter is applied to as second. Additionally up to +/// 4 further parameters are supported. +/// +/// A filter can return any of the following types: +/// +/// * `Rv` where `Rv` implements `Into` +/// * `Result` where `Rv` implements `Into` +/// +/// Filters accept one mandatory parameter which is the value the filter is +/// applied to and up to 4 extra parameters. The extra parameters can be +/// marked optional by using `Option`. All types are supported for which +/// [`ArgType`] is implemented. +/// +/// ``` +/// # use minijinja::Environment; +/// # let mut env = Environment::new(); +/// use minijinja::State; +/// +/// fn slugify(_state: &State, value: String) -> String { +/// value.to_lowercase().split_whitespace().collect::>().join("-") +/// } +/// +/// env.add_filter("slugify", slugify); +/// ``` +/// +/// For a list of built-in filters see [`filters`](crate::filters). pub trait Filter: Send + Sync + 'static { /// Applies a filter to value with the given arguments. #[doc(hidden)] - fn apply_to(&self, state: &State, value: V, args: Args) -> Result; + fn apply_to(&self, state: &State, value: V, args: Args) -> Rv; } macro_rules! tuple_impls { ( $( $name:ident )* ) => { impl Filter for Func where - Func: Fn(&State, V, $($name),*) -> Result + Send + Sync + 'static + Func: Fn(&State, V, $($name),*) -> Rv + Send + Sync + 'static, + V: for<'a> ArgType<'a>, + Rv: FunctionResult, + $($name: for<'a> ArgType<'a>),* { - fn apply_to(&self, state: &State, value: V, args: ($($name,)*)) -> Result { + fn apply_to(&self, state: &State, value: V, args: ($($name,)*)) -> Rv { #[allow(non_snake_case)] let ($($name,)*) = args; (self)(state, value, $($name,)*) @@ -91,7 +135,7 @@ impl BoxedFilter { where F: Filter, V: for<'a> ArgType<'a>, - Rv: Into, + Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { BoxedFilter(Arc::new( @@ -101,7 +145,7 @@ impl BoxedFilter { ArgType::from_value(Some(value))?, FunctionArgs::from_values(args)?, ) - .map(Into::into) + .into_result() }, )) } @@ -155,9 +199,9 @@ pub(crate) fn get_builtin_filters() -> BTreeMap<&'static str, BoxedFilter> { /// Marks a value as safe. This converts it into a string. /// /// When a value is marked as safe, no further auto escaping will take place. -pub fn safe(_state: &State, v: String) -> Result { +pub fn safe(_state: &State, v: String) -> Value { // TODO: this ideally understands which type of escaping is in use - Ok(Value::from_safe_string(v)) + Value::from_safe_string(v) } /// Escapes a string. By default to HTML. @@ -205,8 +249,8 @@ mod builtins { ///

{{ chapter.title|upper }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn upper(_state: &State, v: Value) -> Result { - Ok(v.to_cowstr().to_uppercase()) + pub fn upper(_state: &State, v: Value) -> String { + v.to_cowstr().to_uppercase() } /// Converts a value to lowercase. @@ -215,8 +259,8 @@ mod builtins { ///

{{ chapter.title|lower }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn lower(_state: &State, v: Value) -> Result { - Ok(v.to_cowstr().to_lowercase()) + pub fn lower(_state: &State, v: Value) -> String { + v.to_cowstr().to_lowercase() } /// Converts a value to title case. @@ -225,7 +269,7 @@ mod builtins { ///

{{ chapter.title|title }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn title(_state: &State, v: Value) -> Result { + pub fn title(_state: &State, v: Value) -> String { let mut rv = String::new(); let mut capitalize = true; for c in v.to_cowstr().chars() { @@ -239,7 +283,7 @@ mod builtins { write!(rv, "{}", c.to_lowercase()).unwrap(); } } - Ok(rv) + rv } /// Does a string replace. @@ -251,9 +295,9 @@ mod builtins { /// -> Goodbye World /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn replace(_state: &State, v: Value, from: Value, to: Value) -> Result { - Ok(v.to_cowstr() - .replace(&from.to_cowstr() as &str, &to.to_cowstr() as &str)) + pub fn replace(_state: &State, v: Value, from: Value, to: Value) -> String { + v.to_cowstr() + .replace(&from.to_cowstr() as &str, &to.to_cowstr() as &str) } /// Returns the "length" of the value @@ -264,8 +308,8 @@ mod builtins { ///

Search results: {{ results|length }} /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn length(_state: &State, v: Value) -> Result { - v.len().map(Value::from).ok_or_else(|| { + pub fn length(_state: &State, v: Value) -> Result { + v.len().ok_or_else(|| { Error::new( ErrorKind::ImpossibleOperation, format!("cannot calculate length of value of type {}", v.kind()), @@ -317,7 +361,7 @@ mod builtins { pub fn items(_state: &State, v: Value) -> Result { Ok(Value::from( match v.0 { - ValueRepr::Map(ref v) => v.iter().collect::>(), + ValueRepr::Map(ref v) => v.iter(), _ => { return Err(Error::new( ErrorKind::ImpossibleOperation, @@ -325,7 +369,6 @@ mod builtins { )) } } - .into_iter() .map(|(k, v)| vec![Value::from(k.clone()), v.clone()]) .collect::>(), )) @@ -356,13 +399,13 @@ mod builtins { /// Trims a value #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn trim(_state: &State, s: Value, chars: Option) -> Result { + pub fn trim(_state: &State, s: Value, chars: Option) -> String { match chars { Some(chars) => { let chars = chars.to_cowstr().chars().collect::>(); - Ok(s.to_cowstr().trim_matches(&chars[..]).to_string()) + s.to_cowstr().trim_matches(&chars[..]).to_string() } - None => Ok(s.to_cowstr().trim().to_string()), + None => s.to_cowstr().trim().to_string(), } } @@ -412,12 +455,12 @@ mod builtins { ///

{{ my_variable|default("my_variable was not defined") }}

/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn default(_: &State, value: Value, other: Option) -> Result { - Ok(if value.is_undefined() { + pub fn default(_: &State, value: Value, other: Option) -> Value { + if value.is_undefined() { other.unwrap_or_else(|| Value::from("")) } else { value - }) + } } /// Returns the absolute value of a number. @@ -547,8 +590,8 @@ mod builtins { /// This behaves the same as the if statement does with regards to /// handling of boolean values. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn bool(_: &State, value: Value) -> Result { - Ok(value.is_true()) + pub fn bool(_: &State, value: Value) -> bool { + value.is_true() } /// Slice an iterable and return a list of lists containing diff --git a/minijinja/src/functions.rs b/minijinja/src/functions.rs index df6bf07a..1f836eaa 100644 --- a/minijinja/src/functions.rs +++ b/minijinja/src/functions.rs @@ -26,8 +26,10 @@ //! render invocation. //! //! ```rust -//! # use minijinja::{Environment, State, Error, ErrorKind}; +//! # use minijinja::Environment; //! # let mut env = Environment::new(); +//! use minijinja::{State, Error, ErrorKind}; +//! //! fn include_file(_state: &State, name: String) -> Result { //! std::fs::read_to_string(&name) //! .map_err(|e| Error::new( @@ -38,12 +40,20 @@ //! //! env.add_function("include_file", include_file); //! ``` +//! +//! # Built-in Functions +//! +//! When the `builtins` feature is enabled a range of built-in functions are +//! automatically added to the environment. These are also all provided in +//! this module. Note though that these functions are not to be +//! called from Rust code as their exact interface (arguments and return types) +//! might change from one MiniJinja version to another. use std::collections::BTreeMap; use std::fmt; use std::sync::Arc; use crate::error::Error; -use crate::value::{FunctionArgs, Object, Value}; +use crate::value::{ArgType, FunctionArgs, FunctionResult, Object, Value}; use crate::vm::State; type FuncFunc = dyn Fn(&State, &[Value]) -> Result + Sync + Send + 'static; @@ -53,19 +63,54 @@ type FuncFunc = dyn Fn(&State, &[Value]) -> Result + Sync + Send + pub(crate) struct BoxedFunction(Arc, &'static str); /// A utility trait that represents global functions. +/// +/// This trait is used by the [`add_functino`](crate::Environment::add_function) +/// method to abstract over different types of functions. +/// +/// Functions which at the very least accept the [`State`] by reference as first +/// parameter and additionally up to 4 further parameters. They share much of +/// their interface with [`filters`](crate::filters). +/// +/// A function can return any of the following types: +/// +/// * `Rv` where `Rv` implements `Into` +/// * `Result` where `Rv` implements `Into` +/// +/// The parameters can be marked optional by using `Option`. All types are +/// supported for which [`ArgType`] is implemented. +/// +/// ```rust +/// # use minijinja::Environment; +/// # let mut env = Environment::new(); +/// use minijinja::{State, Error, ErrorKind}; +/// +/// fn include_file(_state: &State, name: String) -> Result { +/// std::fs::read_to_string(&name) +/// .map_err(|e| Error::new( +/// ErrorKind::ImpossibleOperation, +/// "cannot load file" +/// ).with_source(e)) +/// } +/// +/// env.add_function("include_file", include_file); +/// ``` +/// +/// For a list of built-in functions see [`functions`](crate::functions). pub trait Function: Send + Sync + 'static { /// Calls a function with the given arguments. #[doc(hidden)] - fn invoke(&self, env: &State, args: Args) -> Result; + fn invoke(&self, env: &State, args: Args) -> Rv; } macro_rules! tuple_impls { ( $( $name:ident )* ) => { - impl Function for F + impl Function for Func where - F: Fn(&State, $($name),*) -> Result + Send + Sync + 'static + Func: Fn(&State, $($name),*) -> Rv + Send + Sync + 'static, + Rv: FunctionResult, + $($name: for<'a> ArgType<'a>),* { - fn invoke(&self, state: &State, args: ($($name,)*)) -> Result { + fn invoke(&self, state: &State, args: ($($name,)*)) -> Rv { #[allow(non_snake_case)] let ($($name,)*) = args; (self)(state, $($name,)*) @@ -85,13 +130,13 @@ impl BoxedFunction { pub fn new(f: F) -> BoxedFunction where F: Function, - Rv: Into, + Rv: FunctionResult, Args: for<'a> FunctionArgs<'a>, { BoxedFunction( Arc::new(move |env, args| -> Result { f.invoke(env, FunctionArgs::from_values(args)?) - .map(Into::into) + .into_result() }), std::any::type_name::(), ) @@ -168,21 +213,16 @@ mod builtins { /// /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn range( - _state: &State, - lower: u32, - upper: Option, - step: Option, - ) -> Result, Error> { + pub fn range(_state: &State, lower: u32, upper: Option, step: Option) -> Vec { let rng = match upper { Some(upper) => lower..upper, None => 0..lower, }; - Ok(if let Some(step) = step { + if let Some(step) = step { rng.step_by(step as usize).collect() } else { rng.collect() - }) + } } /// Creates a dictionary. @@ -218,8 +258,8 @@ mod builtins { ///
{{ debug() }}
/// ``` #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn debug(state: &State) -> Result { - Ok(format!("{:#?}", state)) + pub fn debug(state: &State) -> String { + format!("{:#?}", state) } } diff --git a/minijinja/src/tests.rs b/minijinja/src/tests.rs index f0a135da..f71e3649 100644 --- a/minijinja/src/tests.rs +++ b/minijinja/src/tests.rs @@ -1,9 +1,9 @@ //! Test functions and abstractions. //! -//! Test functions in MiniJinja are like (filters)[crate::filters] but a different syntax -//! is used to invoke them and they have to return boolean values. For instance the -//! expression `{% if foo is odd %}` invokes the [`is_odd`] test to check if the value -//! is indeed an odd number. +//! Test functions in MiniJinja are like [`filters`](crate::filters) but a +//! different syntax is used to invoke them and they have to return boolean +//! values. For instance the expression `{% if foo is odd %}` invokes the +//! [`is_odd`] test to check if the value is indeed an odd number. //! //! MiniJinja comes with some built-in test functions that are listed below. To //! create a custom test write a function that takes at least a @@ -28,23 +28,33 @@ //! //! # Custom Tests //! -//! A custom test function is just a simple function which accepts inputs as -//! parameters and then returns a bool wrapped in a result. For instance the -//! following shows a test function which takes an input value and checks if -//! it's lowercase: +//! A custom test function is just a simple function which accepts [`State`] and +//! inputs as parameters and then returns a bool. For instance the following +//! shows a test function which takes an input value and checks if it's +//! lowercase: //! //! ``` -//! # use minijinja::{State, Environment, Error}; +//! # use minijinja::Environment; //! # let mut env = Environment::new(); -//! fn is_lowercase(_state: &State, value: String) -> Result { -//! Ok(value.chars().all(|x| x.is_lowercase())) +//! use minijinja::State; +//! +//! fn is_lowercase(_state: &State, value: String) -> bool { +//! value.chars().all(|x| x.is_lowercase()) //! } //! //! env.add_test("lowercase", is_lowercase); //! ``` //! -//! MiniJinja will perform the necessary conversions automatically via the -//! [`FunctionArgs`](crate::value::FunctionArgs) trait. +//! MiniJinja will perform the necessary conversions automatically. For more +//! information see the [`Test`] trait. +//! +//! # Built-in Tests +//! +//! When the `builtins` feature is enabled a range of built-in tests are +//! automatically added to the environment. These are also all provided in +//! this module. Note though that these functions are not to be +//! called from Rust code as their exact interface (arguments and return types) +//! might change from one MiniJinja version to another. use std::collections::BTreeMap; use std::sync::Arc; @@ -57,20 +67,79 @@ type TestFunc = dyn Fn(&State, &Value, &[Value]) -> Result + Sync + #[derive(Clone)] pub(crate) struct BoxedTest(Arc); -/// A utility trait that represents filters. -pub trait Test: Send + Sync + 'static { +/// A utility trait that represents the return value of filters. +/// +/// It's implemented for the following types: +/// +/// * `bool` +/// * `Result` +/// +/// The equivalent for filters or functions is [`FunctionResult`](crate::value::FunctionResult). +pub trait TestResult { + #[doc(hidden)] + fn into_result(self) -> Result; +} + +impl TestResult for Result { + fn into_result(self) -> Result { + self + } +} + +impl TestResult for bool { + fn into_result(self) -> Result { + Ok(self) + } +} + +/// A utility trait that represents test functions. +/// +/// This trait is used by the [`add_test`](crate::Environment::add_test) method to abstract over +/// different types of functions that implement tests. Tests are similar to +/// [`filters`](crate::filters) but they always return boolean values and use a +/// slightly different syntax to filters. Like filters they accept the [`State`] by +/// reference as first parameter and the value that that the test is applied to as second. +/// Additionally up to 4 further parameters are supported. +/// +/// A test function can return any of the following types: +/// +/// * `bool` +/// * `Result` +/// +/// Tests accept one mandatory parameter which is the value the filter is +/// applied to and up to 4 extra parameters. The extra parameters can be +/// marked optional by using `Option`. All types are supported for which +/// [`ArgType`] is implemented. +/// +/// ``` +/// # use minijinja::Environment; +/// # let mut env = Environment::new(); +/// use minijinja::State; +/// +/// fn is_lowercase(_state: &State, value: String) -> bool { +/// value.chars().all(|x| x.is_lowercase()) +/// } +/// +/// env.add_test("lowercase", is_lowercase); +/// ``` +/// +/// For a list of built-in tests see [`tests`](crate::tests). +pub trait Test: Send + Sync + 'static { /// Performs a test to value with the given arguments. #[doc(hidden)] - fn perform(&self, state: &State, value: V, args: Args) -> Result; + fn perform(&self, state: &State, value: V, args: Args) -> Rv; } macro_rules! tuple_impls { ( $( $name:ident )* ) => { - impl Test for Func + impl Test for Func where - Func: Fn(&State, V, $($name),*) -> Result + Send + Sync + 'static + Func: Fn(&State, V, $($name),*) -> Rv + Send + Sync + 'static, + V: for<'a> ArgType<'a>, + Rv: TestResult, + $($name: for<'a> ArgType<'a>),* { - fn perform(&self, state: &State, value: V, args: ($($name,)*)) -> Result { + fn perform(&self, state: &State, value: V, args: ($($name,)*)) -> Rv { #[allow(non_snake_case)] let ($($name,)*) = args; (self)(state, value, $($name,)*) @@ -87,10 +156,11 @@ tuple_impls! { A B C D } impl BoxedTest { /// Creates a new boxed filter. - pub fn new(f: F) -> BoxedTest + pub fn new(f: F) -> BoxedTest where - F: Test, + F: Test, V: for<'a> ArgType<'a>, + Rv: TestResult, Args: for<'a> FunctionArgs<'a>, { BoxedTest(Arc::new(move |state, value, args| -> Result { @@ -100,6 +170,7 @@ impl BoxedTest { ArgType::from_value(value)?, FunctionArgs::from_values(args)?, ) + .into_result() })) } @@ -138,68 +209,68 @@ mod builtins { /// Checks if a value is odd. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_odd(_state: &State, v: Value) -> Result { - Ok(i128::try_from(v).ok().map_or(false, |x| x % 2 != 0)) + pub fn is_odd(_state: &State, v: Value) -> bool { + i128::try_from(v).ok().map_or(false, |x| x % 2 != 0) } /// Checks if a value is even. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_even(_state: &State, v: Value) -> Result { - Ok(i128::try_from(v).ok().map_or(false, |x| x % 2 == 0)) + pub fn is_even(_state: &State, v: Value) -> bool { + i128::try_from(v).ok().map_or(false, |x| x % 2 == 0) } /// Checks if a value is undefined. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_undefined(_state: &State, v: Value) -> Result { - Ok(v.is_undefined()) + pub fn is_undefined(_state: &State, v: Value) -> bool { + v.is_undefined() } /// Checks if a value is defined. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_defined(_state: &State, v: Value) -> Result { - Ok(!v.is_undefined()) + pub fn is_defined(_state: &State, v: Value) -> bool { + !v.is_undefined() } /// Checks if this value is a number. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_number(_state: &State, v: Value) -> Result { - Ok(matches!(v.kind(), ValueKind::Number)) + pub fn is_number(_state: &State, v: Value) -> bool { + matches!(v.kind(), ValueKind::Number) } /// Checks if this value is a string. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_string(_state: &State, v: Value) -> Result { - Ok(matches!(v.kind(), ValueKind::String)) + pub fn is_string(_state: &State, v: Value) -> bool { + matches!(v.kind(), ValueKind::String) } /// Checks if this value is a sequence #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_sequence(_state: &State, v: Value) -> Result { - Ok(matches!(v.kind(), ValueKind::Seq)) + pub fn is_sequence(_state: &State, v: Value) -> bool { + matches!(v.kind(), ValueKind::Seq) } /// Checks if this value is a mapping #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_mapping(_state: &State, v: Value) -> Result { - Ok(matches!(v.kind(), ValueKind::Map)) + pub fn is_mapping(_state: &State, v: Value) -> bool { + matches!(v.kind(), ValueKind::Map) } /// Checks if the value is starting with a string. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_startingwith(_state: &State, v: String, other: String) -> Result { - Ok(v.starts_with(&other)) + pub fn is_startingwith(_state: &State, v: String, other: String) -> bool { + v.starts_with(&other) } /// Checks if the value is ending with a string. #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))] - pub fn is_endingwith(_state: &State, v: String, other: String) -> Result { - Ok(v.ends_with(&other)) + pub fn is_endingwith(_state: &State, v: String, other: String) -> bool { + v.ends_with(&other) } #[test] fn test_basics() { - fn test(_: &State, a: u32, b: u32) -> Result { - Ok(a == b) + fn test(_: &State, a: u32, b: u32) -> bool { + a == b } let env = crate::Environment::new(); diff --git a/minijinja/src/value/argtypes.rs b/minijinja/src/value/argtypes.rs index d1d65a52..25bb6474 100644 --- a/minijinja/src/value/argtypes.rs +++ b/minijinja/src/value/argtypes.rs @@ -6,16 +6,40 @@ use crate::error::{Error, ErrorKind}; use crate::key::{Key, StaticKey}; use crate::value::{Arc, Value, ValueRepr}; -/// Helper trait representing valid filter and test arguments. +/// A utility trait that represents the return value of functions and filters. +/// +/// It's implemented for the following types: +/// +/// * `Rv` where `Rv` implements `Into` +/// * `Result` where `Rv` implements `Into` +/// +/// The equivalent for test functions is [`TestResult`](crate::tests::TestResult). +pub trait FunctionResult { + #[doc(hidden)] + fn into_result(self) -> Result; +} + +impl> FunctionResult for Result { + fn into_result(self) -> Result { + self.map(Into::into) + } +} + +impl> FunctionResult for I { + fn into_result(self) -> Result { + Ok(self.into()) + } +} + +/// Helper trait representing valid filter, test and function arguments. /// /// Since it's more convenient to write filters and tests with concrete /// types instead of values, this helper trait exists to automatically /// perform this conversion. It is implemented for functions up to an -/// arity of 5 parameters. +/// arity of 4 parameters. /// /// For each argument the conversion is performed via the [`ArgType`] -/// trait which is implemented for some primitive concrete types as well -/// as these types wrapped in [`Option`]. +/// trait which is implemented for many common types. pub trait FunctionArgs<'a>: Sized { /// Converts to function arguments from a slice of values. fn from_values(values: &'a [Value]) -> Result; @@ -23,10 +47,20 @@ pub trait FunctionArgs<'a>: Sized { /// A trait implemented by all filter/test argument types. /// -/// This trait is the companion to [`FunctionArgs`]. It's passed an -/// `Option` where `Some` means the argument was provided or -/// `None` if it was not. This is used to implement optional arguments -/// to functions. +/// This trait is used by [`FunctionArgs`]. It's implemented for many common +/// types that are typically passed to filters, tests or functions. It's +/// implemented for the following types: +/// +/// * unsigned integers: [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] +/// * signed integers: [`i8`], [`i16`], [`i32`], [`i64`], [`i128`] +/// * floats: [`f64`] +/// * bool: [`bool`] +/// * string: [`String`] +/// * values: [`Value`] +/// * vectors: [`Vec`] +/// +/// The type is also implemented for optional values (`Value`) which is used +/// to encode optional parameters to filters, functions or tests. pub trait ArgType<'a>: Sized { #[doc(hidden)] fn from_value(value: Option<&'a Value>) -> Result; @@ -34,7 +68,9 @@ pub trait ArgType<'a>: Sized { macro_rules! tuple_impls { ( $( $name:ident )* ) => { - impl<'a, $($name: ArgType<'a>,)*> FunctionArgs<'a> for ($($name,)*) { + impl<'a, $($name),*> FunctionArgs<'a> for ($($name,)*) + where $($name: ArgType<'a>,)* + { fn from_values(values: &'a [Value]) -> Result { #![allow(non_snake_case, unused)] let arg_count = 0 $( diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs index 10a9cac5..be3bca14 100644 --- a/minijinja/src/value/mod.rs +++ b/minijinja/src/value/mod.rs @@ -83,7 +83,7 @@ use crate::utils::OnDrop; use crate::value::serialize::ValueSerializer; use crate::vm::State; -pub use crate::value::argtypes::{ArgType, FunctionArgs}; +pub use crate::value::argtypes::{ArgType, FunctionArgs, FunctionResult}; pub use crate::value::object::Object; mod argtypes;