diff --git a/benches/template_benchmark.rs b/benches/template_benchmark.rs index 5199c9d9..b6467150 100644 --- a/benches/template_benchmark.rs +++ b/benches/template_benchmark.rs @@ -890,6 +890,43 @@ fn bench_template_memory_patterns(c: &mut Criterion) { // Filter Optimization Benchmarks // ============================================================================ +fn bench_to_nice_json_opt(c: &mut Criterion) { + let mut group = c.benchmark_group("to_nice_json_optimization"); + + let engine = TemplateEngine::new(); + let mut vars = HashMap::new(); + + // Create a moderately complex JSON object + let data = serde_json::json!({ + "name": "benchmark", + "values": (0..100).collect::>(), + "nested": { + "a": 1, + "b": "test", + "c": [1, 2, 3] + }, + "list_of_objects": (0..50).map(|i| { + serde_json::json!({ + "id": i, + "name": format!("item_{}", i) + }) + }).collect::>() + }); + + vars.insert("data".to_string(), data); + + // Test with indent=2 (common case) + let template = "{{ data | to_nice_json(indent=2) }}"; + + group.bench_function("to_nice_json_indent_2", |b| { + b.iter(|| { + engine.render(black_box(template), black_box(&vars)).unwrap() + }) + }); + + group.finish(); +} + fn bench_filter_join_opt(c: &mut Criterion) { let mut group = c.benchmark_group("filter_join_optimization"); @@ -941,6 +978,7 @@ fn bench_filter_title_opt(c: &mut Criterion) { criterion_group!( optimization_benches, + bench_to_nice_json_opt, bench_filter_join_opt, bench_filter_title_opt, ); diff --git a/src/modules/get_url.rs b/src/modules/get_url.rs index 60851a62..837be7b9 100644 --- a/src/modules/get_url.rs +++ b/src/modules/get_url.rs @@ -30,7 +30,6 @@ use super::{ }; use reqwest::Client; use sha2::{Digest, Sha256}; -use std::collections::HashMap; use std::path::Path; use std::time::Duration; @@ -243,6 +242,7 @@ impl Module for GetUrlModule { #[cfg(test)] mod tests { use super::*; + use std::collections::HashMap; #[test] fn test_get_url_name() { diff --git a/src/template.rs b/src/template.rs index fd5998dd..5beb2409 100644 --- a/src/template.rs +++ b/src/template.rs @@ -343,7 +343,7 @@ impl TemplateEngine { let templated = self.render_with_indexmap(s, vars)?; // Optimization: fast check if string starts with digit or sign before attempting expensive float parse - let is_maybe_number = templated.as_bytes().first().map_or(false, |&c| { + let is_maybe_number = templated.as_bytes().first().is_some_and(|&c| { c.is_ascii_digit() || c == b'-' || c == b'+' || c == b'.' }); @@ -962,8 +962,15 @@ fn format_json_with_indent( value: &T, indent: usize, ) -> std::result::Result { - let mut buf = Vec::new(); - let indent_bytes = vec![b' '; indent]; + let mut buf = Vec::with_capacity(128); + + const SPACES: [u8; 32] = [b' '; 32]; + let indent_bytes = if indent <= 32 { + std::borrow::Cow::Borrowed(&SPACES[0..indent]) + } else { + std::borrow::Cow::Owned(vec![b' '; indent]) + }; + let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes); let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter); value.serialize(&mut ser).map_err(|e| { @@ -972,12 +979,9 @@ fn format_json_with_indent( format!("JSON serialization failed: {}", e), ) })?; - String::from_utf8(buf).map_err(|e| { - minijinja::Error::new( - ErrorKind::InvalidOperation, - format!("JSON serialization failed: {}", e), - ) - }) + + // Safety: serde_json always produces valid UTF-8 + Ok(unsafe { String::from_utf8_unchecked(buf) }) } fn filter_mandatory(