upon/fmt.rs
1//! Types for value formatters.
2//!
3//! Value formatters allow you to change the way a [`Value`] is formatted in the
4//! rendered template. They can be configured on the engine using
5//! [`set_default_formatter`][crate::Engine::set_default_formatter] or
6//! [`add_formatter`][crate::Engine::add_formatter].
7//!
8//! This module defines a [`Formatter`] type that is similar to
9//! [`std::fmt::Formatter`] so it should be a familiar API. A mutable reference
10//! to this struct is passed to formatter functions and writing to it will
11//! update the underlying buffer, be it a [`String`] or an arbitrary
12//! [`std::io::Write`] buffer.
13//!
14//! All formatter functions must have the following signature.
15//!
16//! ```text
17//! use upon::{Value, fmt};
18//! Fn(&mut fmt::Formatter<'_>, &Value) -> fmt::Result;
19//! ```
20//!
21//! Since [`Error`] implements `From<String>` and `From<&str>` it is possible
22//! to return custom messages from formatter functions. You can also easily
23//! propagate the standard library [`std::fmt::Error`].
24//!
25//! # Examples
26//!
27//! ### Escape ASCII
28//!
29//! Consider a use case where you want to escape all non-ascii characters in
30//! strings. We could define a value formatter for that using the standard
31//! library function [`escape_ascii`][slice::escape_ascii].
32//!
33//! ```
34//! use std::fmt::Write;
35//! use upon::{fmt, Engine, Value};
36//!
37//! fn escape_ascii(f: &mut fmt::Formatter<'_>, value: &Value) -> fmt::Result {
38//! match value {
39//! Value::String(s) => write!(f, "{}", s.as_bytes().escape_ascii())?,
40//! v => fmt::default(f, v)?, // fallback to default formatter
41//! };
42//! Ok(())
43//! }
44//!
45//! let mut engine = Engine::new();
46//! engine.add_formatter("escape_ascii", escape_ascii);
47//! ```
48//!
49//! We could then use this this formatter in templates like this.
50//!
51//! ```text
52//! {{ user.name | escape_ascii }}
53//! ```
54//!
55//! ### Error on [`Value::None`]
56//!
57//! The [`default`] value formatter formats [`Value::None`] as an empty string.
58//! This example demonstrates how you can configure a default formatter to error
59//! instead.
60//!
61//! ```
62//! use std::fmt::Write;
63//! use upon::{fmt, Engine, Value};
64//!
65//! fn error_on_none(f: &mut fmt::Formatter<'_>, value: &Value) -> fmt::Result {
66//! match value {
67//! Value::None => Err(fmt::Error::from("unable to format None")),
68//! v => fmt::default(f, v), // fallback to default formatter
69//! }
70//! }
71//!
72//! let mut engine = Engine::new();
73//! engine.set_default_formatter(&error_on_none);
74//! ```
75
76use std::fmt;
77use std::fmt::Write;
78use std::io;
79
80use crate::Value;
81
82/// A formatter function or closure.
83pub(crate) type DynFormatter = dyn Fn(&mut Formatter<'_>, &Value) -> Result + Sync + Send + 'static;
84
85/// A [`std::fmt::Write`] façade.
86pub struct Formatter<'a> {
87 buf: &'a mut (dyn fmt::Write + 'a),
88}
89
90/// The result type returned from a formatter function.
91pub type Result = std::result::Result<(), Error>;
92
93/// The error type returned from a formatter function.
94#[derive(Debug, Clone)]
95pub struct Error(Option<String>);
96
97pub(crate) struct Writer<W> {
98 writer: W,
99 err: Option<io::Error>,
100}
101
102impl<'a> Formatter<'a> {
103 pub(crate) fn with_string(buf: &'a mut String) -> Self {
104 Self { buf }
105 }
106
107 pub(crate) fn with_writer<W>(buf: &'a mut Writer<W>) -> Self
108 where
109 W: io::Write,
110 {
111 Self { buf }
112 }
113}
114
115impl fmt::Write for Formatter<'_> {
116 #[inline]
117 fn write_str(&mut self, s: &str) -> fmt::Result {
118 fmt::Write::write_str(self.buf, s)
119 }
120
121 #[inline]
122 fn write_char(&mut self, c: char) -> fmt::Result {
123 fmt::Write::write_char(self.buf, c)
124 }
125
126 #[inline]
127 fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
128 fmt::Write::write_fmt(self.buf, args)
129 }
130}
131
132impl Error {
133 pub(crate) fn message(self) -> Option<String> {
134 self.0
135 }
136}
137
138impl std::error::Error for Error {}
139
140impl std::fmt::Display for Error {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match &self.0 {
143 Some(msg) => write!(f, "{msg}"),
144 None => write!(f, "format error"),
145 }
146 }
147}
148
149impl From<&str> for Error {
150 fn from(msg: &str) -> Self {
151 Self(Some(msg.to_owned()))
152 }
153}
154
155impl From<String> for Error {
156 fn from(msg: String) -> Self {
157 Self(Some(msg))
158 }
159}
160
161impl From<fmt::Error> for Error {
162 fn from(_: fmt::Error) -> Self {
163 Self(None)
164 }
165}
166
167impl<W> Writer<W>
168where
169 W: io::Write,
170{
171 pub fn new(writer: W) -> Self {
172 Self { writer, err: None }
173 }
174
175 pub fn take_err(&mut self) -> Option<io::Error> {
176 self.err.take()
177 }
178}
179
180impl<W> fmt::Write for Writer<W>
181where
182 W: io::Write,
183{
184 #[inline]
185 fn write_str(&mut self, s: &str) -> fmt::Result {
186 self.writer.write_all(s.as_bytes()).map_err(|e| {
187 self.err = Some(e);
188 fmt::Error
189 })
190 }
191
192 #[inline]
193 fn write_char(&mut self, c: char) -> fmt::Result {
194 self.writer
195 .write_all(c.encode_utf8(&mut [0; 4]).as_bytes())
196 .map_err(|e| {
197 self.err = Some(e);
198 fmt::Error
199 })
200 }
201}
202
203/// The default value formatter.
204///
205/// Values are formatted as follows:
206/// - [`Value::None`]: empty string
207/// - [`Value::Bool`]: `true` or `false`
208/// - [`Value::Integer`]: the integer formatted using [`Display`][std::fmt::Display]
209/// - [`Value::Float`]: the float formatted using [`Display`][std::fmt::Display]
210/// - [`Value::String`]: the string, unescaped
211///
212/// Errors if the value is a [`Value::List`] or [`Value::Map`].
213#[inline]
214pub fn default(f: &mut Formatter<'_>, value: &Value) -> Result {
215 match value {
216 Value::None => {}
217 Value::Bool(b) => write!(f, "{b}")?,
218 Value::Integer(n) => write!(f, "{n}")?,
219 Value::Float(n) => write!(f, "{n}")?,
220 Value::String(s) => write!(f, "{s}")?,
221 value => {
222 return Err(Error::from(format!(
223 "expression evaluated to unformattable type {}",
224 value.human()
225 )));
226 }
227 }
228 Ok(())
229}