From f432015022f91e053dfcd2a17087eef6adb84824 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Tue, 12 Oct 2021 22:37:12 -0700 Subject: [PATCH] Speed up get_json_pointer The regex only needs to get called when there's a double quote somewhere in the input. Benchmarks below are for the two cases: When there's no double quote, we skip the regex and go fast. When there is a double quote, we run the regex and go slow. test bench_get_json_pointer ... bench: 96 ns/iter (+/- 1) test bench_get_json_pointer_with_map ... bench: 518 ns/iter (+/- 18) --- benches/json_pointer.rs | 13 +++++++++++++ src/context.rs | 27 ++++++++++++++++++++++----- src/lib.rs | 4 +++- 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 benches/json_pointer.rs diff --git a/benches/json_pointer.rs b/benches/json_pointer.rs new file mode 100644 index 000000000..42a1986da --- /dev/null +++ b/benches/json_pointer.rs @@ -0,0 +1,13 @@ +#![feature(test)] +extern crate tera; +extern crate test; + +#[bench] +fn bench_get_json_pointer(b: &mut test::Bencher) { + b.iter(|| tera::get_json_pointer("foo.bar.baz")) +} + +#[bench] +fn bench_get_json_pointer_with_map(b: &mut test::Bencher) { + b.iter(|| tera::get_json_pointer("foo[\"http://example.com/\"].bar.baz")) +} diff --git a/src/context.rs b/src/context.rs index e8f84d3c1..8e8c8bee8 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,5 +1,5 @@ -use std::collections::BTreeMap; use std::io::Write; +use std::{collections::BTreeMap, iter}; use serde::ser::Serialize; use serde_json::value::{to_value, Map, Value}; @@ -212,12 +212,17 @@ pub fn get_json_pointer(key: &str) -> String { lazy_static::lazy_static! { // Split the key into dot-separated segments, respecting quoted strings as single units // to fix https://github.com/Keats/tera/issues/590 - static ref JSON_POINTER_REGEX: regex::Regex = regex::Regex::new("\"[^\"]*\"|[^.]+").unwrap(); + static ref JSON_POINTER_REGEX: regex::Regex = regex::Regex::new(r#""[^"]*"|[^.]+"#).unwrap(); } - let mut segments = vec![""]; - segments.extend(JSON_POINTER_REGEX.find_iter(key).map(|mat| mat.as_str().trim_matches('"'))); - segments.join("/") + if key.find('"').is_some() { + let segments: Vec<&str> = iter::once("") + .chain(JSON_POINTER_REGEX.find_iter(key).map(|mat| mat.as_str().trim_matches('"'))) + .collect(); + segments.join("/") + } else { + ["/", &key.replace(".", "/")].join("") + } } #[cfg(test)] @@ -227,6 +232,18 @@ mod tests { use serde_json::json; use std::collections::HashMap; + #[test] + fn test_get_json_pointer() { + assert_eq!(get_json_pointer(""), "/"); + assert_eq!(get_json_pointer("foo"), "/foo"); + assert_eq!(get_json_pointer("foo.bar.baz"), "/foo/bar/baz"); + assert_eq!(get_json_pointer(r#"foo["bar"].baz"#), r#"/foo["bar"]/baz"#); + assert_eq!( + get_json_pointer(r#"foo["bar"].baz["qux"].blub"#), + r#"/foo["bar"]/baz["qux"]/blub"# + ); + } + #[test] fn can_extend_context() { let mut target = Context::new(); diff --git a/src/lib.rs b/src/lib.rs index 114761308..b46f19ebb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,12 +22,14 @@ mod utils; // Library exports. -// Template is meant to be used internally only but is exported for test/bench. pub use crate::builtins::filters::Filter; pub use crate::builtins::functions::Function; pub use crate::builtins::testers::Test; pub use crate::context::Context; pub use crate::errors::{Error, ErrorKind, Result}; +// Template and get_json_pointer are meant to be used internally only but is exported for test/bench. +#[doc(hidden)] +pub use crate::context::get_json_pointer; #[doc(hidden)] pub use crate::template::Template; pub use crate::tera::Tera;