1use std::{error::Error as StdError, fmt, io, ops};
10
11pub use russx_macros::{templates, tmpl};
12
13pub type Result<T, E = Error> = core::result::Result<T, E>;
14
15#[derive(Debug, thiserror::Error)]
17#[non_exhaustive]
18pub enum Error {
19 #[error("formatting error: {0}")]
21 Fmt(#[from] fmt::Error),
22 #[error("attribute doesn't conform to html standard: {0:?}")]
25 AttributeError(String),
26 #[error("custom error: {0}")]
29 Custom(Box<dyn StdError + Send + Sync>),
30}
31
32impl Error {
33 pub fn custom(err: impl StdError + Send + Sync + 'static) -> Self {
35 Self::Custom(err.into())
36 }
37}
38
39impl From<io::Error> for Error {
40 fn from(err: io::Error) -> Self {
41 Self::custom(err)
42 }
43}
44
45#[doc(hidden)]
46pub mod __typed_builder {
47 pub use typed_builder::*;
48}
49
50#[doc(hidden)]
51pub fn __write_escaped(
53 writer: &mut (impl fmt::Write + ?Sized),
54 value: &(impl fmt::Display + ?Sized),
55) -> Result<()> {
56 use fmt::Write;
57
58 pub struct EscapeWriter<'a, W: fmt::Write + ?Sized>(&'a mut W);
59
60 impl<W: fmt::Write + ?Sized> fmt::Write for EscapeWriter<'_, W> {
61 #[inline]
62 fn write_str(&mut self, s: &str) -> fmt::Result {
63 use askama_escape::Escaper;
64
65 askama_escape::Html.write_escaped(&mut *self.0, s)
66 }
67 }
68
69 write!(EscapeWriter(writer), "{value}")?;
70
71 Ok(())
72}
73
74fn write_attribute(
76 writer: &mut (impl fmt::Write + ?Sized),
77 value: &(impl fmt::Display + ?Sized),
78) -> Result<()> {
79 use fmt::Write;
80
81 pub struct AttributeWriter<'a, W: fmt::Write + ?Sized> {
82 has_written: bool,
83 writer: &'a mut W,
84 }
85
86 impl<W: fmt::Write + ?Sized> fmt::Write for AttributeWriter<'_, W> {
87 fn write_str(&mut self, s: &str) -> fmt::Result {
88 #[rustfmt::skip]
89 fn is_invalid_attribute_char(ch: char) -> bool {
90 matches!(
91 ch,
92 '\0'..='\x1F' | '\x7F'..='\u{9F}'
93 | ' ' | '"' | '\'' | '>' | '/' | '='
94 | '\u{FDD0}'..='\u{FDEF}'
95 | '\u{0FFFE}' | '\u{0FFFF}' | '\u{01FFFE}' | '\u{01FFFF}' | '\u{2FFFE}'
96 | '\u{2FFFF}' | '\u{3FFFE}' | '\u{03FFFF}' | '\u{04FFFE}' | '\u{4FFFF}'
97 | '\u{5FFFE}' | '\u{5FFFF}' | '\u{06FFFE}' | '\u{06FFFF}' | '\u{7FFFE}'
98 | '\u{7FFFF}' | '\u{8FFFE}' | '\u{08FFFF}' | '\u{09FFFE}' | '\u{9FFFF}'
99 | '\u{AFFFE}' | '\u{AFFFF}' | '\u{0BFFFE}' | '\u{0BFFFF}' | '\u{CFFFE}'
100 | '\u{CFFFF}' | '\u{DFFFE}' | '\u{0DFFFF}' | '\u{0EFFFE}' | '\u{EFFFF}'
101 | '\u{FFFFE}' | '\u{FFFFF}' | '\u{10FFFE}' | '\u{10FFFF}'
102 )
103 }
104
105 self.has_written |= !s.is_empty();
106 if s.contains(is_invalid_attribute_char) {
107 return Err(fmt::Error);
108 }
109 self.writer.write_str(s)
110 }
111 }
112
113 let mut attr_writer = AttributeWriter {
114 has_written: false,
115 writer,
116 };
117
118 write!(attr_writer, "{value}")?;
119
120 if !attr_writer.has_written {
121 return Err(Error::AttributeError(value.to_string()));
122 }
123
124 Ok(())
125}
126
127mod sealed {
128 pub trait SealedAttribute {}
129 pub trait SealedAttributes {}
130}
131
132pub trait Attribute: sealed::SealedAttribute {
134 fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()>;
136}
137
138impl<T: Attribute + ?Sized> sealed::SealedAttribute for &'_ T {}
139impl<T: Attribute + ?Sized> Attribute for &'_ T {
140 #[inline]
141 fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
142 T::render_into(self, writer)
143 }
144}
145
146impl<T: Attribute + ?Sized> sealed::SealedAttribute for &'_ mut T {}
147impl<T: Attribute + ?Sized> Attribute for &'_ mut T {
148 #[inline]
149 fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
150 T::render_into(self, writer)
151 }
152}
153
154impl sealed::SealedAttribute for String {}
155impl Attribute for String {
156 fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
158 writer.write_char(' ')?;
159 write_attribute(writer, self)?;
160
161 Ok(())
162 }
163}
164
165impl sealed::SealedAttribute for str {}
166impl Attribute for str {
167 fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
169 writer.write_char(' ')?;
170 write_attribute(writer, self)?;
171
172 Ok(())
173 }
174}
175
176impl<N: fmt::Display, T: fmt::Display> sealed::SealedAttribute for (N, T) {}
177impl<N: fmt::Display, T: fmt::Display> Attribute for (N, T) {
178 fn render_into(&self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
179 writer.write_char(' ')?;
180 write_attribute(writer, &self.0)?;
181 writer.write_str("=\"")?;
182 __write_escaped(writer, &self.1)?;
183 writer.write_char('"')?;
184
185 Ok(())
186 }
187}
188
189pub trait Attributes: sealed::SealedAttributes {
201 fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()>;
204}
205
206impl<T: Attribute> sealed::SealedAttributes for T {}
207impl<T: Attribute> Attributes for T {
208 fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
209 Attribute::render_into(&self, writer)
210 }
211}
212
213impl sealed::SealedAttributes for () {}
214impl Attributes for () {
215 #[inline]
217 fn render_into(self, _writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
218 Ok(())
219 }
220}
221
222impl<I: Attribute, T: IntoIterator<Item = I>> sealed::SealedAttributes for ops::RangeTo<T> {}
223impl<I: Attribute, T: IntoIterator<Item = I>> Attributes for ops::RangeTo<T> {
224 fn render_into(self, writer: &mut (impl fmt::Write + ?Sized)) -> Result<()> {
225 for attr in self.end {
226 attr.render_into(writer)?;
227 }
228
229 Ok(())
230 }
231}
232
233pub trait Template: Sized {
236 const SIZE_HINT: usize;
242
243 fn size_hint(&self) -> usize {
246 Self::SIZE_HINT
247 }
248
249 fn render_into(self, writer: &mut dyn fmt::Write) -> Result<()>;
251
252 fn render(self) -> Result<String> {
254 let mut buf = String::new();
255 let _ = buf.try_reserve(self.size_hint());
256 self.render_into(&mut buf)?;
257 Ok(buf)
258 }
259
260 #[inline]
262 fn write_into(self, writer: &mut (impl io::Write + ?Sized)) -> io::Result<()> {
263 struct Adapter<'a, T: ?Sized + 'a> {
266 inner: &'a mut T,
267 error: io::Result<()>,
268 }
269
270 impl<T: io::Write + ?Sized> fmt::Write for Adapter<'_, T> {
271 fn write_str(&mut self, s: &str) -> fmt::Result {
272 match self.inner.write_all(s.as_bytes()) {
273 Ok(()) => Ok(()),
274 Err(e) => {
275 self.error = Err(e);
276 Err(fmt::Error)
277 }
278 }
279 }
280 }
281
282 struct DisplayError;
283 impl fmt::Display for DisplayError {
284 fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 Err(fmt::Error)
286 }
287 }
288
289 let mut output = Adapter {
290 inner: writer,
291 error: Ok(()),
292 };
293 match self.render_into(&mut output) {
294 Ok(()) => Ok(()),
295 Err(err) => {
296 if output.error.is_err() {
298 output.error
299 } else {
300 match err {
301 Error::Fmt(fmt::Error) => Err(writer
302 .write_fmt(format_args!("{DisplayError}"))
303 .unwrap_err()),
304 Error::AttributeError(_) => {
305 Err(io::Error::new(io::ErrorKind::InvalidData, err))
306 }
307 err => Err(io::Error::new(io::ErrorKind::Other, err)),
308 }
309 }
310 }
311 }
312 }
313}
314
315type DynRenderInto<'a> = dyn FnOnce(&mut dyn fmt::Write) -> Result<()> + Send + 'a;
316
317pub struct TemplateFn<'a> {
320 size_hint: usize,
321 render_into: Box<DynRenderInto<'a>>,
322}
323
324impl<'a> Default for TemplateFn<'a> {
325 #[inline]
327 fn default() -> Self {
328 Self::new(0, |_| Ok(()))
329 }
330}
331
332impl<'a> TemplateFn<'a> {
333 pub fn new(
336 size_hint: usize,
337 render_into: impl FnOnce(&mut dyn fmt::Write) -> Result<()> + Send + 'a,
338 ) -> Self {
339 Self {
340 size_hint,
341 render_into: Box::new(render_into),
342 }
343 }
344
345 pub fn from_template<T: Template + Send + 'a>(template: T) -> Self {
348 Self::new(template.size_hint(), |writer| template.render_into(writer))
349 }
350}
351
352impl Template for TemplateFn<'_> {
353 const SIZE_HINT: usize = 20;
354
355 fn size_hint(&self) -> usize {
356 self.size_hint
357 }
358
359 fn render_into(self, writer: &mut dyn fmt::Write) -> Result<()> {
360 (self.render_into)(writer)
361 }
362}
363
364impl fmt::Debug for TemplateFn<'_> {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 #[derive(Debug)]
367 struct RenderInto;
368
369 f.debug_struct(std::any::type_name::<Self>())
370 .field("size_hint", &self.size_hint)
371 .field("render_into", &RenderInto)
372 .finish()
373 }
374}
375
376#[allow(dead_code)]
377const HTML_MIME_TYPE: &str = "text/html";
378
379#[doc(hidden)]
380#[cfg(feature = "axum")]
381pub mod __axum {
382 pub use axum_core::response::{IntoResponse, Response};
383 use http::{header, HeaderValue, StatusCode};
384
385 use super::*;
386
387 pub fn into_response<T: Template>(t: T) -> Response {
388 match t.render() {
389 Ok(body) => IntoResponse::into_response((
390 [(
391 header::CONTENT_TYPE,
392 HeaderValue::from_static(HTML_MIME_TYPE),
393 )],
394 body,
395 )),
396 Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
397 }
398 }
399
400 impl<'a> IntoResponse for TemplateFn<'a> {
401 fn into_response(self) -> Response {
402 into_response(self)
403 }
404 }
405}
406
407#[doc(hidden)]
408#[cfg(feature = "actix-web")]
409pub mod __actix_web {
410 pub use actix_web::{body::BoxBody, HttpRequest, HttpResponse, Responder};
411 use actix_web::{
412 http::{header::HeaderValue, StatusCode},
413 HttpResponseBuilder, ResponseError,
414 };
415
416 use super::*;
417
418 impl ResponseError for Error {}
419
420 pub fn respond_to<T: Template>(t: T) -> HttpResponse {
421 match t.render() {
422 Ok(body) => HttpResponseBuilder::new(StatusCode::OK)
423 .content_type(HeaderValue::from_static(HTML_MIME_TYPE))
424 .body(body),
425 Err(err) => HttpResponse::from_error(err),
426 }
427 }
428
429 impl<'a> Responder for TemplateFn<'a> {
430 type Body = BoxBody;
431
432 fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
433 respond_to(self)
434 }
435 }
436}
437
438#[doc(hidden)]
439#[cfg(feature = "hyper")]
440pub mod __hyper {
441 use hyper::{
442 header::{self, HeaderValue},
443 StatusCode,
444 };
445
446 use super::*;
447
448 pub type Body = String;
449 pub type Response<B = Body> = hyper::Response<B>;
450
451 fn try_respond<T: Template>(t: T) -> Result<Response> {
452 Ok(Response::builder()
453 .status(StatusCode::OK)
454 .header(
455 header::CONTENT_TYPE,
456 HeaderValue::from_static(HTML_MIME_TYPE),
457 )
458 .body(t.render()?.into())
459 .unwrap())
460 }
461
462 pub fn respond<T: Template>(t: T) -> Response {
463 try_respond(t).unwrap_or_else(|_| {
464 Response::builder()
465 .status(StatusCode::INTERNAL_SERVER_ERROR)
466 .body(Default::default())
467 .unwrap()
468 })
469 }
470
471 impl<'a> From<TemplateFn<'a>> for Response {
472 fn from(slf: TemplateFn<'a>) -> Self {
473 respond(slf)
474 }
475 }
476
477 }
484
485#[doc(hidden)]
486#[cfg(feature = "warp")]
487pub mod __warp {
488 pub use warp::reply::{Reply, Response};
489 use warp::{
490 http::{self, header, StatusCode},
491 hyper::Body,
492 };
493
494 use super::*;
495
496 pub fn reply<T: Template>(t: T) -> Response {
497 match t.render() {
498 Ok(body) => http::Response::builder()
499 .status(StatusCode::OK)
500 .header(header::CONTENT_TYPE, HTML_MIME_TYPE)
501 .body(body.into()),
502 Err(_) => http::Response::builder()
503 .status(StatusCode::INTERNAL_SERVER_ERROR)
504 .body(Body::empty()),
505 }
506 .unwrap()
507 }
508
509 impl<'a> Reply for TemplateFn<'a> {
510 fn into_response(self) -> Response {
511 reply(self)
512 }
513 }
514}
515
516#[doc(hidden)]
517#[cfg(feature = "tide")]
518pub mod __tide {
519 pub use tide::{Body, Response};
520
521 use super::*;
522
523 pub fn try_into_body<T: Template>(t: T) -> Result<Body> {
524 let mut body = Body::from_string(t.render()?);
525 body.set_mime(HTML_MIME_TYPE);
526 Ok(body)
527 }
528
529 pub fn into_response<T: Template>(t: T) -> Response {
530 match try_into_body(t) {
531 Ok(body) => {
532 let mut response = Response::new(200);
533 response.set_body(body);
534 response
535 }
536
537 Err(error) => {
538 let mut response = Response::new(500);
539 response.set_error(error);
540 response
541 }
542 }
543 }
544
545 impl<'a> TryFrom<TemplateFn<'a>> for Body {
546 type Error = Error;
547
548 fn try_from(slf: TemplateFn<'a>) -> Result<Self, Self::Error> {
549 try_into_body(slf)
550 }
551 }
552
553 impl<'a> From<TemplateFn<'a>> for Response {
554 fn from(slf: TemplateFn<'a>) -> Self {
555 into_response(slf)
556 }
557 }
558}
559
560#[doc(hidden)]
561#[cfg(feature = "gotham")]
562pub mod __gotham {
563 use gotham::hyper::{
564 self,
565 header::{self, HeaderValue},
566 StatusCode,
567 };
568 pub use gotham::{handler::IntoResponse, state::State};
569
570 use super::*;
571
572 pub type Response<B = hyper::Body> = hyper::Response<B>;
573
574 pub fn respond<T: Template>(t: T) -> Response {
575 match t.render() {
576 Ok(body) => Response::builder()
577 .status(StatusCode::OK)
578 .header(
579 header::CONTENT_TYPE,
580 HeaderValue::from_static(HTML_MIME_TYPE),
581 )
582 .body(body.into())
583 .unwrap(),
584 Err(_) => Response::builder()
585 .status(StatusCode::INTERNAL_SERVER_ERROR)
586 .body(vec![].into())
587 .unwrap(),
588 }
589 }
590
591 impl<'a> IntoResponse for TemplateFn<'a> {
592 fn into_response(self, _state: &State) -> Response {
593 respond(self)
594 }
595 }
596}
597
598#[doc(hidden)]
599#[cfg(feature = "rocket")]
600pub mod __rocket {
601 use std::io::Cursor;
602
603 use rocket::{
604 http::{Header, Status},
605 response::Response,
606 };
607 pub use rocket::{
608 response::{Responder, Result},
609 Request,
610 };
611
612 use super::*;
613
614 pub fn respond<T: Template>(t: T) -> Result<'static> {
615 let rsp = t.render().map_err(|_| Status::InternalServerError)?;
616 Response::build()
617 .header(Header::new("content-type", HTML_MIME_TYPE))
618 .sized_body(rsp.len(), Cursor::new(rsp))
619 .ok()
620 }
621
622 impl<'a, 'r, 'o: 'r> Responder<'r, 'o> for TemplateFn<'a> {
623 fn respond_to(self, _req: &'r Request<'_>) -> Result<'o> {
624 respond(self)
625 }
626 }
627}