sailfish/sailfish/src/render.rs

794 lines
22 KiB
Rust

use std::borrow::Cow;
use std::cell::{Ref, RefMut};
use std::fmt::{self, Arguments, Write};
use std::num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize,
NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::{Arc, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
use super::{Buffer, Escape, SizeHint};
/// types which can be rendered inside buffer block (`<%- %>`) by reference
///
/// If you want to render the custom data, you must implement this trait and specify
/// the behaviour.
///
/// # Safety
///
/// This trait allows modifying the previously-rendered contents or even decreasing the
/// buffer size. However, such an operation easily cause unexpected rendering results.
/// In order to avoid this, implementors should ensure that the contents which is already
/// rendered won't be changed during `render` or `render_escaped` method is called.
///
/// # Examples
///
/// ```
/// use sailfish::{Buffer, Render, RenderError};
///
/// struct MyU64(u64);
///
/// impl Render for MyU64 {
/// #[inline]
/// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
/// self.0.render(b)
/// }
/// }
/// ```
pub trait Render {
/// render to `Buffer` without escaping
fn render(&self, b: &mut Buffer) -> Result<(), RenderError>;
/// render to `Buffer` with HTML escaping
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
default_render_escaped(self, b, e)
}
}
/// types which can be rendered inside buffer block (`<%- %>`)
///
/// See [`Render`] for more information.
pub trait RenderOnce: Sized {
/// render to `Buffer` without escaping
///
/// Render the template and append the result to `buf`.
///
/// This method never returns `Err`, unless you explicitly return RenderError
/// inside templates
///
/// ```should_fail
/// use sailfish::{Buffer, RenderOnce};
///
/// # pub struct HelloTemplate {
/// # messages: Vec<String>,
/// # }
/// #
/// # impl RenderOnce for HelloTemplate {
/// # fn render_once(self, buf: &mut Buffer) -> Result<(), sailfish::RenderError> {
/// # unimplemented!()
/// # }
/// # }
/// #
/// let tpl = HelloTemplate {
/// messages: vec!["foo".to_string()]
/// };
///
/// // custom pre-allocation
/// let mut buffer = Buffer::with_capacity(100);
/// tpl.render_once(&mut buffer).unwrap();
/// ```
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError>;
/// render to `Buffer` with HTML escaping
#[inline]
fn render_once_escaped<E: Escape>(
self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
default_render_once_escaped(self, b, e)
}
/// Render the template and return the rendering result as `RenderResult`
///
/// This method never returns `Err`, unless you explicitly return RenderError
/// inside templates
///
/// When you use `render_once_to_string` method, total rendered size will be cached,
/// and at the next time, buffer will be pre-allocated based on the cached length.
///
/// If you don't want this behaviour, you can use `render_once` method instead.
#[inline]
fn render_once_to_string(self) -> RenderResult {
static SIZE_HINT: SizeHint = SizeHint::new();
let mut buf = Buffer::with_capacity(SIZE_HINT.get());
self.render_once(&mut buf)?;
SIZE_HINT.update(buf.len());
Ok(buf.into_string())
}
}
#[inline]
pub fn default_render_escaped<T: Render + ?Sized, E: Escape>(
t: &T,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
let mut tmp = Buffer::new();
t.render(&mut tmp)?;
e.escape_to_buf(b, tmp.as_str());
Ok(())
}
#[inline]
pub fn default_render_once_escaped<T: RenderOnce, E: Escape>(
t: T,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
let mut tmp = Buffer::new();
t.render_once(&mut tmp)?;
e.escape_to_buf(b, tmp.as_str());
Ok(())
}
// impl<'a, T: ?Sized> Render for &'a T
// where
// T: Render,
// {
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// T::render(self, b)
// }
// fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
// T::render_escaped(self, b, e)
// }
// }
impl<T: Render> RenderOnce for T {
#[inline]
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
self.render(b)
}
#[inline]
fn render_once_escaped<E: Escape>(
self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
self.render_escaped(b, e)
}
}
impl Render for String {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
b.push_str(self);
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
e.escape_to_buf(b, self);
Ok(())
}
}
impl Render for str {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
b.push_str(self);
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
e.escape_to_buf(b, self);
Ok(())
}
}
impl Render for char {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
b.push(*self);
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
match e.escape(*self) {
Some(s) => b.push_str(s.as_ref()),
None => b.push(*self),
}
Ok(())
}
}
impl Render for PathBuf {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// TODO: speed up on Windows using OsStrExt
b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
e.escape_to_buf(b, self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
Ok(())
}
}
impl Render for Path {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// TODO: speed up on Windows using OsStrExt
b.push_str(self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
e.escape_to_buf(b, self.to_str().ok_or(RenderError::Fmt(fmt::Error))?);
Ok(())
}
}
// impl Render for [u8] {
// #[inline]
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// b.write_bytes(self);
// Ok(())
// }
// }
//
// impl<'a> Render for &'a [u8] {
// #[inline]
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// b.write_bytes(self);
// Ok(())
// }
// }
//
// impl Render for Vec<u8> {
// #[inline]
// fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
// b.write_bytes(&**self);
// Ok(())
// }
// }
impl Render for bool {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
let s = if *self { "true" } else { "false" };
b.push_str(s);
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
if E::IDENT_BOOLS {
self.render(b)
} else {
if *self {
e.escape_to_buf(b, "true");
} else {
e.escape_to_buf(b, "false");
}
Ok(())
}
}
}
macro_rules! render_int {
($ident_tag:ident, $($int:ty),*) => {
$(
impl Render for $int {
#[cfg_attr(feature = "perf-inline", inline)]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
use itoap::Integer;
// SAFETY: `MAX_LEN < 40` and then does not overflows `isize::MAX`.
// Also `b.len()` should be always less than or equal to `isize::MAX`.
unsafe {
b.reserve_small(Self::MAX_LEN);
let ptr = b.as_mut_ptr().add(b.len());
// SAFETY: `MAX_LEN` is always greater than zero, so
// `b.as_mut_ptr()` always point to valid block of memory
let l = itoap::write_to_ptr(ptr, *self);
b.advance(l);
}
debug_assert!(b.len() <= b.capacity());
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
if E::$ident_tag {
// push_str without escape
self.render(b)
} else {
default_render_once_escaped(self, b, e)
}
}
}
)*
}
}
render_int!(IDENT_UINTS, u8, u16, u32, u64, u128, usize);
render_int!(IDENT_INTS, i8, i16, i32, i64, i128, isize);
impl Render for f32 {
#[cfg_attr(feature = "perf-inline", inline)]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
if likely!(self.is_finite()) {
unsafe {
b.reserve_small(16);
let ptr = b.as_mut_ptr().add(b.len());
let l = ryu::raw::format32(*self, ptr);
b.advance(l);
debug_assert!(b.len() <= b.capacity());
}
} else if self.is_nan() {
b.push_str("NaN");
} else if *self > 0.0 {
b.push_str("inf");
} else {
b.push_str("-inf");
}
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
if E::IDENT_FLOATS {
self.render(b)
} else {
default_render_escaped(self, b, e)
}
}
}
impl Render for f64 {
#[cfg_attr(feature = "perf-inline", inline)]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
if likely!(self.is_finite()) {
unsafe {
b.reserve_small(24);
let ptr = b.as_mut_ptr().add(b.len());
let l = ryu::raw::format64(*self, ptr);
b.advance(l);
debug_assert!(b.len() <= b.capacity());
}
} else if self.is_nan() {
b.push_str("NaN");
} else if *self > 0.0 {
b.push_str("inf");
} else {
b.push_str("-inf");
}
Ok(())
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
if E::IDENT_FLOATS {
self.render(b)
} else {
default_render_escaped(self, b, e)
}
}
}
impl Render for Arguments<'_> {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
if let Some(s) = self.as_str() {
b.push_str(s);
Ok(())
} else {
b.write_fmt(*self).map_err(RenderError::Fmt)
}
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
if let Some(s) = self.as_str() {
e.escape_to_buf(b, s);
Ok(())
} else {
default_render_escaped(self, b, e)
}
}
}
macro_rules! render_deref {
(
$(#[doc = $doc:tt])*
$(default[$($default:tt)+])? [$($generics:tt)+] [$($bounds:tt)*] $desc:ty $(, [$($deref:tt)+])?
) => {
$(#[doc = $doc])*
$($($default)+)? impl <$($generics)+> Render for $desc where $($bounds)* {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
(**self).render(b)
}
#[inline]
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
(**self).render_escaped(b, e)
}
}
};
}
// render_ref!(['a, T] [&'a T: Render] T);
// render_ref!(['a] [] String);
render_deref!([T: Render + ?Sized] [] &T);
render_deref!([T: Render + ?Sized] [] &mut T);
render_deref!([T: Render + ?Sized] [] Box<T>);
render_deref!([T: Render + ?Sized] [] Rc<T>);
render_deref!([T: Render + ?Sized] [] Arc<T>);
render_deref!([T: Render + ToOwned + ?Sized] [] Cow<'_, T>);
render_deref!([T: Render + ?Sized] [] Ref<'_, T>, [*]);
render_deref!([T: Render + ?Sized] [] RefMut<'_, T>, [*]);
render_deref!([T: Render + ?Sized] [] MutexGuard<'_, T>, [*]);
render_deref!([T: Render + ?Sized] [] RwLockReadGuard<'_, T>, [*]);
render_deref!([T: Render + ?Sized] [] RwLockWriteGuard<'_, T>, [*]);
macro_rules! render_nonzero {
($($type:ty,)*) => {
$(
impl Render for $type {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
self.get().render(b)
}
#[inline]
fn render_escaped<E: Escape>(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> {
self.get().render_escaped(b, e)
}
}
)*
}
}
render_nonzero!(
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize,
);
impl<T: Render> Render for Wrapping<T> {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
self.0.render(b)
}
#[inline]
fn render_escaped<E: Escape>(
&self,
b: &mut Buffer,
e: &E,
) -> Result<(), RenderError> {
self.0.render_escaped(b, e)
}
}
#[allow(unused_macros)]
macro_rules! render_tuple {
($A:ident) => {
render_tuple!(@ $A);
};
($A:ident $(, $T:ident)+) => {
render_tuple!(@ $A $(, $T)+);
render_tuple!($($T),+);
};
(@ $($T:ident),*) => {
impl<$($T: Render),+> Render for ($($T,)+) {
#[inline]
fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
#[allow(non_snake_case)]
let ($($T,)+) = self;
$($T.render(b)?;)+
Ok(())
}
#[inline]
fn render_escaped<Esc: Escape>(&self, b: &mut Buffer, e: &Esc) -> Result<(), RenderError> {
#[allow(non_snake_case)]
let ($($T,)+) = self;
$($T.render_escaped(b, e)?;)+
Ok(())
}
}
};
}
macro_rules! render_once_tuple {
($A:ident) => {
render_once_tuple!(@ $A);
};
($A:ident $(, $T:ident)+) => {
render_once_tuple!(@ $A $(, $T)+);
render_once_tuple!($($T),+);
};
(@ $($T:ident),*) => {
impl<$($T: RenderOnce),+> RenderOnce for ($($T,)+) {
#[inline]
fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> {
#[allow(non_snake_case)]
let ($($T,)+) = self;
$($T.render_once(b)?;)+
Ok(())
}
#[inline]
fn render_once_escaped<Esc: Escape>(self, b: &mut Buffer, e: &Esc) -> Result<(), RenderError> {
#[allow(non_snake_case)]
let ($($T,)+) = self;
$($T.render_once_escaped(b, e)?;)+
Ok(())
}
}
};
}
// TODO: figure out how to use specialization to allow a Render impl on tuples, if possible. If
// not, oh well, it can be worked around.
// render_tuple!(A, B, C, D, E, F, G, H);
render_once_tuple!(A, B, C, D, E, F, G, H);
/// The error type which is returned from template function
#[derive(Clone, Debug)]
pub enum RenderError {
/// Custom error message
Msg(String),
/// fmt::Error was raised during rendering
Fmt(fmt::Error),
/// Buffer size shrinked during rendering
///
/// This method won't be raised unless you implement `Render` trait for custom type.
///
/// Also there is no guarentee that this error will be returned whenever the buffer
/// size shrinked.
BufSize,
}
impl RenderError {
/// Construct a new error with custom message
pub fn new(msg: &str) -> Self {
RenderError::Msg(msg.to_owned())
}
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RenderError::Msg(ref s) => f.pad(&**s),
RenderError::Fmt(ref e) => fmt::Display::fmt(e, f),
RenderError::BufSize => f.pad("buffer size shrinked while rendering"),
}
}
}
impl std::error::Error for RenderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
RenderError::Msg(_) | RenderError::BufSize => None,
RenderError::Fmt(ref e) => Some(e),
}
}
}
impl From<fmt::Error> for RenderError {
#[inline]
fn from(other: fmt::Error) -> Self {
RenderError::Fmt(other)
}
}
/// Result type returned from [`RenderOnce::render_once_to_string`] method
pub type RenderResult = Result<String, RenderError>;
#[cfg(test)]
mod tests {
use std::error::Error;
use crate::EscapeHtml;
use super::*;
#[test]
fn receiver_coercion() {
let mut b = Buffer::new();
RenderOnce::render_once(&1, &mut b).unwrap();
RenderOnce::render_once(&&1, &mut b).unwrap();
RenderOnce::render_once(&&&1, &mut b).unwrap();
RenderOnce::render_once(&&&&1, &mut b).unwrap();
assert_eq!(b.as_str(), "1111");
b.clear();
RenderOnce::render_once(&true, &mut b).unwrap();
RenderOnce::render_once(&&false, &mut b).unwrap();
RenderOnce::render_once_escaped(&&&true, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&&&&false, &mut b, &EscapeHtml).unwrap();
assert_eq!(b.as_str(), "truefalsetruefalse");
b.clear();
let s = "apple";
RenderOnce::render_once_escaped(&s, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&s, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&&s, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&&&s, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&&&&s, &mut b, &EscapeHtml).unwrap();
assert_eq!(b.as_str(), "appleappleappleappleapple");
b.clear();
RenderOnce::render_once_escaped(&'c', &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&&'<', &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&&&'&', &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(&&&&' ', &mut b, &EscapeHtml).unwrap();
assert_eq!(b.as_str(), "c&lt;&amp; ");
b.clear();
}
#[test]
fn deref_coercion() {
use std::path::{Path, PathBuf};
use std::rc::Rc;
let mut b = Buffer::new();
Render::render(&String::from("a"), &mut b).unwrap();
Render::render(&PathBuf::from("b"), &mut b).unwrap();
Render::render_escaped(&Rc::new(4u32), &mut b, &EscapeHtml).unwrap();
Render::render_escaped(&Rc::new(2.3f32), &mut b, &EscapeHtml).unwrap();
Render::render_escaped(Path::new("<"), &mut b, &EscapeHtml).unwrap();
Render::render_escaped(&Path::new("d"), &mut b, &EscapeHtml).unwrap();
assert_eq!(b.as_str(), "ab42.3&lt;d");
}
#[test]
fn float() {
let mut b = Buffer::new();
RenderOnce::render_once_escaped(0.0f64, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(std::f64::INFINITY, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(std::f64::NEG_INFINITY, &mut b, &EscapeHtml)
.unwrap();
RenderOnce::render_once_escaped(std::f64::NAN, &mut b, &EscapeHtml).unwrap();
assert_eq!(b.as_str(), "0.0inf-infNaN");
b.clear();
RenderOnce::render_once_escaped(0.0f32, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(std::f32::INFINITY, &mut b, &EscapeHtml).unwrap();
RenderOnce::render_once_escaped(std::f32::NEG_INFINITY, &mut b, &EscapeHtml)
.unwrap();
RenderOnce::render_once_escaped(std::f32::NAN, &mut b, &EscapeHtml).unwrap();
assert_eq!(b.as_str(), "0.0inf-infNaN");
}
#[test]
fn test_char() {
let mut b = Buffer::new();
let funcs: Vec<fn(char, &mut Buffer) -> Result<(), RenderError>> =
vec![RenderOnce::render_once, |c, b| {
RenderOnce::render_once_escaped(c, b, &EscapeHtml)
}];
for func in funcs {
func('a', &mut b).unwrap();
func('b', &mut b).unwrap();
func('c', &mut b).unwrap();
func('d', &mut b).unwrap();
assert_eq!(b.as_str(), "abcd");
b.clear();
func('あ', &mut b).unwrap();
func('い', &mut b).unwrap();
func('う', &mut b).unwrap();
func('え', &mut b).unwrap();
assert_eq!(b.as_str(), "あいうえ");
b.clear();
}
}
#[test]
fn test_nonzero() {
let mut b = Buffer::with_capacity(2);
RenderOnce::render_once(NonZeroU8::new(10).unwrap(), &mut b).unwrap();
RenderOnce::render_once_escaped(
NonZeroI16::new(-20).unwrap(),
&mut b,
&EscapeHtml,
)
.unwrap();
assert_eq!(b.as_str(), "10-20");
}
#[test]
fn render_error() {
let err = RenderError::new("custom error");
assert!(err.source().is_none());
assert_eq!(format!("{}", err), "custom error");
let err = RenderError::from(std::fmt::Error::default());
assert!(err.source().is_some());
assert_eq!(
format!("{}", err),
format!("{}", std::fmt::Error::default())
);
let err = RenderError::BufSize;
assert!(err.source().is_none());
format!("{}", err);
}
}