Skip to content

Commit 4cd62ca

Browse files
committed
Split pretty and ugly JSON filter impl
1 parent f7710a9 commit 4cd62ca

File tree

4 files changed

+106
-77
lines changed

4 files changed

+106
-77
lines changed

rinja/benches/to-json.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,49 @@
11
use criterion::{criterion_group, criterion_main, Criterion};
2-
use rinja::filters::json;
2+
use rinja::filters::{json, json_pretty};
33
use rinja_escape::{Html, MarkupDisplay};
44

55
criterion_main!(benches);
66
criterion_group!(benches, functions);
77

88
fn functions(c: &mut Criterion) {
99
c.bench_function("escape JSON", escape_json);
10+
c.bench_function("escape JSON (pretty)", escape_json_pretty);
1011
c.bench_function("escape JSON for HTML", escape_json_for_html);
12+
c.bench_function("escape JSON for HTML (pretty)", escape_json_for_html);
13+
c.bench_function("escape JSON for HTML (pretty)", escape_json_for_html_pretty);
1114
}
1215

1316
fn escape_json(b: &mut criterion::Bencher<'_>) {
1417
b.iter(|| {
1518
for &s in STRINGS {
16-
format!("{}", json(s, ()).unwrap());
19+
format!("{}", json(s).unwrap());
20+
}
21+
});
22+
}
23+
24+
fn escape_json_pretty(b: &mut criterion::Bencher<'_>) {
25+
b.iter(|| {
26+
for &s in STRINGS {
27+
format!("{}", json_pretty(s, 2).unwrap());
1728
}
1829
});
1930
}
2031

2132
fn escape_json_for_html(b: &mut criterion::Bencher<'_>) {
2233
b.iter(|| {
2334
for &s in STRINGS {
24-
format!("{}", MarkupDisplay::new_unsafe(json(s, ()).unwrap(), Html));
35+
format!("{}", MarkupDisplay::new_unsafe(json(s).unwrap(), Html));
36+
}
37+
});
38+
}
39+
40+
fn escape_json_for_html_pretty(b: &mut criterion::Bencher<'_>) {
41+
b.iter(|| {
42+
for &s in STRINGS {
43+
format!(
44+
"{}",
45+
MarkupDisplay::new_unsafe(json_pretty(s, 2).unwrap(), Html),
46+
);
2547
}
2648
});
2749
}

rinja/src/filters/json.rs

