Skip to content

Commit

Permalink
Revert "Optionally Send+Sync Context (Keats#688)"
Browse files Browse the repository at this point in the history
This reverts commit 7e5daf5.
  • Loading branch information
Keats committed Nov 3, 2021
1 parent a6bcf2d commit fbf5a36
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 184 deletions.
15 changes: 0 additions & 15 deletions src/builtins/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,6 @@ use serde_json::value::{from_value, to_value, Value};

use crate::errors::{Error, Result};

/// Abstraction over both Function traits
pub trait FunctionGeneral {
fn call(&self, args: &HashMap<String, Value>) -> Result<Value>;

/// Whether the current function's output should be treated as safe, defaults to `false`
fn is_safe(&self) -> bool;
}

/// Sealed marker trait for allowing varying constrains on other traits for a `Context`.
pub trait ContextSafety: private::Sealed + FunctionGeneral + Clone {}

pub(crate) mod private {
pub trait Sealed {}
}

/// The context-local function type definition
pub trait FunctionRelaxed {
/// The context-local function type definition
Expand Down
166 changes: 47 additions & 119 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,25 @@
use std::collections::HashMap;
use std::io::Write;
use std::ops::Deref;
use std::{collections::BTreeMap, iter};

use serde::ser::Serialize;
use serde_json::value::{to_value, Map, Value};

use crate::builtins::functions::private::Sealed;
use crate::builtins::functions::{ContextSafety, FunctionGeneral};
use crate::errors::{Error, Result as TeraResult};
use crate::{Function, FunctionRelaxed};
use crate::FunctionRelaxed;
use std::sync::Arc;

/// Expressive name for the `ContextSafety` implying `Send` and `Sync`.
pub type CtxThreadSafe = Arc<dyn Function>;

/// Expressive name for the `ContextSafety` implying only thread-local closures.
pub type CtxThreadLocal = Arc<dyn FunctionRelaxed>;

impl Sealed for CtxThreadSafe {}

impl ContextSafety for CtxThreadSafe {}

impl FunctionGeneral for CtxThreadSafe {
fn call(&self, args: &HashMap<String, Value>) -> TeraResult<Value> {
self.deref().call(args)
}

fn is_safe(&self) -> bool {
self.deref().is_safe()
}
}

impl Sealed for CtxThreadLocal {}

impl ContextSafety for CtxThreadLocal {}

impl FunctionGeneral for CtxThreadLocal {
fn call(&self, args: &HashMap<String, Value>) -> TeraResult<Value> {
self.deref().call(args)
}

fn is_safe(&self) -> bool {
self.deref().is_safe()
}
}

/// The struct that holds the context of a template rendering.
///
/// Light wrapper around a `BTreeMap` for easier insertions of Serializable
/// values
///
/// `ContextSafety` will be defined at creation time as either `CtxThreadSafe` or `CtxThreadLocal`,
/// deciding what values `Context`-local functions may capture.
#[derive(Clone)]
pub struct Context<S: ContextSafety> {
pub struct Context {
data: BTreeMap<String, Value>,
/// Ignored by PartialEq!
functions: BTreeMap<String, S>,
functions: BTreeMap<String, Arc<dyn FunctionRelaxed>>,
}

impl<S: ContextSafety> std::fmt::Debug for Context<S> {
impl std::fmt::Debug for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Context")
.field("data", &self.data)
Expand All @@ -69,67 +28,18 @@ impl<S: ContextSafety> std::fmt::Debug for Context<S> {
}
}

impl<S: ContextSafety> PartialEq for Context<S> {
impl PartialEq for Context {
fn eq(&self, other: &Self) -> bool {
self.data.eq(&other.data)
}
}

impl Context<CtxThreadSafe> {
/// Initializes an empty context that is `Send` and `Sync` and can have functions
/// capturing only `Send` and `Sync` values.
impl Context {
/// Initializes an empty context
pub fn new() -> Self {
Context { data: BTreeMap::new(), functions: Default::default() }
}

/// Takes a serde-json `Value` and convert it into a `Context` with no overhead/cloning.
pub fn from_value(obj: Value) -> TeraResult<Self> {
match obj {
Value::Object(m) => {
let mut data = BTreeMap::new();
for (key, value) in m {
data.insert(key, value);
}
Ok(Context { data, functions: Default::default() })
}
_ => Err(Error::msg(
"Creating a Context from a Value/Serialize requires it being a JSON object",
)),
}
}

/// Takes something that impl Serialize and create a context with it.
/// Meant to be used if you have a hashmap or a struct and don't want to insert values
/// one by one in the context.
pub fn from_serialize(value: impl Serialize) -> TeraResult<Self> {
let obj = to_value(value).map_err(Error::json)?;
Context::from_value(obj)
}

/// Registers Context-local function
pub fn register_function<T: Function + 'static, S: Into<String>>(&mut self, key: S, val: T) {
self.functions.insert(key.into(), Arc::new(val));
}
}

impl Context<CtxThreadLocal> {
/// Initializes an empty context that is neither `Send` nor `Sync` but can have functions
/// capturing non-`Send` and non-`Sync` values.
pub fn new_threadlocal() -> Self {
Context { data: BTreeMap::new(), functions: Default::default() }
}

/// Registers Context-local function
pub fn register_function<T: FunctionRelaxed + 'static, S: Into<String>>(
&mut self,
key: S,
val: T,
) {
self.functions.insert(key.into(), Arc::new(val));
}
}

impl<S: ContextSafety> Context<S> {
/// Converts the `val` parameter to `Value` and insert it into the context.
///
/// Panics if the serialization fails.
Expand All @@ -139,7 +49,7 @@ impl<S: ContextSafety> Context<S> {
/// let mut context = tera::Context::new();
/// context.insert("number_users", &42);
/// ```
pub fn insert<T: Serialize + ?Sized, S1: Into<String>>(&mut self, key: S1, val: &T) {
pub fn insert<T: Serialize + ?Sized, S: Into<String>>(&mut self, key: S, val: &T) {
self.data.insert(key.into(), to_value(val).unwrap());
}

Expand All @@ -162,16 +72,25 @@ impl<S: ContextSafety> Context<S> {
/// // Serialization failed
/// }
/// ```
pub fn try_insert<T: Serialize + ?Sized, S1: Into<String>>(
pub fn try_insert<T: Serialize + ?Sized, S: Into<String>>(
&mut self,
key: S1,
key: S,
val: &T,
) -> TeraResult<()> {
self.data.insert(key.into(), to_value(val)?);

Ok(())
}

/// Registers Context-local function
pub fn register_function<T: FunctionRelaxed + 'static, S: Into<String>>(
&mut self,
key: S,
val: T,
) {
self.functions.insert(key.into(), Arc::new(val));
}

/// Appends the data of the `source` parameter to `self`, overwriting existing keys.
/// The source context will be dropped.
///
Expand All @@ -185,7 +104,7 @@ impl<S: ContextSafety> Context<S> {
/// source.insert("d", &4);
/// target.extend(source);
/// ```
pub fn extend<S2: ContextSafety>(&mut self, mut source: Context<S2>) {
pub fn extend(&mut self, mut source: Context) {
self.data.append(&mut source.data);
}

Expand All @@ -198,6 +117,30 @@ impl<S: ContextSafety> Context<S> {
Value::Object(m)
}

/// Takes a serde-json `Value` and convert it into a `Context` with no overhead/cloning.
pub fn from_value(obj: Value) -> TeraResult<Self> {
match obj {
Value::Object(m) => {
let mut data = BTreeMap::new();
for (key, value) in m {
data.insert(key, value);
}
Ok(Context { data, functions: Default::default() })
}
_ => Err(Error::msg(
"Creating a Context from a Value/Serialize requires it being a JSON object",
)),
}
}

/// Takes something that impl Serialize and create a context with it.
/// Meant to be used if you have a hashmap or a struct and don't want to insert values
/// one by one in the context.
pub fn from_serialize(value: impl Serialize) -> TeraResult<Self> {
let obj = to_value(value).map_err(Error::json)?;
Context::from_value(obj)
}

/// Returns the value at a given key index.
pub fn get(&self, index: &str) -> Option<&Value> {
self.data.get(index)
Expand All @@ -215,13 +158,13 @@ impl<S: ContextSafety> Context<S> {

/// Looks up Context-local registered function
#[inline]
pub fn get_function(&self, fn_name: &str) -> Option<&S> {
pub fn get_function(&self, fn_name: &str) -> Option<&Arc<dyn FunctionRelaxed>> {
self.functions.get(fn_name)
}
}

impl Default for Context<CtxThreadSafe> {
fn default() -> Context<CtxThreadSafe> {
impl Default for Context {
fn default() -> Context {
Context::new()
}
}
Expand Down Expand Up @@ -392,19 +335,4 @@ mod tests {
let mut context = Context::new();
assert_eq!(context.remove("unknown"), None);
}

#[test]
fn ensure_trait_bounds_for_context() {
fn takes_syncsend_thing<T>(_thing: T)
where
T: Send + Sync,
{
// nothing, compile-time check
}

takes_syncsend_thing(Context::new());

// this will not compile as CtxThreadLocal does not impl Send + Sync
// takes_syncsend_thing(Context::new_threadlocal());
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub use crate::builtins::functions::{
Function, FunctionRelaxed, FunctionRelaxedSafe, FunctionSafe,
};
pub use crate::builtins::testers::Test;
pub use crate::context::{Context, CtxThreadLocal, CtxThreadSafe};
pub use crate::context::Context;
pub use crate::errors::{Error, ErrorKind, Result};
// Template and get_json_pointer are meant to be used internally only but is exported for test/bench.
#[doc(hidden)]
Expand Down
22 changes: 11 additions & 11 deletions src/renderer/call_stack.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
use std::borrow::Cow;
use std::collections::HashMap;

use crate::builtins::functions::ContextSafety;
use serde_json::{to_value, Value};

use crate::context::get_json_pointer;
use crate::errors::{Error, Result};
use crate::renderer::for_loop::{ForLoop, ForLoopState};
use crate::renderer::stack_frame::{FrameContext, FrameType, StackFrame, Val};
use crate::template::Template;
use crate::Context;
use crate::{Context, FunctionRelaxed};
use std::sync::Arc;

/// Contains the user data and allows no mutation
#[derive(Debug)]
pub struct UserContext<'a, S: ContextSafety> {
pub struct UserContext<'a> {
/// Read-only context
inner: &'a Context<S>,
inner: &'a Context,
}

impl<'a, S: ContextSafety> UserContext<'a, S> {
impl<'a> UserContext<'a> {
/// Create an immutable user context to be used in the call stack
pub fn new(context: &'a Context<S>) -> Self {
pub fn new(context: &'a Context) -> Self {
UserContext { inner: context }
}

Expand All @@ -38,16 +38,16 @@ impl<'a, S: ContextSafety> UserContext<'a, S> {

/// Contains the stack of frames
#[derive(Debug)]
pub struct CallStack<'a, S: ContextSafety> {
pub struct CallStack<'a> {
/// The stack of frames
stack: Vec<StackFrame<'a>>,
/// User supplied context for the render
context: UserContext<'a, S>,
context: UserContext<'a>,
}

impl<'a, S: ContextSafety> CallStack<'a, S> {
impl<'a> CallStack<'a> {
/// Create the initial call stack
pub fn new(context: &'a Context<S>, template: &'a Template) -> CallStack<'a, S> {
pub fn new(context: &'a Context, template: &'a Template) -> CallStack<'a> {
CallStack {
stack: vec![StackFrame::new(FrameType::Origin, "ORIGIN", template)],
context: UserContext::new(context),
Expand Down Expand Up @@ -132,7 +132,7 @@ impl<'a, S: ContextSafety> CallStack<'a, S> {
None
}

pub fn lookup_function(&self, fn_name: &str) -> Option<&S> {
pub fn lookup_function(&self, fn_name: &str) -> Option<&Arc<dyn FunctionRelaxed>> {
self.context.inner.get_function(fn_name)
}

Expand Down
9 changes: 4 additions & 5 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ mod macros;
mod processor;
mod stack_frame;

use crate::builtins::functions::ContextSafety;
use std::io::Write;

use self::processor::Processor;
Expand All @@ -20,21 +19,21 @@ use crate::Context;

/// Given a `Tera` and reference to `Template` and a `Context`, renders text
#[derive(Debug)]
pub struct Renderer<'a, S: ContextSafety> {
pub struct Renderer<'a> {
/// Template to render
template: &'a Template,
/// Houses other templates, filters, global functions, etc
tera: &'a Tera,
/// Read-only context to be bound to template˝
context: &'a Context<S>,
context: &'a Context,
/// If set rendering should be escaped
should_escape: bool,
}

impl<'a, S: ContextSafety> Renderer<'a, S> {
impl<'a> Renderer<'a> {
/// Create a new `Renderer`
#[inline]
pub fn new(template: &'a Template, tera: &'a Tera, context: &'a Context<S>) -> Renderer<'a, S> {
pub fn new(template: &'a Template, tera: &'a Tera, context: &'a Context) -> Renderer<'a> {
let should_escape = tera.autoescape_suffixes.iter().any(|ext| {
// We prefer a `path` if set, otherwise use the `name`
if let Some(ref p) = template.path {
Expand Down
Loading

0 comments on commit fbf5a36

Please sign in to comment.