jmespatch/
lib.rs

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