jmespath/
lib.rs

1//! Rust implementation of JMESPath, a query language for JSON.
2//!
3//! # Compiling JMESPath expressions
4//!
5//! Use the `jmespath::compile` function to compile JMESPath expressions
6//! into reusable `Expression` structs. The `Expression` struct can be
7//! used multiple times on different values without having to recompile
8//! the expression.
9//!
10//! ```
11//! use jmespath;
12//!
13//! let expr = jmespath::compile("foo.bar | baz").unwrap();
14//!
15//! // Parse some JSON data into a JMESPath variable
16//! let json_str = "{\"foo\":{\"bar\":{\"baz\":true}}}";
17//! let data = jmespath::Variable::from_json(json_str).unwrap();
18//!
19//! // Search the data with the compiled expression
20//! let result = expr.search(data).unwrap();
21//! assert_eq!(true, result.as_boolean().unwrap());
22//! ```
23//!
24//! You can get the original expression as a string and the parsed expression
25//! AST from the `Expression` struct:
26//!
27//! ```
28//! use jmespath;
29//! use jmespath::ast::Ast;
30//!
31//! let expr = jmespath::compile("foo").unwrap();
32//! assert_eq!("foo", expr.as_str());
33//! assert_eq!(&Ast::Field {name: "foo".to_string(), offset: 0}, expr.as_ast());
34//! ```
35//!
36//! ## JMESPath variables
37//!
38//! In order to evaluate expressions against a known data type, the
39//! `jmespath::Variable` enum is used as both the input and output type.
40//! More specifically, `Rcvar` (or `jmespath::Rcvar`) is used to allow
41//! shared, reference counted data to be used by the JMESPath interpreter at
42//! runtime.
43//!
44//! By default, `Rcvar` is an `std::rc::Rc<Variable>`. However, by specifying
45//! the `sync` feature, you can utilize an `std::sync::Arc<Variable>` to
46//! share `Expression` structs across threads.
47//!
48//! Any type that implements `jmespath::ToJmespath` can be used in a JMESPath
49//! Expression. Various types have default `ToJmespath` implementations,
50//! including `serde::ser::Serialize`. Because `jmespath::Variable` implements
51//! `serde::ser::Serialize`, many existing types can be searched without needing
52//! an explicit coercion, and any type that needs coercion can be implemented
53//! using serde's macros or code generation capabilities. This includes a
54//! number of common types, including serde's `serde_json::Value` enum.
55//!
56//! The return value of searching data with JMESPath is also an `Rcvar`.
57//! `Variable` has a number of helper methods that make it a data type that
58//! can be used directly, or you can convert `Variable` to any serde value
59//! implementing `serde::de::Deserialize`.
60//!
61//! # Custom Functions
62//!
63//! You can register custom functions with a JMESPath expression by using
64//! a custom `Runtime`. When you call `jmespath::compile`, you are using a
65//! shared `Runtime` instance that is created lazily using `lazy_static`.
66//! This shared `Runtime` utilizes all of the builtin JMESPath functions
67//! by default. However, custom functions may be utilized by creating a custom
68//! `Runtime` and compiling expressions directly from the `Runtime`.
69//!
70//! ```
71//! use jmespath::{Runtime, Context, Rcvar};
72//! use jmespath::functions::{CustomFunction, Signature, ArgumentType};
73//!
74//! // Create a new Runtime and register the builtin JMESPath functions.
75//! let mut runtime = Runtime::new();
76//! runtime.register_builtin_functions();
77//!
78//! // Create an identity string function that returns string values as-is.
79//! runtime.register_function("str_identity", Box::new(CustomFunction::new(
80//!     Signature::new(vec![ArgumentType::String], None),
81//!     Box::new(|args: &[Rcvar], _: &mut Context| Ok(args[0].clone()))
82//! )));
83//!
84//! // You can also use normal closures as functions.
85//! runtime.register_function("identity",
86//!     Box::new(|args: &[Rcvar], _: &mut Context| Ok(args[0].clone())));
87//!
88//! let expr = runtime.compile("str_identity('foo')").unwrap();
89//! assert_eq!("foo", expr.search(()).unwrap().as_string().unwrap());
90//!
91//! let expr = runtime.compile("identity('bar')").unwrap();
92//! assert_eq!("bar", expr.search(()).unwrap().as_string().unwrap());
93//! ```
94
95#![cfg_attr(feature = "specialized", feature(specialization))]
96
97pub use crate::errors::{ErrorReason, JmespathError, RuntimeError};
98pub use crate::parser::{ParseResult, parse};
99pub use crate::runtime::Runtime;
100pub use crate::variable::Variable;
101
102pub mod ast;
103pub mod functions;
104
105use serde::ser;
106#[cfg(feature = "specialized")]
107use serde_json::Value;
108#[cfg(feature = "specialized")]
109use std::convert::TryInto;
110use std::fmt;
111use std::sync::LazyLock;
112
113use crate::ast::Ast;
114use crate::interpreter::{SearchResult, interpret};
115
116mod errors;
117mod interpreter;
118mod lexer;
119mod parser;
120mod runtime;
121mod variable;
122
123pub static DEFAULT_RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
124    let mut runtime = Runtime::new();
125    runtime.register_builtin_functions();
126    runtime
127});
128
129/// `Rc` reference counted JMESPath `Variable`.
130#[cfg(not(feature = "sync"))]
131pub type Rcvar = std::rc::Rc<Variable>;
132/// `Arc` reference counted JMESPath `Variable`.
133#[cfg(feature = "sync")]
134pub type Rcvar = std::sync::Arc<Variable>;
135
136/// Compiles a JMESPath expression using the default Runtime.
137///
138/// The default Runtime is created lazily the first time it is dereferenced
139/// by using the `lazy_static` macro.
140///
141/// The provided expression is expected to adhere to the JMESPath
142/// grammar: <https://jmespath.org/specification.html>
143#[inline]
144pub fn compile(expression: &str) -> Result<Expression<'static>, JmespathError> {
145    DEFAULT_RUNTIME.compile(expression)
146}
147
148/// Converts a value into a reference-counted JMESPath Variable.
149///
150#[cfg_attr(
151    feature = "specialized",
152    doc = "\
153There is a generic serde Serialize implementation, and since this
154documentation was compiled with the `specialized` feature turned
155**on**, there are also a number of specialized implementations for
156`ToJmespath` built into the library that should work for most
157cases."
158)]
159#[cfg_attr(
160    not(feature = "specialized"),
161    doc = "\
162There is a generic serde Serialize implementation. Since this
163documentation was compiled with the `specialized` feature turned
164**off**, this is the only implementation available.
165
166(If the `specialized` feature were turned on, there there would be
167a number of additional specialized implementations for `ToJmespath`
168built into the library that should work for most cases.)"
169)]
170pub trait ToJmespath {
171    fn to_jmespath(self) -> Result<Rcvar, JmespathError>;
172}
173
174/// Create searchable values from Serde serializable values.
175impl<T: ser::Serialize> ToJmespath for T {
176    #[cfg(not(feature = "specialized"))]
177    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
178        Variable::from_serializable(self).map(Rcvar::new)
179    }
180
181    #[cfg(feature = "specialized")]
182    default fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
183        Variable::from_serializable(self).map(Rcvar::new)
184    }
185}
186
187#[cfg(feature = "specialized")]
188impl ToJmespath for Value {
189    #[inline]
190    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
191        self.try_into().map(|var: Variable| Rcvar::new(var))
192    }
193}
194
195#[cfg(feature = "specialized")]
196impl ToJmespath for &Value {
197    #[inline]
198    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
199        self.try_into().map(|var: Variable| Rcvar::new(var))
200    }
201}
202
203#[cfg(feature = "specialized")]
204/// Identity coercion.
205impl ToJmespath for Rcvar {
206    #[inline]
207    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
208        Ok(self)
209    }
210}
211
212#[cfg(feature = "specialized")]
213impl ToJmespath for &Rcvar {
214    #[inline]
215    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
216        Ok(self.clone())
217    }
218}
219
220#[cfg(feature = "specialized")]
221impl ToJmespath for Variable {
222    #[inline]
223    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
224        Ok(Rcvar::new(self))
225    }
226}
227
228#[cfg(feature = "specialized")]
229impl ToJmespath for &Variable {
230    #[inline]
231    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
232        Ok(Rcvar::new(self.clone()))
233    }
234}
235
236#[cfg(feature = "specialized")]
237impl ToJmespath for String {
238    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
239        Ok(Rcvar::new(Variable::String(self)))
240    }
241}
242
243#[cfg(feature = "specialized")]
244impl ToJmespath for &str {
245    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
246        Ok(Rcvar::new(Variable::String(self.to_owned())))
247    }
248}
249
250#[cfg(feature = "specialized")]
251impl ToJmespath for i8 {
252    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
253        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
254    }
255}
256
257#[cfg(feature = "specialized")]
258impl ToJmespath for i16 {
259    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
260        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
261    }
262}
263
264#[cfg(feature = "specialized")]
265impl ToJmespath for i32 {
266    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
267        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
268    }
269}
270
271#[cfg(feature = "specialized")]
272impl ToJmespath for i64 {
273    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
274        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
275    }
276}
277
278#[cfg(feature = "specialized")]
279impl ToJmespath for u8 {
280    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
281        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
282    }
283}
284
285#[cfg(feature = "specialized")]
286impl ToJmespath for u16 {
287    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
288        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
289    }
290}
291
292#[cfg(feature = "specialized")]
293impl ToJmespath for u32 {
294    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
295        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
296    }
297}
298
299#[cfg(feature = "specialized")]
300impl ToJmespath for u64 {
301    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
302        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
303    }
304}
305
306#[cfg(feature = "specialized")]
307impl ToJmespath for isize {
308    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
309        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
310    }
311}
312
313#[cfg(feature = "specialized")]
314impl ToJmespath for usize {
315    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
316        Ok(Rcvar::new(Variable::Number(serde_json::Number::from(self))))
317    }
318}
319
320#[cfg(feature = "specialized")]
321impl ToJmespath for f32 {
322    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
323        (self as f64).to_jmespath()
324    }
325}
326
327#[cfg(feature = "specialized")]
328impl ToJmespath for f64 {
329    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
330        Ok(Rcvar::new(Variable::Number(
331            serde_json::Number::from_f64(self).ok_or_else(|| {
332                JmespathError::new(
333                    "",
334                    0,
335                    ErrorReason::Parse(format!("Cannot parse {self} into a Number")),
336                )
337            })?,
338        )))
339    }
340}
341
342#[cfg(feature = "specialized")]
343impl ToJmespath for () {
344    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
345        Ok(Rcvar::new(Variable::Null))
346    }
347}
348
349#[cfg(feature = "specialized")]
350impl ToJmespath for bool {
351    fn to_jmespath(self) -> Result<Rcvar, JmespathError> {
352        Ok(Rcvar::new(Variable::Bool(self)))
353    }
354}
355
356/// A compiled JMESPath expression.
357///
358/// The compiled expression can be used multiple times without incurring
359/// the cost of re-parsing the expression each time. The expression may
360/// be shared between threads if JMESPath is compiled with the `sync`
361/// feature, which forces the use of an `Arc` instead of an `Rc` for
362/// runtime variables.
363#[derive(Clone)]
364pub struct Expression<'a> {
365    ast: Ast,
366    expression: String,
367    runtime: &'a Runtime,
368}
369
370impl<'a> Expression<'a> {
371    /// Creates a new JMESPath expression.
372    ///
373    /// Normally you will create expressions using either `jmespath::compile()`
374    /// or using a jmespath::Runtime.
375    #[inline]
376    pub fn new<S>(expression: S, ast: Ast, runtime: &'a Runtime) -> Expression<'a>
377    where
378        S: Into<String>,
379    {
380        Expression {
381            expression: expression.into(),
382            ast,
383            runtime,
384        }
385    }
386
387    /// Returns the result of searching data with the compiled expression.
388    ///
389    /// The SearchResult contains a JMESPath Rcvar, or a reference counted
390    /// Variable. This value can be used directly like a JSON object.
391    /// Alternatively, Variable does implement Serde serialzation and
392    /// deserialization, so it can easily be marshalled to another type.
393    pub fn search<T: ToJmespath>(&self, data: T) -> SearchResult {
394        let mut ctx = Context::new(&self.expression, self.runtime);
395        interpret(&data.to_jmespath()?, &self.ast, &mut ctx)
396    }
397
398    /// Returns the JMESPath expression from which the Expression was compiled.
399    ///
400    /// Note that this is the same value that is returned by calling
401    /// `to_string`.
402    pub fn as_str(&self) -> &str {
403        &self.expression
404    }
405
406    /// Returns the AST of the parsed JMESPath expression.
407    ///
408    /// This can be useful for debugging purposes, caching, etc.
409    pub fn as_ast(&self) -> &Ast {
410        &self.ast
411    }
412}
413
414impl<'a> fmt::Display for Expression<'a> {
415    /// Shows the jmespath expression as a string.
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        write!(f, "{}", self.as_str())
418    }
419}
420
421impl<'a> fmt::Debug for Expression<'a> {
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        fmt::Display::fmt(self, f)
424    }
425}
426
427impl<'a> PartialEq for Expression<'a> {
428    fn eq(&self, other: &Expression<'_>) -> bool {
429        self.as_str() == other.as_str()
430    }
431}
432
433/// Context object used for error reporting.
434///
435/// The Context struct is mostly used when interacting between the
436/// interpreter and function implemenations. Unless you're writing custom
437/// JMESPath functions, this struct is an implementation detail.
438pub struct Context<'a> {
439    /// Expression string that is being interpreted.
440    pub expression: &'a str,
441    /// JMESPath runtime used to compile the expression and call functions.
442    pub runtime: &'a Runtime,
443    /// Ast offset that is currently being evaluated.
444    pub offset: usize,
445}
446
447impl<'a> Context<'a> {
448    /// Create a new context struct.
449    #[inline]
450    pub fn new(expression: &'a str, runtime: &'a Runtime) -> Context<'a> {
451        Context {
452            expression,
453            runtime,
454            offset: 0,
455        }
456    }
457}
458
459#[cfg(test)]
460mod test {
461    use super::ast::Ast;
462    use super::*;
463
464    #[test]
465    fn formats_expression_as_string_or_debug() {
466        let expr = compile("foo | baz").unwrap();
467        assert_eq!("foo | baz/foo | baz", format!("{expr}/{expr:?}"));
468    }
469
470    #[test]
471    fn implements_partial_eq() {
472        let a = compile("@").unwrap();
473        let b = compile("@").unwrap();
474        assert!(a == b);
475    }
476
477    #[test]
478    fn can_evaluate_jmespath_expression() {
479        let expr = compile("foo.bar").unwrap();
480        let var = Variable::from_json("{\"foo\":{\"bar\":true}}").unwrap();
481        assert_eq!(Rcvar::new(Variable::Bool(true)), expr.search(var).unwrap());
482    }
483
484    #[test]
485    fn can_get_expression_ast() {
486        let expr = compile("foo").unwrap();
487        assert_eq!(
488            &Ast::Field {
489                offset: 0,
490                name: "foo".to_string(),
491            },
492            expr.as_ast()
493        );
494    }
495
496    #[test]
497    fn test_creates_rcvar_from_tuple_serialization() {
498        use super::ToJmespath;
499        let t = (true, false);
500        assert_eq!("[true,false]", t.to_jmespath().unwrap().to_string());
501    }
502
503    #[test]
504    fn expression_clone() {
505        let expr = compile("foo").unwrap();
506        let _ = expr.clone();
507    }
508
509    #[test]
510    fn test_invalid_number() {
511        let _ = compile("6455555524");
512    }
513}