+73-66
Original file line numberDiff line numberDiff line change
@@ -19,105 +19,120 @@ use serde_json::ser::{to_writer, PrettyFormatter, Serializer};
1919
/// or in apostrophes with the (optional) safe filter `'{{data|json|safe}}'`.
2020
/// In HTML texts the output of e.g. `<pre>{{data|json|safe}}</pre>` is safe, too.
2121
#[inline]
22-
pub fn json(value: impl Serialize, indent: impl AsIndent) -> Result<impl fmt::Display, Infallible> {
23-
Ok(ToJson { value, indent })
22+
pub fn json(value: impl Serialize) -> Result<impl fmt::Display, Infallible> {
23+
Ok(ToJson { value })
24+
}
25+
26+
/// Serialize to formatted/prettified JSON (requires `json` feature)
27+
///
28+
/// This filter works the same as [`json()`], but it formats the data for human readability.
29+
/// It has an additional "indent" argument, which can either be an integer how many spaces to use
30+
/// for indentation (capped to 16 characters), or a string (e.g. `"\u{A0}\u{A0}"` for two
31+
/// non-breaking spaces).
32+
///
33+
/// ### Note
34+
///
35+
/// In rinja's template language, this filter is called `|json`, too. The right function is
36+
/// automatically selected depending on whether an `indent` argument was provided or not.
37+
#[inline]
38+
pub fn json_pretty(
39+
value: impl Serialize,
40+
indent: impl AsIndent,
41+
) -> Result<impl fmt::Display, Infallible> {
42+
Ok(ToJsonPretty { value, indent })
43+
}
44+
45+
#[derive(Debug, Clone)]
46+
struct ToJson<S> {
47+
value: S,
48+
}
49+
50+
#[derive(Debug, Clone)]
51+
struct ToJsonPretty<S, I> {
52+
value: S,
53+
indent: I,
2454
}
2555

2656
pub trait AsIndent {
27-
fn as_indent(&self) -> Option<&str>;
57+
fn as_indent(&self) -> &str;
2858
}
2959

3060
impl AsIndent for str {
3161
#[inline]
32-
fn as_indent(&self) -> Option<&str> {
33-
Some(self)
62+
fn as_indent(&self) -> &str {
63+
self
3464
}
3565
}
3666

3767
impl AsIndent for String {
3868
#[inline]
39-
fn as_indent(&self) -> Option<&str> {
40-
Some(self)
69+
fn as_indent(&self) -> &str {
70+
self
4171
}
4272
}
4373

44-
impl AsIndent for isize {
45-
fn as_indent(&self) -> Option<&str> {
46-
const SPACES: &str = " ";
47-
match *self < 0 {
48-
true => None,
49-
false => Some(&SPACES[..(*self as usize).min(SPACES.len())]),
50-
}
51-
}
52-
}
53-
54-
impl AsIndent for () {
74+
impl AsIndent for usize {
5575
#[inline]
56-
fn as_indent(&self) -> Option<&str> {
57-
None
76+
fn as_indent(&self) -> &str {
77+
const MAX_SPACES: usize = 16;
78+
const SPACES: &str = match str::from_utf8(&[b' '; MAX_SPACES]) {
79+
Ok(spaces) => spaces,
80+
Err(_) => panic!(),
81+
};
82+
83+
&SPACES[..(*self).min(SPACES.len())]
5884
}
5985
}
6086

6187
impl<T: AsIndent + ?Sized> AsIndent for &T {
6288
#[inline]
63-
fn as_indent(&self) -> Option<&str> {
89+
fn as_indent(&self) -> &str {
6490
T::as_indent(self)
6591
}
6692
}
6793

68-
impl<T: AsIndent> AsIndent for Option<T> {
69-
#[inline]
70-
fn as_indent(&self) -> Option<&str> {
71-
self.as_ref().and_then(T::as_indent)
72-
}
73-
}
74-
7594
impl<T: AsIndent + ?Sized> AsIndent for Box<T> {
7695
#[inline]
77-
fn as_indent(&self) -> Option<&str> {
96+
fn as_indent(&self) -> &str {
7897
T::as_indent(self.as_ref())
7998
}
8099
}
81100

82101
impl<T: AsIndent + ToOwned + ?Sized> AsIndent for std::borrow::Cow<'_, T> {
83102
#[inline]
84-
fn as_indent(&self) -> Option<&str> {
103+
fn as_indent(&self) -> &str {
85104
T::as_indent(self.as_ref())
86105
}
87106
}
88107

89108
impl<T: AsIndent + ?Sized> AsIndent for std::rc::Rc<T> {
90109
#[inline]
91-
fn as_indent(&self) -> Option<&str> {
110+
fn as_indent(&self) -> &str {
92111
T::as_indent(self.as_ref())
93112
}
94113
}
95114

96115
impl<T: AsIndent + ?Sized> AsIndent for std::sync::Arc<T> {
97116
#[inline]
98-
fn as_indent(&self) -> Option<&str> {
117+
fn as_indent(&self) -> &str {
99118
T::as_indent(self.as_ref())
100119
}
101120
}
102121

103-
#[derive(Debug, Clone)]
104-
struct ToJson<S, I> {
105-
value: S,
106-
indent: I,
122+
impl<S: Serialize> fmt::Display for ToJson<S> {
123+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124+
to_writer(JsonWriter(f), &self.value).map_err(|_| fmt::Error)
125+
}
107126
}
108127

109-
impl<S: Serialize, I: AsIndent> fmt::Display for ToJson<S, I> {
128+
impl<S: Serialize, I: AsIndent> fmt::Display for ToJsonPretty<S, I> {
110129
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111-
let f = JsonWriter(f);
112-
if let Some(indent) = self.indent.as_indent() {
113-
let formatter = PrettyFormatter::with_indent(indent.as_bytes());
114-
let mut serializer = Serializer::with_formatter(f, formatter);
115-
self.value
116-
.serialize(&mut serializer)
117-
.map_err(|_| fmt::Error)
118-
} else {
119-
to_writer(f, &self.value).map_err(|_| fmt::Error)
120-
}
130+
let indent = self.indent.as_indent();
131+
let formatter = PrettyFormatter::with_indent(indent.as_bytes());
132+
let mut serializer = Serializer::with_formatter(JsonWriter(f), formatter);
133+
self.value
134+
.serialize(&mut serializer)
135+
.map_err(|_| fmt::Error)
121136
}
122137
}
123138

@@ -166,51 +181,43 @@ mod tests {
166181

167182
#[test]
168183
fn test_ugly() {
169-
assert_eq!(json(true, ()).unwrap().to_string(), "true");
170-
assert_eq!(json("foo", ()).unwrap().to_string(), r#""foo""#);
171-
assert_eq!(json(true, ()).unwrap().to_string(), "true");
172-
assert_eq!(json("foo", ()).unwrap().to_string(), r#""foo""#);
184+
assert_eq!(json(true).unwrap().to_string(), "true");
185+
assert_eq!(json("foo").unwrap().to_string(), r#""foo""#);
186+
assert_eq!(json(true).unwrap().to_string(), "true");
187+
assert_eq!(json("foo").unwrap().to_string(), r#""foo""#);
173188
assert_eq!(
174-
json("<script>", ()).unwrap().to_string(),
189+
json("<script>").unwrap().to_string(),
175190
r#""\u003cscript\u003e""#
176191
);
177192
assert_eq!(
178-
json(vec!["foo", "bar"], ()).unwrap().to_string(),
193+
json(vec!["foo", "bar"]).unwrap().to_string(),
179194
r#"["foo","bar"]"#
180195
);
181-
assert_eq!(json(true, -1).unwrap().to_string(), "true");
182-
assert_eq!(json(true, Some(())).unwrap().to_string(), "true");
183-
assert_eq!(
184-
json(true, &Some(None::<isize>)).unwrap().to_string(),
185-
"true"
186-
);
187196
}
188197

189198
#[test]
190199
fn test_pretty() {
191-
assert_eq!(json(true, "").unwrap().to_string(), "true");
200+
assert_eq!(json_pretty(true, "").unwrap().to_string(), "true");
192201
assert_eq!(
193-
json("<script>", Some("")).unwrap().to_string(),
202+
json_pretty("<script>", "").unwrap().to_string(),
194203
r#""\u003cscript\u003e""#
195204
);
196205
assert_eq!(
197-
json(vec!["foo", "bar"], Some("")).unwrap().to_string(),
206+
json_pretty(vec!["foo", "bar"], "").unwrap().to_string(),
198207
r#"[
199208
"foo",
200209
"bar"
201210
]"#
202211
);
203212
assert_eq!(
204-
json(vec!["foo", "bar"], 2).unwrap().to_string(),
213+
json_pretty(vec!["foo", "bar"], 2).unwrap().to_string(),
205214
r#"[
206215
"foo",
207216
"bar"
208217
]"#
209218
);
210219
assert_eq!(
211-
json(vec!["foo", "bar"], &Some(&"————"))
212-
.unwrap()
213-
.to_string(),
220+
json_pretty(vec!["foo", "bar"], "————").unwrap().to_string(),
214221
r#"[
215222
————"foo",
216223
————"bar"

rinja/src/filters/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::fmt::{self, Write};
1010
#[cfg(feature = "serde_json")]
1111
mod json;
1212
#[cfg(feature = "serde_json")]
13-
pub use self::json::{json, AsIndent};
13+
pub use self::json::{json, json_pretty, AsIndent};
1414

1515
#[cfg(feature = "humansize")]
1616
use humansize::{ISizeFormatter, ToF64, DECIMAL};

rinja_derive/src/generator.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1364,14 +1364,14 @@ impl<'a> Generator<'a> {
13641364
));
13651365
}
13661366

1367-
buf.write(CRATE);
1368-
buf.write("::filters::json(");
1369-
self._visit_args(ctx, buf, args)?;
1370-
match args.len() {
1371-
1 => buf.write(", ()"),
1372-
2 => {}
1367+
let filter = match args.len() {
1368+
1 => "json",
1369+
2 => "json_pretty",
13731370
_ => return Err(ctx.generate_error("unexpected argument(s) in `json` filter", node)),
1374-
}
1371+
};
1372+
1373+
buf.write(format_args!("{CRATE}::filters::{filter}("));
1374+
self._visit_args(ctx, buf, args)?;
13751375
buf.write(")?");
13761376
Ok(DisplayWrap::Unwrapped)
13771377
}

0 commit comments

Comments
 (0)