Skip to content

Commit

Permalink
Drop the serde_json dependency
Browse files Browse the repository at this point in the history
This implements an internal `Value` type which functions like
`serde_json::Value`. Note that because this commit changes the `Error`
type, this is an API-breaking change.

Resolves: #1

Signed-off-by: Nathaniel McCallum <[email protected]>
  • Loading branch information
npmccallum committed Aug 12, 2021
1 parent b1436cb commit f9d6e40
Show file tree
Hide file tree
Showing 6 changed files with 569 additions and 66 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ maintenance = { status = "passively-maintained" }

[dependencies]
serde = "1.0"
serde_json = "1.0"

[dev-dependencies]
serde_derive = "1.0"
Expand Down
36 changes: 13 additions & 23 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
//! Module containing the error type returned by TinyTemplate if an error occurs.

use instruction::{path_to_str, PathSlice};
use serde_json::Error as SerdeJsonError;
use serde_json::Value;
use value::Value;

use std::error::Error as StdError;
use std::fmt;

/// An opaque type representing a serialization error.
#[derive(Debug)]
pub struct SerializationError(super::value::Error);

/// Enum representing the potential errors that TinyTemplate can encounter.
#[derive(Debug)]
#[non_exhaustive]
Expand All @@ -20,9 +24,7 @@ pub enum Error {
line: usize,
column: usize,
},
SerdeError {
err: SerdeJsonError,
},
SerializationError(SerializationError),
GenericError {
msg: String,
},
Expand All @@ -42,9 +44,9 @@ pub enum Error {
column: usize,
},
}
impl From<SerdeJsonError> for Error {
fn from(err: SerdeJsonError) -> Error {
Error::SerdeError { err }
impl From<super::value::Error> for Error {
fn from(err: super::value::Error) -> Error {
Error::SerializationError(SerializationError(err))
}
}
impl From<fmt::Error> for Error {
Expand All @@ -67,8 +69,8 @@ impl fmt::Display for Error {
line, column, msg
)
}
Error::SerdeError { err } => {
write!(f, "Unexpected serde error while converting the context to a serde_json::Value. Error: {}", err)
Error::SerializationError(err) => {
write!(f, "Unexpected error during serialization. Error: {}", err.0)
}
Error::GenericError { msg } => {
write!(f, "{}", msg)
Expand Down Expand Up @@ -108,7 +110,7 @@ impl StdError for Error {
match self {
Error::ParseError { .. } => "ParseError",
Error::RenderError { .. } => "RenderError",
Error::SerdeError { .. } => "SerdeError",
Error::SerializationError { .. } => "SerializationError",
Error::GenericError { msg } => msg,
Error::StdFormatError { .. } => "StdFormatError",
Error::CalledTemplateError { .. } => "CalledTemplateError",
Expand Down Expand Up @@ -149,18 +151,6 @@ pub(crate) fn lookup_error(source: &str, step: &str, path: PathSlice, current: &
}
}

pub(crate) fn truthiness_error(source: &str, path: PathSlice) -> Error {
let (line, column) = get_offset(source, path.last().unwrap());
Error::RenderError {
msg: format!(
"Path '{}' produced a value which could not be checked for truthiness.",
path_to_str(path)
),
line,
column,
}
}

pub(crate) fn unprintable_error() -> Error {
Error::GenericError {
msg: "Expected a printable value but found array or object.".to_string(),
Expand Down
33 changes: 21 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
//!

extern crate serde;
extern crate serde_json;

#[cfg(test)]
#[cfg_attr(test, macro_use)]
Expand All @@ -74,13 +73,14 @@ pub mod error;
mod instruction;
pub mod syntax;
mod template;
mod value;

use error::*;
use serde::Serialize;
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Write;
use template::Template;
use value::Value;

/// Type alias for closures which can be used as value formatters.
pub type ValueFormatter = dyn Fn(&Value, &mut String) -> Result<()>;
Expand Down Expand Up @@ -119,20 +119,25 @@ pub fn escape(value: &str, output: &mut String) {
/// Values are formatted as follows:
///
/// * `Value::Null` => the empty string
/// * `Value::Bool` => true|false
/// * `Value::Number` => the number, as formatted by `serde_json`.
/// * `Value::Boolean` => true|false
/// * `Value::Integer` => the integer
/// * `Value::Float` => the float
/// * `Value::String` => the string, HTML-escaped
///
/// Arrays and objects are not formatted, and attempting to do so will result in a rendering error.
pub fn format(value: &Value, output: &mut String) -> Result<()> {
match value {
Value::Null => Ok(()),
Value::Bool(b) => {
Value::Boolean(b) => {
write!(output, "{}", b)?;
Ok(())
}
Value::Number(n) => {
write!(output, "{}", n)?;
Value::Integer(i) => {
write!(output, "{}", i)?;
Ok(())
}
Value::Float(f) => {
write!(output, "{}", f)?;
Ok(())
}
Value::String(s) => {
Expand All @@ -147,12 +152,16 @@ pub fn format(value: &Value, output: &mut String) -> Result<()> {
pub fn format_unescaped(value: &Value, output: &mut String) -> Result<()> {
match value {
Value::Null => Ok(()),
Value::Bool(b) => {
Value::Boolean(b) => {
write!(output, "{}", b)?;
Ok(())
}
Value::Number(n) => {
write!(output, "{}", n)?;
Value::Integer(i) => {
write!(output, "{}", i)?;
Ok(())
}
Value::Float(f) => {
write!(output, "{}", f)?;
Ok(())
}
Value::String(s) => {
Expand Down Expand Up @@ -208,12 +217,12 @@ impl<'template> TinyTemplate<'template> {
}

/// Render the template with the given name using the given context object. The context
/// object must implement `serde::Serialize` as it will be converted to `serde_json::Value`.
/// object must implement `serde::Serialize`.
pub fn render<C>(&self, template: &str, context: &C) -> Result<String>
where
C: Serialize,
{
let value = serde_json::to_value(context)?;
let value = Value::serialize_from(context)?;
match self.templates.get(template) {
Some(tmpl) => tmpl.render(
&value,
Expand Down
4 changes: 2 additions & 2 deletions src/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
//!
//! ### Context Types
//!
//! TinyTemplate uses `serde_json`'s Value structure to represent the context. Therefore, any
//! TinyTemplate uses a internal `Value` structure to represent the context. Therefore, any
//! `Serializable` structure can be used as a context. All values in such structures are mapped to
//! their JSON representations - booleans, numbers, strings, arrays, objects and nulls.
//! representations similar to JSON - booleans, integers, floats, strings, arrays, objects and nulls.
//!
//! ### Values
//!
Expand Down
58 changes: 30 additions & 28 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use compiler::TemplateCompiler;
use error::Error::*;
use error::*;
use instruction::{Instruction, PathSlice, PathStep};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt::Write;
use std::slice;
use value::Value;
use ValueFormatter;

/// Enum defining the different kinds of records on the context stack.
Expand Down Expand Up @@ -64,17 +64,23 @@ impl<'render, 'template> RenderContext<'render, 'template> {
let mut current = object;
for step in path.iter() {
if let PathStep::Index(_, n) = step {
if let Some(next) = current.get(n) {
current = next;
continue;
if let Value::Array(array) = current {
if let Some(next) = array.get(*n) {
current = next;
continue;
}
}
}

let step: &str = &*step;

match current.get(step) {
Some(next) => current = next,
None => return Err(lookup_error(self.original_text, step, path, current)),
match &current {
Value::Object(map) => match map.get(step) {
Some(next) => current = next,
None => return Err(lookup_error(self.original_text, step, path, current)),
},

_ => return Err(lookup_error(self.original_text, step, path, current)),
}
}
Ok(current)
Expand Down Expand Up @@ -222,12 +228,12 @@ impl<'template> Template<'template> {
let (index, length) = render_context.lookup_index()?;
index == (length - 1)
}
"@root" => self.value_is_truthy(render_context.lookup_root()?, path)?,
"@root" => self.value_is_truthy(render_context.lookup_root()?),
other => panic!("Unknown keyword {}", other), // This should have been caught by the parser.
}
} else {
let value_to_render = render_context.lookup(path)?;
self.value_is_truthy(value_to_render, path)?
self.value_is_truthy(value_to_render)
};
if *negate {
truthy = !truthy;
Expand Down Expand Up @@ -325,21 +331,16 @@ impl<'template> Template<'template> {
Ok(())
}

fn value_is_truthy(&self, value: &Value, path: PathSlice) -> Result<bool> {
let truthy = match value {
fn value_is_truthy(&self, value: &Value) -> bool {
match value {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => match n.as_f64() {
Some(float) => float != 0.0,
None => {
return Err(truthiness_error(self.original_text, path));
}
},
Value::Boolean(b) => *b,
Value::Integer(i) => *i != 0,
Value::Float(f) => *f != 0.0,
Value::String(s) => !s.is_empty(),
Value::Array(arr) => !arr.is_empty(),
Value::Object(_) => true,
};
Ok(truthy)
}
}
}

Expand Down Expand Up @@ -382,7 +383,8 @@ mod test {
nested: NestedContext { value: 10 },
escapes: "1:< 2:> 3:& 4:' 5:\"",
};
::serde_json::to_value(&ctx).unwrap()

Value::serialize_from(&ctx).unwrap()
}

fn other_templates() -> HashMap<&'static str, Template<'static>> {
Expand Down Expand Up @@ -807,7 +809,7 @@ mod test {
fn test_root_print() {
let template = compile("{ @root }");
let context = "Hello World!";
let context = ::serde_json::to_value(&context).unwrap();
let context = Value::serialize_from(&context).unwrap();
let template_registry = other_templates();
let formatter_registry = formatters();
let string = template
Expand All @@ -825,7 +827,7 @@ mod test {
fn test_root_branch() {
let template = compile("{{ if @root }}Hello World!{{ endif }}");
let context = true;
let context = ::serde_json::to_value(&context).unwrap();
let context = Value::serialize_from(&context).unwrap();
let template_registry = other_templates();
let formatter_registry = formatters();
let string = template
Expand All @@ -843,7 +845,7 @@ mod test {
fn test_root_iterate() {
let template = compile("{{ for a in @root }}{ a }{{ endfor }}");
let context = vec!["foo", "bar"];
let context = ::serde_json::to_value(&context).unwrap();
let context = Value::serialize_from(&context).unwrap();
let template_registry = other_templates();
let formatter_registry = formatters();
let string = template
Expand All @@ -861,7 +863,7 @@ mod test {
fn test_number_truthiness_zero() {
let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
let context = 0;
let context = ::serde_json::to_value(&context).unwrap();
let context = Value::serialize_from(&context).unwrap();
let template_registry = other_templates();
let formatter_registry = formatters();
let string = template
Expand All @@ -879,7 +881,7 @@ mod test {
fn test_number_truthiness_one() {
let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
let context = 1;
let context = ::serde_json::to_value(&context).unwrap();
let context = Value::serialize_from(&context).unwrap();
let template_registry = other_templates();
let formatter_registry = formatters();
let string = template
Expand All @@ -902,7 +904,7 @@ mod test {

let template = compile("{ foo.1 }{ foo.0 }");
let context = Context { foo: (123, 456) };
let context = ::serde_json::to_value(&context).unwrap();
let context = Value::serialize_from(&context).unwrap();
let template_registry = other_templates();
let formatter_registry = formatters();
let string = template
Expand All @@ -928,7 +930,7 @@ mod test {
foo.insert("0", 123);
foo.insert("1", 456);
let context = Context { foo };
let context = ::serde_json::to_value(&context).unwrap();
let context = Value::serialize_from(&context).unwrap();
let template_registry = other_templates();
let formatter_registry = formatters();
let string = template
Expand Down
Loading

0 comments on commit f9d6e40

Please sign in to comment.