minijinja

Module value

Source
Expand description

Provides a dynamic value type abstraction.

This module gives access to a dynamically typed value which is used by the template engine during execution.

For the most part the existence of the value type can be ignored as MiniJinja will perform the necessary conversions for you. For instance if you write a filter that converts a string you can directly declare the filter to take a String. However for some more advanced use cases it’s useful to know that this type exists.

§Basic Value Conversions

Values are typically created via the From trait:

use std::collections::BTreeMap;
let int_value = Value::from(42);
let none_value = Value::from(());
let true_value = Value::from(true);
let map = Value::from({
    let mut m = BTreeMap::new();
    m.insert("foo", 1);
    m.insert("bar", 2);
    m
});

Or via the FromIterator trait which can create sequences or maps. When given a tuple it creates maps, otherwise it makes a sequence.

// collection into a sequence
let value: Value = (1..10).into_iter().collect();

// collection into a map
let value: Value = [("key", "value")].into_iter().collect();

For certain types of iterators (Send + Sync + 'static) it’s also possible to make the value lazily iterate over the value by using the Value::make_iterable function instead. Whenever the value requires iteration, the function is called to create that iterator.

let value: Value = Value::make_iterable(|| 1..10);

To to into the inverse directly the various TryFrom implementations can be used:

use std::convert::TryFrom;
let v = u64::try_from(Value::from(42)).unwrap();

The special Undefined value also exists but does not have a rust equivalent. It can be created via the UNDEFINED constant.

§Collections

The standard library’s collection types such as HashMap, Vec and various others from the collections module are implemented are objects. There is a cavet here which is that maps can only have string or Value as key. The values in the collections are lazily converted into value when accessed or iterated over. These types can be constructed either from Value::from or Value::from_object. Because the types are boxed unchanged, you can also downcast them.

let vec = Value::from(vec![1i32, 2, 3, 4]);
let vec_ref = vec.downcast_object_ref::<Vec<i32>>().unwrap();
assert_eq!(vec_ref, &vec![1, 2, 3, 4]);

Caveat: for convenience reasons maps with &str keys can be stored. The keys however are converted into Arc<str>.

§Serde Conversions

MiniJinja will usually however create values via an indirection via serde when a template is rendered or an expression is evaluated. This can also be triggered manually by using the Value::from_serialize method:

let value = Value::from_serialize(&[1, 2, 3]);

The inverse of that operation is to pass a value directly as serializer to a type that supports deserialization. This requires the deserialization feature.

use serde::Deserialize;
let value = Value::from(vec![1, 2, 3]);
let vec = Vec::<i32>::deserialize(value).unwrap();

§Value Function Arguments

Filters and tests can take values as arguments but optionally also rust types directly. This conversion for function arguments is performed by the FunctionArgs and related traits (ArgType, FunctionResult).

§Memory Management

Values are immutable objects which are internally reference counted which means they can be copied relatively cheaply. Special care must be taken so that cycles are not created to avoid causing memory leaks.

§HTML Escaping

MiniJinja inherits the general desire to be clever about escaping. For this purpose a value will (when auto escaping is enabled) always be escaped. To prevent this behavior the safe filter can be used in the template. Outside of templates the Value::from_safe_string method can be used to achieve the same result.

§Dynamic Objects

Values can also hold “dynamic” objects. These are objects which implement the Object trait. These can be used to implement dynamic functionality such as stateful values and more. Dynamic objects are internally also used to implement the special loop variable, macros and similar things.

To create a Value from a dynamic object use Value::from_object, Value::from_dyn_object:

#[derive(Debug)]
struct Foo;

impl Object for Foo {
    /* implementation */
}

let value = Value::from_object(Foo);
let value = Value::from_dyn_object(Arc::new(Foo));

§Invalid Values

MiniJinja knows the concept of an “invalid value”. These are rare in practice and should not be used, but they are needed in some situations. An invalid value looks like a value but working with that value in the context of the engine will fail in most situations. In principle an invalid value is a value that holds an error internally. It’s created with From:

use minijinja::{Value, Error, ErrorKind};
let error = Error::new(ErrorKind::InvalidOperation, "failed to generate an item");
let invalid_value = Value::from(error);

Invalid values are typically encountered in the following situations:

  • serialization fails with an error: this is the case when a value is crated via Value::from_serialize and the underlying Serialize implementation fails with an error.
  • fallible iteration: there might be situations where an iterator cannot indicate failure ahead of iteration and must abort. In that case the only option an iterator in MiniJinja has is to create an invalid value.

It’s generally recommende to ignore the existence of invalid objects and let them fail naturally as they are encountered.

§Notes on Bytes and Strings

Usually one would pass strings to templates as Jinja is entirely based on string rendering. However there are situations where it can be useful to pass bytes instead. As such MiniJinja allows a value type to carry bytes even though there is no syntax within the template language to create a byte literal.

When rendering bytes as strings, MiniJinja will attempt to interpret them as lossy utf-8. This is a bit different to Jinja2 which in Python 3 stopped rendering byte strings as strings. This is an intentional change that was deemed acceptable given how infrequently bytes are used but how relatively commonly bytes are often holding “almost utf-8” in templates. Most conversions to strings also will do almost the same. The debug rendering of bytes however is different and bytes are not iterable. Like strings however they can be sliced and indexed, but they will be sliced by bytes and not by characters.

Structs§

  • Type-erased version of Object
  • Utility to accept keyword arguments.
  • Utility type to capture remaining arguments.
  • Represents a dynamically typed value in the template engine.
  • Utility to iterate over values.
  • Utility type to deserialize an argument.

Enums§

Traits§

  • A trait implemented by all filter/test argument types.
  • Helper trait representing valid filter, test and function arguments.
  • A utility trait that represents the return value of functions and filters.
  • A trait that represents a dynamic object.
  • Provides utility methods for working with objects.

Functions§