rstml_component/
fmt.rs

1use bytes::{BufMut, Bytes, BytesMut};
2use std::fmt;
3
4mod escape;
5
6/// A formatter for serializing HTML attribute values.
7pub struct HtmlAttributeFormatter<'a> {
8	any_written: bool,
9	buffer: &'a mut BytesMut,
10}
11
12impl<'a> HtmlAttributeFormatter<'a> {
13	/// Creates a new `HtmlAttributeFormatter` instance with the provided buffer.
14	///
15	/// # Arguments
16	///
17	/// - `buffer`: A mutable reference to the [BytesMut] buffer where the formatted content will be written.
18	///
19	/// # Returns
20	///
21	/// A new `HtmlAttributeFormatter` instance associated with the provided buffer.
22	fn new(buffer: &'a mut BytesMut) -> Self {
23		Self {
24			any_written: false,
25			buffer,
26		}
27	}
28
29	/// Write raw bytes to the attribute formatter. Note that this method does no sanitization or escaping of
30	/// the values. If you want the values to be sanitized, use the [write] method insted.
31	///
32	/// # Arguments
33	///
34	/// - `raw`: A reference to the raw byte slice that will be written to the buffer.
35	///
36	/// [write]: Self::write
37	pub fn write_bytes(&mut self, raw: &[u8]) {
38		self.buffer.reserve(raw.len() + 3);
39		if !self.any_written {
40			self.any_written = true;
41			self.buffer.put_slice(b"=\"");
42		}
43
44		self.buffer.put_slice(raw);
45	}
46
47	/// Writes escaped bytes to the attribute formatter, ensuring valid HTML attribute characters.
48	///
49	/// This method accepts a reference to a byte slice containing the content to be written to the
50	/// formatter's buffer. The provided `value` is escaped to ensure that it only contains valid HTML
51	/// attribute characters, preventing any unintentional issues with attribute values. The escaped
52	/// content is then written to the formatter's buffer.
53	///
54	/// # Arguments
55	///
56	/// - `value`: A reference to the raw byte slice containing the content to be escaped and written.
57	pub fn write(&mut self, value: &[u8]) {
58		self.write_bytes(&escape::attribute(value))
59	}
60
61	/// Reserves space in the buffer for writing additional bytes without re-allocation.
62	///
63	/// This method ensures that enough space is reserved in the formatter's buffer to accommodate
64	/// the writing of `additional` bytes without needing to reallocate memory. It's useful to call
65	/// this method before writing a significant amount of content, as it can help prevent frequent
66	/// reallocations and improve performance.
67	///
68	/// # Arguments
69	///
70	/// - `additional`: The number of additional bytes to reserve space for in the buffer.
71	pub fn reserve(&mut self, additional: usize) {
72		// add 3 for the opening and closing constant parts
73		self.buffer.reserve(additional + 3);
74	}
75
76	fn write_value(buffer: &mut BytesMut, value: impl HtmlAttributeValue) -> fmt::Result {
77		let mut attribute_formatter = HtmlAttributeFormatter::new(buffer);
78
79		value.fmt(&mut attribute_formatter)?;
80		if attribute_formatter.any_written {
81			buffer.reserve(1);
82			buffer.put_slice(b"\"");
83		}
84
85		Ok(())
86	}
87}
88
89/// A formatter for serializing HTML nodes and content.
90///
91/// The `HtmlFormatter` struct provides a versatile way to serialize HTML nodes and content,
92/// ensuring proper spacing, indentation, and formatting. It's designed to handle various
93/// types of HTML content and produce well-structured and readable HTML output.
94///
95/// NOTE: Currently, no indentation/readibility is supported. The plan is to implement that
96/// later.
97pub struct HtmlFormatter<'a> {
98	buffer: &'a mut BytesMut,
99}
100
101impl<'a> AsMut<HtmlFormatter<'a>> for HtmlFormatter<'a> {
102	fn as_mut(&mut self) -> &mut HtmlFormatter<'a> {
103		self
104	}
105}
106
107impl<'a> HtmlFormatter<'a> {
108	/// Creates a new `HtmlFormatter` instance with the provided buffer.
109	///
110	/// # Arguments
111	///
112	/// - `buffer`: A mutable reference to the [BytesMut] buffer where the formatted content will be written.
113	///
114	/// # Returns
115	///
116	/// A new `HtmlFormatter` instance associated with the provided buffer.
117	pub fn new(buffer: &'a mut BytesMut) -> Self {
118		Self { buffer }
119	}
120
121	/// Writes raw bytes to the formatter's buffer without escaping.
122	///
123	/// This method appends the specified raw bytes to the formatter's buffer without performing any
124	/// additional escaping or modification. It provides a low-level, raw API for directly writing
125	/// content to the buffer, which can be useful for situations where the content is already
126	/// properly formatted and safe to include as-is.
127	///
128	/// # Arguments
129	///
130	/// - `raw`: A reference to the raw byte slice that will be written to the buffer.
131	pub fn write_bytes(&mut self, raw: &[u8]) {
132		self.buffer.extend_from_slice(raw);
133	}
134
135	/// Writes escaped bytes to the formatter's buffer, ensuring valid HTML characters.
136	///
137	/// This method accepts a reference to a byte slice containing the content to be written to
138	/// the formatter's buffer. The provided `value` is escaped to ensure that it only contains
139	/// valid HTML characters, preventing any unintentional issues with the produced HTML content.
140	///
141	/// # Arguments
142	///
143	/// - `value`: A reference to the raw byte slice containing the content to be escaped and written.
144	pub fn write(&mut self, value: &[u8]) {
145		self.write_bytes(&escape::text(value))
146	}
147
148	// Writes a DOCTYPE declaration to the formatter's buffer.
149	///
150	/// This method appends a DOCTYPE declaration to the formatter's buffer. The provided `value` is
151	/// escaped to ensure that it only contains valid characters for a DOCTYPE declaration. The resulting
152	/// DOCTYPE declaration is properly formatted and follows the standard syntax of "<!DOCTYPE ...>".
153	///
154	/// # Arguments
155	///
156	/// - `value`: A reference to the raw byte slice containing the content for the DOCTYPE declaration.
157	pub fn write_doctype(&mut self, value: &[u8]) {
158		const DOCTYPE_PREFIX: &[u8] = b"<!DOCTYPE ";
159		const DOCTYPE_SUFFIX: &[u8] = b">";
160
161		let escaped = escape::text(value);
162		self
163			.buffer
164			.reserve(escaped.len() + DOCTYPE_PREFIX.len() + DOCTYPE_SUFFIX.len());
165
166		self.write_bytes(DOCTYPE_PREFIX);
167		self.write_bytes(&escaped);
168		self.write_bytes(DOCTYPE_SUFFIX);
169	}
170
171	/// Writes the start of an opening HTML tag to the formatter's buffer.
172	///
173	/// This method appends the start of an opening HTML tag to the formatter's buffer. The provided `tag`
174	/// is used as the tag name, and the tag is not closed. is commonly followed by either [write_attribute_name],
175	/// [write_self_close_tag], or [write_open_tag_end].
176	///
177	/// # Arguments
178	///
179	/// - `tag`: A reference to the raw byte slice containing the tag name for the opening tag.
180	///
181	/// [write_attribute_name]: Self::write_attribute_name
182	/// [write_self_close_tag]: Self::write_self_close_tag
183	/// [write_open_tag_end]: Self::write_open_tag_end
184	pub fn write_open_tag_start(&mut self, tag: &[u8]) {
185		self.buffer.reserve(tag.len() + 1);
186		self.write_bytes(b"<");
187		self.write_bytes(tag);
188	}
189
190	/// Writes an HTML attribute name to the formatter's buffer.
191	///
192	/// This method appends an HTML attribute name to the formatter's buffer. The provided `name` is
193	/// used as the attribute name.
194	///
195	/// # Arguments
196	///
197	/// - `name`: A reference to the raw byte slice containing the attribute name.
198	pub fn write_attribute_name(&mut self, name: &[u8]) {
199		self.buffer.reserve(name.len() + 1);
200		self.write_bytes(b" ");
201		self.write_bytes(name);
202	}
203
204	/// Writes an HTML attribute value to the formatter's buffer.
205	///
206	/// This method appends an HTML attribute value to the formatter's buffer. The provided `value` is
207	/// an instance of a type implementing the [HtmlAttributeValue] trait. The value is written to the
208	/// buffer, ensuring proper formatting and escaping if required.
209	///
210	/// # Arguments
211	///
212	/// - `value`: An instance implementing the [HtmlAttributeValue] trait, representing the attribute value.
213	///
214	/// # Returns
215	///
216	/// A [std::fmt::Result] indicating the success or failure of the writing operation.
217	pub fn write_attribute_value(&mut self, value: impl HtmlAttributeValue) -> fmt::Result {
218		HtmlAttributeFormatter::write_value(self.buffer, value)
219	}
220
221	pub fn write_attributes(&mut self, values: impl HtmlAttributes) -> fmt::Result {
222		let mut attribute_formatter = HtmlAttributesFormatter { inner: self };
223		values.fmt(&mut attribute_formatter)
224	}
225
226	/// Writes a self-closing indicator to the formatter's buffer.
227	///
228	/// This method appends a self-closing indicator " />" to the formatter's buffer. It's commonly used
229	/// after writing an opening tag to indicate that the tag is self-closing and has no associated content.
230	pub fn write_self_close_tag(&mut self) {
231		self.write_bytes(b" />");
232	}
233
234	/// Writes the end of an opening HTML tag to the formatter's buffer.
235	///
236	/// This method appends the end of an opening HTML tag ">" to the formatter's buffer. It's commonly
237	/// used after writing the tag name and its attributes to indicate the completion of the tag's opening.
238	pub fn write_open_tag_end(&mut self) {
239		self.write_bytes(b">");
240	}
241
242	/// Writes an HTML end tag to the formatter's buffer.
243	///
244	/// This method appends an HTML end tag "&lt;/tag&gt;" to the formatter's buffer. The provided `tag` is used
245	/// as the tag name for the end tag.
246	///
247	/// # Arguments
248	///
249	/// - `tag`: A reference to the raw byte slice containing the tag name for the end tag.
250	pub fn write_end_tag(&mut self, tag: &[u8]) {
251		self.buffer.reserve(tag.len() + 3);
252		self.write_bytes(b"</");
253		self.write_bytes(tag);
254		self.write_bytes(b">");
255	}
256
257	/// Writes HTML content to the formatter's buffer.
258	///
259	/// This method appends HTML content to the formatter's buffer. The provided `content` is an
260	/// instance of a type implementing the [HtmlContent] trait. The content is formatted and written
261	/// to the buffer according to the implementation of the [HtmlContent] trait.
262	///
263	/// # Arguments
264	///
265	/// - `content`: An instance implementing the [HtmlContent] trait, representing the HTML content to write.
266	///
267	/// # Returns
268	///
269	/// A [std::fmt::Result] indicating the success or failure of the writing operation.
270	pub fn write_content(&mut self, content: impl HtmlContent) -> fmt::Result {
271		content.fmt(self)
272	}
273
274	/// Writes an HTML comment to the formatter's buffer.
275	///
276	/// This method appends an HTML comment to the formatter's buffer. The provided `comment` is escaped
277	/// to ensure that it only contains valid characters for an HTML comment.
278	///
279	/// # Arguments
280	///
281	/// - `comment`: A reference to the raw byte slice containing the content for the HTML comment.
282	pub fn write_comment(&mut self, comment: &[u8]) {
283		const COMMENT_PREFIX: &[u8] = b"<!--";
284		const COMMENT_SUFFIX: &[u8] = b"-->";
285
286		let escaped = escape::text(comment);
287		self
288			.buffer
289			.reserve(escaped.len() + COMMENT_PREFIX.len() + COMMENT_SUFFIX.len());
290
291		self.write_bytes(COMMENT_PREFIX);
292		self.write_bytes(&escaped);
293		self.write_bytes(COMMENT_SUFFIX);
294	}
295
296	/// Reserves space in the buffer for writing additional bytes without reallocation.
297	///
298	/// This method ensures that enough space is reserved in the formatter's buffer to accommodate
299	/// the writing of `additional` bytes without needing to reallocate memory. It's useful to call
300	/// this method before writing a significant amount of content, as it can help prevent frequent
301	/// reallocations and improve performance.
302	///
303	/// # Arguments
304	///
305	/// - `additional`: The number of additional bytes to reserve space for in the buffer.
306	pub fn reserve(&mut self, additional: usize) {
307		self.buffer.reserve(additional);
308	}
309}
310
311pub struct HtmlAttributesFormatter<'a, 'b> {
312	inner: &'a mut HtmlFormatter<'b>,
313}
314
315impl<'a, 'b> HtmlAttributesFormatter<'a, 'b> {
316	pub fn write_attribute(&mut self, name: &[u8], value: impl HtmlAttributeValue) -> fmt::Result {
317		self.inner.write_attribute_name(name);
318		self.inner.write_attribute_value(value)
319	}
320}
321
322/// A trait representing content that can be formatted into HTML representation.
323///
324/// Types that implement this trait define how they should be formatted as HTML content.
325/// This trait provides methods to write the formatted content to various output formats,
326/// such as a byte buffer or a string.
327pub trait HtmlContent: Sized {
328	/// Formats the content and writes it to the provided [HtmlFormatter].
329	///
330	/// This method defines how the implementing type's content should be formatted as HTML.
331	/// Implementations of this method should use the provided [HtmlFormatter] to write the
332	/// formatted content according to the desired syntax and structure.
333	///
334	/// # Arguments
335	///
336	/// - `formatter`: A mutable reference to the [HtmlFormatter] that handles the output.
337	///
338	/// # Returns
339	///
340	/// A [std::fmt::Result] indicating the success or failure of the formatting operation.
341	fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result;
342
343	/// Writes the formatted content to the provided byte buffer.
344	///
345	/// This method creates an [HtmlFormatter] that writes to the given `buffer` and uses
346	/// the `fmt` method to write the formatted content into the buffer.
347	///
348	/// # Arguments
349	///
350	/// - `buffer`: A mutable reference to the byte buffer where the formatted content will be written.
351	///
352	/// # Returns
353	///
354	/// A [std::fmt::Result] indicating the success or failure of the formatting operation.
355	fn write_to(self, buffer: &mut BytesMut) -> fmt::Result {
356		let mut formatter = HtmlFormatter::new(buffer);
357		self.fmt(&mut formatter)
358	}
359
360	/// Converts the formatted content into a [Bytes] buffer.
361	///
362	/// This method writes the formatted content to a byte buffer and returns it as a [Bytes] object.
363	///
364	/// # Returns
365	///
366	/// A [Result] containing the [Bytes] object if successful, or a [std::fmt::Error] if formatting fails.
367	fn into_bytes(self) -> Result<Bytes, fmt::Error> {
368		let mut buffer = BytesMut::new();
369
370		self.write_to(&mut buffer)?;
371		Ok(buffer.freeze())
372	}
373
374	/// Converts the formatted content into a [String].
375	///
376	/// This method writes the formatted content to a byte buffer, then attempts to convert it into
377	/// a [String].
378	///
379	/// # Returns
380	///
381	/// A [Result] containing the [String] if successful, or a [std::fmt::Error] if formatting or
382	/// conversion to [String] fails.
383	fn into_string(self) -> Result<String, fmt::Error> {
384		let bytes = self.into_bytes()?;
385		String::from_utf8(bytes.to_vec()).map_err(|_| fmt::Error)
386	}
387}
388
389pub trait HtmlAttributes {
390	fn fmt(self, formatter: &mut HtmlAttributesFormatter) -> fmt::Result;
391}
392
393/// A trait representing a value that can be used as an attribute value in HTML components.
394///
395/// Types that implement this trait allow customization of how their values are formatted
396/// when used as attribute values in HTML tags. This trait is primarily used in conjunction
397/// with the [HtmlAttributeFormatter] to control the serialization of attribute values.
398pub trait HtmlAttributeValue {
399	/// Formats the value and writes it to the provided [HtmlAttributeFormatter].
400	///
401	/// This method is used to customize how the implementing type's value is serialized as an
402	/// attribute value in HTML. Implementations of this method should write the formatted value
403	/// to the provided [HtmlAttributeFormatter] using the appropriate formatting syntax.
404	///
405	/// # Arguments
406	///
407	/// - `formatter`: A mutable reference to the [HtmlAttributeFormatter] that handles the output.
408	///
409	/// # Returns
410	///
411	/// A [std::fmt::Result] indicating the success or failure of the formatting operation.
412	fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result;
413}
414
415/// A struct for embedding raw, unsanitized HTML content.
416///
417/// The `RawText` struct allows you to include raw HTML content without any sanitization or
418/// modification. This is useful when you need to merge multiple HTML fragments that are known
419/// to be safe or pre-sanitized. The `RawText` content is intended for situations where you have
420/// direct control over the content being embedded and ensure its safety.
421pub struct RawText<V>(V);
422
423impl<V: AsRef<[u8]>> RawText<V> {
424	/// Creates a new `RawText` instance with the given raw HTML content.
425	///
426	/// # Arguments
427	///
428	/// - `value`: The raw HTML content as a byte slice.
429	///
430	/// # Returns
431	///
432	/// A `RawText` instance wrapping the raw HTML content.
433	pub fn new(value: V) -> Self {
434		Self(value)
435	}
436}
437
438impl<V: AsRef<[u8]>> HtmlContent for RawText<V> {
439	fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
440		formatter.write_bytes(self.0.as_ref());
441		Ok(())
442	}
443}
444
445impl<V: AsRef<[u8]>> HtmlAttributeValue for RawText<V> {
446	fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
447		formatter.write_bytes(self.0.as_ref());
448		Ok(())
449	}
450}
451
452impl<F> HtmlContent for F
453where
454	F: FnOnce(&mut HtmlFormatter) -> fmt::Result,
455{
456	fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
457		self(formatter)
458	}
459}
460
461impl HtmlContent for () {
462	fn fmt(self, _formatter: &mut HtmlFormatter) -> fmt::Result {
463		Ok(())
464	}
465}
466
467impl HtmlAttributeValue for () {
468	fn fmt(self, _formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
469		Ok(())
470	}
471}
472
473impl HtmlAttributes for () {
474	fn fmt(self, _formatter: &mut HtmlAttributesFormatter) -> fmt::Result {
475		Ok(())
476	}
477}
478
479impl<T: HtmlContent> HtmlContent for Option<T> {
480	fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
481		match self {
482			None => Ok(()),
483			Some(template) => template.fmt(formatter),
484		}
485	}
486}
487
488impl<T: HtmlAttributeValue> HtmlAttributeValue for Option<T> {
489	fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
490		match self {
491			None => Ok(()),
492			Some(template) => template.fmt(formatter),
493		}
494	}
495}
496
497impl<T: HtmlAttributes> HtmlAttributes for Option<T> {
498	fn fmt(self, formatter: &mut HtmlAttributesFormatter) -> fmt::Result {
499		match self {
500			None => Ok(()),
501			Some(template) => template.fmt(formatter),
502		}
503	}
504}
505
506impl<N: AsRef<[u8]>, T: HtmlAttributeValue> HtmlAttributes for (N, T) {
507	fn fmt(self, formatter: &mut HtmlAttributesFormatter) -> fmt::Result {
508		let (name, value) = self;
509		formatter.write_attribute(name.as_ref(), value)
510	}
511}
512
513fn display(value: fmt::Arguments, mut write: impl FnMut(&[u8])) -> fmt::Result {
514	match value.as_str() {
515		Some(s) => {
516			write(s.as_bytes());
517			Ok(())
518		}
519
520		None => {
521			use fmt::Write;
522			struct Writer<F> {
523				writer: F,
524			}
525
526			impl<F> Write for Writer<F>
527			where
528				F: FnMut(&[u8]),
529			{
530				fn write_str(&mut self, s: &str) -> fmt::Result {
531					(self.writer)(s.as_bytes());
532					Ok(())
533				}
534			}
535
536			let mut writer = Writer { writer: &mut write };
537
538			write!(&mut writer, "{}", value)
539		}
540	}
541}
542
543impl<'a> HtmlContent for fmt::Arguments<'a> {
544	fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
545		display(self, |value| formatter.write(value))
546	}
547}
548
549impl<'a> HtmlAttributeValue for fmt::Arguments<'a> {
550	fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
551		display(self, |value| formatter.write(value))
552	}
553}
554
555macro_rules! impl_simple_write {
556	($ty:ty, as_ref) => {
557		impl HtmlAttributeValue for $ty {
558			fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
559				formatter.write(self.as_ref());
560				Ok(())
561			}
562		}
563
564		impl HtmlContent for $ty {
565			fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
566				formatter.write(self.as_ref());
567				Ok(())
568			}
569		}
570	};
571	($ty:ty, raw Display) => {
572		impl HtmlAttributeValue for $ty {
573			fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
574				display(format_args!("{}", self), |value| {
575					formatter.write_bytes(value)
576				})
577			}
578		}
579
580		impl HtmlContent for $ty {
581			fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
582				display(format_args!("{}", self), |value| {
583					formatter.write_bytes(value)
584				})
585			}
586		}
587	};
588}
589
590impl_simple_write!(String, as_ref);
591impl_simple_write!(&str, as_ref);
592impl_simple_write!(&String, as_ref);
593impl_simple_write!(Bytes, as_ref);
594impl_simple_write!(bool, raw Display);
595impl_simple_write!(u8, raw Display);
596impl_simple_write!(u16, raw Display);
597impl_simple_write!(u32, raw Display);
598impl_simple_write!(u64, raw Display);
599impl_simple_write!(u128, raw Display);
600impl_simple_write!(usize, raw Display);
601impl_simple_write!(i8, raw Display);
602impl_simple_write!(i16, raw Display);
603impl_simple_write!(i32, raw Display);
604impl_simple_write!(i64, raw Display);
605impl_simple_write!(i128, raw Display);
606impl_simple_write!(isize, raw Display);
607impl_simple_write!(f32, raw Display);
608impl_simple_write!(f64, raw Display);
609
610macro_rules! impl_tuple_write {
611	((
612		$($i:ident,)+
613	)) => {
614		#[automatically_derived]
615		impl<$($i,)+> HtmlContent for ($($i,)+)
616		where
617			$($i: HtmlContent,)+
618		{
619			fn fmt(self, formatter: &mut HtmlFormatter) -> fmt::Result {
620				#[allow(non_snake_case)]
621				let ($($i,)+) = self;
622				$(
623					$i.fmt(formatter)?;
624				)+
625				Ok(())
626			}
627		}
628
629		#[automatically_derived]
630		impl<$($i,)+> HtmlAttributeValue for ($($i,)+)
631		where
632			$($i: HtmlAttributeValue,)+
633		{
634			fn fmt(self, formatter: &mut HtmlAttributeFormatter) -> fmt::Result {
635				#[allow(non_snake_case)]
636				let ($($i,)+) = self;
637				$(
638					$i.fmt(formatter)?;
639				)+
640				Ok(())
641			}
642		}
643	};
644
645	($f:ident) => {
646		impl_tuple_write!(($f,));
647	};
648
649	($f:ident $($i:ident)+) => {
650		impl_tuple_write!(($f, $($i,)+));
651		impl_tuple_write!($($i)+);
652	};
653}
654
655impl_tuple_write!(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z);