rama_http/protocols/html/mod.rs
1//! Type-safe HTML templating support.
2//!
3//! Enabled via the `html` feature. The core idea: every HTML5 element gets
4//! its own proc-macro (`html!`, `body!`, `div!`, ...) that constructs a
5//! type implementing both [`IntoHtml`] (for composition) and
6//! [`IntoResponse`](crate::service::web::response::IntoResponse) (so the
7//! result can be returned directly from a web handler). For runtime tag
8//! names — typically [web components] — see the [`custom!`] macro.
9//!
10//! ```ignore
11//! use rama_http::protocols::html::*;
12//! use rama_http::service::web::response::IntoResponse;
13//!
14//! async fn handler() -> impl IntoResponse {
15//! html!(
16//! head!(title!("Hi")),
17//! body!(
18//! h1!("Hello, ", "world!"),
19//! custom!("my-icon", name = "smile"),
20//! ),
21//! )
22//! }
23//! ```
24//!
25//! `<html>` is always the document root, so [`html!`] automatically
26//! prepends `<!DOCTYPE html>` to its output — i.e. `html!(...)` returns
27//! a complete page that can be returned from a handler as-is. If you
28//! really need a bare `<html>` element without the doctype, use
29//! `custom!("html", ...)`.
30//!
31//! ## What gets escaped, what does not
32//!
33//! Anything spliced in via an expression (`{name}`, `self.value`, etc.)
34//! goes through HTML escaping by virtue of [`IntoHtml`]. The static parts
35//! emitted by the macros themselves are wrapped in [`PreEscaped`] and
36//! written verbatim. If you have *trusted* HTML you want to splice in
37//! raw — e.g. an SVG icon or a pre-rendered fragment — wrap it in
38//! `PreEscaped(...)` yourself.
39//!
40//! ## Custom (user-defined) types
41//!
42//! Implementing [`IntoHtml`] on your own type lets it participate in
43//! templates just like the built-in scalars. For composite types, simply
44//! return another [`IntoHtml`] from `into_html`; for "leaf" types, return
45//! `self` and override `escape_and_write`.
46//!
47//! This is the main path for adding type-safe support for custom HTML
48//! shapes — e.g. components with strongly-typed required attributes —
49//! built on top of the macro layer:
50//!
51//! ```ignore
52//! use rama_http::protocols::html::*;
53//!
54//! struct UserIcon { user_id: u64, size: IconSize }
55//! enum IconSize { Sm, Md, Lg }
56//!
57//! impl IntoHtml for UserIcon {
58//! fn into_html(self) -> impl IntoHtml {
59//! let size = match self.size { IconSize::Sm => "sm", IconSize::Md => "md", IconSize::Lg => "lg" };
60//! custom!("user-icon", "data-user-id" = self.user_id, size = size)
61//! }
62//! }
63//! ```
64//!
65//! [web components]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components
66//! [`custom!`]: rama_http_macros::custom
67
68mod core;
69mod either_impls;
70mod join;
71mod rama_impls;
72mod response;
73
74pub mod rewrite;
75pub mod selector;
76pub mod tokenizer;
77
78#[doc(inline)]
79pub use self::core::{
80 End, IntoHtml, Marker, PreEscaped, Start, decode_entities, end, escape, escape_into, marker,
81 start,
82};
83#[doc(inline)]
84pub use self::join::join_display;
85#[doc(inline)]
86pub use self::response::HtmlBuf;
87
88pub(crate) use self::core::escape_attr_value_into;
89
90// Re-exported so the proc-macros emitted by `rama-http-macros` can refer
91// to them via a single root path (`rama_http::protocols::html::Either{,3..9}`),
92// without depending on the user knowing where `rama-core` lives.
93//
94// In practice the user normally only encounters these via `if`/`else if`
95// chains inside element macros — the Either chain is generated for them.
96#[doc(inline)]
97pub use rama_core::combinators::{
98 Either, Either3, Either4, Either5, Either6, Either7, Either8, Either9,
99};
100
101#[doc(inline)]
102pub use rama_http_macros::custom;
103
104// One re-exported proc-macro per known HTML5 element.
105//
106// Kept in alphabetical order to match the canonical list at MDN
107// (<https://developer.mozilla.org/en-US/docs/Web/HTML/Element>).
108#[doc(inline)]
109pub use rama_http_macros::{
110 a, abbr, address, area, article, aside, audio, b, base, bdi, bdo, blockquote, body, br, button,
111 canvas, caption, cite, code, col, colgroup, data, datalist, dd, del, details, dfn, dialog, div,
112 dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, head,
113 header, hgroup, hr, html, i, iframe, img, input, ins, kbd, label, legend, li, link, main, map,
114 mark, menu, meta, meter, nav, noscript, object, ol, optgroup, option, output, p, param,
115 picture, pre, progress, q, rp, rt, ruby, s, samp, script, search, section, select, small,
116 source, span, strong, style, sub, summary, sup, svg, table, tbody, td, template, textarea,
117 tfoot, th, thead, time, title, tr, track, u, ul, var, video, wbr,
118};
119
120#[cfg(test)]
121mod tests;