diff --git a/Cargo.toml b/Cargo.toml index 30b5073..a5e9290 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,7 @@ url = { version = "2", optional = true } [features] default = ["std"] std = ["serde/std", "serde_json/std", "fluent-uri?/std"] + +[dev-dependencies] +quickcheck = "1.0.3" +quickcheck_macros = "1.0.0" diff --git a/README.md b/README.md index 0a7935f..e605871 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ use jsonptr::Pointer; use serde_json::json; let mut data = json!({"foo": { "bar": "baz" }}); -let ptr = Pointer::new(["foo", "bar"]); +let ptr = Pointer::from_static("/foo/bar"); let bar = ptr.resolve(&data).unwrap(); assert_eq!(bar, "baz"); ``` @@ -33,7 +33,7 @@ use jsonptr::{Pointer, Resolve}; use serde_json::json; let mut data = json!({ "foo": { "bar": "baz" }}); -let ptr = Pointer::new(["foo", "bar"]); +let ptr = Pointer::from_static("/foo/bar"); let bar = data.resolve(&ptr).unwrap(); assert_eq!(bar, "baz"); @@ -45,7 +45,7 @@ assert_eq!(bar, "baz"); use jsonptr::{Pointer, ResolveMut}; use serde_json::json; -let ptr = Pointer::try_from("/foo/bar").unwrap(); +let ptr = Pointer::from_static("/foo/bar"); let mut data = json!({ "foo": { "bar": "baz" }}); let mut bar = data.resolve_mut(&ptr).unwrap(); assert_eq!(bar, "baz"); @@ -59,7 +59,7 @@ assert_eq!(bar, "baz"); use jsonptr::Pointer; use serde_json::json; -let ptr = Pointer::try_from("/foo/bar").unwrap(); +let ptr = Pointer::from_static("/foo/bar"); let mut data = json!({}); let _previous = ptr.assign(&mut data, "qux").unwrap(); assert_eq!(data, json!({ "foo": { "bar": "qux" }})) @@ -71,7 +71,7 @@ assert_eq!(data, json!({ "foo": { "bar": "qux" }})) use jsonptr::{Assign, Pointer}; use serde_json::json; -let ptr = Pointer::try_from("/foo/bar").unwrap(); +let ptr = Pointer::from_static("/foo/bar"); let mut data = json!({}); let _previous = data.assign(&ptr, "qux").unwrap(); assert_eq!(data, json!({ "foo": { "bar": "qux" }})) @@ -86,7 +86,7 @@ use jsonptr::Pointer; use serde_json::json; let mut data = json!({ "foo": { "bar": { "baz": "qux" } } }); -let ptr = Pointer::new(&["foo", "bar", "baz"]); +let ptr = Pointer::from_static("/foo/bar/baz"); assert_eq!(ptr.delete(&mut data), Some("qux".into())); assert_eq!(data, json!({ "foo": { "bar": {} } })); @@ -102,12 +102,12 @@ use jsonptr::{Pointer, Delete}; use serde_json::json; let mut data = json!({ "foo": { "bar": { "baz": "qux" } } }); -let ptr = Pointer::new(["foo", "bar", "baz"]); +let ptr = Pointer::from_static("/foo/bar/baz"); assert_eq!(ptr.delete(&mut data), Some("qux".into())); assert_eq!(data, json!({ "foo": { "bar": {} } })); // replacing a root pointer replaces data with `Value::Null` -let ptr = Pointer::default(); +let ptr = Pointer::root(); let deleted = json!({ "foo": { "bar": {} } }); assert_eq!(data.delete(&ptr), Some(deleted)); assert!(data.is_null()); diff --git a/src/arbitrary.rs b/src/arbitrary.rs new file mode 100644 index 0000000..14288d3 --- /dev/null +++ b/src/arbitrary.rs @@ -0,0 +1,36 @@ +use crate::{PointerBuf, Token}; +use alloc::{ + boxed::Box, + string::{String, ToString}, + vec::Vec, +}; +use quickcheck::Arbitrary; + +impl Arbitrary for Token { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + Self::new(String::arbitrary(g)) + } + + fn shrink(&self) -> Box> { + Box::new(ToString::to_string(self).shrink().map(Self::new)) + } +} + +impl Arbitrary for PointerBuf { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let size = usize::arbitrary(g) % g.size(); + Self::from_tokens((0..size).map(|_| Token::arbitrary(g)).collect::>()) + } + + fn shrink(&self) -> Box> { + let tokens: Vec<_> = self.tokens().collect(); + Box::new((0..self.count()).map(move |i| { + let subset: Vec<_> = tokens + .iter() + .enumerate() + .filter_map(|(j, t)| (i != j).then_some(t.clone())) + .collect(); + Self::from_tokens(subset) + })) + } +} diff --git a/src/assign.rs b/src/assign.rs index 205cec6..c22cc02 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -1,7 +1,7 @@ use alloc::borrow::Cow; use serde_json::Value; -use crate::{Error, Pointer}; +use crate::{Error, Pointer, PointerBuf}; /// Assign is implemented by types which can internally assign a /// `serde_json::Value` by a JSON Pointer. @@ -23,6 +23,7 @@ impl Assign for Value { ptr.assign(self, value) } } + #[derive(Debug)] /// The data structure returned from a successful call to `assign`. pub struct Assignment<'a> { @@ -65,7 +66,7 @@ pub struct Assignment<'a> { /// ``` /// and you assigned `"new_value"` to `"/foo/bar/baz"`, then the value would /// be `Some("/foo/bar")`. - pub created_or_mutated: Pointer, + pub created_or_mutated: PointerBuf, /// A `Pointer` consisting of the path which was assigned. /// /// ## Example @@ -73,9 +74,9 @@ pub struct Assignment<'a> { /// use serde_json::json; /// use jsonptr::{Pointer, Assign}; /// let mut data = json!({ "foo": ["zero"] }); - /// let mut ptr = Pointer::try_from("/foo/-").unwrap(); + /// let mut ptr = Pointer::from_static("/foo/-"); /// let assignment = data.assign(&mut ptr, "one").unwrap(); - /// assert_eq!(assignment.assigned_to, Pointer::try_from("/foo/1").unwrap()); + /// assert_eq!(assignment.assigned_to, Pointer::from_static("/foo/1")); /// ``` - pub assigned_to: Pointer, + pub assigned_to: PointerBuf, } diff --git a/src/delete.rs b/src/delete.rs index b5f3862..049bc7f 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -19,20 +19,16 @@ impl Delete for Value { #[cfg(test)] mod tests { - use serde_json::json; - use super::*; + use serde_json::json; #[test] fn test_issue_18() { - let mut data = json!( - { - "Example": 21, - "test": "test" - } - ); - let pointer = Pointer::new(["Example"]); - println!("{}", pointer); + let mut data = json!({ + "Example": 21, + "test": "test" + }); + let pointer = Pointer::from_static("/Example"); pointer.delete(&mut data); assert_eq!(json!({"test": "test"}), data); } diff --git a/src/error.rs b/src/error.rs index 447fa2e..784fb67 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,7 @@ use alloc::{ vec::Vec, }; -use crate::{Pointer, Token}; +use crate::{PointerBuf, Token}; use core::{ // error::Error as StdError, fmt::{Debug, Display, Formatter}, @@ -97,14 +97,14 @@ impl std::error::Error for Error {} /// use serde_json::json; /// use jsonptr::{Pointer, ResolveMut, Resolve, UnresolvableError}; /// let mut data = json!({ "foo": "bar" }); -/// let ptr = Pointer::try_from("/foo/unreachable").unwrap(); +/// let ptr = Pointer::from_static("/foo/unreachable"); /// let err = data.resolve_mut(&ptr).unwrap_err(); -/// assert_eq!(err, UnresolvableError::new(ptr.clone()).into()); +/// assert_eq!(err, UnresolvableError::new(ptr.to_buf()).into()); /// ``` #[derive(Clone, PartialEq, Eq, Debug)] pub struct UnresolvableError { /// The unresolved `Pointer`. - pub pointer: Pointer, + pub pointer: PointerBuf, /// The leaf node, if applicable, which was expected to be either an /// `Object` or an `Array`. pub leaf: Option, @@ -115,7 +115,7 @@ impl std::error::Error for UnresolvableError {} impl UnresolvableError { /// Creates a new `UnresolvableError` with the given `Pointer`. - pub fn new(pointer: Pointer) -> Self { + pub fn new(pointer: PointerBuf) -> Self { let leaf = if pointer.count() >= 2 { Some(pointer.get(pointer.count() - 2).unwrap()) } else { @@ -288,11 +288,11 @@ impl std::error::Error for MalformedPointerError {} #[derive(Debug, PartialEq, Eq, Clone)] pub struct NotFoundError { /// The `Pointer` which could not be resolved. - pub pointer: Pointer, + pub pointer: PointerBuf, } impl NotFoundError { /// Creates a new `NotFoundError` with the given `Pointer`. - pub fn new(pointer: Pointer) -> Self { + pub fn new(pointer: PointerBuf) -> Self { NotFoundError { pointer } } } @@ -317,7 +317,7 @@ pub struct ReplaceTokenError { /// The number of tokens in the `Pointer`. pub count: usize, /// The subject `Pointer`. - pub pointer: Pointer, + pub pointer: PointerBuf, } #[cfg(feature = "std")] impl std::error::Error for ReplaceTokenError {} diff --git a/src/lib.rs b/src/lib.rs index 14cc4b6..49aede6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,3 +22,6 @@ pub mod prelude; mod tokens; pub use tokens::*; + +#[cfg(test)] +mod arbitrary; diff --git a/src/pointer.rs b/src/pointer.rs index 02258cf..a51e47f 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1,30 +1,22 @@ -use core::{ - borrow::Borrow, - cmp::Ordering, - mem, - ops::{ControlFlow, Deref}, - str::{FromStr, Split}, -}; - use crate::{ - Assignment, Error, MalformedPointerError, NotFoundError, OutOfBoundsError, ReplaceTokenError, - Token, Tokens, UnresolvableError, + Assignment, Error, MalformedPointerError, NotFoundError, ReplaceTokenError, Token, Tokens, + UnresolvableError, }; use alloc::{ borrow::{Cow, ToOwned}, string::{String, ToString}, - vec, vec::Vec, }; +use core::{borrow::Borrow, cmp::Ordering, mem, ops::Deref, slice, str::FromStr}; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{map::Entry, Map, Value}; /// Helper type for deserialization. Either a valid [`Pointer`] or a string and /// the parsing error #[derive(Debug, Clone, PartialEq, Eq)] pub enum MaybePointer { /// A valid [`Pointer`]. - Pointer(Pointer), + Pointer(PointerBuf), /// An invalid [`Pointer`] and the error that caused it. Malformed(String, MalformedPointerError), } @@ -43,7 +35,7 @@ impl<'de> Deserialize<'de> for MaybePointer { where D: serde::Deserializer<'de>, { - String::deserialize(deserializer).map(|s| match Pointer::from_str(&s) { + String::deserialize(deserializer).map(|s| match PointerBuf::from_str(&s) { Ok(ptr) => MaybePointer::Pointer(ptr), Err(e) => MaybePointer::Malformed(s, e), }) @@ -61,6 +53,87 @@ impl Serialize for MaybePointer { } } +fn validate(value: &str) -> Result<&str, MalformedPointerError> { + let mut chars = value.chars(); + + match chars.next() { + Some('#') => { + let next = chars.next(); + if next.is_none() { + return Ok(value); + } + if next != Some('/') { + return Err(MalformedPointerError::NoLeadingSlash(value.into())); + } + } + Some('/') => {} + Some(_) => { + return Err(MalformedPointerError::NoLeadingSlash(value.into())); + } + None => { + return Ok(value); + } + } + + while let Some(c) = chars.next() { + if c == '~' { + match chars.next() { + Some('0') | Some('1') => {} + _ => { + return Err(MalformedPointerError::InvalidEncoding(value.into())); + } + } + } + } + + Ok(value) +} + +unsafe fn extend_one_before(s: &str) -> &str { + let ptr = s.as_ptr().offset(-1); + let len = s.len() + 1; + let slice = slice::from_raw_parts(ptr, len); + core::str::from_utf8_unchecked(slice) +} + +const fn is_valid_ptr(value: &str) -> bool { + let bytes = value.as_bytes(); + + if bytes.is_empty() { + // root pointer + return true; + } + + match bytes[0] { + b'#' => { + if bytes.len() == 1 { + // also root pointer + return true; + } + if bytes[1] != b'/' { + return false; + } + } + b'/' => {} + _ => return false, + } + + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'~' { + if i + 1 >= bytes.len() { + return false; + } + if bytes[i + 1] != b'0' && bytes[i + 1] != b'1' { + return false; + } + } + i += 1; + } + + true +} + /// A JSON Pointer is a string containing a sequence of zero or more reference /// tokens, each prefixed by a '/' character. /// @@ -73,169 +146,166 @@ impl Serialize for MaybePointer { /// use serde_json::{json, Value}; /// /// let data = json!({ "foo": { "bar": "baz" } }); -/// let ptr = Pointer::new(&["foo", "bar"]); +/// let ptr = Pointer::from_static("/foo/bar"); /// let bar = data.resolve(&ptr).unwrap(); /// assert_eq!(bar, "baz"); /// ``` -#[cfg_attr( - feature = "url", - doc = r##" -```rust -use jsonptr::Pointer; -let expected = Pointer::new(&["foo", "bar"]); -let url = url::Url::parse("https://example.com#/foo/bar").unwrap(); -assert_eq!(expected, Pointer::try_from(url).unwrap()) -``` -"## -)] -#[derive(Clone, Default)] -pub struct Pointer { - inner: String, - count: usize, +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Pointer(str); + +impl core::fmt::Display for Pointer { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } } impl Pointer { - /// Creates a root json pointer. + /// Private constructor for strings that are known to be correctly encoded /// - /// alias for `default` - pub fn root() -> Self { - Self::default() + /// This is a zero-copy constructor + fn new + ?Sized>(s: &S) -> &Self { + unsafe { &*(s.as_ref() as *const str as *const Self) } } - /// Creates a new `Pointer` from a slice of non-encoded strings. - pub fn new(tokens: V) -> Self - where - V: AsRef<[T]>, - Token: for<'a> From<&'a T>, - { - let mut inner = String::new(); - let tokens = tokens.as_ref(); - for t in tokens.iter().map(Into::::into) { - inner.push('/'); - inner.push_str(t.encoded()); - } - Pointer { - inner, - count: tokens.len(), - } + /// Constant reference to a root pointer + pub const fn root() -> &'static Self { + unsafe { &*("" as *const str as *const Self) } } - /// Parses `value` as a JSON Pointer. + + /// Attempts to parse a string into a `Pointer`. /// - /// # Errors - /// returns `MalformedPointerError` if `value` is not able to be parsed as a - /// valid JSON Pointer. - pub fn parse(value: &str) -> Result { - Self::from_str(value) - } - /// Extracts a string slice containing the entire encoded `Pointer`. - pub fn as_str(&self) -> &str { - &self.inner + /// If successful, this does not allocate. + pub fn parse + ?Sized>(s: &S) -> Result<&Self, MalformedPointerError> { + validate(s.as_ref()).map(Self::new) } - /// Pushes a `Token` onto the front of this `Pointer`. - pub fn push_front(&mut self, token: Token) { - self.count += 1; - self.inner.insert(0, '/'); - self.inner.insert_str(1, token.encoded()); - } - /// Pushes a `Token` onto the back of this `Pointer`. - pub fn push_back(&mut self, token: Token) { - self.count += 1; - self.inner.push('/'); - self.inner.push_str(token.encoded()); + /// Creates a static `Pointer` from a string. + /// + /// # Panics + /// + /// Will panic if the string does not represent a valid pointer. + /// + /// # Examples + /// + /// ``` + /// use jsonptr::{Pointer, Resolve}; + /// use serde_json::{json, Value}; + /// + /// const POINTER: &Pointer = Pointer::from_static("/foo/bar"); + /// let data = json!({ "foo": { "bar": "baz" } }); + /// let bar = data.resolve(POINTER).unwrap(); + /// assert_eq!(bar, "baz"); + /// ```` + pub const fn from_static(s: &'static str) -> &'static Self { + assert!(is_valid_ptr(s), "invalid JSON Pointer"); + unsafe { &*(s as *const str as *const Self) } } - /// Removes and returns the last `Token` in the `Pointer` if it exists. - pub fn pop_back(&mut self) -> Option { - if self.is_root() { - return None; - } - if self.count > 0 { - self.count -= 1; - } - let (front, back) = self.inner.rsplit_once('/').expect("`self.count` was > 0"); - let back = Token::from_encoded(back); - - self.inner = front.to_owned(); + /// The encoded string representation of this `Pointer` + pub fn as_str(&self) -> &str { + &self.0 + } - Some(back) + /// Converts into an owned PointerBuf + pub fn to_buf(&self) -> PointerBuf { + PointerBuf(self.0.to_string()) } - /// Removes and returns the first `Token` in the `Pointer` if it exists. - pub fn pop_front(&mut self) -> Option { - (!self.inner.is_empty() && self.count > 0).then(|| { - self.count -= 1; - if let Some((front, back)) = self.inner[1..].split_once('/') { - let front = Token::from_encoded(front); - self.inner = String::from("/") + back; - front - } else { - let token = Token::from_encoded(&self.inner[1..]); - self.inner.truncate(0); - token - } - }) + /// Returns an iterator of `Token`s in the `Pointer`. + pub fn tokens(&self) -> Tokens { + let mut s = self.0.split('/'); + // skipping the first '/' + s.next(); + Tokens::new(s) } + /// Returns the number of tokens in the `Pointer`. pub fn count(&self) -> usize { - self.count + self.tokens().count() } + /// Returns `true` if the JSON Pointer equals `""`. pub fn is_root(&self) -> bool { - self.inner.is_empty() + self.0.is_empty() } /// Returns a `serde_json::Value` representation of this `Pointer` pub fn to_value(&self) -> Value { - Value::String(self.to_string()) + Value::String(self.0.to_string()) } + /// Returns the last `Token` in the `Pointer`. pub fn back(&self) -> Option { - if self.is_root() { - return None; - } - - let (_, back) = self - .inner + self.0 .rsplit_once('/') - .expect("`self.is_root()` is false, thus pointer starts with `/`"); - Some(Token::from_encoded(back)) + .map(|(_, back)| Token::from_encoded(back)) } + /// Returns the last token in the `Pointer`. /// /// alias for `back` pub fn last(&self) -> Option { self.back() } + /// Returns the first `Token` in the `Pointer`. pub fn front(&self) -> Option { if self.is_root() { return None; } - self.inner[1..] + self.0[1..] .split_once('/') .map_or_else( - || Token::from_encoded(&self.inner[1..]), + || Token::from_encoded(&self.0[1..]), |(front, _)| Token::from_encoded(front), ) .into() } + /// Returns the first `Token` in the `Pointer`. /// /// alias for `front` pub fn first(&self) -> Option { self.front() } - /// Merges two `Pointer`s by appending `other` onto `self`. - pub fn append(&mut self, other: &Pointer) -> &Pointer { - self.count += other.count; + + /// Splits the `Pointer` into the first `Token` and a remainder. + pub fn split_front(&self) -> Option<(Token, &Self)> { if self.is_root() { - self.inner = other.inner.clone(); - } else if !other.is_root() { - self.inner.push_str(&other.inner); + return None; } - self + self.0[1..] + .split_once('/') + .map_or_else( + || (Token::from_encoded(&self.0[1..]), Self::root()), + |(front, back)| { + // We want to include the delimiter character too! + // SAFETY: if split was successful, then the delimiter + // character exists before the start of the second `str`. + let back = unsafe { extend_one_before(back) }; + (Token::from_encoded(front), Self::new(back)) + }, + ) + .into() } + + /// Splits the `Pointer` into the parent path and the last `Token`. + pub fn split_back(&self) -> Option<(&Self, Token)> { + self.0 + .rsplit_once('/') + .map(|(front, back)| (Self::new(front), Token::from_encoded(back))) + } + + /// A pointer to the parent of the current path. + pub fn parent(&self) -> Option<&Self> { + self.0.rsplit_once('/').map(|(front, _)| Self::new(front)) + } + + /// Returns the pointer stripped of the given suffix. + pub fn strip_suffix<'a>(&'a self, suffix: &Self) -> Option<&'a Self> { + self.0.strip_suffix(&suffix.0).map(Self::new) + } + /// Attempts to get a `Token` by the index. Returns `None` if the index is /// out of bounds. /// @@ -243,163 +313,96 @@ impl Pointer { /// ```rust /// use jsonptr::{Pointer, Token}; /// - /// let ptr = Pointer::new(&["foo", "bar"]); + /// let ptr = Pointer::from_static("/foo/bar"); /// assert_eq!(ptr.get(0), Some("foo".into())); /// assert_eq!(ptr.get(1), Some("bar".into())); /// assert_eq!(ptr.get(2), None); /// - /// let ptr = Pointer::default(); + /// let ptr = Pointer::root(); /// assert_eq!(ptr.get(0), None); /// ``` pub fn get(&self, index: usize) -> Option { - if self.is_root() { - return None; - } - let tokens = self.tokens().collect::>(); - tokens.get(index).cloned() - } - /// Attempts to replace a `Token` by the index, returning the replaced - /// `Token` if it already exists. Returns `None` otherwise. - /// - /// A `ReplaceTokenError` is returned if the index is out of bounds. - pub fn replace_token( - &mut self, - index: usize, - token: Token, - ) -> Result, ReplaceTokenError> { - if self.is_root() { - return Err(ReplaceTokenError { - count: self.count, - index, - pointer: self.clone(), - }); - } - let mut tokens = self.tokens().collect::>(); - if index > tokens.len() { - return Err(ReplaceTokenError { - count: tokens.len(), - index, - pointer: self.clone(), - }); - } - let old = tokens.get(index).cloned(); - tokens[index] = token; - - self.inner = String::from("/") - + &tokens - .iter() - .map(Token::encoded) - .collect::>() - .join("/"); - Ok(old) - } - - /// Clears the `Pointer`, setting it to root (`""`). - pub fn clear(&mut self) { - self.count = 0; - self.inner = String::from(""); - } - - /// Returns an iterator of `Token`s in the `Pointer`. - pub fn tokens(&self) -> Tokens { - Tokens::new(self.split()) + self.tokens().nth(index).to_owned() } /// Attempts to resolve a `Value` based on the path in this `Pointer`. - pub fn resolve<'v>(&self, value: &'v Value) -> Result<&'v Value, Error> { - let Resolved { - value, - remaining, - mut resolved, - } = self.try_resolve(value)?; - - if remaining.is_root() { - Ok(value) - } else { + pub fn resolve<'v>(&self, mut value: &'v Value) -> Result<&'v Value, Error> { + let partial_path = |suffix| { + self.strip_suffix(suffix) + .expect("suffix came from self") + .to_buf() + }; + let mut pointer = self; + while let Some((token, rem)) = pointer.split_front() { + pointer = rem; match value { - Value::Object(_) => { - if !remaining.is_root() { - resolved.push_back(remaining.front().unwrap()); - } - Err(NotFoundError::new(resolved).into()) + // found a leaf node but the pointer hasn't been exhausted + Value::Null | Value::Number(_) | Value::String(_) | Value::Bool(_) => { + return Err(UnresolvableError::new(partial_path(rem)).into()); } - Value::Array(_) => { - if !remaining.is_root() { - resolved.push_back(remaining.front().unwrap()); - } - Err(NotFoundError::new(resolved).into()) - } - Value::Null => { - if !remaining.is_root() { - resolved.push_back(remaining.front().unwrap()); - } - Err(NotFoundError::new(resolved).into()) + Value::Array(v) => { + let idx = token.as_index(v.len()).map_err(Error::Index)?; + value = &v[idx]; } - _ => { - if let Some(t) = remaining.front() { - resolved.push_back(t); - } - Err(UnresolvableError::new(resolved).into()) + Value::Object(v) => { + value = v + .get(token.as_key()) + .ok_or_else(|| Error::from(NotFoundError::new(partial_path(rem))))?; } } } + Ok(value) } /// Attempts to resolve a mutable `Value` based on the path in this `Pointer`. - pub fn resolve_mut<'v>(&self, value: &'v mut Value) -> Result<&'v mut Value, Error> { - let ResolvedMut { - value, - remaining, - mut resolved, - } = self.try_resolve_mut(value)?; - - if remaining.is_root() { - Ok(value) - } else { + pub fn resolve_mut<'v>(&self, mut value: &'v mut Value) -> Result<&'v mut Value, Error> { + let partial_path = |suffix| { + self.strip_suffix(suffix) + .expect("suffix came from self") + .to_buf() + }; + let mut pointer = self; + while let Some((token, rem)) = pointer.split_front() { + pointer = rem; match value { - Value::Object(_) => { - if !remaining.is_root() { - resolved.push_back(remaining.front().unwrap()); - } - Err(NotFoundError::new(resolved).into()) + // found a leaf node but the pointer hasn't been exhausted + Value::Null | Value::Number(_) | Value::String(_) | Value::Bool(_) => { + return Err(UnresolvableError::new(partial_path(rem)).into()); } - Value::Array(_) => { - if !remaining.is_root() { - resolved.push_back(remaining.front().unwrap()); - } - Err(NotFoundError::new(resolved).into()) + Value::Array(v) => { + let idx = token.as_index(v.len()).map_err(Error::Index)?; + value = &mut v[idx]; } - Value::Null => { - if !remaining.is_root() { - resolved.push_back(remaining.front().unwrap()); - } - Err(NotFoundError::new(resolved).into()) - } - _ => { - if let Some(t) = remaining.front() { - resolved.push_back(t); - } - Err(UnresolvableError::new(resolved).into()) + Value::Object(v) => { + value = v + .get_mut(token.as_key()) + .ok_or_else(|| Error::from(NotFoundError::new(partial_path(rem))))?; } } } + Ok(value) } + /// Finds the commonality between this and another `Pointer`. - pub fn union(&self, other: &Pointer) -> Pointer { - let mut res = Pointer::default(); - for (i, token) in self.tokens().enumerate() { - if let Some(t) = other.get(i) { - if t == token { - res.push_back(t); - } else { - break; - } - } else { - break; + pub fn intersection<'a>(&'a self, other: &Self) -> &'a Self { + let mut last_slash = 0; + let mut idx = 0; + for (a, b) in self.0.bytes().zip(other.0.bytes()) { + if a != b { + return Self::new(&self.0[..last_slash]); } + if a == b'/' { + last_slash = idx; + } + idx += 1; + } + if idx == self.0.len() || self.0.as_bytes()[idx] == b'/' { + Self::new(&self.0[..idx]) + } else { + Self::new(&self.0[..last_slash]) } - res } + /// Attempts to delete a `serde_json::Value` based upon the path in this /// `Pointer`. /// @@ -415,17 +418,17 @@ impl Pointer { /// use serde_json::json; /// /// let mut data = json!({ "foo": { "bar": { "baz": "qux" } } }); - /// let ptr = Pointer::new(&["foo", "bar", "baz"]); + /// let ptr = Pointer::from_static("/foo/bar/baz"); /// assert_eq!(data.delete(&ptr), Some("qux".into())); /// assert_eq!(data, json!({ "foo": { "bar": {} } })); /// ``` /// ### Deleting a non-existent Pointer returns `None`: /// ```rust - /// use jsonptr::{Pointer}; + /// use jsonptr::Pointer; /// use serde_json::json; /// /// let mut data = json!({}); - /// let ptr = Pointer::new(&["foo", "bar", "baz"]); + /// let ptr = Pointer::from_static("/foo/bar/baz"); /// assert_eq!(ptr.delete(&mut data), None); /// assert_eq!(data, json!({})); /// ``` @@ -435,55 +438,43 @@ impl Pointer { /// use serde_json::json; /// /// let mut data = json!({ "foo": { "bar": "baz" } }); - /// let ptr = Pointer::default(); + /// let ptr = Pointer::root(); /// assert_eq!(data.delete(&ptr), Some(json!({ "foo": { "bar": "baz" } }))); /// assert!(data.is_null()); /// ``` pub fn delete(&self, value: &mut Value) -> Option { - if self.is_root() { + let Some((parent_ptr, last)) = self.split_back() else { + // deleting at root return Some(mem::replace(value, Value::Null)); - } - let mut ptr = self.clone(); - let last_token = ptr.pop_back().unwrap(); - - match ptr.try_resolve_mut(value) { - Ok(ResolvedMut { - remaining, - resolved: _resolved, - value, - }) => { - if remaining.is_root() { - match value { - Value::Array(arr) => match last_token.as_index(arr.len()) { - Ok(idx) => Some(arr.remove(idx)), - Err(_) => None, - }, - Value::Object(obj) => obj.remove(last_token.as_key()), - _ => None, - } - } else { - None + }; + + parent_ptr + .resolve_mut(value) + .ok() + .and_then(|parent| match parent { + Value::Array(children) => { + let idx = last.as_index(children.len()).ok()?; + children.remove(idx).into() } - } - Err(_) => None, - } + Value::Object(children) => children.remove(last.as_key()), + _ => None, + }) } /// Attempts to assign `src` to `dest` based on the path in this `Pointer`. /// - /// If the path is - /// partially available, the missing portions will be created. If the path contains an index, - /// such as `"/0"` or `"/1"` then an array will be created. Otherwise, objects will be utilized - /// to create the missing path. + /// If the path is partially available, the missing portions will be created. If the path + /// contains a zero index, such as `"/0"`, then an array will be created. Otherwise, objects + /// will be utilized to create the missing path. /// /// ## Example /// ```rust - /// use jsonptr::{Pointer}; + /// use jsonptr::Pointer; /// use jsonptr::prelude::*; // <-- for Assign trait /// use serde_json::{json, Value}; /// let mut data = json!([]); /// - /// let mut ptr = Pointer::new(&["0", "foo"]); + /// let mut ptr = Pointer::from_static("/0/foo"); /// let src = json!("bar"); /// let assignment = data.assign(&ptr, src).unwrap(); /// assert_eq!(data, json!([{ "foo": "bar" } ])); @@ -492,453 +483,400 @@ impl Pointer { where V: Into, { - match self.try_resolve_mut(dest) { - Ok(ResolvedMut { - value: dest, - mut remaining, - mut resolved, - }) => { - if remaining.is_root() { - let replaced = mem::replace(dest, src.into()); - Ok(Assignment { - replaced, - assigned: Cow::Borrowed(dest), - created_or_mutated: self.clone(), - assigned_to: self.clone(), - }) - } else { - let Assigned { - assigned, - replaced, - to, - } = remaining.assign_value(dest, src.into())?; - resolved.append(&to); - Ok(Assignment { - replaced, - assigned: Cow::Borrowed(assigned), - created_or_mutated: remaining, - assigned_to: resolved, - }) - } - } - Err(err) => Err(err), - } + self._assign(dest, src, None, PointerBuf::new()) } - fn assign_value<'a, V>(&mut self, dest: &'a mut Value, src: V) -> Result, Error> + fn _assign<'d, V>( + &self, + dest: &'d mut Value, + src: V, + created_or_mutated: Option, + mut assigned_to: PointerBuf, + ) -> Result, Error> where V: Into, { - if self.is_root() { - let replaced = mem::replace(dest, src.into()); - return Ok(Assigned { - assigned: dest, - replaced, - to: Pointer::default(), - }); - } - let mut replaced = Value::Null; - // this is safe as we know that this pointer is not root and has tokens. - let token = self.pop_front().unwrap(); - // if dest is either a scalar or null value, we replace it with either - // an object or an array, based upon the token. - if dest.is_boolean() || dest.is_null() || dest.is_string() || dest.is_number() { - let new_dest = if token.as_index(0).is_ok() { - Value::Array(vec![]) - } else { - Value::Object(serde_json::Map::new()) - }; - replaced = mem::replace(dest, new_dest); - } - let mut idx: Option = None; - // if the value is an array, attempt to parse the token as an index and - // insert at that index. - if dest.is_array() { - let len = dest.as_array().unwrap().len(); - let i = token.as_index(len)?; - idx = Some(i); - // if the token is equal to the length of the array, we push a new - // value onto the array. - if self.is_root() { - if i == len { - dest.as_array_mut().unwrap().push(src.into()); - } else { - replaced = dest.as_array_mut().unwrap().get(i).cloned().unwrap(); - dest.as_array_mut().unwrap()[i] = src.into(); + if let Some((token, tail)) = self.split_front() { + match dest { + Value::Null | Value::Number(_) | Value::String(_) | Value::Bool(_) => { + match token.as_str() { + "0" => { + // first element will be traversed when we recurse + *dest = alloc::vec![Value::Null].into(); + } + "-" => { + // new element will be appended when we recurse + *dest = Value::Array(Vec::new()); + } + _ => { + // any other values must be interpreted as map keys + *dest = Map::new().into(); + } + } + // Now that the node is a container type, we can recurse into the other cases + self._assign( + dest, + src, + // if this is the first node we created while descending, we record that here + created_or_mutated.or_else(|| assigned_to.clone().into()), + assigned_to, + ) + } + Value::Array(v) => { + let idx = token.as_index(v.len()).map_err(Error::Index)?; + debug_assert!(idx <= v.len()); + if idx == v.len() { + assigned_to.push_back(idx.into()); + v.push(Value::Null); + tail._assign( + v.last_mut().expect("just pushed"), + src, + // NOTE: this MUST be the first node we're creating, because we can't + // append to arrays that do not yet exist! So `created_or_mutated` must + // be `None` at this point, and we always set it to the current path. + assigned_to.clone().into(), + assigned_to, + ) + } else { + assigned_to.push_back(token); + tail._assign(&mut v[idx], src, created_or_mutated, assigned_to) + } + } + Value::Object(v) => { + assigned_to.push_back(token.clone()); + match v.entry(token.to_string()) { + Entry::Occupied(entry) => { + tail._assign(entry.into_mut(), src, created_or_mutated, assigned_to) + } + Entry::Vacant(entry) => { + let dest = entry.insert(Value::Null); + tail._assign( + dest, + src, + // if this is the first node we created while descending, we record that here + created_or_mutated.or_else(|| assigned_to.clone().into()), + assigned_to, + ) + } + } } - let assigned = dest.as_array_mut().unwrap().get_mut(i).unwrap(); - - return Ok(Assigned { - assigned, - replaced, - to: i.into(), - }); - } else if let Some(repl) = self.create_next(dest, &token, Some(i))? { - replaced = repl; - } - // if not an array, the value is an object (due to the assignment above) - // if root, replace the value - } else if self.is_root() { - if let Some(old_val) = dest - .as_object_mut() - .unwrap() - .insert(token.to_string(), src.into()) - { - replaced = old_val; } - let assigned = dest - .as_object_mut() - .unwrap() - .get_mut(token.as_key()) - .unwrap(); - - return Ok(Assigned { - assigned, + } else { + // Pointer is root, we can replace `dest` directly + let replaced = core::mem::replace(dest, src.into()); + Ok(Assignment { + assigned: Cow::Borrowed(dest), replaced, - to: token.into(), - }); - // otherwise, create a new value based on the next token - } else if let Some(repl) = self.create_next(dest, &token, None)? { - replaced = repl; + created_or_mutated: created_or_mutated.unwrap_or_else(|| assigned_to.clone()), + // NOTE: in practice, `assigned_to` is always equivalent to the original pointer, + // with `-` replaced by the actual index (if it occurs) + assigned_to, + }) } + } +} - let assigned = if dest.is_array() { - let idx = idx.unwrap(); - dest.as_array_mut().unwrap().get_mut(idx).unwrap() - } else { - dest.as_object_mut() - .unwrap() - .get_mut(token.as_key()) - .unwrap() - }; - let Assigned { - assigned: _assigned, - replaced: _replaced, - mut to, - } = self.assign_value(assigned, src)?; - let last_token = if let Some(i) = idx { i.into() } else { token }; - to.push_front(last_token); - Ok(Assigned { - assigned, - replaced, - to, - }) +impl Serialize for Pointer { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ::serialize(&self.0, serializer) } - fn create_next( - &self, - dest: &mut Value, - token: &Token, - idx: Option, - ) -> Result, Error> { - // we need to peak ahead to the next token to see if it is an object or an array - let next_token = self.front().unwrap(); - // if the next token is an index, we push a new array onto the array. - let next = if next_token.as_index(0).is_ok() { - Value::Array(vec![]) +} + +impl ToOwned for Pointer { + type Owned = PointerBuf; + + fn to_owned(&self) -> Self::Owned { + self.to_buf() + } +} + +impl PartialEq<&str> for Pointer { + fn eq(&self, other: &&str) -> bool { + &&self.0 == other + } +} + +impl PartialEq for Pointer { + fn eq(&self, other: &str) -> bool { + &self.0 == other + } +} + +impl PartialEq for str { + fn eq(&self, other: &Pointer) -> bool { + self == &other.0 + } +} + +impl PartialEq for Pointer { + fn eq(&self, other: &String) -> bool { + &self.0 == other + } +} + +impl PartialEq for Pointer { + fn eq(&self, other: &PointerBuf) -> bool { + self.0 == other.0 + } +} + +impl PartialEq for PointerBuf { + fn eq(&self, other: &Pointer) -> bool { + self.0 == other.0 + } +} + +impl PartialEq for &Pointer { + fn eq(&self, other: &PointerBuf) -> bool { + self.0 == other.0 + } +} + +impl PartialEq<&Pointer> for PointerBuf { + fn eq(&self, other: &&Pointer) -> bool { + self.0 == other.0 + } +} + +impl From<&Pointer> for Value { + fn from(ptr: &Pointer) -> Self { + ptr.to_value() + } +} + +impl AsRef for Pointer { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl Borrow for Pointer { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl AsRef<[u8]> for Pointer { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl PartialOrd<&str> for &Pointer { + fn partial_cmp(&self, other: &&str) -> Option { + PartialOrd::partial_cmp(&self.0[..], &other[..]) + } +} + +impl PartialOrd for Pointer { + fn partial_cmp(&self, other: &String) -> Option { + self.0.partial_cmp(other) + } +} + +impl<'a> IntoIterator for &'a Pointer { + type Item = Token; + type IntoIter = Tokens<'a>; + fn into_iter(self) -> Self::IntoIter { + self.tokens() + } +} + +/// An owned, mutable Pointer (akin to String). +/// +/// This type provides methods like [`PointerBuf::push_back`] and [`PointerBuf::replace_token`] that +/// mutate the pointer in place. It also implements [`core::ops::Deref`] to [`Pointer`], meaning that +/// all methods on [`Pointer`] slices are available on `PointerBuf` values as well. +#[cfg_attr( + feature = "url", + doc = r##" +```rust +use jsonptr::PointerBuf; +let expected = PointerBuf::from_tokens(&["foo", "bar"]); +let url = url::Url::parse("https://example.com#/foo/bar").unwrap(); +assert_eq!(expected, PointerBuf::try_from(url).unwrap()) +``` +"## +)] +#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PointerBuf(String); + +impl PointerBuf { + /// Creates a new `PointerBuf` pointing to a document root. + pub fn new() -> Self { + Self(String::new()) + } + + /// Creates a new `PointerBuf` from a slice of non-encoded strings. + pub fn from_tokens(tokens: V) -> Self + where + V: AsRef<[T]>, + Token: for<'a> From<&'a T>, + { + let mut inner = String::new(); + let tokens = tokens.as_ref(); + + for t in tokens.iter().map(Into::::into) { + inner.push('/'); + inner.push_str(t.encoded()); + } + PointerBuf(inner) + } + + /// Coerces to a Pointer slice. + pub fn as_ptr(&self) -> &Pointer { + self + } + + /// Pushes a `Token` onto the front of this `Pointer`. + pub fn push_front(&mut self, token: Token) { + self.0.insert(0, '/'); + self.0.insert_str(1, token.encoded()); + } + + /// Pushes a `Token` onto the back of this `Pointer`. + pub fn push_back(&mut self, token: Token) { + self.0.push('/'); + self.0.push_str(token.encoded()); + } + + /// Removes and returns the last `Token` in the `Pointer` if it exists. + pub fn pop_back(&mut self) -> Option { + if let Some((front, back)) = self.0.rsplit_once('/') { + let back = Token::from_encoded(back); + self.0 = front.to_owned(); + Some(back) } else { - Value::Object(serde_json::Map::new()) - }; - if let Some(idx) = idx { - let len = dest.as_array().unwrap().len(); - if idx == len { - dest.as_array_mut().unwrap().push(next); - Ok(None) + None // is root + } + } + + /// Removes and returns the first `Token` in the `Pointer` if it exists. + pub fn pop_front(&mut self) -> Option { + (!self.is_root()).then(|| { + if let Some((front, back)) = self.0[1..].split_once('/') { + let front = Token::from_encoded(front); + self.0 = String::from("/") + back; + front } else { - let prev = dest.as_array_mut().unwrap().get_mut(idx).unwrap().clone(); - dest.as_array_mut().unwrap()[idx] = next; - Ok(Some(prev)) + let token = Token::from_encoded(&self.0[1..]); + self.0.truncate(0); + token } - } else { - let prev = dest - .as_object_mut() - .unwrap() - .insert(token.to_string(), next); - Ok(prev) + }) + } + + /// Merges two `Pointer`s by appending `other` onto `self`. + pub fn append(&mut self, other: &PointerBuf) -> &PointerBuf { + if self.is_root() { + self.0 = other.0.clone(); + } else if !other.is_root() { + self.0.push_str(&other.0); } + self } - /// Resolves a `Pointer` as far as possible. If it encounters an an - /// array without the given index, an object without the given key, or a null - /// value then the pointer is returned at the last resolved location along with - /// the last resolved value. - /// - /// If a leaf node is found (`String`, `Number`, `Boolean`) and the pointer is - /// not at the root, an error is returned. + + /// Attempts to replace a `Token` by the index, returning the replaced + /// `Token` if it already exists. Returns `None` otherwise. /// - /// If the resolution is successful, the pointer will be empty. - fn try_resolve_mut<'v>(&self, value: &'v mut Value) -> Result, Error> { - let mut tokens = self.tokens(); - let mut resolved = Pointer::default(); - let res = tokens.try_fold((value, self.clone()), |(v, mut p), token| match v { - Value::Null => ControlFlow::Break((v, p)), - Value::Number(_) | Value::String(_) | Value::Bool(_) => ControlFlow::Break((v, p)), - Value::Array(_) => match token.as_index(v.as_array_mut().unwrap().len()) { - Ok(idx) => { - if idx < v.as_array_mut().unwrap().len() { - let t = p.pop_front(); - if let Some(t) = t { - resolved.push_back(t); - } - ControlFlow::Continue((v.as_array_mut().unwrap().get_mut(idx).unwrap(), p)) - } else { - ControlFlow::Break((v, p)) - } - } - Err(_) => ControlFlow::Break((v, p)), - }, - Value::Object(_) => { - if v.as_object_mut().unwrap().contains_key(&*token) { - let t = p.pop_front(); - if let Some(t) = t { - resolved.push_back(t); - } - ControlFlow::Continue(( - v.as_object_mut().unwrap().get_mut(token.as_key()).unwrap(), - p, - )) - } else { - ControlFlow::Break((v, p)) - } - } - }); - match res { - ControlFlow::Continue((v, remaining)) => Ok(ResolvedMut { - value: v, - remaining, - resolved, - }), - ControlFlow::Break((value, remaining)) => match value { - Value::Null | Value::Object(_) => Ok(ResolvedMut { - value, - remaining, - resolved, - }), - Value::Array(_) => match remaining.first() { - Some(token) => { - let len = value.as_array().unwrap().len(); - let idx = token.as_index(len)?; - if idx <= len { - Ok(ResolvedMut { - value, - remaining, - resolved, - }) - } else { - Err(OutOfBoundsError { - index: idx, - len, - token, - } - .into()) - } - } - None => Ok(ResolvedMut { - value, - remaining, - resolved, - }), - }, - // this should return `UnresovableError` but currently makes - // `assign` impossible without unsafe code. - Value::Bool(_) | Value::Number(_) | Value::String(_) => Ok(ResolvedMut { - value, - remaining, - resolved, - }), - }, + /// A `ReplaceTokenError` is returned if the index is out of bounds. + pub fn replace_token( + &mut self, + index: usize, + token: Token, + ) -> Result, ReplaceTokenError> { + if self.is_root() { + return Err(ReplaceTokenError { + count: self.count(), + index, + pointer: self.clone(), + }); + } + let mut tokens = self.tokens().collect::>(); + if index > tokens.len() { + return Err(ReplaceTokenError { + count: tokens.len(), + index, + pointer: self.clone(), + }); } + let old = tokens.get(index).cloned(); + tokens[index] = token; + + self.0 = String::from("/") + + &tokens + .iter() + .map(Token::encoded) + .collect::>() + .join("/"); + Ok(old) } - /// Resolves a `Pointer` as far as possible. If it encounters an an - /// array without the given index, an object without the given key, or a null - /// value then the pointer is returned at the last resolved location along with - /// the last resolved value. - /// - /// If a leaf node is found (`String`, `Number`, `Boolean`) and the pointer is - /// not at the root, an error is returned. - /// - /// If the resolution is successful, the pointer will be empty. - fn try_resolve<'v>(&self, value: &'v Value) -> Result, Error> { - let mut tokens = self.tokens(); - let mut resolved = Pointer::default(); - let res = tokens.try_fold((value, self.clone()), |(v, mut p), token| match v { - Value::Null => ControlFlow::Break((v, p)), - Value::Number(_) | Value::String(_) | Value::Bool(_) => ControlFlow::Break((v, p)), - Value::Array(_) => match token.as_index(v.as_array().unwrap().len()) { - Ok(idx) => { - if idx < v.as_array().unwrap().len() { - let t = p.pop_front(); - if let Some(t) = t { - resolved.push_back(t); - } - ControlFlow::Continue((v.as_array().unwrap().get(idx).unwrap(), p)) - } else { - ControlFlow::Break((v, p)) - } - } - Err(_) => ControlFlow::Break((v, p)), - }, - Value::Object(_) => { - if v.as_object().unwrap().contains_key(&*token) { - let t = p.pop_front(); - if let Some(t) = t { - resolved.push_back(t); - } - ControlFlow::Continue((v.as_object().unwrap().get(&*token).unwrap(), p)) - } else { - ControlFlow::Break((v, p)) - } - } - }); - match res { - ControlFlow::Continue((v, remaining)) => Ok(Resolved { - value: v, - remaining, - resolved, - }), - ControlFlow::Break((value, remaining)) => match value { - Value::Null | Value::Object(_) => Ok(Resolved { - value, - remaining, - resolved, - }), - Value::Array(_) => match remaining.first() { - Some(token) => { - let len = value.as_array().unwrap().len(); - let idx = token.as_index(len)?; - if idx <= len { - Ok(Resolved { - value, - remaining, - resolved, - }) - } else { - Err(OutOfBoundsError { - index: idx, - len, - token, - } - .into()) - } - } - None => Ok(Resolved { - value, - remaining, - resolved, - }), - }, - // this should return `UnresovableError` but currently makes - // `assign` impossible without unsafe code. - Value::Bool(_) | Value::Number(_) | Value::String(_) => Ok(Resolved { - value, - remaining, - resolved, - }), - }, - } + /// Clears the `Pointer`, setting it to root (`""`). + pub fn clear(&mut self) { + self.0.clear(); } +} - fn split(&self) -> Split<'_, char> { - let mut s = self.inner.split('/'); - // skipping the first '/' - s.next(); - s +impl FromStr for PointerBuf { + type Err = MalformedPointerError; + fn from_str(s: &str) -> Result { + Self::try_from(s) } } -impl Eq for Pointer {} -impl Deref for Pointer { - type Target = str; - fn deref(&self) -> &Self::Target { - &self.inner +impl Borrow for PointerBuf { + fn borrow(&self) -> &Pointer { + self.as_ptr() } } -impl Serialize for Pointer { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - String::serialize(&self.inner, serializer) +impl Deref for PointerBuf { + type Target = Pointer; + fn deref(&self) -> &Self::Target { + Pointer::new(&self.0) } } -impl<'de> Deserialize<'de> for Pointer { +impl<'de> Deserialize<'de> for PointerBuf { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { use serde::de::Error; let s = String::deserialize(deserializer)?; - match Pointer::try_from(s.as_str()) { - Ok(p) => Ok(p), - Err(err) => Err(D::Error::custom(err)), - } - } -} -impl AsRef for Pointer { - fn as_ref(&self) -> &str { - &self.inner - } -} -impl Borrow for Pointer { - fn borrow(&self) -> &str { - &self.inner - } -} -impl From for Pointer { - fn from(t: Token) -> Self { - Pointer::new([t]) - } -} - -impl PartialEq<&str> for Pointer { - fn eq(&self, other: &&str) -> bool { - &self.inner == other - } -} -impl PartialEq for Pointer { - fn eq(&self, other: &str) -> bool { - self.inner == other + PointerBuf::try_from(s).map_err(D::Error::custom) } } -impl PartialEq for Pointer { - fn eq(&self, other: &Pointer) -> bool { - self.inner == other.inner +impl Serialize for PointerBuf { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + String::serialize(&self.0, serializer) } } -impl PartialEq for str { - fn eq(&self, other: &Pointer) -> bool { - self == other.inner - } -} -impl PartialEq for Pointer { - fn eq(&self, other: &String) -> bool { - &self.inner == other +impl From for PointerBuf { + fn from(t: Token) -> Self { + PointerBuf::from_tokens([t]) } } -impl TryFrom for Pointer { +impl TryFrom for PointerBuf { type Error = MalformedPointerError; fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} -impl AsRef<[u8]> for Pointer { - fn as_ref(&self) -> &[u8] { - self.inner.as_bytes() - } -} - -impl Borrow<[u8]> for Pointer { - fn borrow(&self) -> &[u8] { - self.inner.as_bytes() + let _ = validate(&value)?; + Ok(Self(value)) } } #[cfg(feature = "uniresid")] -impl TryFrom<&uniresid::Uri> for Pointer { +impl TryFrom<&uniresid::Uri> for PointerBuf { type Error = MalformedPointerError; fn try_from(uri: &uniresid::Uri) -> Result { @@ -949,16 +887,18 @@ impl TryFrom<&uniresid::Uri> for Pointer { } } } + #[cfg(feature = "uniresid")] -impl TryFrom for Pointer { +impl TryFrom for PointerBuf { type Error = MalformedPointerError; fn try_from(uri: uniresid::Uri) -> Result { Self::try_from(&uri) } } + #[cfg(feature = "uniresid")] -impl TryFrom<&uniresid::AbsoluteUri> for Pointer { +impl TryFrom<&uniresid::AbsoluteUri> for PointerBuf { type Error = MalformedPointerError; fn try_from(uri: &uniresid::AbsoluteUri) -> Result { @@ -969,87 +909,78 @@ impl TryFrom<&uniresid::AbsoluteUri> for Pointer { } } } + #[cfg(feature = "uniresid")] -impl TryFrom for Pointer { +impl TryFrom for PointerBuf { type Error = MalformedPointerError; fn try_from(uri: uniresid::AbsoluteUri) -> Result { Self::try_from(&uri) } } + #[cfg(feature = "url")] -impl TryFrom<&url::Url> for Pointer { +impl TryFrom<&url::Url> for PointerBuf { type Error = MalformedPointerError; fn try_from(url: &url::Url) -> Result { match url.fragment() { Some(f) => Self::try_from(f), - None => Ok(Pointer::default()), + None => Ok(PointerBuf::default()), } } } + #[cfg(feature = "url")] -impl TryFrom for Pointer { +impl TryFrom for PointerBuf { type Error = MalformedPointerError; fn try_from(url: url::Url) -> Result { match url.fragment() { Some(f) => Self::try_from(f), - None => Ok(Pointer::default()), + None => Ok(PointerBuf::default()), } } } #[cfg(feature = "fluent-uri")] -impl TryFrom<&fluent_uri::Uri<&str>> for Pointer { +impl TryFrom<&fluent_uri::Uri<&str>> for PointerBuf { type Error = MalformedPointerError; fn try_from(uri: &fluent_uri::Uri<&str>) -> Result { match uri.fragment() { Some(f) => Self::try_from(f.as_str()), - None => Ok(Pointer::default()), + None => Ok(PointerBuf::default()), } } } #[cfg(feature = "fluent-uri")] -impl TryFrom<&fluent_uri::Uri> for Pointer { +impl TryFrom<&fluent_uri::Uri> for PointerBuf { type Error = MalformedPointerError; fn try_from(uri: &fluent_uri::Uri) -> Result { match uri.fragment() { Some(f) => Self::try_from(f.as_str()), - None => Ok(Pointer::default()), + None => Ok(PointerBuf::default()), } } } #[cfg(feature = "fluent-uri")] -impl TryFrom<&fluent_uri::Uri<&mut [u8]>> for Pointer { +impl TryFrom<&fluent_uri::Uri<&mut [u8]>> for PointerBuf { type Error = MalformedPointerError; fn try_from(uri: &fluent_uri::Uri<&mut [u8]>) -> Result { match uri.fragment() { Some(f) => Self::try_from(f.as_str()), - None => Ok(Pointer::default()), + None => Ok(PointerBuf::default()), } } } -impl From for Value { - fn from(ptr: Pointer) -> Self { - ptr.to_value() - } -} - -impl From<&Pointer> for Value { - fn from(ptr: &Pointer) -> Self { - ptr.to_value() - } -} - -impl From for Pointer { +impl From for PointerBuf { fn from(value: usize) -> Self { - Pointer::new([value]) + PointerBuf::from_tokens([value]) } } -impl<'a> IntoIterator for &'a Pointer { +impl<'a> IntoIterator for &'a PointerBuf { type Item = Token; type IntoIter = Tokens<'a>; fn into_iter(self) -> Self::IntoIter { @@ -1057,137 +988,45 @@ impl<'a> IntoIterator for &'a Pointer { } } -impl TryFrom<&str> for Pointer { +impl TryFrom<&str> for PointerBuf { type Error = MalformedPointerError; fn try_from(value: &str) -> Result { - let (count, inner) = validate_and_format(value)?; - Ok(Pointer { count, inner }) - } -} - -fn validate_and_format(value: &str) -> Result<(usize, String), MalformedPointerError> { - let mut chars = value.chars(); - - match chars.next() { - Some('#') => { - let next = chars.next(); - if next.is_none() { - return Ok((0, String::default())); - } - if next != Some('/') { - return Err(MalformedPointerError::NoLeadingSlash(value.into())); - } - } - Some('/') => {} - Some(_) => { - return Err(MalformedPointerError::NoLeadingSlash(value.into())); - } - None => { - return Ok((0, String::default())); - } - } - let mut res = String::with_capacity(value.len()); - res.push('/'); - let mut count = 1; // accounting for the first slash - - while let Some(c) = chars.next() { - res.push(c); - match c { - '~' => match chars.next() { - Some('0') => res.push('0'), - Some('1') => res.push('1'), - _ => { - return Err(MalformedPointerError::InvalidEncoding(value.to_string())); - } - }, - '/' => { - count += 1; - } - _ => {} - } + Pointer::parse(value).map(Pointer::to_buf) } - Ok((count, res)) } -impl PartialOrd<&str> for Pointer { - fn partial_cmp(&self, other: &&str) -> Option { - PartialOrd::partial_cmp(&self.inner[..], &other[..]) - } -} -impl PartialOrd for Pointer { - fn partial_cmp(&self, other: &String) -> Option { - self.inner.partial_cmp(other) - } -} -impl PartialOrd for Pointer { - fn partial_cmp(&self, other: &Pointer) -> Option { - self.inner.partial_cmp(&other.inner) - } -} -impl PartialEq for String { - fn eq(&self, other: &Pointer) -> bool { - PartialEq::eq(&self[..], &other.inner[..]) +impl PartialEq<&str> for PointerBuf { + fn eq(&self, other: &&str) -> bool { + &self.0 == other } } -impl PartialOrd for String { - fn partial_cmp(&self, other: &Pointer) -> Option { - self.partial_cmp(&other.inner) - } -} -impl core::hash::Hash for Pointer { - fn hash(&self, state: &mut H) { - self.inner.hash(state) +impl PartialEq for PointerBuf { + fn eq(&self, other: &str) -> bool { + self.0 == other } } -impl core::fmt::Debug for Pointer { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "\"{}\"", self.inner) - } -} -impl core::fmt::Display for Pointer { +impl core::fmt::Display for PointerBuf { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.inner) - } -} -impl FromStr for Pointer { - type Err = MalformedPointerError; - fn from_str(s: &str) -> Result { - Self::try_from(s) + self.0.fmt(f) } } -#[derive(Debug)] -struct Assigned<'a> { - assigned: &'a mut Value, - replaced: Value, - to: Pointer, -} - -/// Used internally as a response from `try_resolve` -#[derive(Debug)] -struct ResolvedMut<'a> { - value: &'a mut Value, - remaining: Pointer, - resolved: Pointer, -} - -/// Used internally as a response from `try_resolve` -#[derive(Debug)] -struct Resolved<'a> { - value: &'a Value, - remaining: Pointer, - resolved: Pointer, -} - #[cfg(test)] mod tests { - use serde_json::json; - + use super::*; use crate::{Resolve, ResolveMut}; + use alloc::vec; + use quickcheck::TestResult; + use quickcheck_macros::quickcheck; + use serde_json::json; - use super::*; + #[test] + #[should_panic] + fn from_const_validates() { + Pointer::from_static("foo/bar"); + } #[test] fn test_rfc_examples() { @@ -1208,65 +1047,64 @@ mod tests { assert_eq!(val, 0); // "" // the whole document - let ptr = Pointer::default(); - assert_eq!(data.resolve(&ptr).unwrap(), &data); + let ptr = Pointer::root(); + assert_eq!(data.resolve(ptr).unwrap(), &data); // "/foo" ["bar", "baz"] - let ptr = Pointer::try_from("/foo").unwrap(); - - assert_eq!(data.resolve(&ptr).unwrap(), &json!(["bar", "baz"])); + let ptr = Pointer::from_static("/foo"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(["bar", "baz"])); // "/foo/0" "bar" - let ptr = Pointer::try_from("/foo/0").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!("bar")); + let ptr = Pointer::from_static("/foo/0"); + assert_eq!(data.resolve(ptr).unwrap(), &json!("bar")); // // "/" 0 - let ptr = Pointer::try_from("/").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(0)); + let ptr = Pointer::from_static("/"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(0)); // "/a~1b" 1 assert_eq!(data.get("a/b").unwrap(), 1); - let ptr = Pointer::try_from("/a~1b").unwrap(); + let ptr = Pointer::from_static("/a~1b"); assert_eq!(ptr.as_str(), "/a~1b"); assert_eq!(data.get("a/b").unwrap(), 1); assert_eq!(&ptr.first().unwrap(), "a/b"); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(1)); + assert_eq!(data.resolve(ptr).unwrap(), &json!(1)); // "/c%d" 2 - let ptr = Pointer::try_from("/c%d").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(2)); + let ptr = Pointer::from_static("/c%d"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(2)); // // "/e^f" 3 - let ptr = Pointer::try_from("/e^f").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(3)); + let ptr = Pointer::from_static("/e^f"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(3)); // // "/g|h" 4 - let ptr = Pointer::try_from("/g|h").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(4)); + let ptr = Pointer::from_static("/g|h"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(4)); // "/i\\j" 5 - let ptr = Pointer::try_from("/i\\j").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(5)); + let ptr = Pointer::from_static("/i\\j"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(5)); // // "/k\"l" 6 - let ptr = Pointer::try_from("/k\"l").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(6)); + let ptr = Pointer::from_static("/k\"l"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(6)); // // "/ " 7 - let ptr = Pointer::try_from("/ ").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(7)); + let ptr = Pointer::from_static("/ "); + assert_eq!(data.resolve(ptr).unwrap(), &json!(7)); // // "/m~0n" 8 - let ptr = Pointer::try_from("/m~0n").unwrap(); - assert_eq!(data.resolve(&ptr).unwrap(), &json!(8)); + let ptr = Pointer::from_static("/m~0n"); + assert_eq!(data.resolve(ptr).unwrap(), &json!(8)); } #[test] fn test_try_from_validation() { - assert!(Pointer::try_from("").is_ok()); - assert!(Pointer::try_from("/").is_ok()); - assert!(Pointer::try_from("/foo").is_ok()); + assert!(PointerBuf::try_from("").is_ok()); + assert!(PointerBuf::try_from("/").is_ok()); + assert!(PointerBuf::try_from("/foo").is_ok()); - let res = Pointer::try_from("/foo~"); + let res = PointerBuf::try_from("/foo~"); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!( @@ -1274,7 +1112,7 @@ mod tests { MalformedPointerError::InvalidEncoding("/foo~".to_string()) ); - let res = Pointer::try_from("foo"); + let res = PointerBuf::try_from("foo"); assert!(res.is_err()); let err = res.unwrap_err(); assert_eq!( @@ -1282,17 +1120,17 @@ mod tests { MalformedPointerError::NoLeadingSlash("foo".to_string()) ); - assert!(Pointer::try_from("/foo/bar/baz/~1/~0").is_ok()); + assert!(PointerBuf::try_from("/foo/bar/baz/~1/~0").is_ok()); assert_eq!( - &Pointer::try_from("/foo/bar/baz/~1/~0").unwrap(), + &PointerBuf::try_from("/foo/bar/baz/~1/~0").unwrap(), "/foo/bar/baz/~1/~0" ); } #[test] fn test_push_pop_back() { - let mut ptr = Pointer::default(); + let mut ptr = PointerBuf::default(); assert_eq!(ptr, "", "default, root pointer should equal \"\""); assert_eq!(ptr.count(), 0, "default pointer should have 0 tokens"); @@ -1310,7 +1148,7 @@ mod tests { "pointer should equal \"/foo/bar/~1baz\" after push_back" ); - let mut ptr = Pointer::new(["foo", "bar"]); + let mut ptr = PointerBuf::from_tokens(["foo", "bar"]); assert_eq!(ptr.pop_back(), Some("bar".into())); assert_eq!(ptr, "/foo", "pointer should equal \"/foo\" after pop_back"); assert_eq!( @@ -1323,7 +1161,7 @@ mod tests { #[test] fn test_replace_token() { - let mut ptr = Pointer::try_from("/test/token").unwrap(); + let mut ptr = PointerBuf::try_from("/test/token").unwrap(); let res = ptr.replace_token(0, "new".into()); assert!(res.is_ok()); @@ -1339,7 +1177,7 @@ mod tests { #[test] fn test_push_pop_front() { - let mut ptr = Pointer::default(); + let mut ptr = PointerBuf::default(); assert_eq!(ptr, ""); assert_eq!(ptr.count(), 0); ptr.push_front("bar".into()); @@ -1368,7 +1206,7 @@ mod tests { #[test] fn pop_front_works_with_empty_strings() { { - let mut ptr = Pointer::new(["bar", "", ""]); + let mut ptr = PointerBuf::from_tokens(["bar", "", ""]); assert_eq!(ptr.tokens().count(), 3); let mut token = ptr.pop_front(); @@ -1383,7 +1221,7 @@ mod tests { assert_eq!(ptr, Pointer::root()); } { - let mut ptr = Pointer::root(); + let mut ptr = PointerBuf::new(); assert_eq!(ptr.tokens().count(), 0); ptr.push_back("".into()); assert_eq!(ptr.tokens().count(), 1); @@ -1391,7 +1229,7 @@ mod tests { assert_eq!(ptr.tokens().count(), 0); } { - let mut ptr = Pointer::root(); + let mut ptr = PointerBuf::new(); let input = ["", "", "", "foo", "", "bar", "baz", ""]; for (idx, s) in input.iter().enumerate() { assert_eq!(ptr.tokens().count(), idx); @@ -1411,43 +1249,43 @@ mod tests { #[test] fn test_formatting() { - assert_eq!(Pointer::new(["foo", "bar"]), "/foo/bar"); + assert_eq!(PointerBuf::from_tokens(["foo", "bar"]), "/foo/bar"); assert_eq!( - Pointer::new(["~/foo", "~bar", "/baz"]), + PointerBuf::from_tokens(["~/foo", "~bar", "/baz"]), "/~0~1foo/~0bar/~1baz" ); - assert_eq!(Pointer::new(["field", "", "baz"]), "/field//baz"); - assert_eq!(Pointer::default(), ""); + assert_eq!(PointerBuf::from_tokens(["field", "", "baz"]), "/field//baz"); + assert_eq!(PointerBuf::default(), ""); } #[test] fn test_last() { - let ptr = Pointer::try_from("/foo/bar").unwrap(); + let ptr = Pointer::from_static("/foo/bar"); assert_eq!(ptr.last(), Some("bar".into())); - let ptr = Pointer::try_from("/foo/bar/-").unwrap(); + let ptr = Pointer::from_static("/foo/bar/-"); assert_eq!(ptr.last(), Some("-".into())); - let ptr = Pointer::try_from("/-").unwrap(); + let ptr = Pointer::from_static("/-"); assert_eq!(ptr.last(), Some("-".into())); - let ptr = Pointer::default(); + let ptr = Pointer::root(); assert_eq!(ptr.last(), None); - let ptr = Pointer::try_from("/bar").unwrap(); + let ptr = Pointer::from_static("/bar"); assert_eq!(ptr.last(), Some("bar".into())); } #[test] fn test_first() { - let ptr = Pointer::try_from("/foo/bar").unwrap(); + let ptr = Pointer::from_static("/foo/bar"); assert_eq!(ptr.first(), Some("foo".into())); - let ptr = Pointer::try_from("/foo/bar/-").unwrap(); + let ptr = Pointer::from_static("/foo/bar/-"); assert_eq!(ptr.first(), Some("foo".into())); - let ptr = Pointer::default(); + let ptr = Pointer::root(); assert_eq!(ptr.first(), None); } @@ -1473,82 +1311,21 @@ mod tests { }) } - #[test] - fn test_try_resolve_mut() { - let mut data = test_data(); - let ptr = Pointer::try_from("/foo/bar/baz/qux").unwrap(); - let ResolvedMut { - value, - remaining, - resolved: _resolved, - } = ptr.try_resolve_mut(&mut data).unwrap(); - assert_eq!(&remaining, ""); - assert_eq!(value, &mut json!("quux")); - - let ptr = Pointer::try_from("/foo/bar/does_not_exist/derp").unwrap(); - let ResolvedMut { - value, - remaining, - resolved: _resolved, - } = ptr.try_resolve_mut(&mut data).unwrap(); - assert_eq!(remaining, "/does_not_exist/derp"); - - assert_eq!( - value, - &mut json!({ - "baz": { - "qux": "quux" - } - }) - ); - - let ptr = Pointer::try_from("/foo/strings/0").unwrap(); - let res = ptr.try_resolve_mut(&mut data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("zero")); - - let ptr = Pointer::try_from("/foo/strings/1").unwrap(); - let res = ptr.try_resolve_mut(&mut data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("one")); - - let ptr = Pointer::try_from("/foo/strings/2").unwrap(); - let res = ptr.try_resolve_mut(&mut data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("two")); - - let ptr = Pointer::try_from("/foo/strings/-").unwrap(); - let res = ptr.try_resolve_mut(&mut data).unwrap(); - assert_eq!(res.remaining, "/-"); - assert_eq!(res.value, &mut json!(["zero", "one", "two"])); - - let ptr = Pointer::try_from("/foo/strings/0").unwrap(); - let res = ptr.try_resolve_mut(&mut data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("zero")); - } - - #[test] - fn test_try_resolve_mut_overflow_error() { - let mut data = test_data(); - let ptr = Pointer::try_from("/foo/strings/7").unwrap(); - let res = ptr.try_resolve_mut(&mut data); - assert!(res.is_err()); - } #[test] fn test_resolve_unresolvable() { let mut data = test_data(); - let ptr = Pointer::try_from("/foo/bool/unresolvable").unwrap(); + let ptr = Pointer::from_static("/foo/bool/unresolvable"); let res = ptr.resolve_mut(&mut data); assert!(res.is_err()); let err = res.unwrap_err(); assert!(err.is_unresolvable()); } + #[test] fn test_resolve_not_found() { let mut data = test_data(); - let ptr = Pointer::new(["foo", "not_found", "nope"]); + let ptr = PointerBuf::from_tokens(["foo", "not_found", "nope"]); let res = ptr.resolve_mut(&mut data); assert!(res.is_err()); let err = res.unwrap_err(); @@ -1561,94 +1338,19 @@ mod tests { } } - #[test] - fn test_try_resolve_mut_unresolvable() { - let mut data = test_data(); - let ptr = Pointer::try_from("/foo/bool/unresolvable").unwrap(); - let res = ptr.try_resolve_mut(&mut data).unwrap(); - - assert_eq!(res.remaining, "/unresolvable"); - assert_eq!(res.resolved, "/foo/bool"); - assert!(res.value.is_boolean()); - } #[test] fn test_try_from() { - let ptr = Pointer::new(["foo", "bar", "~/"]); + let ptr = PointerBuf::from_tokens(["foo", "bar", "~/"]); - assert_eq!(Pointer::try_from("/foo/bar/~0~1").unwrap(), ptr); - let into: Pointer = "/foo/bar/~0~1".try_into().unwrap(); + assert_eq!(PointerBuf::try_from("/foo/bar/~0~1").unwrap(), ptr); + let into: PointerBuf = "/foo/bar/~0~1".try_into().unwrap(); assert_eq!(ptr, into); } - #[test] - fn test_try_resolve() { - let data = test_data(); - let ptr = Pointer::try_from("/foo/bar/baz/qux").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!(&res.remaining, ""); - assert_eq!(res.value, &mut json!("quux")); - - let ptr = Pointer::try_from("/foo/bar/does_not_exist/derp").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!(res.remaining, "/does_not_exist/derp"); - - assert_eq!( - res.value, - &mut json!({ - "baz": { - "qux": "quux" - } - }) - ); - - let ptr = Pointer::try_from("/foo/strings/0").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("zero")); - - let ptr = Pointer::try_from("/foo/strings/1").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("one")); - - let ptr = Pointer::try_from("/foo/strings/2").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("two")); - - let ptr = Pointer::try_from("/foo/strings/-").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!(res.remaining, "/-"); - assert_eq!(res.value, &mut json!(["zero", "one", "two"])); - - let ptr = Pointer::try_from("/foo/strings/0").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!(res.remaining, ""); - assert_eq!(res.value, &mut json!("zero")); - } - - #[test] - fn test_try_resolve_overflow_error() { - let data = test_data(); - let ptr = Pointer::try_from("/foo/strings/7").unwrap(); - let res = ptr.try_resolve(&data); - assert!(res.is_err()); - } - #[test] - fn test_try_resolve_unresolvable_error() { - let data = test_data(); - let ptr = Pointer::try_from("/foo/bool/unresolvable/not-in-token").unwrap(); - let res = ptr.try_resolve(&data).unwrap(); - assert_eq!( - res.remaining, - Pointer::try_from("/unresolvable/not-in-token").unwrap() - ); - } - #[test] fn test_resolve_mut_unresolvable_error() { let mut data = test_data(); - let ptr = Pointer::try_from("/foo/bool/unresolvable/not-in-token").unwrap(); + let ptr = Pointer::from_static("/foo/bool/unresolvable/not-in-token"); let res = ptr.resolve_mut(&mut data); assert!(res.is_err()); let unresolvable = "/foo/bool/unresolvable".try_into().unwrap(); @@ -1658,15 +1360,15 @@ mod tests { ); let mut data = json!({"foo": "bar"}); - let ptr = Pointer::try_from("/foo/unresolvable").unwrap(); + let ptr = PointerBuf::try_from("/foo/unresolvable").unwrap(); let err = data.resolve_mut(&ptr).unwrap_err(); - assert_eq!(err, UnresolvableError::new(ptr.clone()).into()); + assert_eq!(err, UnresolvableError::new(ptr).into()); } #[test] fn test_resolve_unresolvable_error() { let data = test_data(); - let ptr = Pointer::try_from("/foo/bool/unresolvable/not-in-token").unwrap(); + let ptr = Pointer::from_static("/foo/bool/unresolvable/not-in-token"); let res = ptr.resolve(&data); assert!(res.is_err()); @@ -1680,7 +1382,7 @@ mod tests { #[test] fn test_assign() { let mut data = json!({}); - let ptr = Pointer::try_from("/foo").unwrap(); + let ptr = Pointer::from_static("/foo"); let val = json!("bar"); let assignment = ptr.assign(&mut data, val.clone()).unwrap(); assert_eq!(assignment.replaced, Value::Null); @@ -1694,10 +1396,11 @@ mod tests { assert_eq!(assignment.assigned.clone().into_owned(), val2); assert_eq!(assignment.assigned_to, "/foo"); } + #[test] fn test_assign_with_explicit_array_path() { let mut data = json!({}); - let ptr = Pointer::try_from("/foo/0/bar").unwrap(); + let ptr = Pointer::from_static("/foo/0/bar"); let val = json!("baz"); let assignment = ptr.assign(&mut data, val).unwrap(); @@ -1715,10 +1418,11 @@ mod tests { data.clone() ); } + #[test] fn test_assign_array_with_next_token() { let mut data = json!({}); - let ptr = Pointer::try_from("/foo/-/bar").unwrap(); + let ptr = PointerBuf::try_from("/foo/-/bar").unwrap(); let val = json!("baz"); let assignment = ptr.assign(&mut data, val).unwrap(); assert_eq!(assignment.replaced, Value::Null); @@ -1758,7 +1462,7 @@ mod tests { }), data.clone() ); - let ptr = Pointer::try_from("/foo/0/bar").unwrap(); + let ptr = PointerBuf::try_from("/foo/0/bar").unwrap(); let val = json!("qux"); let assignment = ptr.assign(&mut data, val).unwrap(); // assert_eq!(assignment.assigned_to, "/foo/0/bar"); @@ -1777,10 +1481,11 @@ mod tests { data.clone() ); } + #[test] fn test_assign_with_obj_path() { let mut data = json!({}); - let ptr = Pointer::try_from("/foo/bar").unwrap(); + let ptr = PointerBuf::try_from("/foo/bar").unwrap(); let val = json!("baz"); let assignment = ptr.assign(&mut data, val).unwrap(); @@ -1795,13 +1500,14 @@ mod tests { data.clone() ); } + #[test] fn test_assign_with_scalar_replace() { let mut data = json!({ "foo": "bar" }); - let ptr = Pointer::try_from("/foo/bar/baz").unwrap(); + let ptr = Pointer::from_static("/foo/bar/baz"); let val = json!("qux"); ptr.assign(&mut data, val).unwrap(); @@ -1828,11 +1534,11 @@ mod tests { }); { - let ptr = Pointer::try_from("///bar").unwrap(); + let ptr = Pointer::from_static("///bar"); assert_eq!(ptr.resolve(&data).unwrap(), 42); } { - let mut ptr = Pointer::root(); + let mut ptr = PointerBuf::new(); ptr.push_back("".into()); ptr.push_back("".into()); ptr.push_back("bar".into()); @@ -1843,7 +1549,7 @@ mod tests { #[test] fn pop_back_works_with_empty_strings() { { - let mut ptr = Pointer::root(); + let mut ptr = PointerBuf::new(); ptr.push_back("".into()); ptr.push_back("".into()); ptr.push_back("bar".into()); @@ -1855,10 +1561,10 @@ mod tests { assert_eq!(ptr.tokens().count(), 1); ptr.pop_back(); assert_eq!(ptr.tokens().count(), 0); - assert_eq!(ptr, Pointer::root()); + assert_eq!(ptr, PointerBuf::new()); } { - let mut ptr = Pointer::root(); + let mut ptr = PointerBuf::new(); assert_eq!(ptr.tokens().count(), 0); ptr.push_back("".into()); assert_eq!(ptr.tokens().count(), 1); @@ -1866,7 +1572,7 @@ mod tests { assert_eq!(ptr.tokens().count(), 0); } { - let mut ptr = Pointer::root(); + let mut ptr = PointerBuf::new(); let input = ["", "", "", "foo", "", "bar", "baz", ""]; for (idx, s) in input.iter().enumerate() { assert_eq!(ptr.tokens().count(), idx); @@ -1884,11 +1590,156 @@ mod tests { } } + #[test] + fn intersect() { + let base = Pointer::from_static("/foo/bar"); + let a = Pointer::from_static("/foo/bar/qux"); + let b = Pointer::from_static("/foo/bar"); + assert_eq!(a.intersection(b), base); + + let base = Pointer::from_static(""); + let a = Pointer::from_static("/foo"); + let b = Pointer::from_static("/"); + assert_eq!(a.intersection(b), base); + + let base = Pointer::from_static(""); + let a = Pointer::from_static("/fooqux"); + let b = Pointer::from_static("/foobar"); + assert_eq!(a.intersection(b), base); + } + #[test] #[cfg(feature = "fluent-uri")] fn test_try_from_fluent_uri() { let uri = fluent_uri::Uri::parse("#/foo/bar").unwrap(); - let ptr = Pointer::try_from(&uri).unwrap(); + let ptr = PointerBuf::try_from(&uri).unwrap(); assert_eq!(ptr, "/foo/bar"); } + + #[quickcheck] + fn qc_pop_and_push(mut ptr: PointerBuf) -> bool { + let original_ptr = ptr.clone(); + let mut tokens = Vec::with_capacity(ptr.count()); + while let Some(token) = ptr.pop_back() { + tokens.push(token); + } + if ptr.count() != 0 || !ptr.is_root() || ptr.last().is_some() || ptr.first().is_some() { + return false; + } + for token in tokens.drain(..) { + ptr.push_front(token); + } + if ptr != original_ptr { + return false; + } + while let Some(token) = ptr.pop_front() { + tokens.push(token); + } + if ptr.count() != 0 || !ptr.is_root() || ptr.last().is_some() || ptr.first().is_some() { + return false; + } + for token in tokens { + ptr.push_back(token); + } + ptr == original_ptr + } + + #[quickcheck] + fn qc_split(ptr: PointerBuf) -> bool { + if let Some((head, tail)) = ptr.split_front() { + { + let Some(first) = ptr.first() else { + return false; + }; + if first != head { + return false; + } + } + { + let mut copy = ptr.clone(); + copy.pop_front(); + if copy != tail { + return false; + } + } + { + let mut buf = tail.to_buf(); + buf.push_front(head.clone()); + if buf != ptr { + return false; + } + } + { + let fmt = alloc::format!("/{}{tail}", head.encoded()); + if Pointer::parse(&fmt).unwrap() != ptr { + return false; + } + } + } else { + return ptr.is_root() + && ptr.count() == 0 + && ptr.last().is_none() + && ptr.first().is_none(); + } + if let Some((head, tail)) = ptr.split_back() { + { + let Some(last) = ptr.last() else { + return false; + }; + if last != tail { + return false; + } + } + { + let mut copy = ptr.clone(); + copy.pop_back(); + if copy != head { + return false; + } + } + { + let mut buf = head.to_buf(); + buf.push_back(tail.clone()); + if buf != ptr { + return false; + } + } + { + let fmt = alloc::format!("{head}/{}", tail.encoded()); + if Pointer::parse(&fmt).unwrap() != ptr { + return false; + } + } + if Some(head) != ptr.parent() { + return false; + } + } else { + return ptr.is_root() + && ptr.count() == 0 + && ptr.last().is_none() + && ptr.first().is_none(); + } + true + } + + #[quickcheck] + fn qc_from_tokens(tokens: Vec) -> bool { + let buf = PointerBuf::from_tokens(&tokens); + let reconstructed: Vec<_> = buf.tokens().collect(); + tokens == reconstructed + } + + #[quickcheck] + fn qc_intersection(base: PointerBuf, suffix_0: PointerBuf, suffix_1: PointerBuf) -> TestResult { + if suffix_0.first() == suffix_1.first() { + // base must be the true intersection + return TestResult::discard(); + } + let mut a = base.clone(); + a.append(&suffix_0); + let mut b = base.clone(); + b.append(&suffix_1); + let isect = a.intersection(&b); + TestResult::from_bool(isect == base) + } } diff --git a/src/token.rs b/src/token.rs index 26692f0..1d26b67 100644 --- a/src/token.rs +++ b/src/token.rs @@ -271,7 +271,7 @@ impl PartialOrd for Token { } impl PartialOrd for Token { fn partial_cmp(&self, other: &Token) -> Option { - self.decoded().partial_cmp(other.decoded()) + Some(self.cmp(other)) } } impl PartialEq for String { @@ -437,3 +437,16 @@ impl Into<&str> for Escaped { } } } + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck_macros::quickcheck; + + #[quickcheck] + fn encode_decode(token: Token) -> bool { + let encoded = token.encoded(); + let decoded = Token::from_encoded(encoded); + token == decoded + } +} diff --git a/src/tokens.rs b/src/tokens.rs index c0d7707..4b38d88 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -15,8 +15,8 @@ impl<'a> Iterator for Tokens<'a> { } } impl<'t> Tokens<'t> { - pub(crate) fn new(split: Split<'t, char>) -> Self { - Self { inner: split } + pub(crate) fn new(inner: Split<'t, char>) -> Self { + Self { inner } } }