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]
- Remove blanket `Render` impl for `T: Display`
[#320](https://github.com/lambda-fairy/maud/pull/320)
## [0.23.0] - 2021-11-10
- Update to support axum 0.2

1
docs/Cargo.lock generated
View file

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

View file

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

View file

@ -94,11 +94,11 @@ html! {
### 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,
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.
The [`PreEscaped`][PreEscaped] wrapper type,
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
[PreEscaped]: https://docs.rs/maud/*/maud/struct.PreEscaped.html

View file

@ -19,6 +19,7 @@ actix-web = ["actix-web-dep", "futures-util"]
[dependencies]
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 }
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 }

View file

@ -11,8 +11,8 @@
extern crate alloc;
use alloc::string::String;
use core::fmt::{self, Write};
use alloc::{borrow::Cow, boxed::Box, string::String};
use core::fmt::{self, Arguments, Write};
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.
///
/// If your type implements [`Display`][1], then it will implement this
/// trait automatically through a blanket impl.
///
/// [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.
/// To implement this for your own type, 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
///
@ -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) {
let _ = write!(Escaper::new(w), "{}", self);
escape::escape_to_string(self, w);
}
}
/// Spicy hack to specialize `Render` for `T: AsRef<str>`.
///
/// The `std::fmt` machinery is rather heavyweight, both in code size and speed.
/// 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);
impl Render for String {
fn render_to(&self, w: &mut String) {
str::render_to(self, w);
}
}
pub struct RenderWrapper<'a, T: ?Sized>(pub &'a T);
impl<'a, T: AsRef<str> + ?Sized> RenderWrapper<'a, T> {
pub fn __maud_render_to(&self, w: &mut String) {
let _ = Escaper::new(w).write_str(self.0.as_ref());
}
impl<'a> Render for Cow<'a, str> {
fn render_to(&self, w: &mut String) {
str::render_to(self, w);
}
}
impl<'a, T: Render + ?Sized> RenderInternal for RenderWrapper<'a, T> {
fn __maud_render_to(&self, w: &mut String) {
self.0.render_to(w);
}
impl<'a> Render for Arguments<'a> {
fn render_to(&self, w: &mut String) {
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.
#[derive(Debug, Clone, Copy)]
pub struct PreEscaped<T: AsRef<str>>(pub T);

View file

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