From cc31e37b57798e6a0757f5fbf060cbcef415272e Mon Sep 17 00:00:00 2001 From: Chance Date: Wed, 10 Jul 2024 17:05:36 -0400 Subject: [PATCH 1/2] modifies components --- src/component.rs | 98 ++++++++++++++++++++++++++++++++++++------------ src/pointer.rs | 41 ++++++++++++++++++-- 2 files changed, 112 insertions(+), 27 deletions(-) diff --git a/src/component.rs b/src/component.rs index 9950161..01fd700 100644 --- a/src/component.rs +++ b/src/component.rs @@ -2,40 +2,59 @@ use crate::{Pointer, Token, Tokens}; /// A single [`Token`] or the root of a JSON Pointer #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Component<'t> { +pub enum Component<'p> { /// The document root Root, - /// A segment of a JSON Pointer - Token(Token<'t>), -} -impl<'t> From> for Component<'t> { - fn from(token: Token<'t>) -> Self { - Self::Token(token) - } + /// A token of a [`Pointer`] + Token { + /// the [`Token`] + token: Token<'p>, + /// The full location of this `Token`, including the `Token` itself + pointer: &'p Pointer, + /// index of the token in the pointer + index: usize, + /// offset of the [`Token`] in the pointer + offset: usize, + }, } /// An iterator over the [`Component`]s of a JSON Pointer #[derive(Debug)] -pub struct Components<'t> { - tokens: Tokens<'t>, - sent_root: bool, +pub struct Components<'p> { + pointer: &'p Pointer, + tokens: Tokens<'p>, + sent: usize, + offset: usize, } -impl<'t> Iterator for Components<'t> { - type Item = Component<'t>; +impl<'p> Iterator for Components<'p> { + type Item = Component<'p>; fn next(&mut self) -> Option { - if !self.sent_root { - self.sent_root = true; + if self.sent == 0 { + self.sent += 1; return Some(Component::Root); } - self.tokens.next().map(Component::Token) + let token = self.tokens.next()?; + let offset = self.offset; + let index = self.sent - 1; + + self.offset += 1 + token.encoded().len(); + self.sent += 1; + Some(Component::Token { + token, + offset, + index, + pointer: self.pointer.partial(index).unwrap(), + }) } } impl<'t> From<&'t Pointer> for Components<'t> { fn from(pointer: &'t Pointer) -> Self { Self { - sent_root: false, + pointer, + offset: 0, + sent: 0, tokens: pointer.tokens(), } } @@ -55,7 +74,15 @@ mod tests { let components = ptr.components().collect::>(); assert_eq!( components, - vec![Component::Root, Component::Token("foo".into())] + vec![ + Component::Root, + Component::Token { + token: "foo".into(), + index: 0, + offset: 0, + pointer: Pointer::from_static("/foo") + } + ] ); let ptr = Pointer::from_static("/foo/bar/-/0/baz"); @@ -64,11 +91,36 @@ mod tests { components, vec![ Component::Root, - Component::from(Token::from("foo")), - Component::Token("bar".into()), - Component::Token("-".into()), - Component::Token("0".into()), - Component::Token("baz".into()) + Component::Token { + token: "foo".into(), + offset: 0, + index: 0, + pointer: Pointer::from_static("/foo"), + }, + Component::Token { + token: "bar".into(), + index: 1, + offset: 4, + pointer: Pointer::from_static("/foo/bar") + }, + Component::Token { + token: "-".into(), + index: 2, + offset: 8, + pointer: Pointer::from_static("/foo/bar/-") + }, + Component::Token { + token: "0".into(), + index: 3, + offset: 10, + pointer: Pointer::from_static("/foo/bar/-/0") + }, + Component::Token { + token: "baz".into(), + index: 4, + offset: 12, + pointer: Pointer::from_static("/foo/bar/-/0/baz") + } ] ); } diff --git a/src/pointer.rs b/src/pointer.rs index 7312b0d..8224c40 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -97,6 +97,17 @@ impl Pointer { unsafe { &*(core::ptr::from_ref::(s) as *const Self) } } + /// Returns the length in bytes of the encoded string representation of this + /// `Pointer`. + pub fn len(&self) -> usize { + self.0.len() + } + /// Returns `true` if `self` is a root pointer, meaning it has a length of + /// zero bytes. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// The encoded string representation of this `Pointer` pub fn as_str(&self) -> &str { &self.0 @@ -125,6 +136,27 @@ impl Pointer { self.0.is_empty() } + /// Returns the partial pointer which includes all tokens up to and + /// including the token at the the given index. + // TODO: this needs a better name + pub(crate) fn partial(&self, index: usize) -> Option<&Self> { + let mut split = self.0.split('/').skip(1).enumerate(); + let mut offset = 0; + + #[allow(clippy::while_let_loop, clippy::while_let_on_iterator)] + while let Some((i, tok)) = split.next() { + if i == index { + let Some((_, tok)) = split.next() else { + return Some(self); + }; + offset = offset + tok.len() + 1; + return Some(Self::new(&self.0[..offset])); + } + offset += tok.len() + 1; + } + None + } + /// Returns a `serde_json::Value` representation of this `Pointer` #[cfg(feature = "json")] pub fn to_json_value(&self) -> serde_json::Value { @@ -2006,9 +2038,10 @@ mod tests { } #[test] - fn borrow() { - let ptr = PointerBuf::from_tokens(["foo", "bar"]); - let borrowed: &Pointer = ptr.borrow(); - assert_eq!(borrowed, "/foo/bar"); + fn partial() { + let p = Pointer::from_static("/foo/bar/baz"); + assert_eq!(p.partial(0).unwrap(), "/foo"); + assert_eq!(p.partial(1).unwrap(), "/foo/bar"); + assert_eq!(p.partial(2).unwrap(), "/foo/bar/baz"); } } From 8bbbbce5ca9685c9193f43df80f6010b3668fb8b Mon Sep 17 00:00:00 2001 From: Chance Date: Wed, 10 Jul 2024 17:36:51 -0400 Subject: [PATCH 2/2] adds walk_to to `Resolve` --- src/resolve.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/src/resolve.rs b/src/resolve.rs index 17ee9e0..bf8b790 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -33,9 +33,11 @@ //! | TOML | `toml::Value` | `"toml"` | | //! //! +use core::{borrow::Borrow, fmt, ops::ControlFlow}; + use crate::{ index::{OutOfBoundsError, ParseIndexError}, - Pointer, Token, + Component, Pointer, Token, }; /* @@ -55,7 +57,7 @@ pub trait Resolve { type Value; /// Error associated with `Resolve` - type Error; + type Error: fmt::Debug; /// Resolve a reference to `Self::Value` based on the path in a [Pointer]. /// @@ -63,6 +65,51 @@ pub trait Resolve { /// Returns a [`Self::Error`](Resolve::Error) if the [`Pointer`] can not /// be resolved. fn resolve(&self, ptr: &Pointer) -> Result<&Self::Value, Self::Error>; + + /// TODO + /// ## Errors + fn walk_to(&self, ptr: &Pointer, f: F) -> Result, Self::Error> + where + F: Fn(Component, &Self::Value) -> ControlFlow, + { + let _ = self.resolve(ptr)?; + let root = self.resolve(Pointer::root()).unwrap(); + let mut last = None; + for component in ptr.components() { + match component { + Component::Root => { + last = Some(f(Component::Root, root)); + } + Component::Token { + token, + index, + offset, + pointer, + } => { + let value = self.resolve(pointer).unwrap(); + let tok_len = token.encoded().len(); + let ctrl_flow = f( + Component::Token { + token, + pointer, + index, + offset, + }, + value, + ); + if offset + tok_len >= ptr.len() { + // last token + return Ok(ctrl_flow); + } + if ctrl_flow.is_break() { + return Ok(ctrl_flow); + } + last = Some(ctrl_flow); + } + } + } + Ok(last.unwrap()) + } } /* @@ -724,6 +771,26 @@ mod tests { ]); } + #[test] + #[cfg(feature = "json")] + fn json_walk_to() { + use core::ops::ControlFlow; + + use serde_json::json; + + let value = json!({ + "foo": { + "bar": { + "baz": { + "qux": [0,1,2,3] + } + } + } + }); + + let ptr = Pointer::from_static("/foo/bar/baz/qux/2"); + value.walk_to(ptr, |c, v| {}); + } /* ╔═══════════════════════════════════════════════════╗ ║ toml ║