Remove blanket Render impl for T: Display ()

Closes 
This commit is contained in:
Chris Wong 2021-11-21 19:58:58 +11:00 committed by GitHub
parent f6bbece1c6
commit 2f3d68c8a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 60 deletions

View file

@ -2,6 +2,9 @@
## [Unreleased] ## [Unreleased]
- Remove blanket `Render` impl for `T: Display`
[#320](https://github.com/lambda-fairy/maud/pull/320)
## [0.23.0] - 2021-11-10 ## [0.23.0] - 2021-11-10
- Update to support axum 0.2 - Update to support axum 0.2

1
docs/Cargo.lock generated
View file

@ -326,6 +326,7 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
name = "maud" name = "maud"
version = "0.23.0" version = "0.23.0"
dependencies = [ dependencies = [
"itoa",
"maud_macros", "maud_macros",
] ]

View file

@ -1,15 +1,9 @@
# The `Render` trait # The `Render` trait
By default, Maud uses the [`Render`][Render] trait to convert [`(spliced)`](splices-toggles.md) values to HTML.
a [`(splice)`](splices-toggles.md) is rendered using the [`std::fmt::Display`][Display] trait, This is implemented for many Rust primitive types (`&str`, `i32`) by default, but you can implement it for your own types as well.
with any HTML special characters escaped automatically.
To change this behavior, Below are some examples of implementing `Render`.
implement the [`Render`][Render] trait for your type.
Then, when a value of this type is used in a template,
Maud will call your custom code instead.
Below are some examples of using `Render`.
Feel free to use these snippets in your own project! Feel free to use these snippets in your own project!
## Example: a shorthand for including CSS stylesheets ## Example: a shorthand for including CSS stylesheets

View file

@ -94,11 +94,11 @@ html! {
### What can be spliced? ### What can be spliced?
You can splice any value that implements [`std::fmt::Display`][Display]. You can splice any value that implements [`Render`][Render].
Most primitive types (such as `str` and `i32`) implement this trait, Most primitive types (such as `str` and `i32`) implement this trait,
so they should work out of the box. so they should work out of the box.
To change this behavior for some type, To get this behavior for a custom type,
you can implement the [`Render`][Render] trait by hand. you can implement the [`Render`][Render] trait by hand.
The [`PreEscaped`][PreEscaped] wrapper type, The [`PreEscaped`][PreEscaped] wrapper type,
which outputs its argument without escaping, which outputs its argument without escaping,
@ -116,7 +116,6 @@ html! {
# ; # ;
``` ```
[Display]: http://doc.rust-lang.org/std/fmt/trait.Display.html
[Render]: https://docs.rs/maud/*/maud/trait.Render.html [Render]: https://docs.rs/maud/*/maud/trait.Render.html
[PreEscaped]: https://docs.rs/maud/*/maud/struct.PreEscaped.html [PreEscaped]: https://docs.rs/maud/*/maud/struct.PreEscaped.html

View file

@ -19,6 +19,7 @@ actix-web = ["actix-web-dep", "futures-util"]
[dependencies] [dependencies]
maud_macros = { version = "0.23.0", path = "../maud_macros" } maud_macros = { version = "0.23.0", path = "../maud_macros" }
itoa = { version = "0.4.8", default-features = false, features = ["i128"] }
rocket = { version = ">= 0.3, < 0.5", optional = true } rocket = { version = ">= 0.3, < 0.5", optional = true }
futures-util = { version = "0.3.0", optional = true, default-features = false } futures-util = { version = "0.3.0", optional = true, default-features = false }
actix-web-dep = { package = "actix-web", version = ">= 2, < 4", optional = true, default-features = false } actix-web-dep = { package = "actix-web", version = ">= 2, < 4", optional = true, default-features = false }

View file

@ -11,8 +11,8 @@
extern crate alloc; extern crate alloc;
use alloc::string::String; use alloc::{borrow::Cow, boxed::Box, string::String};
use core::fmt::{self, Write}; use core::fmt::{self, Arguments, Write};
pub use maud_macros::{html, html_debug}; pub use maud_macros::{html, html_debug};
@ -59,16 +59,9 @@ impl<'a> fmt::Write for Escaper<'a> {
/// Represents a type that can be rendered as HTML. /// Represents a type that can be rendered as HTML.
/// ///
/// If your type implements [`Display`][1], then it will implement this /// To implement this for your own type, override either the `.render()`
/// trait automatically through a blanket impl. /// or `.render_to()` methods; since each is defined in terms of the
/// /// other, you only need to implement one of them. See the example below.
/// [1]: https://doc.rust-lang.org/std/fmt/trait.Display.html
///
/// On the other hand, if your type has a custom HTML representation,
/// then you can implement `Render` by hand. To do this, override
/// either the `.render()` or `.render_to()` methods; since each is
/// defined in terms of the other, you only need to implement one of
/// them. See the example below.
/// ///
/// # Minimal implementation /// # Minimal implementation
/// ///
@ -115,48 +108,81 @@ pub trait Render {
} }
} }
impl<T: fmt::Display + ?Sized> Render for T { impl Render for str {
fn render_to(&self, w: &mut String) { fn render_to(&self, w: &mut String) {
let _ = write!(Escaper::new(w), "{}", self); escape::escape_to_string(self, w);
} }
} }
/// Spicy hack to specialize `Render` for `T: AsRef<str>`. impl Render for String {
/// fn render_to(&self, w: &mut String) {
/// The `std::fmt` machinery is rather heavyweight, both in code size and speed. str::render_to(self, w);
/// It would be nice to skip this overhead for the common cases of `&str` and
/// `String`. But the obvious solution uses *specialization*, which (as of this
/// writing) requires Nightly. The [*inherent method specialization*][1] trick
/// is less clear but works on Stable.
///
/// This module is an implementation detail and should not be used directly.
///
/// [1]: https://github.com/dtolnay/case-studies/issues/14
#[doc(hidden)]
pub mod render {
use crate::{Escaper, Render};
use alloc::string::String;
use core::fmt::Write;
pub trait RenderInternal {
fn __maud_render_to(&self, w: &mut String);
} }
}
pub struct RenderWrapper<'a, T: ?Sized>(pub &'a T); impl<'a> Render for Cow<'a, str> {
fn render_to(&self, w: &mut String) {
impl<'a, T: AsRef<str> + ?Sized> RenderWrapper<'a, T> { str::render_to(self, w);
pub fn __maud_render_to(&self, w: &mut String) {
let _ = Escaper::new(w).write_str(self.0.as_ref());
}
} }
}
impl<'a, T: Render + ?Sized> RenderInternal for RenderWrapper<'a, T> { impl<'a> Render for Arguments<'a> {
fn __maud_render_to(&self, w: &mut String) { fn render_to(&self, w: &mut String) {
self.0.render_to(w); let _ = Escaper::new(w).write_fmt(*self);
}
} }
} }
impl<'a, T: Render + ?Sized> Render for &'a T {
fn render_to(&self, w: &mut String) {
T::render_to(self, w);
}
}
impl<'a, T: Render + ?Sized> Render for &'a mut T {
fn render_to(&self, w: &mut String) {
T::render_to(self, w);
}
}
impl<T: Render + ?Sized> Render for Box<T> {
fn render_to(&self, w: &mut String) {
T::render_to(self, w);
}
}
macro_rules! impl_render_with_display {
($($ty:ty)*) => {
$(
impl Render for $ty {
fn render_to(&self, w: &mut String) {
format_args!("{self}").render_to(w);
}
}
)*
};
}
impl_render_with_display! {
char f32 f64
}
macro_rules! impl_render_with_itoa {
($($ty:ty)*) => {
$(
impl Render for $ty {
fn render_to(&self, w: &mut String) {
let _ = itoa::fmt(w, *self);
}
}
)*
};
}
impl_render_with_itoa! {
i8 i16 i32 i64 i128 isize
u8 u16 u32 u64 u128 usize
}
/// A wrapper that renders the inner value without escaping. /// A wrapper that renders the inner value without escaping.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct PreEscaped<T: AsRef<str>>(pub T); pub struct PreEscaped<T: AsRef<str>>(pub T);

View file

@ -103,11 +103,7 @@ impl Generator {
fn splice(&self, expr: TokenStream, build: &mut Builder) { fn splice(&self, expr: TokenStream, build: &mut Builder) {
let output_ident = self.output_ident.clone(); let output_ident = self.output_ident.clone();
let tokens = quote!({ build.push_tokens(quote!(maud::Render::render_to(&#expr, &mut #output_ident);));
use maud::render::{RenderInternal, RenderWrapper};
RenderWrapper(&#expr).__maud_render_to(&mut #output_ident);
});
build.push_tokens(tokens);
} }
fn element(&self, name: TokenStream, attrs: Vec<Attr>, body: ElementBody, build: &mut Builder) { fn element(&self, name: TokenStream, attrs: Vec<Attr>, body: ElementBody, build: &mut Builder) {