|
|
@ -2,34 +2,41 @@ use serde::de::DeserializeOwned; |
|
|
|
use serde::Serialize; |
|
|
|
use serde::Serialize; |
|
|
|
use serde_json::{Map, Value}; |
|
|
|
use serde_json::{Map, Value}; |
|
|
|
use std::mem; |
|
|
|
use std::mem; |
|
|
|
use thiserror::Error; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
#[cfg(test)] |
|
|
|
#[macro_use] |
|
|
|
#[macro_use] |
|
|
|
extern crate serde_derive; |
|
|
|
extern crate serde_derive; |
|
|
|
|
|
|
|
#[macro_use] |
|
|
|
|
|
|
|
extern crate failure; |
|
|
|
|
|
|
|
|
|
|
|
/// Errors from dot_path methods
|
|
|
|
/// Errors from dot_path methods
|
|
|
|
#[derive(Debug, Error)] |
|
|
|
#[derive(Debug, Fail)] |
|
|
|
pub enum Error { |
|
|
|
pub enum Error { |
|
|
|
/// Path hit a value in the JSON object that is not array or map
|
|
|
|
/// Path hit a value in the JSON object that is not array or map
|
|
|
|
/// and could not continue the traversal.
|
|
|
|
/// and could not continue the traversal.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// (e.g. `foo.bar` in `{"foo": 123}`)
|
|
|
|
/// (e.g. `foo.bar` in `{"foo": 123}`)
|
|
|
|
#[error("Unexpected value reached while traversing path")] |
|
|
|
#[fail(display = "Unexpected value reached while traversing path")] |
|
|
|
BadPathElement, |
|
|
|
BadPathElement, |
|
|
|
|
|
|
|
|
|
|
|
/// Array index out of range
|
|
|
|
/// Array index out of range
|
|
|
|
#[error("Invalid array index: {0}")] |
|
|
|
#[fail(display = "Invalid array index: {}", _0)] |
|
|
|
BadIndex(usize), |
|
|
|
BadIndex(usize), |
|
|
|
|
|
|
|
|
|
|
|
/// Invalid (usually empty) key used in Map or Array.
|
|
|
|
/// Invalid (usually empty) key used in Map or Array.
|
|
|
|
/// If the key is valid but out of bounds, `BadIndex` will be used.
|
|
|
|
/// If the key is valid but out of bounds, `BadIndex` will be used.
|
|
|
|
#[error("Invalid key: {0}")] |
|
|
|
#[fail(display = "Invalid key: {}", _0)] |
|
|
|
InvalidKey(String), |
|
|
|
InvalidKey(String), |
|
|
|
|
|
|
|
|
|
|
|
/// Error serializing or deserializing a value
|
|
|
|
/// Error serializing or deserializing a value
|
|
|
|
#[error("Invalid array or map key")] |
|
|
|
#[fail(display = "Invalid array or map key")] |
|
|
|
SerdeError(#[from] serde_json::Error), |
|
|
|
SerdeError(#[fail(cause)] serde_json::Error), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl From<serde_json::Error> for Error { |
|
|
|
|
|
|
|
fn from(e: serde_json::Error) -> Self { |
|
|
|
|
|
|
|
Error::SerdeError(e) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
use crate::Error::{BadIndex, BadPathElement, InvalidKey}; |
|
|
|
use crate::Error::{BadIndex, BadPathElement, InvalidKey}; |
|
|
@ -112,27 +119,6 @@ pub trait DotPaths { |
|
|
|
self.dot_get_or(path, T::default()) |
|
|
|
self.dot_get_or(path, T::default()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Check if a value exists under a dotted path.
|
|
|
|
|
|
|
|
/// Returns error if the path is invalid, a string key was used to index into an array.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// # Special symbols
|
|
|
|
|
|
|
|
/// - `>` ... last element of an array
|
|
|
|
|
|
|
|
/// - `<` ... first element of an array (same as `0`)
|
|
|
|
|
|
|
|
fn dot_has_checked(&self, path: &str) -> Result<bool>; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Check if a value exists under a dotted path.
|
|
|
|
|
|
|
|
/// Returns false also when the path is invalid.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// Use `dot_has_checked` if you want to distinguish non-existent values from path errors.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// # Special symbols
|
|
|
|
|
|
|
|
/// - `>` ... last element of an array
|
|
|
|
|
|
|
|
/// - `<` ... first element of an array (same as `0`)
|
|
|
|
|
|
|
|
fn dot_has(&self, path: &str) -> bool { |
|
|
|
|
|
|
|
self.dot_has_checked(path) |
|
|
|
|
|
|
|
.unwrap_or_default() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Get a mutable reference to an item
|
|
|
|
/// Get a mutable reference to an item
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// If the path does not exist but a value on the path can be created (i.e. because the path
|
|
|
|
/// If the path does not exist but a value on the path can be created (i.e. because the path
|
|
|
@ -250,22 +236,6 @@ impl DotPaths for serde_json::Value { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn dot_has_checked(&self, path: &str) -> Result<bool> { |
|
|
|
|
|
|
|
match self { |
|
|
|
|
|
|
|
Value::Array(vec) => vec.dot_has_checked(path), |
|
|
|
|
|
|
|
Value::Object(map) => map.dot_has_checked(path), |
|
|
|
|
|
|
|
Value::Null => Ok(false), |
|
|
|
|
|
|
|
_ => { |
|
|
|
|
|
|
|
if path.is_empty() { |
|
|
|
|
|
|
|
Ok(true) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Path continues, but we can't traverse into a scalar
|
|
|
|
|
|
|
|
Ok(false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> { |
|
|
|
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> { |
|
|
|
match self { |
|
|
|
match self { |
|
|
|
Value::Array(vec) => vec.dot_get_mut(path), |
|
|
|
Value::Array(vec) => vec.dot_get_mut(path), |
|
|
@ -298,7 +268,7 @@ impl DotPaths for serde_json::Value { |
|
|
|
Value::Object(map) => map.dot_replace(path, value), |
|
|
|
Value::Object(map) => map.dot_replace(path, value), |
|
|
|
Value::Null => { |
|
|
|
Value::Null => { |
|
|
|
// spawn new
|
|
|
|
// spawn new
|
|
|
|
*self = new_by_path_root(path, value)?; |
|
|
|
mem::replace(self, new_by_path_root(path, value)?); |
|
|
|
Ok(None) |
|
|
|
Ok(None) |
|
|
|
} |
|
|
|
} |
|
|
|
_ => { |
|
|
|
_ => { |
|
|
@ -403,26 +373,6 @@ impl DotPaths for serde_json::Map<String, serde_json::Value> { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn dot_has_checked(&self, path: &str) -> Result<bool> { |
|
|
|
|
|
|
|
let (my, sub) = path_split(path); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if my.is_empty() { |
|
|
|
|
|
|
|
return Err(InvalidKey(my)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(sub_path) = sub { |
|
|
|
|
|
|
|
match self.get(&my).null_to_none() { |
|
|
|
|
|
|
|
None => Ok(false), |
|
|
|
|
|
|
|
Some(child) => child.dot_has_checked(sub_path), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
match self.get(&my).null_to_none() { |
|
|
|
|
|
|
|
None => Ok(false), |
|
|
|
|
|
|
|
Some(_) => Ok(true), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[allow(clippy::collapsible_if)] |
|
|
|
#[allow(clippy::collapsible_if)] |
|
|
|
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> { |
|
|
|
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> { |
|
|
|
let (my, sub) = path_split(path); |
|
|
|
let (my, sub) = path_split(path); |
|
|
@ -573,43 +523,6 @@ impl DotPaths for Vec<serde_json::Value> { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn dot_has_checked(&self, path: &str) -> Result<bool> { |
|
|
|
|
|
|
|
let (my, sub) = path_split(path); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if my.is_empty() { |
|
|
|
|
|
|
|
return Err(InvalidKey(my)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if self.is_empty() { |
|
|
|
|
|
|
|
return Ok(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let index: usize = match my.as_str() { |
|
|
|
|
|
|
|
">" => self.len() - 1, // non-empty checked above
|
|
|
|
|
|
|
|
"<" => 0, |
|
|
|
|
|
|
|
_ => my.parse().map_err(|_| { |
|
|
|
|
|
|
|
InvalidKey(my) |
|
|
|
|
|
|
|
})?, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if index >= self.len() { |
|
|
|
|
|
|
|
return Ok(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(subpath) = sub { |
|
|
|
|
|
|
|
match self.get(index).null_to_none() { |
|
|
|
|
|
|
|
None => Ok(false), |
|
|
|
|
|
|
|
Some(child) => child.dot_has_checked(subpath), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
match self.get(index).null_to_none() { |
|
|
|
|
|
|
|
// null is reported as unset
|
|
|
|
|
|
|
|
None => Ok(false), |
|
|
|
|
|
|
|
Some(_) => Ok(true), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[allow(clippy::collapsible_if)] |
|
|
|
#[allow(clippy::collapsible_if)] |
|
|
|
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> { |
|
|
|
fn dot_get_mut(&mut self, path: &str) -> Result<&mut Value> { |
|
|
|
let (my, sub) = path_split(path); |
|
|
|
let (my, sub) = path_split(path); |
|
|
@ -1195,7 +1108,7 @@ mod tests { |
|
|
|
// Borrow Null as mutable
|
|
|
|
// Borrow Null as mutable
|
|
|
|
let mut obj = Value::Null; |
|
|
|
let mut obj = Value::Null; |
|
|
|
let m = obj.dot_get_mut("").unwrap(); |
|
|
|
let m = obj.dot_get_mut("").unwrap(); |
|
|
|
*m = Value::from(123); |
|
|
|
std::mem::replace(m, Value::from(123)); |
|
|
|
assert_eq!(Value::from(123), obj); |
|
|
|
assert_eq!(Value::from(123), obj); |
|
|
|
|
|
|
|
|
|
|
|
// Create a parents path
|
|
|
|
// Create a parents path
|
|
|
@ -1237,30 +1150,6 @@ mod tests { |
|
|
|
assert_eq!(json!([{"foo": {"bar": {"dog": "cat"}}}]), Value::Array(obj)); |
|
|
|
assert_eq!(json!([{"foo": {"bar": {"dog": "cat"}}}]), Value::Array(obj)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
|
|
fn has() { |
|
|
|
|
|
|
|
let value = json!({ |
|
|
|
|
|
|
|
"one": "two", |
|
|
|
|
|
|
|
"x": [1, 2, {"foo": 123}] |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
assert!(value.dot_has("one")); |
|
|
|
|
|
|
|
assert!(!value.dot_has("two")); |
|
|
|
|
|
|
|
assert!(value.dot_has("x")); |
|
|
|
|
|
|
|
assert!(value.dot_has("x.0")); |
|
|
|
|
|
|
|
assert!(value.dot_has("x.<")); |
|
|
|
|
|
|
|
assert!(value.dot_has("x.>")); |
|
|
|
|
|
|
|
assert!(value.dot_has("x.>.foo")); |
|
|
|
|
|
|
|
assert!(!value.dot_has("x.banana")); |
|
|
|
|
|
|
|
assert!(!value.dot_has("x.>.foo.bar")); |
|
|
|
|
|
|
|
assert!(value.dot_has_checked("x.banana").is_err()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Ok(false) = value.dot_has_checked("x.9999") { |
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
panic!("dot_has_checked failed"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
#[test] |
|
|
|
fn stamps() { |
|
|
|
fn stamps() { |
|
|
|
let mut stamps = Value::Null; |
|
|
|
let mut stamps = Value::Null; |
|
|
|