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 crate::escape; 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( &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, /// # } /// # /// # 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( 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: &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: 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(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> { // T::render_escaped(self, b, e) // } // } impl RenderOnce for T { #[inline] fn render_once(self, b: &mut Buffer) -> Result<(), RenderError> { self.render(b) } #[inline] fn render_once_escaped( 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( &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( &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( &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( &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( &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 { // #[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( &self, b: &mut Buffer, e: &E, ) -> Result<(), RenderError> { if escape::CommonIdents::::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(&self, b: &mut Buffer, e: &E) -> Result<(), RenderError> { if escape::CommonIdents::::$ident_tag { // push_str without escape self.render(b) } else { default_render_once_escaped(self, b, e) } } } )* } } render_int!(UINTS, u8, u16, u32, u64, u128, usize); render_int!(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( &self, b: &mut Buffer, e: &E, ) -> Result<(), RenderError> { if escape::CommonIdents::::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( &self, b: &mut Buffer, e: &E, ) -> Result<(), RenderError> { if escape::CommonIdents::::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( &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(&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); render_deref!([T: Render + ?Sized] [] Rc); render_deref!([T: Render + ?Sized] [] Arc); 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(&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 Render for Wrapping { #[inline] fn render(&self, b: &mut Buffer) -> Result<(), RenderError> { self.0.render(b) } #[inline] fn render_escaped( &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(&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(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 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; #[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<& "); 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<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 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); } }