pub trait Object: Debug + Send + Sync {
// Provided methods
fn repr(self: &Arc<Self>) -> ObjectRepr { ... }
fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> { ... }
fn enumerate(self: &Arc<Self>) -> Enumerator { ... }
fn enumerator_len(self: &Arc<Self>) -> Option<usize> { ... }
fn is_true(self: &Arc<Self>) -> bool { ... }
fn call(
self: &Arc<Self>,
state: &State<'_, '_>,
args: &[Value],
) -> Result<Value, Error> { ... }
fn call_method(
self: &Arc<Self>,
state: &State<'_, '_>,
method: &str,
args: &[Value],
) -> Result<Value, Error> { ... }
fn render(self: &Arc<Self>, f: &mut Formatter<'_>) -> Result
where Self: Sized + 'static { ... }
}
Expand description
A trait that represents a dynamic object.
There is a type erased wrapper of this trait available called
DynObject
which is what the engine actually holds internally.
§Basic Struct
The following example shows how to implement a dynamic object which
represents a struct. All that’s needed is to implement
get_value
to look up a field by name as well as
enumerate
to return an enumerator over the known keys.
The repr
defaults to Map
so nothing needs to be done here.
use std::sync::Arc;
use minijinja::value::{Value, Object, Enumerator};
#[derive(Debug)]
struct Point(f32, f32, f32);
impl Object for Point {
fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
match key.as_str()? {
"x" => Some(Value::from(self.0)),
"y" => Some(Value::from(self.1)),
"z" => Some(Value::from(self.2)),
_ => None,
}
}
fn enumerate(self: &Arc<Self>) -> Enumerator {
Enumerator::Str(&["x", "y", "z"])
}
}
let value = Value::from_object(Point(1.0, 2.5, 3.0));
§Basic Sequence
The following example shows how to implement a dynamic object which
represents a sequence. All that’s needed is to implement
repr
to indicate that this is a sequence,
get_value
to look up a field by index, and
enumerate
to return a sequential enumerator.
This enumerator will automatically call get_value
from 0..length
.
use std::sync::Arc;
use minijinja::value::{Value, Object, ObjectRepr, Enumerator};
#[derive(Debug)]
struct Point(f32, f32, f32);
impl Object for Point {
fn repr(self: &Arc<Self>) -> ObjectRepr {
ObjectRepr::Seq
}
fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
match key.as_usize()? {
0 => Some(Value::from(self.0)),
1 => Some(Value::from(self.1)),
2 => Some(Value::from(self.2)),
_ => None,
}
}
fn enumerate(self: &Arc<Self>) -> Enumerator {
Enumerator::Seq(3)
}
}
let value = Value::from_object(Point(1.0, 2.5, 3.0));
§Iterables
If you have something that is not quite a sequence but is capable of yielding
values over time, you can directly implement an iterable. This is somewhat
uncommon as you can normally directly use Value::make_iterable
. Here
is how this can be done though:
use std::sync::Arc;
use minijinja::value::{Value, Object, ObjectRepr, Enumerator};
#[derive(Debug)]
struct Range10;
impl Object for Range10 {
fn repr(self: &Arc<Self>) -> ObjectRepr {
ObjectRepr::Iterable
}
fn enumerate(self: &Arc<Self>) -> Enumerator {
Enumerator::Iter(Box::new((1..10).map(Value::from)))
}
}
let value = Value::from_object(Range10);
Iteration is encouraged to fail immediately (object is not iterable) or not at all. However this is not always possible, but the iteration interface itself does not support fallible iteration. It is however possible to accomplish the same thing by creating an invalid value.
§Map As Context
Map can also be used as template rendering context. This has a lot of benefits as it means that the serialization overhead can be largely to completely avoided. This means that even if templates take hundreds of values, MiniJinja does not spend time eagerly converting them into values.
Here is a very basic example of how a template can be rendered with a dynamic
context. Note that the implementation of enumerate
is optional for this to work. It’s in fact not used by the engine during
rendering but it is necessary for the debug()
function to be able to show which values exist in the context.
use std::sync::Arc;
use minijinja::value::{Value, Object};
#[derive(Debug)]
pub struct DynamicContext {
magic: i32,
}
impl Object for DynamicContext {
fn get_value(self: &Arc<Self>, field: &Value) -> Option<Value> {
match field.as_str()? {
"pid" => Some(Value::from(std::process::id())),
"env" => Some(Value::from_iter(std::env::vars())),
"magic" => Some(Value::from(self.magic)),
_ => None,
}
}
}
let tmpl = env.template_from_str("HOME={{ env.HOME }}; PID={{ pid }}; MAGIC={{ magic }}")?;
let ctx = Value::from_object(DynamicContext { magic: 42 });
let rv = tmpl.render(ctx)?;
One thing of note here is that in the above example env
would be re-created every
time the template needs it. A better implementation would cache the value after it
was created first.
Provided Methods§
sourcefn repr(self: &Arc<Self>) -> ObjectRepr
fn repr(self: &Arc<Self>) -> ObjectRepr
Indicates the natural representation of an object.
The default implementation returns ObjectRepr::Map
.
sourcefn get_value(self: &Arc<Self>, key: &Value) -> Option<Value>
fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value>
Given a key, looks up the associated value.
sourcefn enumerate(self: &Arc<Self>) -> Enumerator
fn enumerate(self: &Arc<Self>) -> Enumerator
Enumerates the object.
The engine uses the returned enumerator to implement iteration and
the size information of an object. For more information see
Enumerator
. The default implementation returns Empty
for
all object representations other than ObjectRepr::Plain
which
default to NonEnumerable
.
When wrapping other objects you might want to consider using
ObjectExt::mapped_enumerator
and ObjectExt::mapped_rev_enumerator
.
sourcefn enumerator_len(self: &Arc<Self>) -> Option<usize>
fn enumerator_len(self: &Arc<Self>) -> Option<usize>
Returns the length of the enumerator.
By default the length is taken by calling enumerate
and
inspecting the Enumerator
. This means that in order to determine
the length, an iteration is started. If you this is a problem for your
uses, you can manually implement this. This might for instance be
needed if your type can only be iterated over once.
sourcefn is_true(self: &Arc<Self>) -> bool
fn is_true(self: &Arc<Self>) -> bool
Returns true
if this object is considered true for if conditions.
The default implementation checks if the enumerator_len
is not Some(0)
which is the recommended behavior for objects.
sourcefn call(
self: &Arc<Self>,
state: &State<'_, '_>,
args: &[Value],
) -> Result<Value, Error>
fn call( self: &Arc<Self>, state: &State<'_, '_>, args: &[Value], ) -> Result<Value, Error>
The engine calls this to invoke the object itself.
The default implementation returns an
InvalidOperation
error.
sourcefn call_method(
self: &Arc<Self>,
state: &State<'_, '_>,
method: &str,
args: &[Value],
) -> Result<Value, Error>
fn call_method( self: &Arc<Self>, state: &State<'_, '_>, method: &str, args: &[Value], ) -> Result<Value, Error>
The engine calls this to invoke a method on the object.
The default implementation returns an
UnknownMethod
error. When this error
is returned the engine will invoke the
unknown_method_callback
of
the environment.