diff --git a/Cargo.toml b/Cargo.toml index d69a280..c20a005 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ maintenance = { status = "passively-maintained" } [dependencies] serde = "1.0" -serde_json = "1.0" [dev-dependencies] serde_derive = "1.0" diff --git a/src/error.rs b/src/error.rs index 368d95f..ada71a2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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] @@ -20,9 +24,7 @@ pub enum Error { line: usize, column: usize, }, - SerdeError { - err: SerdeJsonError, - }, + SerializationError(SerializationError), GenericError { msg: String, }, @@ -42,9 +44,9 @@ pub enum Error { column: usize, }, } -impl From for Error { - fn from(err: SerdeJsonError) -> Error { - Error::SerdeError { err } +impl From for Error { + fn from(err: super::value::Error) -> Error { + Error::SerializationError(SerializationError(err)) } } impl From for Error { @@ -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) @@ -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", @@ -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(), diff --git a/src/lib.rs b/src/lib.rs index 396be21..7dcb490 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,6 @@ //! extern crate serde; -extern crate serde_json; #[cfg(test)] #[cfg_attr(test, macro_use)] @@ -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<()>; @@ -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) => { @@ -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) => { @@ -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(&self, template: &str, context: &C) -> Result 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, diff --git a/src/syntax.rs b/src/syntax.rs index 2e8eedd..f7a430b 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -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 //! diff --git a/src/template.rs b/src/template.rs index 6f0162d..0ce87c4 100644 --- a/src/template.rs +++ b/src/template.rs @@ -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. @@ -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 ¤t { + 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) @@ -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; @@ -325,21 +331,16 @@ impl<'template> Template<'template> { Ok(()) } - fn value_is_truthy(&self, value: &Value, path: PathSlice) -> Result { - 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) + } } } @@ -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>> { @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..22ce8e3 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,503 @@ +use std::collections::HashMap; + +use serde::Serialize; + +pub enum Value { + Null, + Boolean(bool), + Integer(i128), + Float(f64), + String(String), + Array(Vec), + Object(HashMap), +} + +impl Value { + #[inline] + pub fn serialize_from(value: &T) -> Result { + value.serialize(Serializer) + } +} + +impl From for Value { + #[inline] + fn from(value: bool) -> Self { + Self::Boolean(value) + } +} + +impl From for Value { + #[inline] + fn from(value: i8) -> Self { + Self::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: i16) -> Self { + Self::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: i32) -> Self { + Value::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: i64) -> Self { + Self::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: i128) -> Self { + Self::Integer(value) + } +} + +impl From for Value { + #[inline] + fn from(value: u8) -> Self { + Self::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: u16) -> Self { + Self::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: u32) -> Self { + Self::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: u64) -> Self { + Self::Integer(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: f32) -> Self { + Self::Float(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: f64) -> Self { + Self::Float(value) + } +} + +impl From for Value { + #[inline] + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From<&str> for Value { + #[inline] + fn from(value: &str) -> Self { + Self::String(value.into()) + } +} + +impl From for Value { + #[inline] + fn from(value: char) -> Self { + Self::String(value.into()) + } +} + +impl> From> for Value { + #[inline] + fn from(value: Vec) -> Self { + Self::Array(value.into_iter().map(|x| x.into()).collect()) + } +} + +impl> From> for Value { + #[inline] + fn from(value: HashMap) -> Self { + Self::Object(value.into_iter().map(|(k, v)| (k, v.into())).collect()) + } +} + +#[derive(Debug)] +pub enum Error { + NonStringKeyUnsupported, + BytesUnsupported, +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl serde::ser::StdError for Error {} +impl serde::ser::Error for Error { + fn custom(_msg: T) -> Self { + unimplemented!() + } +} + +struct Serializer; + +impl serde::Serializer for Serializer { + type Ok = Value; + type Error = Error; + type SerializeSeq = SerializeSeq; + type SerializeTuple = SerializeTuple; + type SerializeTupleStruct = SerializeTupleStruct; + type SerializeTupleVariant = SerializeTupleVariant; + type SerializeMap = SerializeMap; + type SerializeStruct = SerializeStruct; + type SerializeStructVariant = SerializeStructVariant; + + #[inline] + fn serialize_bool(self, v: bool) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_i8(self, v: i8) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_i16(self, v: i16) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_i32(self, v: i32) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_i64(self, v: i64) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_i128(self, v: i128) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_u8(self, v: u8) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_u16(self, v: u16) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_u32(self, v: u32) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_u64(self, v: u64) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_f32(self, v: f32) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_f64(self, v: f64) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_char(self, v: char) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_str(self, v: &str) -> Result { + Ok(v.into()) + } + + #[inline] + fn serialize_bytes(self, _v: &[u8]) -> Result { + Err(Error::BytesUnsupported) + } + + #[inline] + fn serialize_none(self) -> Result { + Ok(Value::Null) + } + + #[inline] + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + #[inline] + fn serialize_unit(self) -> Result { + Ok(Value::Null) + } + + #[inline] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(Value::Null) + } + + #[inline] + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + let mut map = HashMap::new(); + map.insert(variant.into(), Value::Null); + Ok(Value::Object(map)) + } + + #[inline] + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + #[inline] + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + let mut map = HashMap::new(); + map.insert(variant.into(), Value::Null); + Ok(Value::Object(map)) + } + + #[inline] + fn serialize_seq(self, len: Option) -> Result { + Ok(SerializeSeq(Vec::with_capacity(len.unwrap_or_default()))) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + Ok(SerializeTuple(Vec::with_capacity(len))) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + Ok(SerializeTupleStruct(Vec::with_capacity(len))) + } + + #[inline] + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(SerializeTupleVariant(variant, Vec::with_capacity(len))) + } + + #[inline] + fn serialize_map(self, _len: Option) -> Result { + Ok(SerializeMap(HashMap::new(), None)) + } + + #[inline] + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Ok(SerializeStruct(HashMap::new())) + } + + #[inline] + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Ok(SerializeStructVariant(variant, HashMap::new())) + } + + #[inline] + fn is_human_readable(&self) -> bool { + true + } +} + +struct SerializeSeq(Vec); +impl serde::ser::SerializeSeq for SerializeSeq { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + self.0.push(value.serialize(Serializer)?); + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(Value::Array(self.0)) + } +} + +struct SerializeTuple(Vec); +impl serde::ser::SerializeTuple for SerializeTuple { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> { + self.0.push(value.serialize(Serializer)?); + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(Value::Array(self.0)) + } +} + +struct SerializeTupleStruct(Vec); +impl serde::ser::SerializeTupleStruct for SerializeTupleStruct { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { + self.0.push(value.serialize(Serializer)?); + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(Value::Array(self.0)) + } +} + +struct SerializeTupleVariant(&'static str, Vec); +impl serde::ser::SerializeTupleVariant for SerializeTupleVariant { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> { + self.1.push(value.serialize(Serializer)?); + Ok(()) + } + + #[inline] + fn end(self) -> Result { + let mut map = HashMap::new(); + map.insert(self.0.into(), Value::Array(self.1)); + Ok(Value::Object(map)) + } +} + +struct SerializeMap(HashMap, Option); +impl serde::ser::SerializeMap for SerializeMap { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> { + if let Value::String(key) = key.serialize(Serializer)? { + self.1 = Some(key); + return Ok(()); + } + + Err(Error::NonStringKeyUnsupported) + } + + #[inline] + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> { + self.0 + .insert(self.1.take().unwrap(), value.serialize(Serializer)?); + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(Value::Object(self.0)) + } +} + +struct SerializeStruct(HashMap); +impl serde::ser::SerializeStruct for SerializeStruct { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + self.0.insert(key.into(), value.serialize(Serializer)?); + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(Value::Object(self.0)) + } +} + +struct SerializeStructVariant(&'static str, HashMap); +impl serde::ser::SerializeStructVariant for SerializeStructVariant { + type Ok = Value; + type Error = Error; + + #[inline] + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> { + self.1.insert(key.into(), value.serialize(Serializer)?); + Ok(()) + } + + #[inline] + fn end(self) -> Result { + let mut map = HashMap::new(); + map.insert(self.0.into(), Value::Object(self.1)); + Ok(Value::Object(map)) + } +}