feat(core): join safe html fragments
Let already-safe fragments compose without downgrading to String, and use it in the v0 page assembly example to keep rendered and lowered fragments typed at the boundary. req: html_safety/001 req: html_safety/002 req: component/003
This commit is contained in:
+1
-1
@@ -561,7 +561,7 @@ what a valid business email is.
|
|||||||
001 Raw HTML insertion requires an explicit safe HTML type (`SafeHtml` or equivalent). Plain `String` renders as escaped text unless explicitly wrapped.
|
001 Raw HTML insertion requires an explicit safe HTML type (`SafeHtml` or equivalent). Plain `String` renders as escaped text unless explicitly wrapped.
|
||||||
|
|
||||||
### req: html/002
|
### req: html/002
|
||||||
002 Hemplate-rendered output may be converted to `SafeHtml` by trusted render APIs. User input is never `SafeHtml` by default. Full-page shell composition may pass already-rendered hemplate fragments through explicit `SafeHtml` fields; handlers should prefer slot/resource render helpers for effect payloads.
|
002 Hemplate-rendered output may be converted to `SafeHtml` by trusted render APIs. User input is never `SafeHtml` by default. Full-page shell composition may pass already-rendered hemplate fragments through explicit `SafeHtml` fields, and already-safe fragments may be joined without downgrading to `String`; handlers should prefer slot/resource render helpers for effect payloads.
|
||||||
|
|
||||||
### req: html/003
|
### req: html/003
|
||||||
003 Slot render commands distinguish text payloads from HTML payloads at the type level.
|
003 Slot render commands distinguish text payloads from HTML payloads at the type level.
|
||||||
|
|||||||
+8
-11
@@ -182,17 +182,14 @@ fn registry(state: Arc<ExampleState>) -> HandlerRegistry {
|
|||||||
fn all_examples() -> SafeHtml {
|
fn all_examples() -> SafeHtml {
|
||||||
// Static `.heml` fragments are lowered by generated code before they join rendered views.
|
// Static `.heml` fragments are lowered by generated code before they join rendered views.
|
||||||
// req: html_safety/001 req: html_safety/002 req: component/003
|
// req: html_safety/001 req: html_safety/002 req: component/003
|
||||||
SafeHtml::trusted(
|
SafeHtml::join([
|
||||||
[
|
SafeHtml::trusted(counter::lower(include_str!("../templates/counter.heml"))),
|
||||||
counter::lower(include_str!("../templates/counter.heml")),
|
SafeHtml::trusted(todos::lower(include_str!("../templates/todos.heml"))),
|
||||||
todos::lower(include_str!("../templates/todos.heml")),
|
SafeHtml::trusted(wizard::lower(include_str!("../templates/wizard.heml"))),
|
||||||
wizard::lower(include_str!("../templates/wizard.heml")),
|
SafeHtml::trusted(auth::lower(include_str!("../templates/auth.heml"))),
|
||||||
auth::lower(include_str!("../templates/auth.heml")),
|
render_page_swap("Welcome", "Welcome"),
|
||||||
render_page_swap("Welcome", "Welcome").into_string(),
|
SafeHtml::trusted(notifications::lower(include_str!("../templates/notifications.heml"))),
|
||||||
notifications::lower(include_str!("../templates/notifications.heml")),
|
])
|
||||||
]
|
|
||||||
.join("\n"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shell(body: SafeHtml) -> SafeHtml {
|
fn shell(body: SafeHtml) -> SafeHtml {
|
||||||
|
|||||||
@@ -141,6 +141,20 @@ impl SafeHtml {
|
|||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn join(fragments: impl IntoIterator<Item = SafeHtml>) -> Self {
|
||||||
|
fragments.into_iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<SafeHtml> for SafeHtml {
|
||||||
|
fn from_iter<T: IntoIterator<Item = SafeHtml>>(iter: T) -> Self {
|
||||||
|
let mut html = String::new();
|
||||||
|
for fragment in iter {
|
||||||
|
html.push_str(fragment.as_str());
|
||||||
|
}
|
||||||
|
Self(html)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<str> for SafeHtml {
|
impl AsRef<str> for SafeHtml {
|
||||||
|
|||||||
@@ -93,6 +93,18 @@ fn slot_html_requires_explicit_safe_html() {
|
|||||||
assert_eq!(payload, Payload::Html(String::from("<strong>ok</strong>")));
|
assert_eq!(payload, Payload::Html(String::from("<strong>ok</strong>")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn safe_html_joins_only_explicit_safe_fragments() {
|
||||||
|
// req: html_safety/001 req: html_safety/002
|
||||||
|
let html = SafeHtml::join([
|
||||||
|
SafeHtml::trusted("<main>"),
|
||||||
|
SafeHtml::trusted("<strong>ok</strong>"),
|
||||||
|
SafeHtml::trusted("</main>"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(html.as_str(), "<main><strong>ok</strong></main>");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn param_names_format_generated_param_names() {
|
fn param_names_format_generated_param_names() {
|
||||||
// req: codegen/003
|
// req: codegen/003
|
||||||
|
|||||||
Reference in New Issue
Block a user