From 4ce6c00c8e101f70ba50c11c3ebf1725f43f6760 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 11:44:52 -0400 Subject: [PATCH 01/57] adds assert to example of `Index::for_len_unchecked` --- src/index.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 2d770f2..30119f0 100644 --- a/src/index.rs +++ b/src/index.rs @@ -127,8 +127,11 @@ impl Index { /// # use jsonptr::Index; /// assert_eq!(Index::Num(42).for_len_unchecked(30), 42); /// assert_eq!(Index::Next.for_len_unchecked(30), 30); + /// + /// // no bounds checks + /// assert_eq!(Index::Num(34).for_len_unchecked(40), 40); + /// assert_eq!(Index::Next.for_len_unchecked(34), 34); /// ```` - pub fn for_len_unchecked(&self, length: usize) -> usize { match *self { Self::Num(idx) => idx, From 7b5d56ddb54000ed36ca9b97091449b7fd121f63 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 11:50:39 -0400 Subject: [PATCH 02/57] fixes `Index::for_len_unchecked` test --- src/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 30119f0..da580e6 100644 --- a/src/index.rs +++ b/src/index.rs @@ -129,7 +129,7 @@ impl Index { /// assert_eq!(Index::Next.for_len_unchecked(30), 30); /// /// // no bounds checks - /// assert_eq!(Index::Num(34).for_len_unchecked(40), 40); + /// assert_eq!(Index::Num(40).for_len_unchecked(34), 40); /// assert_eq!(Index::Next.for_len_unchecked(34), 34); /// ```` pub fn for_len_unchecked(&self, length: usize) -> usize { From e70813f4029f156078ef577016f1d928410ca6c1 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 11:55:51 -0400 Subject: [PATCH 03/57] ignores `src/arbitrary.rs` from codecov --- .github/codecov.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index cd5ce8f..a6c5f70 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,21 +1,22 @@ # ref: https://docs.codecov.com/docs/codecovyml-reference coverage: - # Hold ourselves to a high bar - range: 85..100 - round: down - precision: 1 - status: - # ref: https://docs.codecov.com/docs/commit-status - project: - default: - # Avoid false negatives - threshold: 1% + # Hold ourselves to a high bar + range: 85..100 + round: down + precision: 1 + status: + # ref: https://docs.codecov.com/docs/commit-status + project: + default: + # Avoid false negatives + threshold: 1% # Test files aren't important for coverage ignore: - - "tests" + - "tests" + - "src/arbitrary.rs" # Make comments less noisy comment: - layout: "files" - require_changes: true + layout: "files" + require_changes: true From 4e4bd89ac92664e5f539cd156fe0eaf87d6c9c14 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 11:57:45 -0400 Subject: [PATCH 04/57] `**/arbitrary.rs` from codecov --- .github/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index a6c5f70..f113c1e 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -14,7 +14,7 @@ coverage: # Test files aren't important for coverage ignore: - "tests" - - "src/arbitrary.rs" + - "**/arbitrary.rs" # Make comments less noisy comment: From 4457b45b5df71710200371723f28fd9f24810f07 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 12:34:40 -0400 Subject: [PATCH 05/57] trying to get doc tests to count toward codecov by setting language --- .github/codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/codecov.yml b/.github/codecov.yml index f113c1e..ede5acd 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,5 +1,6 @@ # ref: https://docs.codecov.com/docs/codecovyml-reference coverage: + language: rust, # Hold ourselves to a high bar range: 85..100 round: down From 2b2a4ec009826c2c2de21e5c203e22a80c2ad776 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 12:40:18 -0400 Subject: [PATCH 06/57] removes , --- .github/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index ede5acd..b55406c 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,6 +1,6 @@ # ref: https://docs.codecov.com/docs/codecovyml-reference coverage: - language: rust, + language: rust # Hold ourselves to a high bar range: 85..100 round: down From 3605468cc9fa8cb8980f6846628fd18fe2a286fe Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 12:52:27 -0400 Subject: [PATCH 07/57] trying cargo-tarpaulin --- .github/workflows/test.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6826f77..6392187 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -131,26 +131,26 @@ jobs: # for lots of more discussion runs-on: ubuntu-latest name: ubuntu / stable / coverage + container: + image: xd009642/tarpaulin:develop-nightly + options: --security-opt seccomp=unconfined steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 with: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable with: components: llvm-tools-preview - - name: cargo install cargo-llvm-cov - uses: taiki-e/install-action@cargo-llvm-cov - name: cargo generate-lockfile if: hashFiles('Cargo.lock') == '' run: cargo generate-lockfile - - name: cargo llvm-cov - run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info - - name: Record Rust version - run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" + - name: Generate code coverage + run: | + cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out xml - name: Upload to codecov.io - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v2 with: + # token: ${{secrets.CODECOV_TOKEN}} # not required for public repos fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} - env_vars: OS,RUST From f7a78162813a01eaa37e58e8351dbf9eb099d1fa Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 12:58:24 -0400 Subject: [PATCH 08/57] attempts to fix test.yml --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6392187..7edf659 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,8 +149,11 @@ jobs: - name: Generate code coverage run: | cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out xml + - name: Record Rust version + run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" - name: Upload to codecov.io - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v4 with: - # token: ${{secrets.CODECOV_TOKEN}} # not required for public repos fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + env_vars: OS,RUST From c522b7a10b87b9091154c1247b26e849c652f619 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 13:23:18 -0400 Subject: [PATCH 09/57] switches back to cargo-llvm-cov --- .github/workflows/test.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7edf659..6826f77 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -131,24 +131,21 @@ jobs: # for lots of more discussion runs-on: ubuntu-latest name: ubuntu / stable / coverage - container: - image: xd009642/tarpaulin:develop-nightly - options: --security-opt seccomp=unconfined steps: - - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: submodules: true - name: Install stable uses: dtolnay/rust-toolchain@stable with: components: llvm-tools-preview + - name: cargo install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov - name: cargo generate-lockfile if: hashFiles('Cargo.lock') == '' run: cargo generate-lockfile - - name: Generate code coverage - run: | - cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out xml + - name: cargo llvm-cov + run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info - name: Record Rust version run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" - name: Upload to codecov.io From 695903e427f3f946c080888ecb36aef4e7b7be90 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 13:43:46 -0400 Subject: [PATCH 10/57] fixes `TryFrom` for `Index` + adds tests --- src/index.rs | 109 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/src/index.rs b/src/index.rs index da580e6..6747548 100644 --- a/src/index.rs +++ b/src/index.rs @@ -35,7 +35,7 @@ use crate::{OutOfBoundsError, ParseIndexError, Token}; use alloc::string::String; -use core::fmt::Display; +use core::{fmt::Display, str::FromStr}; /// Represents an abstract index into an array. /// @@ -140,6 +140,18 @@ impl Index { } } +impl FromStr for Index { + type Err = ParseIndexError; + + fn from_str(s: &str) -> Result { + if s == "-" { + Ok(Index::Next) + } else { + Ok(s.parse::().map(Index::Num)?) + } + } +} + impl Display for Index { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match *self { @@ -172,11 +184,7 @@ impl TryFrom<&str> for Index { type Error = ParseIndexError; fn try_from(value: &str) -> Result { - if value == "-" { - Ok(Index::Next) - } else { - Ok(value.parse::().map(Index::Num)?) - } + Index::from_str(value) } } @@ -187,11 +195,96 @@ macro_rules! derive_try_from { type Error = ParseIndexError; fn try_from(value: $t) -> Result { - value.try_into() + Index::from_str(&value) } } )* } } -derive_try_from!(Token<'_>, String, &String); +impl TryFrom> for Index { + type Error = ParseIndexError; + + fn try_from(tok: Token) -> Result { + Index::from_str(tok.encoded()) + } +} +derive_try_from!(String, &String); + +#[cfg(test)] +mod tests { + use super::*; + use crate::Token; + + #[test] + fn index_from_usize() { + let index = Index::from(5usize); + assert_eq!(index, Index::Num(5)); + } + + #[test] + fn test_index_try_from_token_num() { + let token = Token::new("3"); + let index = Index::try_from(&token).unwrap(); + assert_eq!(index, Index::Num(3)); + } + + #[test] + fn test_index_try_from_token_next() { + let token = Token::new("-"); + let index = Index::try_from(&token).unwrap(); + assert_eq!(index, Index::Next); + } + + #[test] + fn test_index_try_from_str_num() { + let index = Index::try_from("42").unwrap(); + assert_eq!(index, Index::Num(42)); + } + + #[test] + fn test_index_try_from_str_next() { + let index = Index::try_from("-").unwrap(); + assert_eq!(index, Index::Next); + } + + #[test] + fn test_index_try_from_string_num() { + let index = Index::try_from(String::from("7")).unwrap(); + assert_eq!(index, Index::Num(7)); + } + + #[test] + fn testindex_try_from_string_next() { + let index = Index::try_from(String::from("-")).unwrap(); + assert_eq!(index, Index::Next); + } + + #[test] + fn test_index_for_len_incl_valid() { + assert_eq!(Index::Num(0).for_len_incl(1), Ok(0)); + assert_eq!(Index::Next.for_len_incl(2), Ok(2)); + } + + #[test] + fn test_index_for_len_incl_out_of_bounds() { + assert!(Index::Num(2).for_len_incl(1).is_err()); + } + + #[test] + fn test_index_for_len_unchecked() { + assert_eq!(Index::Num(10).for_len_unchecked(5), 10); + assert_eq!(Index::Next.for_len_unchecked(3), 3); + } + + #[test] + fn test_display_index_num() { + let index = Index::Num(5); + assert_eq!(index.to_string(), "5"); + } + + #[test] + fn test_display_index_next() { + assert_eq!(Index::Next.to_string(), "-"); + } +} From 35e821a4747e5e31997afe02db6b4924527ca409 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 13:46:44 -0400 Subject: [PATCH 11/57] attempting to ignore arbitrary.rs from codecov --- .github/codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index b55406c..e99f833 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -15,7 +15,8 @@ coverage: # Test files aren't important for coverage ignore: - "tests" - - "**/arbitrary.rs" + - "arbitrary.rs" + - "src/arbitrary.rs" # Make comments less noisy comment: From 102b43185ec69587f6d388a76dfe81daeee23813 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 13:57:13 -0400 Subject: [PATCH 12/57] adds tests for error display --- src/lib.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 76b55fe..2cf1aa8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,3 +332,38 @@ impl fmt::Display for ReplaceTokenError { #[cfg(feature = "std")] impl std::error::Error for ReplaceTokenError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_error_display() { + assert_eq!( + ParseError::NoLeadingBackslash.to_string(), + "json pointer is malformed as it does not start with a backslash ('/')" + ); + } + + #[test] + fn parse_index_error_from_parse_int_error() { + assert_eq!( + Token::new("a").to_index().unwrap_err().to_string(), + "failed to parse token as an integer" + ); + } + + #[test] + fn invalid_encoding_error_display() { + assert_eq!( + Token::from_encoded("~").unwrap_err().to_string(), + "json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')" + ); + } + + #[test] + fn out_of_bounds_error_display() { + let error = Token::new("10").to_index().unwrap().for_len(5).unwrap_err(); + assert_eq!(error.to_string(), "index 10 out of bounds (limit: 5)"); + } +} From d3183d7e1a1fbeb223a19a539f74a756fdc35fe0 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 14:07:59 -0400 Subject: [PATCH 13/57] removes `language: rust` from codecov.yml --- .github/codecov.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index e99f833..657b95d 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,6 +1,5 @@ # ref: https://docs.codecov.com/docs/codecovyml-reference coverage: - language: rust # Hold ourselves to a high bar range: 85..100 round: down From 02326bf324c75b4970f4e7d215a0b45c7fbe14c8 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 14:55:25 -0400 Subject: [PATCH 14/57] adds a few tests for Pointer --- src/pointer.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/src/pointer.rs b/src/pointer.rs index b8501e2..0a6824b 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -228,6 +228,10 @@ impl Pointer { self.0.strip_suffix(&suffix.0).map(Self::new) } + /// Returns the pointer stripped of the given prefix. + pub fn strip_prefix<'a>(&'a self, prefix: &Self) -> Option<&'a Self> { + self.0.strip_prefix(&prefix.0).map(Self::new) + } /// Attempts to get a `Token` by the index. Returns `None` if the index is /// out of bounds. /// @@ -866,7 +870,18 @@ mod tests { fn from_const_validates() { let _ = Pointer::from_static("foo/bar"); } - + #[test] + fn test_strip_suffix() { + let p = Pointer::new("/example/pointer/to/some/value"); + let stripped = p.strip_suffix(Pointer::new("/to/some/value")).unwrap(); + assert_eq!(stripped, "/example/pointer"); + } + #[test] + fn test_strip_prefix() { + let p = Pointer::new("/example/pointer/to/some/value"); + let stripped = p.strip_prefix(Pointer::new("/example/pointer")).unwrap(); + assert_eq!(stripped, "/to/some/value"); + } #[test] fn test_parse() { let tests = [ @@ -1086,6 +1101,72 @@ mod tests { assert_eq!(ptr, into); } + #[cfg(all(feature = "resolve", feature = "json"))] + #[test] + fn test_resolve() { + // full tests in resolve.rs + use serde_json::json; + let value = json!({ + "foo": { + "bar": { + "baz": "qux" + } + } + }); + let ptr = Pointer::from_static("/foo/bar/baz"); + let resolved = ptr.resolve(&value).unwrap(); + assert_eq!(resolved, &json!("qux")); + } + + #[cfg(all(feature = "delete", feature = "json"))] + #[test] + fn test_delete() { + use serde_json::json; + let mut value = json!({ + "foo": { + "bar": { + "baz": "qux" + } + } + }); + let ptr = Pointer::from_static("/foo/bar/baz"); + let deleted = ptr.delete(&mut value).unwrap(); + assert_eq!(deleted, json!("qux")); + assert_eq!( + value, + json!({ + "foo": { + "bar": {} + } + }) + ); + } + + #[cfg(all(feature = "assign", feature = "json"))] + #[test] + fn test_assign() { + use serde_json::json; + let mut value = json!({}); + let ptr = Pointer::from_static("/foo/bar"); + let replaced = ptr.assign(&mut value, json!("baz")).unwrap(); + assert_eq!(replaced, None); + assert_eq!( + value, + json!({ + "foo": { + "bar": "baz" + } + }) + ); + } + + #[test] + fn test_get() { + let ptr = Pointer::from_static("/0/1/2/3/4/5/6/7/8/9"); + for i in 0..10 { + assert_eq!(ptr.get(i).unwrap().decoded(), i.to_string()); + } + } #[test] fn pop_back_works_with_empty_strings() { { @@ -1281,6 +1362,27 @@ mod tests { TestResult::from_bool(isect == base) } + #[cfg(all(feature = "json", feature = "std", feature = "serde"))] + #[test] + fn test_serde() { + use serde::Deserialize; + let ptr = PointerBuf::from_tokens(["foo", "bar"]); + let json = serde_json::to_string(&ptr).unwrap(); + assert_eq!(json, "\"/foo/bar\""); + let deserialized: PointerBuf = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, ptr); + + let ptr = Pointer::from_static("/foo/bar"); + let json = serde_json::to_string(&ptr).unwrap(); + assert_eq!(json, "\"/foo/bar\""); + + let mut de = serde_json::Deserializer::from_str("\"/foo/bar\""); + let p = <&Pointer>::deserialize(&mut de).unwrap(); + assert_eq!(p, ptr); + let s = serde_json::to_string(p).unwrap(); + assert_eq!(json, s); + } + #[test] fn test_intersection() { struct Test { @@ -1295,6 +1397,11 @@ mod tests { a_suffix: "/", b_suffix: "/a/b/c", }, + Test { + base: "", + a_suffix: "", + b_suffix: "", + }, Test { base: "/a", a_suffix: "/", From 59aee5f9cb3c6f5fddc882cee2990948755e57e2 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 15:02:23 -0400 Subject: [PATCH 15/57] fixes `replace_token` bound --- src/pointer.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/pointer.rs b/src/pointer.rs index 0a6824b..fea41ef 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -670,7 +670,7 @@ impl PointerBuf { }); } let mut tokens = self.tokens().collect::>(); - if index > tokens.len() { + if index >= tokens.len() { return Err(ReplaceTokenError { count: tokens.len(), index, @@ -1167,6 +1167,41 @@ mod tests { assert_eq!(ptr.get(i).unwrap().decoded(), i.to_string()); } } + + #[test] + fn test_replace_token_success() { + let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); + assert!(ptr.replace_token(1, "qux".into()).is_ok()); + assert_eq!(ptr, PointerBuf::from_tokens(["foo", "qux", "baz"])); + + assert!(ptr.replace_token(0, "norf".into()).is_ok()); + assert_eq!(ptr, PointerBuf::from_tokens(["norf", "qux", "baz"])); + + assert!(ptr.replace_token(2, "quux".into()).is_ok()); + assert_eq!(ptr, PointerBuf::from_tokens(["norf", "qux", "quux"])); + } + + #[test] + fn test_replace_token_out_of_bounds() { + let mut ptr = PointerBuf::from_tokens(["foo", "bar"]); + assert!(ptr.replace_token(2, "baz".into()).is_err()); + assert_eq!(ptr, PointerBuf::from_tokens(["foo", "bar"])); // Ensure original pointer is unchanged + } + + #[test] + fn test_replace_token_with_empty_string() { + let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); + assert!(ptr.replace_token(1, "".into()).is_ok()); + assert_eq!(ptr, PointerBuf::from_tokens(["foo", "", "baz"])); + } + + #[test] + fn test_replace_token_in_empty_pointer() { + let mut ptr = PointerBuf::default(); + assert!(ptr.replace_token(0, "foo".into()).is_err()); + assert_eq!(ptr, PointerBuf::default()); // Ensure the pointer remains empty + } + #[test] fn pop_back_works_with_empty_strings() { { From 5a0c191d284994cbb97a214bdb5672ab71fe0ad4 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 15:25:07 -0400 Subject: [PATCH 16/57] fixes adds tests for serde `Token` --- src/token.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/token.rs b/src/token.rs index bafa5b1..0675b3a 100644 --- a/src/token.rs +++ b/src/token.rs @@ -336,6 +336,30 @@ mod tests { assert_eq!(Token::from("~/").encoded(), "~0~1"); } + #[test] + fn test_to_index() { + assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); + assert_eq!(Token::new("0").to_index(), Ok(Index::Num(0))); + assert_eq!(Token::new("2").to_index(), Ok(Index::Num(2))); + assert!(Token::new("a").to_index().is_err()); + assert!(Token::new("-1").to_index().is_err()); + } + + #[test] + fn test_new() { + assert_eq!(Token::new("~1").encoded(), "~0~1"); + assert_eq!(Token::new("a/b").encoded(), "~1a~1b"); + } + + #[test] + fn test_serde() { + let token = Token::from_encoded("foo~0").unwrap(); + let json = serde_json::to_string(&token).unwrap(); + assert_eq!(json, "\"foo~\""); + let deserialized: Token = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized, token); + } + #[test] fn test_from_encoded() { assert_eq!(Token::from_encoded("~1").unwrap().encoded(), "~1"); From a8a1dc1b01312e0476ab9ff43993c2694e61b91f Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 15:47:43 -0400 Subject: [PATCH 17/57] fixes test --- src/token.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token.rs b/src/token.rs index 0675b3a..55be2f9 100644 --- a/src/token.rs +++ b/src/token.rs @@ -347,7 +347,7 @@ mod tests { #[test] fn test_new() { - assert_eq!(Token::new("~1").encoded(), "~0~1"); + assert_eq!(Token::new("~1").encoded(), "~01"); assert_eq!(Token::new("a/b").encoded(), "~1a~1b"); } From 81e6d303dc20c832cbfc5ed26da438e96475d85c Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 15:51:49 -0400 Subject: [PATCH 18/57] fixes test.. for real this time --- src/token.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token.rs b/src/token.rs index 55be2f9..0a3f14e 100644 --- a/src/token.rs +++ b/src/token.rs @@ -348,7 +348,7 @@ mod tests { #[test] fn test_new() { assert_eq!(Token::new("~1").encoded(), "~01"); - assert_eq!(Token::new("a/b").encoded(), "~1a~1b"); + assert_eq!(Token::new("a/b").encoded(), "a~1b"); } #[test] From 17f082a2b0fc275cfcd41a2a4c456dbff04472ae Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 16:41:36 -0400 Subject: [PATCH 19/57] more tests --- src/assign.rs | 4 +- src/delete.rs | 4 +- src/index.rs | 20 +++++----- src/pointer.rs | 103 +++++++++++++++++++++++++++++++++++++++---------- src/resolve.rs | 8 ++-- src/token.rs | 10 ++--- 6 files changed, 105 insertions(+), 44 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index 7890b16..11361f8 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -598,7 +598,7 @@ mod tests { #[test] #[cfg(feature = "json")] - fn test_assign_json() { + fn assign_json() { use alloc::vec; use serde_json::json; Test::all([ @@ -757,7 +757,7 @@ mod tests { #[test] #[cfg(feature = "toml")] - fn test_assign_toml() { + fn assign_toml() { use alloc::vec; use toml::{toml, Table, Value}; Test::all([ diff --git a/src/delete.rs b/src/delete.rs index 8db5850..15be7dc 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -212,7 +212,7 @@ mod tests { */ #[test] #[cfg(feature = "json")] - fn test_delete_json() { + fn delete_json() { Test::all([ // 0 Test { @@ -280,7 +280,7 @@ mod tests { */ #[test] #[cfg(feature = "toml")] - fn test_delete_toml() { + fn delete_toml() { use toml::{toml, Table, Value}; Test::all([ diff --git a/src/index.rs b/src/index.rs index 6747548..8be86ba 100644 --- a/src/index.rs +++ b/src/index.rs @@ -223,33 +223,33 @@ mod tests { } #[test] - fn test_index_try_from_token_num() { + fn index_try_from_token_num() { let token = Token::new("3"); let index = Index::try_from(&token).unwrap(); assert_eq!(index, Index::Num(3)); } #[test] - fn test_index_try_from_token_next() { + fn index_try_from_token_next() { let token = Token::new("-"); let index = Index::try_from(&token).unwrap(); assert_eq!(index, Index::Next); } #[test] - fn test_index_try_from_str_num() { + fn index_try_from_str_num() { let index = Index::try_from("42").unwrap(); assert_eq!(index, Index::Num(42)); } #[test] - fn test_index_try_from_str_next() { + fn index_try_from_str_next() { let index = Index::try_from("-").unwrap(); assert_eq!(index, Index::Next); } #[test] - fn test_index_try_from_string_num() { + fn index_try_from_string_num() { let index = Index::try_from(String::from("7")).unwrap(); assert_eq!(index, Index::Num(7)); } @@ -261,30 +261,30 @@ mod tests { } #[test] - fn test_index_for_len_incl_valid() { + fn index_for_len_incl_valid() { assert_eq!(Index::Num(0).for_len_incl(1), Ok(0)); assert_eq!(Index::Next.for_len_incl(2), Ok(2)); } #[test] - fn test_index_for_len_incl_out_of_bounds() { + fn index_for_len_incl_out_of_bounds() { assert!(Index::Num(2).for_len_incl(1).is_err()); } #[test] - fn test_index_for_len_unchecked() { + fn index_for_len_unchecked() { assert_eq!(Index::Num(10).for_len_unchecked(5), 10); assert_eq!(Index::Next.for_len_unchecked(3), 3); } #[test] - fn test_display_index_num() { + fn display_index_num() { let index = Index::Num(5); assert_eq!(index.to_string(), "5"); } #[test] - fn test_display_index_next() { + fn display_index_next() { assert_eq!(Index::Next.to_string(), "-"); } } diff --git a/src/pointer.rs b/src/pointer.rs index fea41ef..01f1add 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -445,13 +445,30 @@ impl PartialEq<&str> for Pointer { &&self.0 == other } } - +impl<'p> PartialEq for &'p Pointer { + fn eq(&self, other: &String) -> bool { + self.0.eq(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 String { + fn eq(&self, other: &Pointer) -> bool { + self == &other.0 + } +} + + impl PartialEq for str { fn eq(&self, other: &Pointer) -> bool { self == &other.0 @@ -871,19 +888,19 @@ mod tests { let _ = Pointer::from_static("foo/bar"); } #[test] - fn test_strip_suffix() { + fn strip_suffix() { let p = Pointer::new("/example/pointer/to/some/value"); let stripped = p.strip_suffix(Pointer::new("/to/some/value")).unwrap(); assert_eq!(stripped, "/example/pointer"); } #[test] - fn test_strip_prefix() { + fn strip_prefix() { let p = Pointer::new("/example/pointer/to/some/value"); let stripped = p.strip_prefix(Pointer::new("/example/pointer")).unwrap(); assert_eq!(stripped, "/to/some/value"); } #[test] - fn test_parse() { + fn parse() { let tests = [ ("", Ok("")), ("/", Ok("/")), @@ -932,7 +949,7 @@ mod tests { } #[test] - fn test_push_pop_back() { + fn push_pop_back() { let mut ptr = PointerBuf::default(); assert_eq!(ptr, "", "default, root pointer should equal \"\""); assert_eq!(ptr.count(), 0, "default pointer should have 0 tokens"); @@ -963,7 +980,7 @@ mod tests { } #[test] - fn test_replace_token() { + fn replace_token() { let mut ptr = PointerBuf::try_from("/test/token").unwrap(); let res = ptr.replace_token(0, "new".into()); @@ -979,7 +996,7 @@ mod tests { } #[test] - fn test_push_pop_front() { + fn push_pop_front() { let mut ptr = PointerBuf::default(); assert_eq!(ptr, ""); assert_eq!(ptr.count(), 0); @@ -1051,7 +1068,7 @@ mod tests { } #[test] - fn test_formatting() { + fn formatting() { assert_eq!(PointerBuf::from_tokens(["foo", "bar"]), "/foo/bar"); assert_eq!( PointerBuf::from_tokens(["~/foo", "~bar", "/baz"]), @@ -1062,7 +1079,7 @@ mod tests { } #[test] - fn test_last() { + fn last() { let ptr = Pointer::from_static("/foo/bar"); assert_eq!(ptr.last(), Some("bar".into())); @@ -1081,7 +1098,7 @@ mod tests { } #[test] - fn test_first() { + fn first() { let ptr = Pointer::from_static("/foo/bar"); assert_eq!(ptr.first(), Some("foo".into())); @@ -1093,7 +1110,7 @@ mod tests { } #[test] - fn test_pointerbuf_try_from() { + fn pointerbuf_try_from() { let ptr = PointerBuf::from_tokens(["foo", "bar", "~/"]); assert_eq!(PointerBuf::try_from("/foo/bar/~0~1").unwrap(), ptr); @@ -1101,9 +1118,27 @@ mod tests { assert_eq!(ptr, into); } + #[test] + fn default() { + let ptr = PointerBuf::default(); + assert_eq!(ptr, ""); + assert_eq!(ptr.count(), 0); + + let ptr = <&Pointer>::default(); + assert_eq!(ptr, ""); + } + + #[test] + #[cfg(all(feature = "serde", feature = "json"))] + fn to_json_value() { + use serde_json::Value; + let ptr = Pointer::from_static("/foo/bar"); + assert_eq!(ptr.to_json_value(), Value::String(String::from("/foo/bar"))); + } + #[cfg(all(feature = "resolve", feature = "json"))] #[test] - fn test_resolve() { + fn resolve() { // full tests in resolve.rs use serde_json::json; let value = json!({ @@ -1120,7 +1155,7 @@ mod tests { #[cfg(all(feature = "delete", feature = "json"))] #[test] - fn test_delete() { + fn delete() { use serde_json::json; let mut value = json!({ "foo": { @@ -1144,7 +1179,7 @@ mod tests { #[cfg(all(feature = "assign", feature = "json"))] #[test] - fn test_assign() { + fn assign() { use serde_json::json; let mut value = json!({}); let ptr = Pointer::from_static("/foo/bar"); @@ -1161,7 +1196,7 @@ mod tests { } #[test] - fn test_get() { + fn get() { let ptr = Pointer::from_static("/0/1/2/3/4/5/6/7/8/9"); for i in 0..10 { assert_eq!(ptr.get(i).unwrap().decoded(), i.to_string()); @@ -1169,7 +1204,7 @@ mod tests { } #[test] - fn test_replace_token_success() { + fn replace_token_success() { let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); assert!(ptr.replace_token(1, "qux".into()).is_ok()); assert_eq!(ptr, PointerBuf::from_tokens(["foo", "qux", "baz"])); @@ -1182,21 +1217,21 @@ mod tests { } #[test] - fn test_replace_token_out_of_bounds() { + fn replace_token_out_of_bounds() { let mut ptr = PointerBuf::from_tokens(["foo", "bar"]); assert!(ptr.replace_token(2, "baz".into()).is_err()); assert_eq!(ptr, PointerBuf::from_tokens(["foo", "bar"])); // Ensure original pointer is unchanged } #[test] - fn test_replace_token_with_empty_string() { + fn replace_token_with_empty_string() { let mut ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); assert!(ptr.replace_token(1, "".into()).is_ok()); assert_eq!(ptr, PointerBuf::from_tokens(["foo", "", "baz"])); } #[test] - fn test_replace_token_in_empty_pointer() { + fn replace_token_in_empty_pointer() { let mut ptr = PointerBuf::default(); assert!(ptr.replace_token(0, "foo".into()).is_err()); assert_eq!(ptr, PointerBuf::default()); // Ensure the pointer remains empty @@ -1399,7 +1434,7 @@ mod tests { #[cfg(all(feature = "json", feature = "std", feature = "serde"))] #[test] - fn test_serde() { + fn serde() { use serde::Deserialize; let ptr = PointerBuf::from_tokens(["foo", "bar"]); let json = serde_json::to_string(&ptr).unwrap(); @@ -1416,10 +1451,36 @@ mod tests { assert_eq!(p, ptr); let s = serde_json::to_string(p).unwrap(); assert_eq!(json, s); + + let invalid = serde_json::from_str::<&Pointer>("\"foo/bar\""); + assert!(invalid.is_err()); + assert_eq!( + invalid.unwrap_err().to_string(), + "failed to parse json pointer\n\ncaused by:\njson pointer is malformed as it does not start with a backslash ('/') at line 1 column 9" + ); + } + + #[test] + fn to_owned(){ + let ptr = Pointer::from_static("/bread/crumbs"); + let buf = ptr.to_owned(); + assert_eq!(buf, "/bread/crumbs"); + } + + + #[test] + #[allow(clippy::cmp_owned)] + fn partial_eq() { + let p = Pointer::from_static("/bread/crumbs"); + assert!(p == "/bread/crumbs"); + assert!(p == String::from("/bread/crumbs")); + assert!(p == p.to_owned()); + assert!(p == Pointer::from_static("/bread/crumbs")); + assert!(p != Pointer::from_static("/not/bread/crumbs/")); } #[test] - fn test_intersection() { + fn intersection() { struct Test { base: &'static str, a_suffix: &'static str, diff --git a/src/resolve.rs b/src/resolve.rs index f11bbd9..822600c 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -454,7 +454,7 @@ mod tests { #[test] #[cfg(feature = "json")] - fn test_resolve_json() { + fn resolve_json() { use serde_json::json; let data = &json!({ @@ -476,7 +476,7 @@ mod tests { " ": 7, "m~n": 8 }); - // let data = &test_data; + // let data = &data; Test::all([ // 0 @@ -567,7 +567,7 @@ mod tests { */ #[test] #[cfg(feature = "toml")] - fn test_resolve_toml() { + fn resolve_toml() { use toml::{toml, Value}; let data = &Value::Table(toml! { @@ -588,7 +588,7 @@ mod tests { " " = 7 "m~n" = 8 }); - // let data = &test_data; + // let data = &data; Test::all([ // 0 diff --git a/src/token.rs b/src/token.rs index 0a3f14e..413e688 100644 --- a/src/token.rs +++ b/src/token.rs @@ -331,13 +331,13 @@ mod tests { use quickcheck_macros::quickcheck; #[test] - fn test_from() { + fn from() { assert_eq!(Token::from("/").encoded(), "~1"); assert_eq!(Token::from("~/").encoded(), "~0~1"); } #[test] - fn test_to_index() { + fn to_index() { assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); assert_eq!(Token::new("0").to_index(), Ok(Index::Num(0))); assert_eq!(Token::new("2").to_index(), Ok(Index::Num(2))); @@ -346,13 +346,13 @@ mod tests { } #[test] - fn test_new() { + fn new() { assert_eq!(Token::new("~1").encoded(), "~01"); assert_eq!(Token::new("a/b").encoded(), "a~1b"); } #[test] - fn test_serde() { + fn serde() { let token = Token::from_encoded("foo~0").unwrap(); let json = serde_json::to_string(&token).unwrap(); assert_eq!(json, "\"foo~\""); @@ -361,7 +361,7 @@ mod tests { } #[test] - fn test_from_encoded() { + fn from_encoded() { assert_eq!(Token::from_encoded("~1").unwrap().encoded(), "~1"); assert_eq!(Token::from_encoded("~0~1").unwrap().encoded(), "~0~1"); let t = Token::from_encoded("a~1b").unwrap(); From 68b7079bb4ca3e27e403bdd11f7d03e9f7c7f095 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 16:48:01 -0400 Subject: [PATCH 20/57] cargo fmt --- src/pointer.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pointer.rs b/src/pointer.rs index 01f1add..d66e895 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -468,7 +468,6 @@ impl PartialEq for String { } } - impl PartialEq for str { fn eq(&self, other: &Pointer) -> bool { self == &other.0 @@ -1455,19 +1454,18 @@ mod tests { let invalid = serde_json::from_str::<&Pointer>("\"foo/bar\""); assert!(invalid.is_err()); assert_eq!( - invalid.unwrap_err().to_string(), + invalid.unwrap_err().to_string(), "failed to parse json pointer\n\ncaused by:\njson pointer is malformed as it does not start with a backslash ('/') at line 1 column 9" ); } #[test] - fn to_owned(){ + fn to_owned() { let ptr = Pointer::from_static("/bread/crumbs"); let buf = ptr.to_owned(); assert_eq!(buf, "/bread/crumbs"); } - #[test] #[allow(clippy::cmp_owned)] fn partial_eq() { From 84c5bf359601c3d688716a57defcbd26b55b9473 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 19:16:13 -0400 Subject: [PATCH 21/57] moves error types of out `lib.rs --- src/assign.rs | 10 +- src/index.rs | 93 +++++++++++++- src/lib.rs | 323 ------------------------------------------------- src/pointer.rs | 185 +++++++++++++++++++++++++++- src/resolve.rs | 5 +- src/token.rs | 48 +++++++- 6 files changed, 334 insertions(+), 330 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index 11361f8..79d8221 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -36,7 +36,10 @@ //! assert_eq!(data, json!({"foo": "baz"})); //! ``` -use crate::{OutOfBoundsError, ParseIndexError, Pointer}; +use crate::{ + index::{OutOfBoundsError, ParseIndexError}, + Pointer, +}; use core::fmt::{self, Debug}; /* @@ -548,7 +551,10 @@ mod toml { #[allow(clippy::too_many_lines)] mod tests { use super::{Assign, AssignError}; - use crate::{OutOfBoundsError, ParseIndexError, Pointer}; + use crate::{ + index::{OutOfBoundsError, ParseIndexError}, + Pointer, + }; use alloc::str::FromStr; use core::fmt::{Debug, Display}; diff --git a/src/index.rs b/src/index.rs index 8be86ba..8b380c1 100644 --- a/src/index.rs +++ b/src/index.rs @@ -33,9 +33,13 @@ //! assert_eq!(Index::Next.for_len_unchecked(30), 30); //! ```` -use crate::{OutOfBoundsError, ParseIndexError, Token}; +use crate::Token; use alloc::string::String; -use core::{fmt::Display, str::FromStr}; +use core::{ + fmt::{self, Display}, + num::ParseIntError, + str::FromStr, +}; /// Represents an abstract index into an array. /// @@ -211,6 +215,91 @@ impl TryFrom> for Index { } derive_try_from!(String, &String); +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ OutOfBoundsError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// Indicates that an `Index` is not within the given bounds. +#[derive(Debug, PartialEq, Eq)] +pub struct OutOfBoundsError { + /// The provided array length. + /// + /// If the range is inclusive, the resolved numerical index will be strictly + /// less than this value, otherwise it could be equal to it. + pub length: usize, + + /// The resolved numerical index. + /// + /// Note that [`Index::Next`] always resolves to the given array length, + /// so it is only valid when the range is inclusive. + pub index: usize, +} + +impl fmt::Display for OutOfBoundsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "index {} out of bounds (limit: {})", + self.index, self.length + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for OutOfBoundsError {} + +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ ParseIndexError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// Indicates that the `Token` could not be parsed as valid RFC 6901 index. +#[derive(Debug, PartialEq, Eq)] +pub struct ParseIndexError { + /// The source `ParseIntError` + pub source: ParseIntError, +} + +impl From for ParseIndexError { + fn from(source: ParseIntError) -> Self { + Self { source } + } +} + +impl fmt::Display for ParseIndexError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "failed to parse token as an integer") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseIndexError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.source) + } +} + +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ Tests ║ +║ ¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 2cf1aa8..33b4382 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,6 @@ #[cfg_attr(not(feature = "std"), macro_use)] extern crate alloc; -use core::{fmt, num::ParseIntError}; pub mod prelude; #[cfg(feature = "assign")] @@ -45,325 +44,3 @@ pub use index::Index; #[cfg(test)] mod arbitrary; - -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ ParseError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// Indicates that a `Pointer` was malformed and unable to be parsed. -#[derive(Debug, PartialEq)] -pub enum ParseError { - /// `Pointer` did not start with a backslash (`'/'`). - NoLeadingBackslash, - - /// `Pointer` contained invalid encoding (e.g. `~` not followed by `0` or - /// `1`). - InvalidEncoding { - /// Offset of the partial pointer starting with the token that contained - /// the invalid encoding - offset: usize, - /// The source `InvalidEncodingError` - source: InvalidEncodingError, - }, -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NoLeadingBackslash { .. } => { - write!( - f, - "json pointer is malformed as it does not start with a backslash ('/')" - ) - } - Self::InvalidEncoding { source, .. } => write!(f, "{source}"), - } - } -} - -impl ParseError { - /// Returns `true` if this error is `NoLeadingBackslash`; otherwise returns - /// `false`. - pub fn is_no_leading_backslash(&self) -> bool { - matches!(self, Self::NoLeadingBackslash { .. }) - } - - /// Returns `true` if this error is `InvalidEncoding`; otherwise returns - /// `false`. - pub fn is_invalid_encoding(&self) -> bool { - matches!(self, Self::InvalidEncoding { .. }) - } - - /// Offset of the partial pointer starting with the token which caused the - /// error. - /// ```text - /// "/foo/invalid~tilde/invalid" - /// ↑ - /// 4 - /// ``` - /// ``` - /// # use jsonptr::PointerBuf; - /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); - /// assert_eq!(err.pointer_offset(), 4) - /// ``` - pub fn pointer_offset(&self) -> usize { - match *self { - Self::NoLeadingBackslash { .. } => 0, - Self::InvalidEncoding { offset, .. } => offset, - } - } - - /// Offset of the character index from within the first token of - /// [`Self::pointer_offset`]) - /// ```text - /// "/foo/invalid~tilde/invalid" - /// ↑ - /// 8 - /// ``` - /// ``` - /// # use jsonptr::PointerBuf; - /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); - /// assert_eq!(err.source_offset(), 8) - /// ``` - pub fn source_offset(&self) -> usize { - match self { - Self::NoLeadingBackslash { .. } => 0, - Self::InvalidEncoding { source, .. } => source.offset, - } - } - - /// Offset of the first invalid encoding from within the pointer. - /// ```text - /// "/foo/invalid~tilde/invalid" - /// ↑ - /// 12 - /// ``` - /// ``` - /// # use jsonptr::PointerBuf; - /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); - /// assert_eq!(err.pointer_offset(), 4) - /// ``` - pub fn complete_offset(&self) -> usize { - self.source_offset() + self.pointer_offset() - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::InvalidEncoding { source, .. } => Some(source), - Self::NoLeadingBackslash => None, - } - } -} - -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ ParseIndexError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// Indicates that the `Token` could not be parsed as valid RFC 6901 index. -#[derive(Debug, PartialEq, Eq)] -pub struct ParseIndexError { - /// The source `ParseIntError` - pub source: ParseIntError, -} - -impl From for ParseIndexError { - fn from(source: ParseIntError) -> Self { - Self { source } - } -} - -impl fmt::Display for ParseIndexError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "failed to parse token as an integer") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseIndexError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.source) - } -} - -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ InvalidEncodingError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// A token within a json pointer contained invalid encoding (`~` not followed -/// by `0` or `1`). -/// -#[derive(Debug, PartialEq, Eq)] -pub struct InvalidEncodingError { - /// offset of the erroneous `~` from within the `Token` - pub offset: usize, -} - -impl InvalidEncodingError { - /// The byte offset of the first invalid `~`. - pub fn offset(&self) -> usize { - self.offset - } -} - -impl fmt::Display for InvalidEncodingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')" - ) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for InvalidEncodingError {} - -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ OutOfBoundsError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// Indicates that an `Index` is not within the given bounds. -#[derive(Debug, PartialEq, Eq)] -pub struct OutOfBoundsError { - /// The provided array length. - /// - /// If the range is inclusive, the resolved numerical index will be strictly - /// less than this value, otherwise it could be equal to it. - pub length: usize, - - /// The resolved numerical index. - /// - /// Note that [`Index::Next`] always resolves to the given array length, - /// so it is only valid when the range is inclusive. - pub index: usize, -} - -impl fmt::Display for OutOfBoundsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "index {} out of bounds (limit: {})", - self.index, self.length - ) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for OutOfBoundsError {} - -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ NotFoundError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// An error that indicates a [`Pointer`]'s path was not found in the data. -#[derive(Debug, PartialEq, Eq)] -pub struct NotFoundError { - /// The starting offset of the [`Token`] within the [`Pointer`] which could not - /// be resolved. - pub offset: usize, -} - -impl fmt::Display for NotFoundError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "path starting at offset {} not found", self.offset) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for NotFoundError {} - -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ ReplaceTokenError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// Returned from `Pointer::replace_token` when the provided index is out of -/// bounds. -#[derive(Debug, PartialEq, Eq)] -pub struct ReplaceTokenError { - /// The index of the token that was out of bounds. - pub index: usize, - /// The number of tokens in the `Pointer`. - pub count: usize, -} - -impl fmt::Display for ReplaceTokenError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "index {} is out of bounds ({})", self.index, self.count) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ReplaceTokenError {} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_error_display() { - assert_eq!( - ParseError::NoLeadingBackslash.to_string(), - "json pointer is malformed as it does not start with a backslash ('/')" - ); - } - - #[test] - fn parse_index_error_from_parse_int_error() { - assert_eq!( - Token::new("a").to_index().unwrap_err().to_string(), - "failed to parse token as an integer" - ); - } - - #[test] - fn invalid_encoding_error_display() { - assert_eq!( - Token::from_encoded("~").unwrap_err().to_string(), - "json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')" - ); - } - - #[test] - fn out_of_bounds_error_display() { - let error = Token::new("10").to_index().unwrap().for_len(5).unwrap_err(); - assert_eq!(error.to_string(), "index 10 out of bounds (limit: 5)"); - } -} diff --git a/src/pointer.rs b/src/pointer.rs index d66e895..a8c736a 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1,6 +1,7 @@ -use crate::{InvalidEncodingError, ParseError, ReplaceTokenError, Token, Tokens}; +use crate::{InvalidEncodingError, Token, Tokens}; use alloc::{ borrow::ToOwned, + fmt, string::{String, ToString}, vec::Vec, }; @@ -865,6 +866,180 @@ const fn validate(value: &str) -> Result<&str, ParseError> { Ok(value) } +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ ParseError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// Indicates that a `Pointer` was malformed and unable to be parsed. +#[derive(Debug, PartialEq)] +pub enum ParseError { + /// `Pointer` did not start with a backslash (`'/'`). + NoLeadingBackslash, + + /// `Pointer` contained invalid encoding (e.g. `~` not followed by `0` or + /// `1`). + InvalidEncoding { + /// Offset of the partial pointer starting with the token that contained + /// the invalid encoding + offset: usize, + /// The source `InvalidEncodingError` + source: InvalidEncodingError, + }, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::NoLeadingBackslash { .. } => { + write!( + f, + "json pointer is malformed as it does not start with a backslash ('/')" + ) + } + Self::InvalidEncoding { source, .. } => write!(f, "{source}"), + } + } +} + +impl ParseError { + /// Returns `true` if this error is `NoLeadingBackslash`; otherwise returns + /// `false`. + pub fn is_no_leading_backslash(&self) -> bool { + matches!(self, Self::NoLeadingBackslash { .. }) + } + + /// Returns `true` if this error is `InvalidEncoding`; otherwise returns + /// `false`. + pub fn is_invalid_encoding(&self) -> bool { + matches!(self, Self::InvalidEncoding { .. }) + } + + /// Offset of the partial pointer starting with the token which caused the + /// error. + /// ```text + /// "/foo/invalid~tilde/invalid" + /// ↑ + /// 4 + /// ``` + /// ``` + /// # use jsonptr::PointerBuf; + /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); + /// assert_eq!(err.pointer_offset(), 4) + /// ``` + pub fn pointer_offset(&self) -> usize { + match *self { + Self::NoLeadingBackslash { .. } => 0, + Self::InvalidEncoding { offset, .. } => offset, + } + } + + /// Offset of the character index from within the first token of + /// [`Self::pointer_offset`]) + /// ```text + /// "/foo/invalid~tilde/invalid" + /// ↑ + /// 8 + /// ``` + /// ``` + /// # use jsonptr::PointerBuf; + /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); + /// assert_eq!(err.source_offset(), 8) + /// ``` + pub fn source_offset(&self) -> usize { + match self { + Self::NoLeadingBackslash { .. } => 0, + Self::InvalidEncoding { source, .. } => source.offset, + } + } + + /// Offset of the first invalid encoding from within the pointer. + /// ```text + /// "/foo/invalid~tilde/invalid" + /// ↑ + /// 12 + /// ``` + /// ``` + /// # use jsonptr::PointerBuf; + /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); + /// assert_eq!(err.pointer_offset(), 4) + /// ``` + pub fn complete_offset(&self) -> usize { + self.source_offset() + self.pointer_offset() + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidEncoding { source, .. } => Some(source), + Self::NoLeadingBackslash => None, + } + } +} + +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ NotFoundError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// An error that indicates a [`Pointer`]'s path was not found in the data. +#[derive(Debug, PartialEq, Eq)] +pub struct NotFoundError { + /// The starting offset of the [`Token`] within the [`Pointer`] which could not + /// be resolved. + pub offset: usize, +} + +impl fmt::Display for NotFoundError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "path starting at offset {} not found", self.offset) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for NotFoundError {} + +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ ReplaceTokenError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// Returned from `Pointer::replace_token` when the provided index is out of +/// bounds. +#[derive(Debug, PartialEq, Eq)] +pub struct ReplaceTokenError { + /// The index of the token that was out of bounds. + pub index: usize, + /// The number of tokens in the `Pointer`. + pub count: usize, +} + +impl fmt::Display for ReplaceTokenError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "index {} is out of bounds ({})", self.index, self.count) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ReplaceTokenError {} + /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -1531,4 +1706,12 @@ mod tests { ); } } + #[test] + fn parse_error_display() { + assert_eq!( + ParseError::NoLeadingBackslash.to_string(), + "json pointer is malformed as it does not start with a backslash ('/')" + ); + } + } diff --git a/src/resolve.rs b/src/resolve.rs index 822600c..dd81f6e 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -15,7 +15,10 @@ //! | TOML | `toml::Value` | `"toml"` | | //! //! -use crate::{OutOfBoundsError, ParseIndexError, Pointer, Token}; +use crate::{ + index::{OutOfBoundsError, ParseIndexError}, + Pointer, Token, +}; /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ diff --git a/src/token.rs b/src/token.rs index 413e688..8268e0c 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,6 +1,7 @@ -use crate::{index::Index, InvalidEncodingError, ParseIndexError}; +use crate::index::{Index, ParseIndexError}; use alloc::{ borrow::Cow, + fmt, string::{String, ToString}, vec::Vec, }; @@ -314,6 +315,43 @@ impl alloc::fmt::Display for Token<'_> { write!(f, "{}", self.decoded()) } } +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ InvalidEncodingError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// A token within a json pointer contained invalid encoding (`~` not followed +/// by `0` or `1`). +/// +#[derive(Debug, PartialEq, Eq)] +pub struct InvalidEncodingError { + /// offset of the erroneous `~` from within the `Token` + pub offset: usize, +} + +impl InvalidEncodingError { + /// The byte offset of the first invalid `~`. + pub fn offset(&self) -> usize { + self.offset + } +} + +impl fmt::Display for InvalidEncodingError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')" + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidEncodingError {} /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ @@ -375,4 +413,12 @@ mod tests { let decoded = Token::from_encoded(token.encoded()).unwrap(); token == decoded } + + #[test] + fn invalid_encoding_error_display() { + assert_eq!( + Token::from_encoded("~").unwrap_err().to_string(), + "json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')" + ); + } } From f673264ff0943a2fdcf7562a20eb29e6a8bc7b94 Mon Sep 17 00:00:00 2001 From: Chance Date: Tue, 2 Jul 2024 19:29:00 -0400 Subject: [PATCH 22/57] fixes fmt --- src/pointer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pointer.rs b/src/pointer.rs index a8c736a..582b3c9 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1713,5 +1713,4 @@ mod tests { "json pointer is malformed as it does not start with a backslash ('/')" ); } - } From ea448544b2030f825ef4bd178fe174e40ca3a18e Mon Sep 17 00:00:00 2001 From: Chance Date: Wed, 3 Jul 2024 17:12:15 -0400 Subject: [PATCH 23/57] increases test cov + adds some trait impls --- .gitignore | 1 + src/index.rs | 46 ++++++ src/lib.rs | 3 +- src/pointer.rs | 387 +++++++++++++++++++++++++++++++++++++++++-------- src/resolve.rs | 272 ++++++++++++++++++++++++++++------ src/token.rs | 76 +++++++++- 6 files changed, 679 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index a3d6b48..1ae19a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target # Cargo.lock +lcov.info diff --git a/src/index.rs b/src/index.rs index 8b380c1..8f9b0e4 100644 --- a/src/index.rs +++ b/src/index.rs @@ -376,4 +376,50 @@ mod tests { fn display_index_next() { assert_eq!(Index::Next.to_string(), "-"); } + + #[test] + fn for_len() { + assert_eq!(Index::Num(0).for_len(1), Ok(0)); + assert!(Index::Num(1).for_len(1).is_err()); + assert!(Index::Next.for_len(1).is_err()); + } + + #[test] + fn out_of_bounds_error_display() { + let err = OutOfBoundsError { + length: 5, + index: 10, + }; + assert_eq!(err.to_string(), "index 10 out of bounds (limit: 5)"); + } + + #[test] + fn parse_index_error_display() { + let err = ParseIndexError { + source: "not a number".parse::().unwrap_err(), + }; + assert_eq!(err.to_string(), "failed to parse token as an integer"); + } + + #[test] + #[cfg(feature = "std")] + fn parse_index_error_source() { + use std::error::Error; + let source = "not a number".parse::().unwrap_err(); + let err = ParseIndexError { source }; + assert_eq!( + err.source().unwrap().to_string(), + "not a number".parse::().unwrap_err().to_string() + ); + } + + #[test] + fn try_from_token() { + let token = Token::new("3"); + let index = >::try_from(token).unwrap(); + assert_eq!(index, Index::Num(3)); + let token = Token::new("-"); + let index = Index::try_from(&token).unwrap(); + assert_eq!(index, Index::Next); + } } diff --git a/src/lib.rs b/src/lib.rs index 33b4382..33317c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ clippy::into_iter_without_iter, clippy::needless_pass_by_value, clippy::expect_fun_call, - clippy::must_use_candidate + clippy::must_use_candidate, + clippy::similar_names )] #[cfg_attr(not(feature = "std"), macro_use)] diff --git a/src/pointer.rs b/src/pointer.rs index 582b3c9..8875816 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -477,6 +477,7 @@ impl PartialEq for str { impl PartialEq for Pointer { fn eq(&self, other: &String) -> bool { + println!("inside partial eq"); &self.0 == other } } @@ -502,6 +503,18 @@ impl PartialEq for PointerBuf { &self.0 == other } } + +impl PartialEq for str { + fn eq(&self, other: &PointerBuf) -> bool { + self == other.0 + } +} +impl PartialEq for &str { + fn eq(&self, other: &PointerBuf) -> bool { + *self == other.0 + } +} + impl AsRef for Pointer { fn as_ref(&self) -> &Pointer { self @@ -550,6 +563,62 @@ impl AsRef<[u8]> for Pointer { } } +impl PartialOrd for Pointer { + fn partial_cmp(&self, other: &PointerBuf) -> Option { + self.0.partial_cmp(other.0.as_str()) + } +} + +impl PartialOrd for PointerBuf { + fn partial_cmp(&self, other: &Pointer) -> Option { + self.0.as_str().partial_cmp(&other.0) + } +} +impl PartialOrd<&Pointer> for PointerBuf { + fn partial_cmp(&self, other: &&Pointer) -> Option { + self.0.as_str().partial_cmp(&other.0) + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &Pointer) -> Option { + self.as_str().partial_cmp(&other.0) + } +} +impl PartialOrd for &Pointer { + fn partial_cmp(&self, other: &String) -> Option { + self.0.partial_cmp(other.as_str()) + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &PointerBuf) -> Option { + self.as_str().partial_cmp(other.0.as_str()) + } +} + +impl PartialOrd for str { + fn partial_cmp(&self, other: &Pointer) -> Option { + self.partial_cmp(&other.0) + } +} + +impl PartialOrd for str { + fn partial_cmp(&self, other: &PointerBuf) -> Option { + self.partial_cmp(other.0.as_str()) + } +} +impl PartialOrd for &str { + fn partial_cmp(&self, other: &PointerBuf) -> Option { + (*self).partial_cmp(other.0.as_str()) + } +} +impl PartialOrd for &str { + fn partial_cmp(&self, other: &Pointer) -> Option { + (*self).partial_cmp(&other.0) + } +} + impl PartialOrd<&str> for &Pointer { fn partial_cmp(&self, other: &&str) -> Option { PartialOrd::partial_cmp(&self.0[..], &other[..]) @@ -562,6 +631,24 @@ impl PartialOrd for Pointer { } } +impl PartialOrd<&str> for PointerBuf { + fn partial_cmp(&self, other: &&str) -> Option { + PartialOrd::partial_cmp(&self.0[..], &other[..]) + } +} + +impl<'p> PartialOrd for &'p Pointer { + fn partial_cmp(&self, other: &PointerBuf) -> Option { + self.0.partial_cmp(other.0.as_str()) + } +} + +impl PartialOrd for PointerBuf { + fn partial_cmp(&self, other: &String) -> Option { + self.0.partial_cmp(other) + } +} + impl<'a> IntoIterator for &'a Pointer { type Item = Token<'a>; type IntoIter = Tokens<'a>; @@ -598,7 +685,7 @@ impl PointerBuf { /// Attempts to parse a string into a `PointerBuf`. /// /// ## Errors - /// Returns a `ParseError` if the string is not a valid JSON Pointer. + /// Returns a [`ParseError`] if the string is not a valid JSON Pointer. pub fn parse + ?Sized>(s: &S) -> Result { Pointer::parse(&s).map(Pointer::to_buf) } @@ -984,33 +1071,6 @@ impl std::error::Error for ParseError { } } -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ NotFoundError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// An error that indicates a [`Pointer`]'s path was not found in the data. -#[derive(Debug, PartialEq, Eq)] -pub struct NotFoundError { - /// The starting offset of the [`Token`] within the [`Pointer`] which could not - /// be resolved. - pub offset: usize, -} - -impl fmt::Display for NotFoundError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "path starting at offset {} not found", self.offset) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for NotFoundError {} - /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -1052,6 +1112,8 @@ impl std::error::Error for ReplaceTokenError {} #[cfg(test)] mod tests { + use std::error::Error; + use super::*; use quickcheck::TestResult; use quickcheck_macros::quickcheck; @@ -1061,18 +1123,38 @@ mod tests { fn from_const_validates() { let _ = Pointer::from_static("foo/bar"); } + #[test] fn strip_suffix() { let p = Pointer::new("/example/pointer/to/some/value"); let stripped = p.strip_suffix(Pointer::new("/to/some/value")).unwrap(); assert_eq!(stripped, "/example/pointer"); } + #[test] fn strip_prefix() { let p = Pointer::new("/example/pointer/to/some/value"); let stripped = p.strip_prefix(Pointer::new("/example/pointer")).unwrap(); assert_eq!(stripped, "/to/some/value"); } + + #[test] + fn parse_error_is_no_leading_backslash() { + let err = ParseError::NoLeadingBackslash; + assert!(err.is_no_leading_backslash()); + assert!(!err.is_invalid_encoding()); + } + + #[test] + fn parse_error_is_invalid_encoding() { + let err = ParseError::InvalidEncoding { + offset: 0, + source: InvalidEncodingError { offset: 1 }, + }; + assert!(!err.is_no_leading_backslash()); + assert!(err.is_invalid_encoding()); + } + #[test] fn parse() { let tests = [ @@ -1112,16 +1194,49 @@ mod tests { ]; for (input, expected) in tests { let actual = Pointer::parse(input).map(Pointer::as_str); - assert_eq!( - actual, expected, - "pointer parsing failed to meet expectations - \ninput: {input} - \nexpected:\n{expected:#?} - \nactual:\n{actual:#?}", - ); + assert_eq!(actual, expected); } } + #[test] + fn parse_error_offsets() { + let err = Pointer::parse("/foo/invalid~encoding").unwrap_err(); + assert_eq!(err.pointer_offset(), 4); + assert_eq!(err.source_offset(), 8); + assert_eq!(err.complete_offset(), 12); + + let err = Pointer::parse("invalid~encoding").unwrap_err(); + assert_eq!(err.pointer_offset(), 0); + assert_eq!(err.source_offset(), 0); + + let err = Pointer::parse("no-leading/slash").unwrap_err(); + assert!(err.source().is_none()); + } + + #[test] + #[cfg(feature = "std")] + fn parse_error_source() { + use std::error::Error; + let err = Pointer::parse("/foo/invalid~encoding").unwrap_err(); + assert!(err.source().is_some()); + let source = err.source().unwrap(); + println!("{source}"); + } + + #[test] + fn as_pointer() { + let ptr = Pointer::from_static("/foo/bar"); + let ptr_buf = ptr.to_buf(); + assert_eq!(ptr_buf.as_ptr(), ptr); + } + + #[test] + fn pointer_buf_clear() { + let mut ptr = PointerBuf::from_tokens(["foo", "bar"]); + ptr.clear(); + assert_eq!(ptr, ""); + } + #[test] fn push_pop_back() { let mut ptr = PointerBuf::default(); @@ -1132,24 +1247,14 @@ mod tests { assert_eq!(ptr, "/foo", "pointer should equal \"/foo\" after push_back"); ptr.push_back("bar".into()); - assert_eq!( - ptr, "/foo/bar", - "pointer should equal \"/foo/bar\" after push_back" - ); + assert_eq!(ptr, "/foo/bar"); ptr.push_back("/baz".into()); - assert_eq!( - ptr, "/foo/bar/~1baz", - "pointer should equal \"/foo/bar/~1baz\" after push_back" - ); + assert_eq!(ptr, "/foo/bar/~1baz"); 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!( - ptr.pop_back(), - Some("foo".into()), - "\"foo\" should have been popped from the back" - ); + assert_eq!(ptr.pop_back(), Some("foo".into())); assert_eq!(ptr, "", "pointer should equal \"\" after pop_back"); } @@ -1159,10 +1264,7 @@ mod tests { let res = ptr.replace_token(0, "new".into()); assert!(res.is_ok()); - assert_eq!( - ptr, "/new/token", - "pointer should equal \"/new/token\" after replace_token" - ); + assert_eq!(ptr, "/new/token"); let res = ptr.replace_token(3, "invalid".into()); @@ -1197,6 +1299,12 @@ mod tests { assert_eq!(ptr, ""); } + #[test] + fn display_replace_token_error() { + let err = ReplaceTokenError { index: 3, count: 2 }; + assert_eq!(format!("{err}"), "index 3 is out of bounds (2)"); + } + #[test] fn pop_front_works_with_empty_strings() { { @@ -1250,6 +1358,9 @@ mod tests { ); assert_eq!(PointerBuf::from_tokens(["field", "", "baz"]), "/field//baz"); assert_eq!(PointerBuf::default(), ""); + + let ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); + assert_eq!(ptr.to_string(), "/foo/bar/baz"); } #[test] @@ -1455,6 +1566,45 @@ mod tests { } } + #[test] + #[allow(clippy::useless_asref)] + fn as_ref() { + let ptr_str = "/foo/bar"; + let ptr = Pointer::from_static(ptr_str); + let ptr_buf = ptr.to_buf(); + assert_eq!(ptr_buf.as_ref(), ptr); + let r: &Pointer = ptr.as_ref(); + assert_eq!(ptr, r); + + let s: &str = ptr.as_ref(); + assert_eq!(s, ptr_str); + + let b: &[u8] = ptr.as_ref(); + assert_eq!(b, ptr_str.as_bytes()); + } + + #[test] + fn from_tokens() { + let ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); + assert_eq!(ptr, "/foo/bar/baz"); + } + + #[test] + fn pointer_borrow() { + let ptr = Pointer::from_static("/foo/bar"); + let borrowed: &str = ptr.borrow(); + assert_eq!(borrowed, "/foo/bar"); + } + + #[test] + #[cfg(feature = "json")] + fn into_value() { + use serde_json::Value; + let ptr = Pointer::from_static("/foo/bar"); + let value: Value = ptr.into(); + assert_eq!(value, Value::String("/foo/bar".to_string())); + } + #[test] fn intersect() { let base = Pointer::from_static("/foo/bar"); @@ -1642,14 +1792,89 @@ mod tests { } #[test] - #[allow(clippy::cmp_owned)] + #[allow(clippy::cmp_owned, unused_must_use)] fn partial_eq() { - let p = Pointer::from_static("/bread/crumbs"); - assert!(p == "/bread/crumbs"); - assert!(p == String::from("/bread/crumbs")); - assert!(p == p.to_owned()); - assert!(p == Pointer::from_static("/bread/crumbs")); - assert!(p != Pointer::from_static("/not/bread/crumbs/")); + let ptr_string = String::from("/bread/crumbs"); + let ptr_str = "/bread/crumbs"; + let ptr = Pointer::from_static(ptr_str); + let ptr_buf = ptr.to_buf(); + <&Pointer as PartialEq<&Pointer>>::eq(&ptr, &ptr); + >::eq(ptr, &ptr_str); + <&Pointer as PartialEq>::eq(&ptr, &ptr_string); + >::eq(ptr, &ptr_string); + >::eq(ptr, &ptr_buf); + <&str as PartialEq>::eq(&ptr_str, ptr); + >::eq(&ptr_string, ptr); + >::eq(ptr_str, ptr); + >::eq(&ptr_buf, ptr_str); + >::eq(&ptr_buf, &ptr_buf); + >::eq(&ptr_buf, ptr); + >::eq(ptr, &ptr_buf); + >::eq(&ptr_buf, &ptr); + >::eq(&ptr_buf, &ptr_str); + >::eq(&ptr_buf, &ptr_string); + <&Pointer as PartialEq>::eq(&ptr, &ptr_buf); + >::eq(ptr_str, &ptr_buf); + <&str as PartialEq>::eq(&ptr_str, &ptr_buf); + >::eq(&ptr_string, &ptr_buf); + } + + #[test] + fn partial_ord() { + let a_str = "/foo/bar"; + let a_string = a_str.to_string(); + let a_ptr = Pointer::from_static(a_str); + let a_buf = a_ptr.to_buf(); + let b_str = "/foo/bar"; + let b_string = b_str.to_string(); + let b_ptr = Pointer::from_static(b_str); + let b_buf = b_ptr.to_buf(); + let c_str = "/foo/bar/baz"; + let c_string = c_str.to_string(); + let c_ptr = Pointer::from_static(c_str); + let c_buf = c_ptr.to_buf(); + + assert!(>::lt(a_ptr, &c_buf)); + assert!(>::lt(&a_buf, c_ptr)); + assert!(>::lt(&a_string, c_ptr)); + assert!(>::lt(a_str, c_ptr)); + assert!(>::lt(a_str, &c_buf)); + assert!(<&str as PartialOrd>::lt(&a_str, c_ptr)); + assert!(<&str as PartialOrd>::lt(&a_str, &c_buf)); + assert!(<&Pointer as PartialOrd>::lt(&a_ptr, &c_buf)); + assert!(<&Pointer as PartialOrd<&str>>::lt(&b_ptr, &c_str)); + assert!(>::lt(a_ptr, &c_string)); + assert!(>::lt(&a_buf, &c_str)); + assert!(>::lt(&a_buf, &c_string)); + assert!(a_ptr < c_buf); + assert!(c_buf > a_ptr); + assert!(a_buf < c_ptr); + assert!(a_ptr < c_buf); + assert!(a_ptr < c_ptr); + assert!(a_ptr <= c_ptr); + assert!(c_ptr > a_ptr); + assert!(c_ptr >= a_ptr); + assert!(a_ptr == b_ptr); + assert!(a_ptr <= b_ptr); + assert!(a_ptr >= b_ptr); + assert!(a_string < c_buf); + assert!(a_string <= c_buf); + assert!(c_string > a_buf); + assert!(c_string >= a_buf); + assert!(a_string == b_buf); + assert!(a_ptr < c_buf); + assert!(a_ptr <= c_buf); + assert!(c_ptr > a_buf); + assert!(c_ptr >= a_buf); + assert!(a_ptr == b_buf); + assert!(a_ptr <= b_buf); + assert!(a_ptr >= b_buf); + assert!(a_ptr < c_buf); + assert!(c_ptr > b_string); + // couldnt inline this + #[allow(clippy::nonminimal_bool)] + let not = !(a_ptr > c_buf); + assert!(not); } #[test] @@ -1700,10 +1925,7 @@ mod tests { a.append(&PointerBuf::parse(a_suffix).unwrap()); b.append(&PointerBuf::parse(b_suffix).unwrap()); let intersection = a.intersection(&b); - assert_eq!( - intersection, base, - "\nintersection failed\n\nbase:{base}\na_suffix:{a_suffix}\nb_suffix:{b_suffix} expected: \"{base}\"\n actual: \"{intersection}\"\n" - ); + assert_eq!(intersection, base); } } #[test] @@ -1713,4 +1935,43 @@ mod tests { "json pointer is malformed as it does not start with a backslash ('/')" ); } + + #[test] + fn into_iter() { + use core::iter::IntoIterator; + + let ptr = PointerBuf::from_tokens(["foo", "bar", "baz"]); + let tokens: Vec = ptr.into_iter().collect(); + let from_tokens = PointerBuf::from_tokens(tokens); + assert_eq!(ptr, from_tokens); + + let ptr = Pointer::from_static("/foo/bar/baz"); + let tokens: Vec<_> = ptr.into_iter().collect(); + assert_eq!(ptr, PointerBuf::from_tokens(tokens)); + } + + #[test] + fn from_str() { + let p = PointerBuf::from_str("/foo/bar").unwrap(); + assert_eq!(p, "/foo/bar"); + } + + #[test] + fn from_token() { + let p = PointerBuf::from(Token::new("foo")); + assert_eq!(p, "/foo"); + } + + #[test] + fn from_usize() { + let p = PointerBuf::from(0); + assert_eq!(p, "/0"); + } + + #[test] + fn borrow() { + let ptr = PointerBuf::from_tokens(["foo", "bar"]); + let borrowed: &Pointer = ptr.borrow(); + assert_eq!(borrowed, "/foo/bar"); + } } diff --git a/src/resolve.rs b/src/resolve.rs index dd81f6e..b18f6d6 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -398,55 +398,198 @@ mod toml { #[cfg(test)] mod tests { use super::{Resolve, ResolveError, ResolveMut}; - use crate::Pointer; + use crate::{ + index::{OutOfBoundsError, ParseIndexError}, + Pointer, + }; use core::fmt; - struct Test<'v, V> { - ptr: &'static str, - expected_result: Result<&'v V, ResolveError>, - data: &'v V, + #[cfg(feature = "std")] + #[test] + fn resolve_error_source() { + use std::error::Error; + let err = ResolveError::FailedToParseIndex { + offset: 0, + source: ParseIndexError { + source: "invalid".parse::().unwrap_err(), + }, + }; + assert!(err.source().is_some()); + + let err = ResolveError::OutOfBounds { + offset: 0, + source: OutOfBoundsError { + index: 1, + length: 0, + }, + }; + assert!(err.source().is_some()); + + let err = ResolveError::NotFound { offset: 0 }; + assert!(err.source().is_none()); + + let err = ResolveError::Unreachable { offset: 0 }; + assert!(err.source().is_none()); } - impl<'v, V> Test<'v, V> - where - V: Resolve - + ResolveMut - + Clone - + PartialEq - + fmt::Display - + fmt::Debug, - { - fn all(tests: impl IntoIterator>) { - tests.into_iter().enumerate().for_each(|(i, t)| t.run(i)); - } + #[test] + fn resolve_error_display() { + let err = ResolveError::FailedToParseIndex { + offset: 0, + source: ParseIndexError { + source: "invalid".parse::().unwrap_err(), + }, + }; + assert_eq!(format!("{err}"), "failed to parse index at offset 0"); + + let err = ResolveError::OutOfBounds { + offset: 0, + source: OutOfBoundsError { + index: 1, + length: 0, + }, + }; + assert_eq!(format!("{err}"), "index at offset 0 out of bounds"); - fn run(self, i: usize) { - _ = self; - let Test { - ptr, - data, - expected_result, - } = self; - let ptr = Pointer::from_static(ptr); + let err = ResolveError::NotFound { offset: 0 }; - // cloning the data & expected_result to make comparison easier - let mut data = data.clone(); - let expected_result = expected_result.cloned(); + assert_eq!(format!("{err}"), "pointer starting at offset 0 not found"); - // testing Resolve - let res = data.resolve(ptr).cloned(); - assert_eq!( - &res, &expected_result, - "test #{i} failed:\n\nexpected\n{expected_result:#?}\n\nactual:\n{res:#?}", - ); + let err = ResolveError::Unreachable { offset: 0 }; + assert_eq!( + format!("{err}"), + "pointer starting at offset 0 is unreachable" + ); + } - // testing ResolveMut - let res = data.resolve_mut(ptr).cloned(); - assert_eq!( - &res, &expected_result, - "test #{i} failed:\n\nexpected\n{expected_result:#?}\n\nactual:\n{res:#?}", - ); - } + #[test] + fn resolve_error_offset() { + let err = ResolveError::FailedToParseIndex { + offset: 0, + source: ParseIndexError { + source: "invalid".parse::().unwrap_err(), + }, + }; + assert_eq!(err.offset(), 0); + + let err = ResolveError::OutOfBounds { + offset: 0, + source: OutOfBoundsError { + index: 1, + length: 0, + }, + }; + assert_eq!(err.offset(), 0); + + let err = ResolveError::NotFound { offset: 0 }; + assert_eq!(err.offset(), 0); + + let err = ResolveError::Unreachable { offset: 0 }; + assert_eq!(err.offset(), 0); + } + + #[test] + fn resolve_error_is_unreachable() { + let err = ResolveError::FailedToParseIndex { + offset: 0, + source: ParseIndexError { + source: "invalid".parse::().unwrap_err(), + }, + }; + assert!(!err.is_unreachable()); + + let err = ResolveError::OutOfBounds { + offset: 0, + source: OutOfBoundsError { + index: 1, + length: 0, + }, + }; + assert!(!err.is_unreachable()); + + let err = ResolveError::NotFound { offset: 0 }; + assert!(!err.is_unreachable()); + + let err = ResolveError::Unreachable { offset: 0 }; + assert!(err.is_unreachable()); + } + + #[test] + fn reolve_error_is_not_found() { + let err = ResolveError::FailedToParseIndex { + offset: 0, + source: ParseIndexError { + source: "invalid".parse::().unwrap_err(), + }, + }; + assert!(!err.is_not_found()); + + let err = ResolveError::OutOfBounds { + offset: 0, + source: OutOfBoundsError { + index: 1, + length: 0, + }, + }; + assert!(!err.is_not_found()); + + let err = ResolveError::NotFound { offset: 0 }; + assert!(err.is_not_found()); + + let err = ResolveError::Unreachable { offset: 0 }; + assert!(!err.is_not_found()); + } + + #[test] + fn resolve_error_is_out_of_bounds() { + let err = ResolveError::FailedToParseIndex { + offset: 0, + source: ParseIndexError { + source: "invalid".parse::().unwrap_err(), + }, + }; + assert!(!err.is_out_of_bounds()); + + let err = ResolveError::OutOfBounds { + offset: 0, + source: OutOfBoundsError { + index: 1, + length: 0, + }, + }; + assert!(err.is_out_of_bounds()); + + let err = ResolveError::NotFound { offset: 0 }; + assert!(!err.is_out_of_bounds()); + + let err = ResolveError::Unreachable { offset: 0 }; + assert!(!err.is_out_of_bounds()); + } + + #[test] + fn resolve_error_is_failed_to_parse_index() { + let err = ResolveError::FailedToParseIndex { + offset: 0, + source: ParseIndexError { + source: "invalid".parse::().unwrap_err(), + }, + }; + assert!(err.is_failed_to_parse_index()); + + let err = ResolveError::OutOfBounds { + offset: 0, + source: OutOfBoundsError { + index: 1, + length: 0, + }, + }; + assert!(!err.is_failed_to_parse_index()); + + let err = ResolveError::NotFound { offset: 0 }; + assert!(!err.is_failed_to_parse_index()); + + let err = ResolveError::Unreachable { offset: 0 }; + assert!(!err.is_failed_to_parse_index()); } /* @@ -674,4 +817,51 @@ mod tests { }, ]); } + struct Test<'v, V> { + ptr: &'static str, + expected_result: Result<&'v V, ResolveError>, + data: &'v V, + } + + impl<'v, V> Test<'v, V> + where + V: Resolve + + ResolveMut + + Clone + + PartialEq + + fmt::Display + + fmt::Debug, + { + fn all(tests: impl IntoIterator>) { + tests.into_iter().enumerate().for_each(|(i, t)| t.run(i)); + } + + fn run(self, i: usize) { + _ = self; + let Test { + ptr, + data, + expected_result, + } = self; + let ptr = Pointer::from_static(ptr); + + // cloning the data & expected_result to make comparison easier + let mut data = data.clone(); + let expected_result = expected_result.cloned(); + + // testing Resolve + let res = data.resolve(ptr).cloned(); + assert_eq!( + &res, &expected_result, + "test #{i} failed:\n\nexpected\n{expected_result:#?}\n\nactual:\n{res:#?}", + ); + + // testing ResolveMut + let res = data.resolve_mut(ptr).cloned(); + assert_eq!( + &res, &expected_result, + "test #{i} failed:\n\nexpected\n{expected_result:#?}\n\nactual:\n{res:#?}", + ); + } + } } diff --git a/src/token.rs b/src/token.rs index 8268e0c..13dc221 100644 --- a/src/token.rs +++ b/src/token.rs @@ -365,6 +365,8 @@ impl std::error::Error for InvalidEncodingError {} #[cfg(test)] mod tests { + use crate::{assign::AssignError, index::OutOfBoundsError}; + use super::*; use quickcheck_macros::quickcheck; @@ -398,13 +400,85 @@ mod tests { assert_eq!(deserialized, token); } + #[test] + fn assign_error_display() { + let err = AssignError::FailedToParseIndex { + offset: 3, + source: ParseIndexError { + source: "a".parse::().unwrap_err(), + }, + }; + assert_eq!( + err.to_string(), + "assignment failed due to an invalid index at offset 3" + ); + + let err = AssignError::OutOfBounds { + offset: 3, + source: OutOfBoundsError { + index: 3, + length: 2, + }, + }; + + assert_eq!( + err.to_string(), + "assignment failed due to index at offset 3 being out of bounds" + ); + } + + #[test] + #[cfg(feature = "std")] + fn assign_error_source() { + use std::error::Error; + let err = AssignError::FailedToParseIndex { + offset: 3, + source: ParseIndexError { + source: "a".parse::().unwrap_err(), + }, + }; + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + + let err = AssignError::OutOfBounds { + offset: 3, + source: OutOfBoundsError { + index: 3, + length: 2, + }, + }; + + assert!(err.source().unwrap().is::()); + } + #[test] fn from_encoded() { assert_eq!(Token::from_encoded("~1").unwrap().encoded(), "~1"); assert_eq!(Token::from_encoded("~0~1").unwrap().encoded(), "~0~1"); let t = Token::from_encoded("a~1b").unwrap(); assert_eq!(t.decoded(), "a/b"); - let _ = Token::from_encoded("a/b").unwrap_err(); + assert!(Token::from_encoded("a/b").is_err()); + assert!(Token::from_encoded("a~a").is_err()); + } + + #[test] + fn test_from() { + assert_eq!(Token::from(34u32).encoded(), "34"); + assert_eq!(Token::from(34u64).encoded(), "34"); + assert_eq!(Token::from(String::from("foo")).encoded(), "foo"); + assert_eq!(Token::from(&Token::new("foo")).encoded(), "foo"); + } + + #[test] + fn test_invalid_encoding_offset() { + let err = InvalidEncodingError { offset: 3 }; + assert_eq!(err.offset(), 3); + } + + #[test] + fn into_owned() { + let token = Token::from_encoded("foo~0").unwrap().into_owned(); + assert_eq!(token.encoded(), "foo~0"); } #[quickcheck] From 53436e5f2910989632d1831e655e92f08e9bc885 Mon Sep 17 00:00:00 2001 From: Chance Date: Wed, 3 Jul 2024 17:32:15 -0400 Subject: [PATCH 24/57] removes println --- src/pointer.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pointer.rs b/src/pointer.rs index 8875816..1f0c4f1 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -477,7 +477,6 @@ impl PartialEq for str { impl PartialEq for Pointer { fn eq(&self, other: &String) -> bool { - println!("inside partial eq"); &self.0 == other } } @@ -1220,7 +1219,10 @@ mod tests { let err = Pointer::parse("/foo/invalid~encoding").unwrap_err(); assert!(err.source().is_some()); let source = err.source().unwrap(); - println!("{source}"); + assert!(source.is::()); + + let err = Pointer::parse("no-leading/slash").unwrap_err(); + assert!(err.source().is_none()); } #[test] @@ -1750,9 +1752,6 @@ mod tests { let mut b = base.clone(); b.append(&suffix_1); let isect = a.intersection(&b); - if isect != base { - println!("\nintersection failed\nbase: {base:?}\nisect: {isect:?}\nsuffix_0: {suffix_0:?}\nsuffix_1: {suffix_1:?}\n"); - } TestResult::from_bool(isect == base) } From 1e0de2ae69a4dfb8ab2e7dca5a8a2019aa449a3c Mon Sep 17 00:00:00 2001 From: Chance Date: Wed, 3 Jul 2024 17:38:32 -0400 Subject: [PATCH 25/57] more coverage --- src/assign.rs | 7 +++++++ src/delete.rs | 20 +++++++++----------- src/resolve.rs | 10 ++-------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index 79d8221..7fb24f0 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -740,6 +740,13 @@ mod tests { }), expected_data: json!([]), }, + Test { + ptr: "/0", + data: json!(["foo"]), + assign: json!("bar"), + expected_result: Ok(Some(json!("foo"))), + expected_data: json!(["bar"]), + }, Test { ptr: "/a", data: json!([]), diff --git a/src/delete.rs b/src/delete.rs index 15be7dc..725b9b3 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -183,7 +183,7 @@ mod tests { fn all(tests: impl IntoIterator>) { tests.into_iter().enumerate().for_each(|(i, t)| t.run(i)); } - fn run(self, i: usize) { + fn run(self, _i: usize) { let Test { mut data, ptr, @@ -193,16 +193,8 @@ mod tests { let ptr = Pointer::from_static(ptr); let deleted = ptr.delete(&mut data); - assert_eq!( - expected_data, - data, - "\ntest delete #{i} failed:\ndata not as expected\n\nptr: \"{ptr}\"\n\nexpected data:\n{expected_data:#?}\n\nactual data:\n{data:#?}\n\n" - ); - assert_eq!( - expected_deleted, - deleted, - "\ntest delete #{i} failed:\n\ndeleted value not as expected\nexpected deleted:{expected_data:#?}\n\nactual deleted:{deleted:#?}\n\n", - ); + assert_eq!(expected_data, data); + assert_eq!(expected_deleted, deleted); } } /* @@ -271,6 +263,12 @@ mod tests { expected_data: json!({"test": "test"}), expected_deleted: Some(json!(21)), }, + Test { + ptr: "", + data: json!({"Example": 21, "test": "test"}), + expected_data: json!(null), + expected_deleted: Some(json!({"Example": 21, "test": "test"})), + }, ]); } /* diff --git a/src/resolve.rs b/src/resolve.rs index b18f6d6..3f890cf 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -851,17 +851,11 @@ mod tests { // testing Resolve let res = data.resolve(ptr).cloned(); - assert_eq!( - &res, &expected_result, - "test #{i} failed:\n\nexpected\n{expected_result:#?}\n\nactual:\n{res:#?}", - ); + assert_eq!(&res, &expected_result); // testing ResolveMut let res = data.resolve_mut(ptr).cloned(); - assert_eq!( - &res, &expected_result, - "test #{i} failed:\n\nexpected\n{expected_result:#?}\n\nactual:\n{res:#?}", - ); + assert_eq!(&res, &expected_result); } } } From 2bf437aa1cf51d37a1f5d419e0b4f98cbd1993b7 Mon Sep 17 00:00:00 2001 From: Chance Date: Wed, 3 Jul 2024 18:47:37 -0400 Subject: [PATCH 26/57] working on docs --- Cargo.toml | 1 - README.md | 16 +++++++++++----- src/lib.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- src/token.rs | 2 +- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5efddef..14d4048 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ syn = { version = "1.0.109", optional = true } assign = [] default = ["std", "serde", "json", "resolve", "assign", "delete"] delete = ["resolve"] -impl = [] json = ["dep:serde_json", "serde"] resolve = [] std = ["serde/std", "serde_json?/std"] diff --git a/README.md b/README.md index 9cb2f52..065b48a 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,18 @@ [build status](https://github.com/chanced/jsonptr/actions?query=branch%3Amain) [code coverage](https://codecov.io/gh/chanced/jsonptr) -Data structures and logic for resolving, assigning, and deleting by JSON Pointers ([RFC -6901](https://datatracker.ietf.org/doc/html/rfc6901)). +Data structures and logic for resolving, assigning, and deleting by JSON +Pointers ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)). -## Usage - -JSON Pointers can be created either with a slice of strings or directly from a properly encoded string representing a JSON Pointer. +## Feature Flags +| Flag | Description | Enables | Default | +| :---------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | :-----: | +| `"std"` | Implements `std::error::Error` for error types | `"serde/std"`, `"serde_json?/std"` | ✓ | +| `"json"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`serde_json::Value`] + a helper methods on `Pointer` | `serde_json` | ✓ | +| `"toml"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`toml::Value`] | `"std"`, `toml` | | +| `"assign"` | Enables the [`Assign`] trait and [`Pointer::assign`] for assigning values based on the location specified by a JSON Pointer | | ✓ | +| `"resolve"` | Enables the [`Resolve`] & [`ResolveMut`] trait and [`Pointer::resolve`], [`Pointer::resolve_mut`] for resolving values based on the location specified by a JSON Pointer | | ✓ | +| `"delete"` | Enables the [`Delete`] trait and [`Pointer::delete`] for deleting values based on the location specified by a JSON Pointer | `"resolve"` | ✓ | ### Resolve values diff --git a/src/lib.rs b/src/lib.rs index 33317c4..bebc0e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,45 @@ -#![doc = include_str!("../README.md")] +//! # jsonptr - JSON Pointers (RFC 6901) +//! [github](https://github.com/chanced/jsonptr) [docs.rs](https://docs.rs/jsonptr) [crates.io](https://crates.io/crates/jsonptr) [build status](https://github.com/chanced/jsonptr/actions?query=branch%3Amain) +//! [code coverage](https://codecov.io/gh/chanced/jsonptr) +//! +//! JSON Pointers are a lightly encoded strings that provide a syntax for +//! identifying a specific value within a JSON document. A properly formatted +//! JSON Pointer is composed of either an empty string, indicating the root +//! document, or a series of tokens, each with a leading backslash (`'/'`) and +//! encoded with the following rules: +//! - `'~'` is encoded as `'~0'` +//! - `'/'` is encoded as `'~1'` +//! +//! This module provides data structures and logic for resolving, assigning, and +//! deleting by JSON Pointers ([RFC +//! 6901](https://datatracker.ietf.org/doc/html/rfc6901)). Two types, +//! [`PointerBuf`] and [`Pointer`] (akin to [`String`] and [`str`]), are +//! available for representing JSON Pointers while the operations ([`Resolve`], +//! [`ResolveMut`], [`Assign`], [`Delete`]) are exposed as traits and +//! implemented for [`serde_json::Value`] and [`toml::Value`] if the respective +//! features are enabled. +//! +//! ## Feature Flags +//! +//! | Flag | Description | Enables | Default | +//! | :------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | :-----: | +//! | `"std"` | Implements `std::error::Error` for error types | `"serde/std"`, `"serde_json?/std"` | ✓ | +//! | `"json"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`serde_json::Value`] | `serde_json` | ✓ | +//! | `"toml"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`toml::Value`] | `"std"`, `toml` | | +//! | `"assign"` | Enables the [`Assign`] trait and [`Pointer::assign`] for assigning values based on the location specified by a JSON Pointer | | ✓ | +//! | `"resolve"` | Enables the [`Resolve`] & [`ResolveMut`] trait and [`Pointer::resolve`], [`Pointer::resolve_mut`] for resolving values based on the location specified by a JSON Pointer | | ✓ | +//! | `"delete"` | Enables the [`Delete`] trait and [`Pointer::delete`] for deleting values based on the location specified by a JSON Pointer | `"resolve"` | ✓ | +//! #![warn(missing_docs)] #![deny(clippy::all, clippy::pedantic)] #![cfg_attr(not(feature = "std"), no_std)] diff --git a/src/token.rs b/src/token.rs index 13dc221..67865c8 100644 --- a/src/token.rs +++ b/src/token.rs @@ -23,7 +23,7 @@ const SLASH_ENC: u8 = b'1'; ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -/// A `Token` is a segment of a JSON Pointer, seperated by '/' (%x2F). It can +/// A `Token` is a segment of a JSON Pointer, seperated by `'/'` (`%x2F`). It can /// represent a key in a JSON object or an index in a JSON array. /// /// - Indexes should not contain leading zeros. From 0d9f4bb091639d50c25bda82a26b51ea20ad6ea4 Mon Sep 17 00:00:00 2001 From: Chance Date: Thu, 4 Jul 2024 11:48:21 -0400 Subject: [PATCH 27/57] adds ./cargo/config.toml --- .cargo/config.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..6b77399 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[alias] +cov = "llvm-cov --lcov --output-path lcov.info" + +[build] +rustdocflags = ["--cfg", "docsrs"] From 55dffefe7c14649de4426fcce78d4017454e5834 Mon Sep 17 00:00:00 2001 From: Chance Date: Fri, 5 Jul 2024 13:17:24 -0400 Subject: [PATCH 28/57] docs wip --- README.md | 151 +++++++++++++++++------------------------------ src/component.rs | 41 +++++++++++++ src/index.rs | 71 +++++++++++----------- src/lib.rs | 59 +++--------------- src/pointer.rs | 24 +++++++- src/prelude.rs | 7 --- src/resolve.rs | 17 +----- src/token.rs | 74 +++++++++++++++++++---- src/tokens.rs | 31 ---------- 9 files changed, 226 insertions(+), 249 deletions(-) create mode 100644 src/component.rs delete mode 100644 src/prelude.rs delete mode 100644 src/tokens.rs diff --git a/README.md b/README.md index 065b48a..1f25353 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# jsonptr - JSON Pointers for Rust + + +# jsonptr - JSON Pointers (RFC 6901) [github](https://github.com/chanced/jsonptr) [crates.io](https://crates.io/crates/jsonptr) @@ -6,126 +8,79 @@ [build status](https://github.com/chanced/jsonptr/actions?query=branch%3Amain) [code coverage](https://codecov.io/gh/chanced/jsonptr) -Data structures and logic for resolving, assigning, and deleting by JSON -Pointers ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)). +This crate offers two types, [`Pointer`] and [`PointerBuf`] (akin to +[`Path`](std::path::Path) and [`PathBuf`](std::path::PathBuf)), for working with +JSON Pointers ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)), +abstractly. + +A [`Pointer`] is composed of zero or more [`Token`]s, single segments which +represent a field of an object or an [`Index`] of an array, and are bounded by +either `'/'` or the end of the string. [`Token`]s are lightly encoded, where +`'~'` is encoded as `"~0"` and `'/'` is encoded as `"~1"`. Combined, the +[`Pointer`] is able to identify a specific location within a JSON, or similar, +document. + +[`Token`]s can be iterated over using either [`Tokens`], returned from the +[`tokens`](`Pointer::tokens`) method of a pointer or [`Components`], returned +from the [`components`](`Pointer::components`). The difference being that +[`Tokens`] iterates over each [`Token`] in the pointer, while a [`Component`] +can represent the [`Root`](Component::Root) document or a single +[`Token`](Component::Token). + +Operations [`resolve`], [`assign`] and [`delete`] are provided as traits with +corresponding methods on [`Pointer`]. Implementations of each trait are provided +for [`serde_json::Value`] and [`toml::Value`](https://docs.rs/toml/0.8). ## Feature Flags -| Flag | Description | Enables | Default | -| :---------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | :-----: | -| `"std"` | Implements `std::error::Error` for error types | `"serde/std"`, `"serde_json?/std"` | ✓ | -| `"json"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`serde_json::Value`] + a helper methods on `Pointer` | `serde_json` | ✓ | -| `"toml"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`toml::Value`] | `"std"`, `toml` | | -| `"assign"` | Enables the [`Assign`] trait and [`Pointer::assign`] for assigning values based on the location specified by a JSON Pointer | | ✓ | -| `"resolve"` | Enables the [`Resolve`] & [`ResolveMut`] trait and [`Pointer::resolve`], [`Pointer::resolve_mut`] for resolving values based on the location specified by a JSON Pointer | | ✓ | -| `"delete"` | Enables the [`Delete`] trait and [`Pointer::delete`] for deleting values based on the location specified by a JSON Pointer | `"resolve"` | ✓ | - -### Resolve values -#### `Pointer::resolve` +| Flag | Description | Enables | Default | +| :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------- | :-----: | +| `"std"` | Implements `std::error::Error` for error types | | ✓ | +| `"serde"` | Enables [`serde`] support for types | | ✓ | +| `"json"` | Implements ops for [`serde_json::Value`] | `"serde"` | ✓ | +| `"toml"` | Implements ops for [`toml::Value`](https://docs.rs/toml/0.8) | `"std"`, `toml` | | +| `"assign"` | Enables the [`assign`] module and related pointer methods, providing a means to assign a value to a specific location within a document | | ✓ | +| `"resolve"` | Enables the [`resolve`] module and related pointer methods, providing a means to resolve a value at a specific location within a document | | ✓ | +| `"delete"` | Enables the [`delete`] module and related pointer methods, providing a means to delete a value at a specific location within a document | `"resolve"` | ✓ | -```rust -use jsonptr::Pointer; -use serde_json::json; +## Usage -let mut data = json!({ "foo": { "bar": "baz" } }); -let ptr = Pointer::from_static("/foo/bar"); -let bar = ptr.resolve(&data).unwrap(); -assert_eq!(bar, "baz"); -``` +### Resolving a value -#### `Resolve::resolve` +Using the `resolve` method, you can resolve a pointer against a value type that +implements [`Resolve`]. ```rust -use jsonptr::{ Pointer, resolve::Resolve }; -use serde_json::json; - -let mut data = json!({ "foo": { "bar": "baz" } }); -let ptr = Pointer::from_static("/foo/bar"); -let bar = data.resolve(&ptr).unwrap(); -assert_eq!(bar, "baz"); - +# use jsonptr::{Pointer}; +let ptr = Pointer::parse("/foo/bar").unwrap(); +let bar = ptr.resolve(&json!({"foo": {"bar": 34}})).unwrap(); +assert_eq!(bar, &json!(34)); ``` -#### `ResolveMut::resolve_mut` - -```rust -use jsonptr::{Pointer, resolve::ResolveMut}; -use serde_json::json; - -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"); -``` - -### Assign - -#### `Pointer::assign` +### Resolve ```rust use jsonptr::Pointer; use serde_json::json; -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" }})) -``` - -#### `Assign::asign` - -```rust -use jsonptr::{assign::Assign, Pointer}; -use serde_json::json; - -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" }})) +let ptr = Pointer::parse("/foo/bar").unwrap(); +let bar = ptr.resolve(&json!({"foo": {"bar": 34}})).unwrap(); +assert_eq!(bar, &json!(34)); ``` -### Delete - -#### `Pointer::delete` +### Assign ```rust use jsonptr::Pointer; use serde_json::json; -let mut data = json!({ "foo": { "bar": { "baz": "qux" } } }); -let ptr = Pointer::from_static("/foo/bar/baz"); -assert_eq!(ptr.delete(&mut data), Some("qux".into())); -assert_eq!(data, json!({ "foo": { "bar": {} } })); - -// unresolved pointers return None +let ptr = Pointer::parse("/foo/bar").unwrap(); let mut data = json!({}); -assert_eq!(ptr.delete(&mut data), None); +let res = ptr.assign(&mut data, json!({"baz": 34})); +assert_eq!(res, Ok(None)); +assert_eq!(data, json!({"foo": {"bar": {"baz": 34}}})); ``` -#### `Delete::delete` - -```rust -use jsonptr::{ Pointer, delete::Delete }; -use serde_json::json; - -let mut data = json!({ "foo": { "bar": { "baz": "qux" } } }); -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::root(); -let deleted = json!({ "foo": { "bar": {} } }); -assert_eq!(data.delete(&ptr), Some(deleted)); -assert!(data.is_null()); -``` - -## Feature Flags - -| Flag | Enables | -| :-----: | ----------------------------------------- | -| `"std"` | implements `std::error::Error` for errors | - ## Contributions / Issues Contributions and feedback are always welcome and appreciated. @@ -135,3 +90,7 @@ If you find an issue, please open a ticket or a pull request. ## License MIT or Apache 2.0. + +``` + +``` diff --git a/src/component.rs b/src/component.rs new file mode 100644 index 0000000..2953f63 --- /dev/null +++ b/src/component.rs @@ -0,0 +1,41 @@ +use crate::{Pointer, Token, Tokens}; + +pub enum Component<'t> { + /// 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) + } +} + +pub struct Components<'t> { + tokens: Tokens<'t>, +} +impl<'t> Components<'t> { + pub(crate) fn new(tokens: Tokens<'t>) -> Self { + Self { tokens } + } +} + +impl<'t> Iterator for Components<'t> { + type Item = Component<'t>; + fn next(&mut self) -> Option { + if !self.tokens.has_sent { + self.tokens.has_sent = true; + return Some(Component::Root); + } + self.tokens.next().map(Component::Token) + } +} + +impl<'t> From<&'t Pointer> for Components<'t> { + fn from(pointer: &'t Pointer) -> Self { + Self { + tokens: pointer.tokens(), + } + } +} diff --git a/src/index.rs b/src/index.rs index 8f9b0e4..f8c5fd6 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,38 +1,3 @@ -//! Abstract index representation for RFC 6901. -//! -//! [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) defines two valid -//! ways to represent array indices as Pointer tokens: non-negative integers, -//! and the character `-`, which stands for the index after the last existing -//! array member. While attempting to use `-` to access an array is an error, -//! the token can be useful when paired with [RFC -//! 6902](https://datatracker.ietf.org/∑doc/html/rfc6902) as a way to express -//! where to put the new element when extending an array. -//! -//! While this crate doesn't implement RFC 6902, it still must consider -//! non-numerical indices as valid, and provide a mechanism for manipulating -//! them. This is what this module provides. -//! -//! The main use of the `Index` type is when resolving a [`Token`] instance as a -//! concrete index for a given array length: -//! -//! ``` -//! # use jsonptr::{Index, Token}; -//! assert_eq!(Token::new("1").to_index(), Ok(Index::Num(1))); -//! assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); -//! assert!(Token::new("a").to_index().is_err()); -//! -//! assert_eq!(Index::Num(0).for_len(1), Ok(0)); -//! assert!(Index::Num(1).for_len(1).is_err()); -//! assert!(Index::Next.for_len(1).is_err()); -//! -//! assert_eq!(Index::Num(1).for_len_incl(1), Ok(1)); -//! assert_eq!(Index::Next.for_len_incl(1), Ok(1)); -//! assert!(Index::Num(2).for_len_incl(1).is_err()); -//! -//! assert_eq!(Index::Num(42).for_len_unchecked(30), 42); -//! assert_eq!(Index::Next.for_len_unchecked(30), 30); -//! ```` - use crate::Token; use alloc::string::String; use core::{ @@ -45,6 +10,42 @@ use core::{ /// /// If provided an upper bound with [`Self::for_len`] or [`Self::for_len_incl`], /// will produce a concrete numerical index. +/// +/// [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) defines two valid +/// ways to represent array indices as Pointer tokens: non-negative integers, +/// and the character `-`, which stands for the index after the last existing +/// array member. While attempting to use `-` to access an array is an error, +/// the token can be useful when paired with [RFC +/// 6902](https://datatracker.ietf.org/∑doc/html/rfc6902) as a way to express +/// where to put the new element when extending an array. +/// +/// While this crate doesn't implement RFC 6902, it still must consider +/// non-numerical indices as valid, and provide a mechanism for manipulating +/// them. This is what this module provides. +/// +/// The main use of the `Index` type is when resolving a [`Token`] instance as a +/// concrete index for a given array length: +/// +/// ## Example +/// ``` +/// # use jsonptr::{Index, Token}; +/// assert_eq!(Token::new("1").to_index(), Ok(Index::Num(1))); +/// assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); +/// assert!(Token::new("a").to_index().is_err()); +/// +/// assert_eq!(Index::Num(0).for_len(1), Ok(0)); +/// assert!(Index::Num(1).for_len(1).is_err()); +/// assert!(Index::Next.for_len(1).is_err()); +/// +/// assert_eq!(Index::Num(1).for_len_incl(1), Ok(1)); +/// assert_eq!(Index::Next.for_len_incl(1), Ok(1)); +/// assert!(Index::Num(2).for_len_incl(1).is_err()); +/// +/// assert_eq!(Index::Num(42).for_len_unchecked(30), 42); +/// assert_eq!(Index::Next.for_len_unchecked(30), 30); +/// ``` +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Index { /// A non-negative integer value diff --git a/src/lib.rs b/src/lib.rs index bebc0e2..6719250 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,45 +1,4 @@ -//! # jsonptr - JSON Pointers (RFC 6901) -//! [github](https://github.com/chanced/jsonptr) [docs.rs](https://docs.rs/jsonptr) [crates.io](https://crates.io/crates/jsonptr) [build status](https://github.com/chanced/jsonptr/actions?query=branch%3Amain) -//! [code coverage](https://codecov.io/gh/chanced/jsonptr) -//! -//! JSON Pointers are a lightly encoded strings that provide a syntax for -//! identifying a specific value within a JSON document. A properly formatted -//! JSON Pointer is composed of either an empty string, indicating the root -//! document, or a series of tokens, each with a leading backslash (`'/'`) and -//! encoded with the following rules: -//! - `'~'` is encoded as `'~0'` -//! - `'/'` is encoded as `'~1'` -//! -//! This module provides data structures and logic for resolving, assigning, and -//! deleting by JSON Pointers ([RFC -//! 6901](https://datatracker.ietf.org/doc/html/rfc6901)). Two types, -//! [`PointerBuf`] and [`Pointer`] (akin to [`String`] and [`str`]), are -//! available for representing JSON Pointers while the operations ([`Resolve`], -//! [`ResolveMut`], [`Assign`], [`Delete`]) are exposed as traits and -//! implemented for [`serde_json::Value`] and [`toml::Value`] if the respective -//! features are enabled. -//! -//! ## Feature Flags -//! -//! | Flag | Description | Enables | Default | -//! | :------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------- | :-----: | -//! | `"std"` | Implements `std::error::Error` for error types | `"serde/std"`, `"serde_json?/std"` | ✓ | -//! | `"json"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`serde_json::Value`] | `serde_json` | ✓ | -//! | `"toml"` | Implements [`Resolve`], [`Assign`], [`Delete`] for [`toml::Value`] | `"std"`, `toml` | | -//! | `"assign"` | Enables the [`Assign`] trait and [`Pointer::assign`] for assigning values based on the location specified by a JSON Pointer | | ✓ | -//! | `"resolve"` | Enables the [`Resolve`] & [`ResolveMut`] trait and [`Pointer::resolve`], [`Pointer::resolve_mut`] for resolving values based on the location specified by a JSON Pointer | | ✓ | -//! | `"delete"` | Enables the [`Delete`] trait and [`Pointer::delete`] for deleting values based on the location specified by a JSON Pointer | `"resolve"` | ✓ | -//! +#![doc = include_str!("../README.md")] #![warn(missing_docs)] #![deny(clippy::all, clippy::pedantic)] #![cfg_attr(not(feature = "std"), no_std)] @@ -55,8 +14,6 @@ #[cfg_attr(not(feature = "std"), macro_use)] extern crate alloc; -pub mod prelude; - #[cfg(feature = "assign")] pub mod assign; #[cfg(feature = "assign")] @@ -72,17 +29,17 @@ pub mod resolve; #[cfg(feature = "resolve")] pub use resolve::{Resolve, ResolveMut}; -mod tokens; -pub use tokens::*; - mod pointer; -pub use pointer::*; +pub use pointer::{ParseError, Pointer, PointerBuf, ReplaceTokenError}; mod token; -pub use token::*; +pub use token::{InvalidEncodingError, Token, Tokens}; + +mod index; +pub use index::{Index, OutOfBoundsError, ParseIndexError}; -pub mod index; -pub use index::Index; +mod component; +pub use component::{Component, Components}; #[cfg(test)] mod arbitrary; diff --git a/src/pointer.rs b/src/pointer.rs index 1f0c4f1..6f264e8 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1,4 +1,4 @@ -use crate::{InvalidEncodingError, Token, Tokens}; +use crate::{token::InvalidEncodingError, Components, Token, Tokens}; use alloc::{ borrow::ToOwned, fmt, @@ -265,7 +265,7 @@ impl Pointer { /// of the path) /// - The path is not found (e.g. a key in an object or an index in an array /// does not exist) - /// - An [`Token`] cannot be parsed as an array + /// - A [`Token`](crate::Token) cannot be parsed as an array /// [`Index`](crate::index::Index) /// - An array [`Index`](crate::index::Index) is out of bounds #[cfg(feature = "resolve")] @@ -286,7 +286,7 @@ impl Pointer { /// of the path) /// - The path is not found (e.g. a key in an object or an index in an array /// does not exist) - /// - An [`Token`] cannot be parsed as an array + /// - A [`Token`](crate::Token) cannot be parsed as an array /// [`Index`](crate::index::Index) /// - An array [`Index`](crate::index::Index) is out of bounds #[cfg(feature = "resolve")] @@ -390,6 +390,24 @@ impl Pointer { { dest.assign(self, src) } + + /// Returns [`Components`] of this JSON Pointer. + /// + /// A [`Component`](crate::Component) is either [`Token`] or the root + /// location of a document. + /// ## Example + /// ``` + /// # use jsonptr::{Component, Pointer}; + /// let ptr = Pointer::parse("/a/b").unwrap(); + /// let mut components = ptr.components(); + /// assert_eq!(components.next(), Some(Component::Root)); + /// assert_eq!(components.next(), Some(Component::Token("a".into()))); + /// assert_eq!(components.next(), Some(Component::Token("b".into()))); + /// assert_eq!(components.next(), None); + /// ``` + pub fn components(&self) -> Components { + self.tokens().into_components() + } } #[cfg(feature = "serde")] diff --git a/src/prelude.rs b/src/prelude.rs deleted file mode 100644 index 4aa5e2f..0000000 --- a/src/prelude.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Exposes the traits `Assign`, `Delete`, `Resolve`, `ResolveMut` if enabled. -#[cfg(feature = "assign")] -pub use crate::assign::Assign; -#[cfg(feature = "delete")] -pub use crate::delete::Delete; -#[cfg(feature = "resolve")] -pub use crate::resolve::{Resolve, ResolveMut}; diff --git a/src/resolve.rs b/src/resolve.rs index 3f890cf..fe5ac40 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -58,7 +58,7 @@ pub trait Resolve { */ /// A trait implemented by types which can resolve a mutable reference to a -/// `serde_json::Value` from the path in a JSON [`Pointer`]. +/// value type from a path represented by a JSON [`Pointer`]. pub trait ResolveMut { /// The type of value that is being resolved. type Value; @@ -737,79 +737,66 @@ mod tests { // let data = &data; Test::all([ - // 0 Test { ptr: "", data, expected_result: Ok(data), }, - // 1 Test { ptr: "/array", data, expected_result: Ok(data.get("array").unwrap()), // ["bar", "baz"] }, - // 2 Test { ptr: "/array/0", data, expected_result: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar" }, - // 3 Test { ptr: "/a~1b", data, expected_result: Ok(data.get("a/b").unwrap()), // 1 }, - // 4 Test { ptr: "/c%d", data, expected_result: Ok(data.get("c%d").unwrap()), // 2 }, - // 5 Test { ptr: "/e^f", data, expected_result: Ok(data.get("e^f").unwrap()), // 3 }, - // 6 Test { ptr: "/g|h", data, expected_result: Ok(data.get("g|h").unwrap()), // 4 }, - // 7 Test { ptr: "/i\\j", data, expected_result: Ok(data.get("i\\j").unwrap()), // 5 }, - // 8 Test { ptr: "/k\"l", data, expected_result: Ok(data.get("k\"l").unwrap()), // 6 }, - // 9 Test { ptr: "/ ", data, expected_result: Ok(data.get(" ").unwrap()), // 7 }, - // 10 Test { ptr: "/m~0n", data, expected_result: Ok(data.get("m~n").unwrap()), // 8 }, - // 11 Test { ptr: "/object/bool/unresolvable", data, expected_result: Err(ResolveError::Unreachable { offset: 12 }), }, - // 12 Test { ptr: "/object/not_found", data, @@ -836,7 +823,7 @@ mod tests { tests.into_iter().enumerate().for_each(|(i, t)| t.run(i)); } - fn run(self, i: usize) { + fn run(self, _i: usize) { _ = self; let Test { ptr, diff --git a/src/token.rs b/src/token.rs index 67865c8..1ccaed6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,3 +1,5 @@ +use core::str::Split; + use crate::index::{Index, ParseIndexError}; use alloc::{ borrow::Cow, @@ -23,7 +25,7 @@ const SLASH_ENC: u8 = b'1'; ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -/// A `Token` is a segment of a JSON Pointer, seperated by `'/'` (`%x2F`). It can +/// A `Token` is a segment of a JSON Pointer, preceded by `'/'` (`%x2F`). It can /// represent a key in a JSON object or an index in a JSON array. /// /// - Indexes should not contain leading zeros. @@ -315,6 +317,46 @@ impl alloc::fmt::Display for Token<'_> { write!(f, "{}", self.decoded()) } } + +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ Tokens ║ +║ ¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// An iterator over the tokens in a Pointer. +#[derive(Debug)] +pub struct Tokens<'a> { + pub(crate) has_sent: bool, + inner: Split<'a, char>, +} + +impl<'a> Iterator for Tokens<'a> { + type Item = Token<'a>; + fn next(&mut self) -> Option { + if !self.has_sent { + self.has_sent = true; + } + self.inner.next().map(Token::from_encoded_unchecked) + } +} +impl<'t> Tokens<'t> { + pub(crate) fn new(inner: Split<'t, char>) -> Self { + Self { + inner, + has_sent: false, + } + } + + pub fn into_components(self) -> crate::Components<'t> { + crate::Components::new(self) + } +} + /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -365,7 +407,7 @@ impl std::error::Error for InvalidEncodingError {} #[cfg(test)] mod tests { - use crate::{assign::AssignError, index::OutOfBoundsError}; + use crate::{assign::AssignError, index::OutOfBoundsError, Pointer}; use super::*; use quickcheck_macros::quickcheck; @@ -374,6 +416,10 @@ mod tests { fn from() { assert_eq!(Token::from("/").encoded(), "~1"); assert_eq!(Token::from("~/").encoded(), "~0~1"); + assert_eq!(Token::from(34u32).encoded(), "34"); + assert_eq!(Token::from(34u64).encoded(), "34"); + assert_eq!(Token::from(String::from("foo")).encoded(), "foo"); + assert_eq!(Token::from(&Token::new("foo")).encoded(), "foo"); } #[test] @@ -462,15 +508,7 @@ mod tests { } #[test] - fn test_from() { - assert_eq!(Token::from(34u32).encoded(), "34"); - assert_eq!(Token::from(34u64).encoded(), "34"); - assert_eq!(Token::from(String::from("foo")).encoded(), "foo"); - assert_eq!(Token::from(&Token::new("foo")).encoded(), "foo"); - } - - #[test] - fn test_invalid_encoding_offset() { + fn invalid_encoding_offset() { let err = InvalidEncodingError { offset: 3 }; assert_eq!(err.offset(), 3); } @@ -495,4 +533,18 @@ mod tests { "json pointer is malformed due to invalid encoding ('~' not followed by '0' or '1')" ); } + + #[test] + fn tokens() { + let pointer = Pointer::from_static("/a/b/c"); + let tokens: Vec = pointer.tokens().collect(); + assert_eq!( + tokens, + vec![ + Token::from_encoded_unchecked("a"), + Token::from_encoded_unchecked("b"), + Token::from_encoded_unchecked("c") + ] + ); + } } diff --git a/src/tokens.rs b/src/tokens.rs deleted file mode 100644 index ae9c3c6..0000000 --- a/src/tokens.rs +++ /dev/null @@ -1,31 +0,0 @@ -use core::str::Split; - -use crate::Token; - -/* -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -╔══════════════════════════════════════════════════════════════════════════════╗ -║ ║ -║ Tokens ║ -║ ¯¯¯¯¯¯¯¯ ║ -╚══════════════════════════════════════════════════════════════════════════════╝ -░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ -*/ - -/// An iterator over the tokens in a Pointer. -#[derive(Debug)] -pub struct Tokens<'a> { - inner: Split<'a, char>, -} - -impl<'a> Iterator for Tokens<'a> { - type Item = Token<'a>; - fn next(&mut self) -> Option { - self.inner.next().map(Token::from_encoded_unchecked) - } -} -impl<'t> Tokens<'t> { - pub(crate) fn new(inner: Split<'t, char>) -> Self { - Self { inner } - } -} From 7ba779af6feeae4a311427f98a6c7dd06361a1e9 Mon Sep 17 00:00:00 2001 From: Chance Date: Fri, 5 Jul 2024 13:18:40 -0400 Subject: [PATCH 29/57] note on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f25353..486b697 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # jsonptr - JSON Pointers (RFC 6901) From 9b7a4d2d2d4bde35ceacb900ce75e90a25c55dae Mon Sep 17 00:00:00 2001 From: Chance Date: Fri, 5 Jul 2024 13:33:28 -0400 Subject: [PATCH 30/57] adds derive traits to `Component` and `Components` --- README.md | 2 +- src/component.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 486b697..ebd9774 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # jsonptr - JSON Pointers (RFC 6901) diff --git a/src/component.rs b/src/component.rs index 2953f63..9ebf35d 100644 --- a/src/component.rs +++ b/src/component.rs @@ -1,5 +1,6 @@ use crate::{Pointer, Token, Tokens}; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Component<'t> { /// The document root Root, @@ -12,6 +13,7 @@ impl<'t> From> for Component<'t> { } } +#[derive(Debug)] pub struct Components<'t> { tokens: Tokens<'t>, } From ef5a8814a0035a5ae7bd5fe6e12e955765bd39cd Mon Sep 17 00:00:00 2001 From: Chance Date: Sat, 6 Jul 2024 01:03:09 -0400 Subject: [PATCH 31/57] restores lib back to being a `pub mod` --- src/index.rs | 110 +++++++++++++++++++++------------------------------ 1 file changed, 45 insertions(+), 65 deletions(-) diff --git a/src/index.rs b/src/index.rs index f8c5fd6..48a663c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,51 +1,46 @@ -use crate::Token; +//! Abstract index representation for RFC 6901. +//! +//! [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) defines two valid +//! ways to represent array indices as Pointer tokens: non-negative integers, +//! and the character `-`, which stands for the index after the last existing +//! array member. While attempting to use `-` to access an array is an error, +//! the token can be useful when paired with [RFC +//! 6902](https://datatracker.ietf.org/∑doc/html/rfc6902) as a way to express +//! where to put the new element when extending an array. +//! +//! While this crate doesn't implement RFC 6902, it still must consider +//! non-numerical indices as valid, and provide a mechanism for manipulating +//! them. This is what this module provides. +//! +//! The main use of the `Index` type is when resolving a [`Token`] instance as a +//! concrete index for a given array length: +//! +//! ``` +//! # use jsonptr::{Index, Token}; +//! assert_eq!(Token::new("1").to_index(), Ok(Index::Num(1))); +//! assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); +//! assert!(Token::new("a").to_index().is_err()); +//! +//! assert_eq!(Index::Num(0).for_len(1), Ok(0)); +//! assert!(Index::Num(1).for_len(1).is_err()); +//! assert!(Index::Next.for_len(1).is_err()); +//! +//! assert_eq!(Index::Num(1).for_len_incl(1), Ok(1)); +//! assert_eq!(Index::Next.for_len_incl(1), Ok(1)); +//! assert!(Index::Num(2).for_len_incl(1).is_err()); +//! +//! assert_eq!(Index::Num(42).for_len_unchecked(30), 42); +//! assert_eq!(Index::Next.for_len_unchecked(30), 30); +//! ```` + +use crate::{OutOfBoundsError, ParseIndexError, Token}; use alloc::string::String; -use core::{ - fmt::{self, Display}, - num::ParseIntError, - str::FromStr, -}; +use core::fmt::Display; /// Represents an abstract index into an array. /// /// If provided an upper bound with [`Self::for_len`] or [`Self::for_len_incl`], /// will produce a concrete numerical index. -/// -/// [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) defines two valid -/// ways to represent array indices as Pointer tokens: non-negative integers, -/// and the character `-`, which stands for the index after the last existing -/// array member. While attempting to use `-` to access an array is an error, -/// the token can be useful when paired with [RFC -/// 6902](https://datatracker.ietf.org/∑doc/html/rfc6902) as a way to express -/// where to put the new element when extending an array. -/// -/// While this crate doesn't implement RFC 6902, it still must consider -/// non-numerical indices as valid, and provide a mechanism for manipulating -/// them. This is what this module provides. -/// -/// The main use of the `Index` type is when resolving a [`Token`] instance as a -/// concrete index for a given array length: -/// -/// ## Example -/// ``` -/// # use jsonptr::{Index, Token}; -/// assert_eq!(Token::new("1").to_index(), Ok(Index::Num(1))); -/// assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); -/// assert!(Token::new("a").to_index().is_err()); -/// -/// assert_eq!(Index::Num(0).for_len(1), Ok(0)); -/// assert!(Index::Num(1).for_len(1).is_err()); -/// assert!(Index::Next.for_len(1).is_err()); -/// -/// assert_eq!(Index::Num(1).for_len_incl(1), Ok(1)); -/// assert_eq!(Index::Next.for_len_incl(1), Ok(1)); -/// assert!(Index::Num(2).for_len_incl(1).is_err()); -/// -/// assert_eq!(Index::Num(42).for_len_unchecked(30), 42); -/// assert_eq!(Index::Next.for_len_unchecked(30), 30); -/// ``` -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Index { /// A non-negative integer value @@ -134,7 +129,7 @@ impl Index { /// assert_eq!(Index::Next.for_len_unchecked(30), 30); /// /// // no bounds checks - /// assert_eq!(Index::Num(40).for_len_unchecked(34), 40); + /// assert_eq!(Index::Num(34).for_len_unchecked(40), 40); /// assert_eq!(Index::Next.for_len_unchecked(34), 34); /// ```` pub fn for_len_unchecked(&self, length: usize) -> usize { @@ -145,18 +140,6 @@ impl Index { } } -impl FromStr for Index { - type Err = ParseIndexError; - - fn from_str(s: &str) -> Result { - if s == "-" { - Ok(Index::Next) - } else { - Ok(s.parse::().map(Index::Num)?) - } - } -} - impl Display for Index { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match *self { @@ -189,7 +172,11 @@ impl TryFrom<&str> for Index { type Error = ParseIndexError; fn try_from(value: &str) -> Result { - Index::from_str(value) + if value == "-" { + Ok(Index::Next) + } else { + Ok(value.parse::().map(Index::Num)?) + } } } @@ -200,21 +187,14 @@ macro_rules! derive_try_from { type Error = ParseIndexError; fn try_from(value: $t) -> Result { - Index::from_str(&value) + value.try_into() } } )* } } -impl TryFrom> for Index { - type Error = ParseIndexError; - - fn try_from(tok: Token) -> Result { - Index::from_str(tok.encoded()) - } -} -derive_try_from!(String, &String); +derive_try_from!(Token<'_>, String, &String); /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ From 29287d51fb5ae50e4ad86bfe6a47c7fc4647f82a Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 13:15:55 -0400 Subject: [PATCH 32/57] moves `has_sent` to `Components` --- src/component.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/component.rs b/src/component.rs index 9ebf35d..76015a4 100644 --- a/src/component.rs +++ b/src/component.rs @@ -1,5 +1,6 @@ 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> { /// The document root @@ -13,21 +14,18 @@ impl<'t> From> for Component<'t> { } } +/// An iterator over the [`Component`]s of a JSON Pointer #[derive(Debug)] pub struct Components<'t> { tokens: Tokens<'t>, -} -impl<'t> Components<'t> { - pub(crate) fn new(tokens: Tokens<'t>) -> Self { - Self { tokens } - } + sent_root: bool, } impl<'t> Iterator for Components<'t> { type Item = Component<'t>; fn next(&mut self) -> Option { - if !self.tokens.has_sent { - self.tokens.has_sent = true; + if !self.sent_root { + self.sent_root = true; return Some(Component::Root); } self.tokens.next().map(Component::Token) @@ -37,6 +35,7 @@ impl<'t> Iterator for Components<'t> { impl<'t> From<&'t Pointer> for Components<'t> { fn from(pointer: &'t Pointer) -> Self { Self { + sent_root: false, tokens: pointer.tokens(), } } From bac26fe955446fd54b07223ec43f9e30a6e82643 Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 13:17:40 -0400 Subject: [PATCH 33/57] restores `index` back to a mod --- src/index.rs | 6 +++--- src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.rs b/src/index.rs index 48a663c..6aebc86 100644 --- a/src/index.rs +++ b/src/index.rs @@ -33,9 +33,9 @@ //! assert_eq!(Index::Next.for_len_unchecked(30), 30); //! ```` -use crate::{OutOfBoundsError, ParseIndexError, Token}; +use crate::Token; use alloc::string::String; -use core::fmt::Display; +use core::{fmt, num::ParseIntError}; /// Represents an abstract index into an array. /// @@ -140,7 +140,7 @@ impl Index { } } -impl Display for Index { +impl fmt::Display for Index { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match *self { Self::Num(index) => write!(f, "{index}"), diff --git a/src/lib.rs b/src/lib.rs index 6719250..e64d66b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ pub use pointer::{ParseError, Pointer, PointerBuf, ReplaceTokenError}; mod token; pub use token::{InvalidEncodingError, Token, Tokens}; -mod index; +pub mod index; pub use index::{Index, OutOfBoundsError, ParseIndexError}; mod component; From e30e6637140b5e200e882713972edb4ca8363596 Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 13:17:49 -0400 Subject: [PATCH 34/57] moves `has_sent` to `Components` --- src/pointer.rs | 2 +- src/token.rs | 15 ++------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/pointer.rs b/src/pointer.rs index 6f264e8..7a1b6f5 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -406,7 +406,7 @@ impl Pointer { /// assert_eq!(components.next(), None); /// ``` pub fn components(&self) -> Components { - self.tokens().into_components() + self.into() } } diff --git a/src/token.rs b/src/token.rs index 1ccaed6..1222a94 100644 --- a/src/token.rs +++ b/src/token.rs @@ -328,32 +328,21 @@ impl alloc::fmt::Display for Token<'_> { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -/// An iterator over the tokens in a Pointer. +/// An iterator over the [`Token`]s of a [`Pointer`](crate::Pointer). #[derive(Debug)] pub struct Tokens<'a> { - pub(crate) has_sent: bool, inner: Split<'a, char>, } impl<'a> Iterator for Tokens<'a> { type Item = Token<'a>; fn next(&mut self) -> Option { - if !self.has_sent { - self.has_sent = true; - } self.inner.next().map(Token::from_encoded_unchecked) } } impl<'t> Tokens<'t> { pub(crate) fn new(inner: Split<'t, char>) -> Self { - Self { - inner, - has_sent: false, - } - } - - pub fn into_components(self) -> crate::Components<'t> { - crate::Components::new(self) + Self { inner } } } From fc24340f87bfcc886bfff8daabdb4e0229b12d6b Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 13:18:18 -0400 Subject: [PATCH 35/57] docs --- README.md | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ebd9774..861597e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ - - # jsonptr - JSON Pointers (RFC 6901) [github](https://github.com/chanced/jsonptr) @@ -15,16 +13,16 @@ abstractly. A [`Pointer`] is composed of zero or more [`Token`]s, single segments which represent a field of an object or an [`Index`] of an array, and are bounded by -either `'/'` or the end of the string. [`Token`]s are lightly encoded, where +either `'/'` or the end of the string. [`Token`]s are lightly encoded, `'~'` is encoded as `"~0"` and `'/'` is encoded as `"~1"`. Combined, the [`Pointer`] is able to identify a specific location within a JSON, or similar, document. [`Token`]s can be iterated over using either [`Tokens`], returned from the [`tokens`](`Pointer::tokens`) method of a pointer or [`Components`], returned -from the [`components`](`Pointer::components`). The difference being that -[`Tokens`] iterates over each [`Token`] in the pointer, while a [`Component`] -can represent the [`Root`](Component::Root) document or a single +from the [`components`](`Pointer::components`) method. The difference being that +[`Tokens`] iterates over each [`Token`] in the [`Pointer`], while a +[`Component`] can represent the [`Root`](Component::Root) document or a single [`Token`](Component::Token). Operations [`resolve`], [`assign`] and [`delete`] are provided as traits with @@ -45,40 +43,33 @@ for [`serde_json::Value`] and [`toml::Value`](https://docs.rs/toml/0.8). ## Usage -### Resolving a value +### Resolving a Value -Using the `resolve` method, you can resolve a pointer against a value type that -implements [`Resolve`]. +See [`resolve`] for more information. ```rust # use jsonptr::{Pointer}; -let ptr = Pointer::parse("/foo/bar").unwrap(); -let bar = ptr.resolve(&json!({"foo": {"bar": 34}})).unwrap(); -assert_eq!(bar, &json!(34)); -``` - -### Resolve - -```rust -use jsonptr::Pointer; -use serde_json::json; +# use serde_json::json; let ptr = Pointer::parse("/foo/bar").unwrap(); -let bar = ptr.resolve(&json!({"foo": {"bar": 34}})).unwrap(); -assert_eq!(bar, &json!(34)); +let data = json!({"foo": { "bar": 34 }}); +let bar = ptr.resolve().unwrap(); +assert_eq!(bar, json!(34)); ``` -### Assign +### Assigning a Value + +see [`assign`] for more information. ```rust -use jsonptr::Pointer; -use serde_json::json; +# use jsonptr::{Pointer}; +# use serde_json::json; -let ptr = Pointer::parse("/foo/bar").unwrap(); -let mut data = json!({}); -let res = ptr.assign(&mut data, json!({"baz": 34})); -assert_eq!(res, Ok(None)); -assert_eq!(data, json!({"foo": {"bar": {"baz": 34}}})); +let ptr = Pointer::parse("/secret/universe").unwrap(); +let mut data = json!({"secret": { "universe": 42 }}); +let replaced = ptr.assign(&mut data, json!(34)).unwrap(); +assert_eq!(replaced, json!(42)); +assert_eq!(data, json!({"secret": { "universe": 34 }})); ``` ## Contributions / Issues From 0b5437335f11d168c0a8435725964cf72fb16983 Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 13:55:45 -0400 Subject: [PATCH 36/57] fixes `TryFrom` for `Index` again --- README.md | 101 ++++++++++++++++++++++++++++++++++++++------------- src/index.rs | 49 ++++++++++++++++--------- 2 files changed, 107 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 861597e..da3c3d2 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,16 @@ [build status](https://github.com/chanced/jsonptr/actions?query=branch%3Amain) [code coverage](https://codecov.io/gh/chanced/jsonptr) -This crate offers two types, [`Pointer`] and [`PointerBuf`] (akin to -[`Path`](std::path::Path) and [`PathBuf`](std::path::PathBuf)), for working with -JSON Pointers ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)), -abstractly. +JSON Pointers ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)) +defines a string syntax for identifying a specific location within a JSON +document. This crate provides two types, [`Pointer`] and [`PointerBuf`] (akin to +[`str`] and [`String`]), for working with them abstractly. A [`Pointer`] is composed of zero or more [`Token`]s, single segments which -represent a field of an object or an [`Index`] of an array, and are bounded by -either `'/'` or the end of the string. [`Token`]s are lightly encoded, -`'~'` is encoded as `"~0"` and `'/'` is encoded as `"~1"`. Combined, the -[`Pointer`] is able to identify a specific location within a JSON, or similar, -document. +represent a field of an object or an [`index`] of an array, and are bounded by +either `'/'` or the end of the string. [`Token`]s are lightly encoded, where +`'~'` is encoded as `"~0"` and `'/'` as `"~1"`. Combined, the [`Pointer`] forms +a path to a specific location within a JSON, or similar, document. [`Token`]s can be iterated over using either [`Tokens`], returned from the [`tokens`](`Pointer::tokens`) method of a pointer or [`Components`], returned @@ -27,21 +26,50 @@ from the [`components`](`Pointer::components`) method. The difference being that Operations [`resolve`], [`assign`] and [`delete`] are provided as traits with corresponding methods on [`Pointer`]. Implementations of each trait are provided -for [`serde_json::Value`] and [`toml::Value`](https://docs.rs/toml/0.8). +for [`serde_json::Value`] and [`toml::Value`](https://docs.rs/toml/0.8). All +operations are enabled by default but are gated by [feature flags](#feature-flags). -## Feature Flags +## Usage -| Flag | Description | Enables | Default | -| :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------- | :-----: | -| `"std"` | Implements `std::error::Error` for error types | | ✓ | -| `"serde"` | Enables [`serde`] support for types | | ✓ | -| `"json"` | Implements ops for [`serde_json::Value`] | `"serde"` | ✓ | -| `"toml"` | Implements ops for [`toml::Value`](https://docs.rs/toml/0.8) | `"std"`, `toml` | | -| `"assign"` | Enables the [`assign`] module and related pointer methods, providing a means to assign a value to a specific location within a document | | ✓ | -| `"resolve"` | Enables the [`resolve`] module and related pointer methods, providing a means to resolve a value at a specific location within a document | | ✓ | -| `"delete"` | Enables the [`delete`] module and related pointer methods, providing a means to delete a value at a specific location within a document | `"resolve"` | ✓ | +### Basic usage -## Usage +To parse a pointer from a string, use the [`parse`](Pointer::parse) method or construct +from an iterator of [`Token`]s: + +```rust +use jsonptr::Pointer; +use serde_json::json; + +let ptr = Pointer::parse("/examples/0/name").unwrap(); + +let from_tokens = Pointer::from_tokens(["examples", "0", "name"]).unwrap(); +assert_eq!(ptr, from_tokens); + +let parent = ptr.parent(); +assert_eq!(parent, Pointer::parse("/examples/0").unwrap()); + +let (front, remaining) = ptr.split_front(); +assert_eq!(front, "examples"); +assert_eq!(remaining, Pointer::parse("/0/name").unwrap()); + + + +``` + +### Assigning a Value + +see [`assign`] for more information. + +```rust +# use jsonptr::{Pointer}; +# use serde_json::json; + +let ptr = Pointer::parse("/secret/universe").unwrap(); +let mut data = json!({"secret": { "universe": 42 }}); +let replaced = ptr.assign(&mut data, json!(34)).unwrap(); +assert_eq!(replaced, json!(42)); +assert_eq!(data, json!({"secret": { "universe": 34 }})); +``` ### Resolving a Value @@ -72,6 +100,33 @@ assert_eq!(replaced, json!(42)); assert_eq!(data, json!({"secret": { "universe": 34 }})); ``` +### Assigning a Value + +see [`assign`] for more information. + +```rust +# use jsonptr::{Pointer}; +# use serde_json::json; + +let ptr = Pointer::parse("/secret/universe").unwrap(); +let mut data = json!({"secret": { "universe": 42 }}); +let replaced = ptr.assign(&mut data, json!(34)).unwrap(); +assert_eq!(replaced, json!(42)); +assert_eq!(data, json!({"secret": { "universe": 34 }})); +``` + +## Feature Flags + +| Flag | Description | Enables | Default | +| :---------: | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------- | :-----: | +| `"std"` | Implements `std::error::Error` for error types | | ✓ | +| `"serde"` | Enables [`serde`] support for types | | ✓ | +| `"json"` | Implements ops for [`serde_json::Value`] | `"serde"` | ✓ | +| `"toml"` | Implements ops for [`toml::Value`](https://docs.rs/toml/0.8) | `"std"`, `toml` | | +| `"assign"` | Enables the [`assign`] module and related pointer methods, providing a means to assign a value to a specific location within a document | | ✓ | +| `"resolve"` | Enables the [`resolve`] module and related pointer methods, providing a means to resolve a value at a specific location within a document | | ✓ | +| `"delete"` | Enables the [`delete`] module and related pointer methods, providing a means to delete a value at a specific location within a document | `"resolve"` | ✓ | + ## Contributions / Issues Contributions and feedback are always welcome and appreciated. @@ -81,7 +136,3 @@ If you find an issue, please open a ticket or a pull request. ## License MIT or Apache 2.0. - -``` - -``` diff --git a/src/index.rs b/src/index.rs index 6aebc86..20c7840 100644 --- a/src/index.rs +++ b/src/index.rs @@ -3,10 +3,12 @@ //! [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) defines two valid //! ways to represent array indices as Pointer tokens: non-negative integers, //! and the character `-`, which stands for the index after the last existing -//! array member. While attempting to use `-` to access an array is an error, -//! the token can be useful when paired with [RFC -//! 6902](https://datatracker.ietf.org/∑doc/html/rfc6902) as a way to express -//! where to put the new element when extending an array. +//! array member. While attempting to use `-` to resolve an array value will +//! always be out of bounds, the token can be useful when paired with utilities +//! which can mutate a value, such as this crate's [`assign`](crate::assign) +//! functionality or JSON Patch [RFC +//! 6902](https://datatracker.ietf.org/doc/html/rfc6902), as it provides a way +//! to express where to put the new element when extending an array. //! //! While this crate doesn't implement RFC 6902, it still must consider //! non-numerical indices as valid, and provide a mechanism for manipulating @@ -35,7 +37,7 @@ use crate::Token; use alloc::string::String; -use core::{fmt, num::ParseIntError}; +use core::{fmt, num::ParseIntError, str::FromStr}; /// Represents an abstract index into an array. /// @@ -155,28 +157,39 @@ impl From for Index { } } -impl TryFrom<&Token<'_>> for Index { - type Error = ParseIndexError; +impl FromStr for Index { + type Err = ParseIndexError; - fn try_from(value: &Token) -> Result { - // we don't need to decode because it's a single char - if value.encoded() == "-" { + fn from_str(s: &str) -> Result { + if s == "-" { Ok(Index::Next) } else { - Ok(value.decoded().parse::().map(Index::Num)?) + Ok(s.parse::().map(Index::Num)?) } } } +impl TryFrom<&Token<'_>> for Index { + type Error = ParseIndexError; + + fn try_from(value: &Token) -> Result { + Index::from_str(value.encoded()) + } +} + impl TryFrom<&str> for Index { type Error = ParseIndexError; fn try_from(value: &str) -> Result { - if value == "-" { - Ok(Index::Next) - } else { - Ok(value.parse::().map(Index::Num)?) - } + Index::from_str(value) + } +} + +impl TryFrom> for Index { + type Error = ParseIndexError; + + fn try_from(value: Token) -> Result { + Index::from_str(value.encoded()) } } @@ -187,14 +200,14 @@ macro_rules! derive_try_from { type Error = ParseIndexError; fn try_from(value: $t) -> Result { - value.try_into() + Index::from_str(&value) } } )* } } -derive_try_from!(Token<'_>, String, &String); +derive_try_from!(String, &String); /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ From 2c13ba59f916ee23ed9be2ee851f392f39bc14f5 Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 17:09:46 -0400 Subject: [PATCH 37/57] docs, removes re-exports of index types --- README.md | 75 +++++++++++++++++++--------------------------------- src/index.rs | 2 +- src/lib.rs | 1 - 3 files changed, 28 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index da3c3d2..49b3113 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ document. This crate provides two types, [`Pointer`] and [`PointerBuf`] (akin to A [`Pointer`] is composed of zero or more [`Token`]s, single segments which represent a field of an object or an [`index`] of an array, and are bounded by either `'/'` or the end of the string. [`Token`]s are lightly encoded, where -`'~'` is encoded as `"~0"` and `'/'` as `"~1"`. Combined, the [`Pointer`] forms +`'~'` is escaped as `"~0"` and `'/'` as `"~1"`. Combined, the [`Pointer`] forms a path to a specific location within a JSON, or similar, document. [`Token`]s can be iterated over using either [`Tokens`], returned from the @@ -31,87 +31,66 @@ operations are enabled by default but are gated by [feature flags](#feature-flag ## Usage -### Basic usage - -To parse a pointer from a string, use the [`parse`](Pointer::parse) method or construct -from an iterator of [`Token`]s: +To parse a pointer from a string, use the [`parse`](Pointer::parse) method. +[`PointerBuf`] can use either the [`parse`](PointerBuf::parse) or +[`from_tokens`](PointerBuf::from_tokens) construct from an iterator of +[`Token`]s: ```rust -use jsonptr::Pointer; +use jsonptr::{Pointer, PointerBuf}; use serde_json::json; let ptr = Pointer::parse("/examples/0/name").unwrap(); -let from_tokens = Pointer::from_tokens(["examples", "0", "name"]).unwrap(); -assert_eq!(ptr, from_tokens); +let buf = PointerBuf::from_tokens(["examples", "0", "name"]); +assert_eq!(ptr, &buf); -let parent = ptr.parent(); +let parent = ptr.parent().unwrap(); assert_eq!(parent, Pointer::parse("/examples/0").unwrap()); -let (front, remaining) = ptr.split_front(); -assert_eq!(front, "examples"); +let (front, remaining) = ptr.split_front().unwrap(); +assert_eq!(front.decoded(), "examples"); assert_eq!(remaining, Pointer::parse("/0/name").unwrap()); - - - -``` - -### Assigning a Value - -see [`assign`] for more information. - -```rust -# use jsonptr::{Pointer}; -# use serde_json::json; - -let ptr = Pointer::parse("/secret/universe").unwrap(); -let mut data = json!({"secret": { "universe": 42 }}); -let replaced = ptr.assign(&mut data, json!(34)).unwrap(); -assert_eq!(replaced, json!(42)); -assert_eq!(data, json!({"secret": { "universe": 34 }})); ``` -### Resolving a Value - -See [`resolve`] for more information. +Values can be resolved by `Pointer`s using either [`Resolve`] or [`ResolveMut`] +traits. See the [`resolve`] mod for more information. ```rust -# use jsonptr::{Pointer}; -# use serde_json::json; +use jsonptr::Pointer; +use serde_json::json; let ptr = Pointer::parse("/foo/bar").unwrap(); let data = json!({"foo": { "bar": 34 }}); -let bar = ptr.resolve().unwrap(); -assert_eq!(bar, json!(34)); +let bar = ptr.resolve(&data).unwrap(); +assert_eq!(*bar, json!(34)); ``` -### Assigning a Value - -see [`assign`] for more information. +Values can be assigned using the [`Assign`] trait. See [`assign`] for more +information. ```rust -# use jsonptr::{Pointer}; -# use serde_json::json; +use jsonptr::Pointer; +use serde_json::json; let ptr = Pointer::parse("/secret/universe").unwrap(); let mut data = json!({"secret": { "universe": 42 }}); let replaced = ptr.assign(&mut data, json!(34)).unwrap(); -assert_eq!(replaced, json!(42)); +assert_eq!(replaced, Some(json!(42))); assert_eq!(data, json!({"secret": { "universe": 34 }})); ``` -### Assigning a Value - -see [`assign`] for more information. +Values can be deleted with the [`Delete`] trait. See [`delete`] for more +information. ```rust -# use jsonptr::{Pointer}; -# use serde_json::json; +use jsonptr::Pointer; +use serde_json::json; let ptr = Pointer::parse("/secret/universe").unwrap(); let mut data = json!({"secret": { "universe": 42 }}); let replaced = ptr.assign(&mut data, json!(34)).unwrap(); -assert_eq!(replaced, json!(42)); +assert_eq!(replaced, Some(json!(42))); assert_eq!(data, json!({"secret": { "universe": 34 }})); ``` diff --git a/src/index.rs b/src/index.rs index 20c7840..d0d1867 100644 --- a/src/index.rs +++ b/src/index.rs @@ -131,7 +131,7 @@ impl Index { /// assert_eq!(Index::Next.for_len_unchecked(30), 30); /// /// // no bounds checks - /// assert_eq!(Index::Num(34).for_len_unchecked(40), 40); + /// assert_eq!(Index::Num(34).for_len_unchecked(40), 34); /// assert_eq!(Index::Next.for_len_unchecked(34), 34); /// ```` pub fn for_len_unchecked(&self, length: usize) -> usize { diff --git a/src/lib.rs b/src/lib.rs index e64d66b..2428dc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,6 @@ mod token; pub use token::{InvalidEncodingError, Token, Tokens}; pub mod index; -pub use index::{Index, OutOfBoundsError, ParseIndexError}; mod component; pub use component::{Component, Components}; From 11ae51596a5a2486a125e1165194b4fa314adea1 Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 17:11:10 -0400 Subject: [PATCH 38/57] fixes docs --- src/index.rs | 2 +- src/token.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.rs b/src/index.rs index d0d1867..6932dc8 100644 --- a/src/index.rs +++ b/src/index.rs @@ -18,7 +18,7 @@ //! concrete index for a given array length: //! //! ``` -//! # use jsonptr::{Index, Token}; +//! # use jsonptr::{index::Index, Token}; //! assert_eq!(Token::new("1").to_index(), Ok(Index::Num(1))); //! assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); //! assert!(Token::new("a").to_index().is_err()); diff --git a/src/token.rs b/src/token.rs index 1222a94..add1995 100644 --- a/src/token.rs +++ b/src/token.rs @@ -235,7 +235,7 @@ impl<'a> Token<'a> { /// ## Examples /// /// ``` - /// # use jsonptr::{Index, Token}; + /// # use jsonptr::{index::Index, Token}; /// assert_eq!(Token::new("-").to_index(), Ok(Index::Next)); /// assert_eq!(Token::new("0").to_index(), Ok(Index::Num(0))); /// assert_eq!(Token::new("2").to_index(), Ok(Index::Num(2))); From b6452e58bf63fea601596acc455931fc8de77b1e Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 17:38:51 -0400 Subject: [PATCH 39/57] fixes example imports of `jsonptr::index::Index` --- src/index.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.rs b/src/index.rs index 6932dc8..286ca8f 100644 --- a/src/index.rs +++ b/src/index.rs @@ -68,7 +68,7 @@ impl Index { /// # Examples /// /// ``` - /// # use jsonptr::Index; + /// # use jsonptr::index::Index; /// assert_eq!(Index::Num(0).for_len(1), Ok(0)); /// assert!(Index::Num(1).for_len(1).is_err()); /// assert!(Index::Next.for_len(1).is_err()); @@ -100,7 +100,7 @@ impl Index { /// # Examples /// /// ``` - /// # use jsonptr::Index; + /// # use jsonptr::index::Index; /// assert_eq!(Index::Num(1).for_len_incl(1), Ok(1)); /// assert_eq!(Index::Next.for_len_incl(1), Ok(1)); /// assert!(Index::Num(2).for_len_incl(1).is_err()); @@ -126,7 +126,7 @@ impl Index { /// # Examples /// /// ``` - /// # use jsonptr::Index; + /// # use jsonptr::index::Index; /// assert_eq!(Index::Num(42).for_len_unchecked(30), 42); /// assert_eq!(Index::Next.for_len_unchecked(30), 30); /// From 540b6a1437c63ea7d00d8403c7eb1cb4efd72a4d Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 17:46:52 -0400 Subject: [PATCH 40/57] docs + coverage for `Components` --- src/component.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/component.rs b/src/component.rs index 76015a4..9950161 100644 --- a/src/component.rs +++ b/src/component.rs @@ -40,3 +40,36 @@ impl<'t> From<&'t Pointer> for Components<'t> { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn components() { + let ptr = Pointer::from_static(""); + let components: Vec<_> = Components::from(ptr).collect(); + assert_eq!(components, vec![Component::Root]); + + let ptr = Pointer::from_static("/foo"); + let components = ptr.components().collect::>(); + assert_eq!( + components, + vec![Component::Root, Component::Token("foo".into())] + ); + + let ptr = Pointer::from_static("/foo/bar/-/0/baz"); + let components = ptr.components().collect::>(); + assert_eq!( + components, + vec![ + Component::Root, + Component::from(Token::from("foo")), + Component::Token("bar".into()), + Component::Token("-".into()), + Component::Token("0".into()), + Component::Token("baz".into()) + ] + ); + } +} From 1321b1c8eef81cc1b52e0b83e8a4b99a361e059e Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 17:57:40 -0400 Subject: [PATCH 41/57] docs --- src/assign.rs | 20 +++++++++++--------- src/delete.rs | 26 +++++++++++++------------- src/resolve.rs | 20 +++++++++++++++++++- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index 7fb24f0..9aea206 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -3,16 +3,8 @@ //! This module provides the [`Assign`] trait which allows for the assignment of //! values based on a JSON Pointer. //! -//! ## Feature Flag //! This module is enabled by default with the `"assign"` feature flag. //! -//! ## Provided implementations -//! -//! | Lang | value type | feature flag | Default | -//! | ----- |: ----------------- :|: ---------- :| ------- | -//! | JSON | `serde_json::Value` | `"json"` | ✓ | -//! | TOML | `toml::Value` | `"toml"` | | -//! //! # Expansion //! The path will automatically be expanded if the [`Pointer`] is not fully //! exhausted before reaching a non-existent key in the case of objects, index @@ -25,7 +17,10 @@ //! //! //! -//! ## Example +//! ## Usage +//! [`Assign`] can be used directly or through the [`assign`](Pointer::assign) +//! method of [`Pointer`]. +//! //! ```rust //! # use jsonptr::Pointer; //! # use serde_json::json; @@ -35,6 +30,13 @@ //! assert_eq!(replaced, Some(json!("bar"))); //! assert_eq!(data, json!({"foo": "baz"})); //! ``` +//! ## Provided implementations +//! +//! | Lang | value type | feature flag | Default | +//! | ----- |: ----------------- :|: ---------- :| ------- | +//! | JSON | `serde_json::Value` | `"json"` | ✓ | +//! | TOML | `toml::Value` | `"toml"` | | +//! use crate::{ index::{OutOfBoundsError, ParseIndexError}, diff --git a/src/delete.rs b/src/delete.rs index 725b9b3..2909b5e 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -13,18 +13,10 @@ //! - `"json"` - `serde_json::Value::Null` //! - `"toml"` - `toml::Value::Table::Default` //! -//! ## Feature Flag -//! This module is enabled by default with the `"resolve"` feature flag. +//! This module is enabled by default with the `"delete"` feature flag. //! -//! ## Provided implementations -//! -//! | Lang | value type | feature flag | Default | -//! | ----- |: ----------------- :|: ---------- :| ------- | -//! | JSON | `serde_json::Value` | `"json"` | ✓ | -//! | TOML | `toml::Value` | `"toml"` | | -//! -//! ## Examples -//! ### Deleting a resolved pointer: +//! ## Usage +//! Deleting a resolved pointer: //! ```rust //! use jsonptr::{Pointer, delete::Delete}; //! use serde_json::json; @@ -34,7 +26,7 @@ //! assert_eq!(data.delete(&ptr), Some("qux".into())); //! assert_eq!(data, json!({ "foo": { "bar": {} } })); //! ``` -//! ### Deleting a non-existent Pointer returns `None`: +//! Deleting a non-existent Pointer returns `None`: //! ```rust //! use jsonptr::{ Pointer, delete::Delete }; //! use serde_json::json; @@ -44,7 +36,7 @@ //! assert_eq!(ptr.delete(&mut data), None); //! assert_eq!(data, json!({})); //! ``` -//! ### Deleting a root pointer replaces the value with `Value::Null`: +//! Deleting a root pointer replaces the value with `Value::Null`: //! ```rust //! use jsonptr::{Pointer, delete::Delete}; //! use serde_json::json; @@ -54,6 +46,14 @@ //! assert_eq!(data.delete(&ptr), Some(json!({ "foo": { "bar": "baz" } }))); //! assert!(data.is_null()); //! ``` +//! +//! ## Provided implementations +//! +//! | Lang | value type | feature flag | Default | +//! | ----- |: ----------------- :|: ---------- :| ------- | +//! | JSON | `serde_json::Value` | `"json"` | ✓ | +//! | TOML | `toml::Value` | `"toml"` | | + use crate::Pointer; /* diff --git a/src/resolve.rs b/src/resolve.rs index fe5ac40..f97dc0b 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -4,9 +4,27 @@ //! implemented by types that can internally resolve a value based on a JSON //! Pointer. //! -//! ## Feature Flag //! This module is enabled by default with the `"resolve"` feature flag. //! +//! ## Usage +//! [`Resolve`] and [`ResolveMut`] can be used directly or through the +//! [`resolve`](Pointer::resolve) and [`resolve_mut`](Pointer::resolve_mut) +//! methods on [`Pointer`] and [`PointerBuf`](crate::PointerBuf). +//! ```rust +//! use jsonptr::{Pointer, Resolve, ResolveMut}; +//! use serde_json::json; +//! +//! let ptr = Pointer::from_static("/foo/1"); +//! let mut data = json!({"foo": ["bar", "baz"]}); +//! +//! let value = ptr.resolve(&data).unwrap(); +//! assert_eq!(value, &json!("baz")); +//! +//! let value = data.resolve_mut(ptr).unwrap(); +//! assert_eq!(value, &json!("baz")); +//! +//! ``` +//! //! ## Provided implementations //! //! | Lang | value type | feature flag | Default | From 9a8d59e98e797cf4bdfa1ec4c24c6beb3811c1d3 Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 18:05:10 -0400 Subject: [PATCH 42/57] minor cleanup --- src/resolve.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/resolve.rs b/src/resolve.rs index f97dc0b..185a7ab 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -647,79 +647,79 @@ mod tests { Test { ptr: "", data, - expected_result: Ok(data), + expected: Ok(data), }, // 1 Test { ptr: "/array", data, - expected_result: Ok(data.get("array").unwrap()), // ["bar", "baz"] + expected: Ok(data.get("array").unwrap()), // ["bar", "baz"] }, // 2 Test { ptr: "/array/0", data, - expected_result: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar" + expected: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar" }, // 3 Test { ptr: "/a~1b", data, - expected_result: Ok(data.get("a/b").unwrap()), // 1 + expected: Ok(data.get("a/b").unwrap()), // 1 }, // 4 Test { ptr: "/c%d", data, - expected_result: Ok(data.get("c%d").unwrap()), // 2 + expected: Ok(data.get("c%d").unwrap()), // 2 }, // 5 Test { ptr: "/e^f", data, - expected_result: Ok(data.get("e^f").unwrap()), // 3 + expected: Ok(data.get("e^f").unwrap()), // 3 }, // 6 Test { ptr: "/g|h", data, - expected_result: Ok(data.get("g|h").unwrap()), // 4 + expected: Ok(data.get("g|h").unwrap()), // 4 }, // 7 Test { ptr: "/i\\j", data, - expected_result: Ok(data.get("i\\j").unwrap()), // 5 + expected: Ok(data.get("i\\j").unwrap()), // 5 }, // 8 Test { ptr: "/k\"l", data, - expected_result: Ok(data.get("k\"l").unwrap()), // 6 + expected: Ok(data.get("k\"l").unwrap()), // 6 }, // 9 Test { ptr: "/ ", data, - expected_result: Ok(data.get(" ").unwrap()), // 7 + expected: Ok(data.get(" ").unwrap()), // 7 }, // 10 Test { ptr: "/m~0n", data, - expected_result: Ok(data.get("m~n").unwrap()), // 8 + expected: Ok(data.get("m~n").unwrap()), // 8 }, // 11 Test { ptr: "/object/bool/unresolvable", data, - expected_result: Err(ResolveError::Unreachable { offset: 12 }), + expected: Err(ResolveError::Unreachable { offset: 12 }), }, // 12 Test { ptr: "/object/not_found", data, - expected_result: Err(ResolveError::NotFound { offset: 7 }), + expected: Err(ResolveError::NotFound { offset: 7 }), }, ]); } @@ -824,7 +824,7 @@ mod tests { } struct Test<'v, V> { ptr: &'static str, - expected_result: Result<&'v V, ResolveError>, + expected: Result<&'v V, ResolveError>, data: &'v V, } @@ -846,21 +846,21 @@ mod tests { let Test { ptr, data, - expected_result, + expected, } = self; let ptr = Pointer::from_static(ptr); // cloning the data & expected_result to make comparison easier let mut data = data.clone(); - let expected_result = expected_result.cloned(); + let expected = expected.cloned(); // testing Resolve let res = data.resolve(ptr).cloned(); - assert_eq!(&res, &expected_result); + assert_eq!(&res, &expected); // testing ResolveMut let res = data.resolve_mut(ptr).cloned(); - assert_eq!(&res, &expected_result); + assert_eq!(&res, &expected); } } } From e2e158e14272513cc3925373d2618a092b04abbf Mon Sep 17 00:00:00 2001 From: Chance Date: Sun, 7 Jul 2024 18:30:34 -0400 Subject: [PATCH 43/57] fixes minor cleanup --- src/assign.rs | 82 +++++++++++++++++++++++++------------------------- src/resolve.rs | 28 ++++++++--------- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index 9aea206..7607e84 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -566,7 +566,7 @@ mod tests { ptr: &'static str, assign: V, expected_data: V, - expected_result: Result, V::Error>, + expected: Result, V::Error>, } impl Test @@ -585,7 +585,7 @@ mod tests { mut data, assign, expected_data, - expected_result, + expected, .. } = self; let ptr = Pointer::from_static(ptr); @@ -594,7 +594,7 @@ mod tests { &expected_data, &data, "test #{i}:\n\ndata: \n{data:#?}\n\nexpected_data\n{expected_data:#?}" ); - assert_eq!(&expected_result, &replaced); + assert_eq!(&expected, &replaced); } } @@ -615,90 +615,90 @@ mod tests { data: json!({}), assign: json!("bar"), expected_data: json!({"foo": "bar"}), - expected_result: Ok(None), + expected: Ok(None), }, Test { ptr: "", data: json!({"foo": "bar"}), assign: json!("baz"), expected_data: json!("baz"), - expected_result: Ok(Some(json!({"foo": "bar"}))), + expected: Ok(Some(json!({"foo": "bar"}))), }, Test { ptr: "/foo", data: json!({"foo": "bar"}), assign: json!("baz"), expected_data: json!({"foo": "baz"}), - expected_result: Ok(Some(json!("bar"))), + expected: Ok(Some(json!("bar"))), }, Test { ptr: "/foo/bar", data: json!({"foo": "bar"}), assign: json!("baz"), expected_data: json!({"foo": {"bar": "baz"}}), - expected_result: Ok(Some(json!("bar"))), + expected: Ok(Some(json!("bar"))), }, Test { ptr: "/foo/bar", data: json!({}), assign: json!("baz"), expected_data: json!({"foo": {"bar": "baz"}}), - expected_result: Ok(None), + expected: Ok(None), }, Test { ptr: "/", data: json!({}), assign: json!("foo"), expected_data: json!({"": "foo"}), - expected_result: Ok(None), + expected: Ok(None), }, Test { ptr: "/-", data: json!({}), assign: json!("foo"), expected_data: json!({"-": "foo"}), - expected_result: Ok(None), + expected: Ok(None), }, Test { ptr: "/-", data: json!(null), assign: json!(34), expected_data: json!([34]), - expected_result: Ok(Some(json!(null))), + expected: Ok(Some(json!(null))), }, Test { ptr: "/foo/-", data: json!({"foo": "bar"}), assign: json!("baz"), expected_data: json!({"foo": ["baz"]}), - expected_result: Ok(Some(json!("bar"))), + expected: Ok(Some(json!("bar"))), }, Test { ptr: "/foo/-/bar", assign: "baz".into(), data: json!({}), - expected_result: Ok(None), + expected: Ok(None), expected_data: json!({"foo":[{"bar": "baz"}]}), }, Test { ptr: "/foo/-/bar", assign: "qux".into(), data: json!({"foo":[{"bar":"baz" }]}), - expected_result: Ok(None), + expected: Ok(None), expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}), }, Test { ptr: "/foo/-/bar", data: json!({"foo":[{"bar":"baz"},{"bar":"qux"}]}), assign: "quux".into(), - expected_result: Ok(None), + expected: Ok(None), expected_data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}), }, Test { ptr: "/foo/0/bar", data: json!({"foo":[{"bar":"baz"},{"bar":"qux"},{"bar":"quux"}]}), assign: "grault".into(), - expected_result: Ok(Some("baz".into())), + expected: Ok(Some("baz".into())), expected_data: json!({"foo":[{"bar":"grault"},{"bar":"qux"},{"bar":"quux"}]}), }, Test { @@ -706,34 +706,34 @@ mod tests { data: json!({}), assign: json!("foo"), expected_data: json!({"0": "foo"}), - expected_result: Ok(None), + expected: Ok(None), }, Test { ptr: "/1", data: json!(null), assign: json!("foo"), expected_data: json!({"1": "foo"}), - expected_result: Ok(Some(json!(null))), + expected: Ok(Some(json!(null))), }, Test { ptr: "/0", data: json!([]), expected_data: json!(["foo"]), assign: json!("foo"), - expected_result: Ok(None), + expected: Ok(None), }, Test { ptr: "///bar", data: json!({"":{"":{"bar": 42}}}), assign: json!(34), expected_data: json!({"":{"":{"bar":34}}}), - expected_result: Ok(Some(json!(42))), + expected: Ok(Some(json!(42))), }, Test { ptr: "/1", data: json!([]), assign: json!("foo"), - expected_result: Err(AssignError::OutOfBounds { + expected: Err(AssignError::OutOfBounds { offset: 0, source: OutOfBoundsError { index: 1, @@ -746,14 +746,14 @@ mod tests { ptr: "/0", data: json!(["foo"]), assign: json!("bar"), - expected_result: Ok(Some(json!("foo"))), + expected: Ok(Some(json!("foo"))), expected_data: json!(["bar"]), }, Test { ptr: "/a", data: json!([]), assign: json!("foo"), - expected_result: Err(AssignError::FailedToParseIndex { + expected: Err(AssignError::FailedToParseIndex { offset: 0, source: ParseIndexError { source: usize::from_str("foo").unwrap_err(), @@ -781,97 +781,97 @@ mod tests { ptr: "/foo", assign: "bar".into(), expected_data: toml! { "foo" = "bar" }.into(), - expected_result: Ok(None), + expected: Ok(None), }, Test { data: toml! {foo = "bar"}.into(), ptr: "", assign: "baz".into(), expected_data: "baz".into(), - expected_result: Ok(Some(toml! {foo = "bar"}.into())), + expected: Ok(Some(toml! {foo = "bar"}.into())), }, Test { data: toml! { foo = "bar"}.into(), ptr: "/foo", assign: "baz".into(), expected_data: toml! {foo = "baz"}.into(), - expected_result: Ok(Some("bar".into())), + expected: Ok(Some("bar".into())), }, Test { data: toml! { foo = "bar"}.into(), ptr: "/foo/bar", assign: "baz".into(), expected_data: toml! {foo = { bar = "baz"}}.into(), - expected_result: Ok(Some("bar".into())), + expected: Ok(Some("bar".into())), }, Test { data: Table::new().into(), ptr: "/", assign: "foo".into(), expected_data: toml! {"" = "foo"}.into(), - expected_result: Ok(None), + expected: Ok(None), }, Test { data: Table::new().into(), ptr: "/-", assign: "foo".into(), expected_data: toml! {"-" = "foo"}.into(), - expected_result: Ok(None), + expected: Ok(None), }, Test { data: "data".into(), ptr: "/-", assign: 34.into(), expected_data: Value::Array(vec![34.into()]), - expected_result: Ok(Some("data".into())), + expected: Ok(Some("data".into())), }, Test { data: toml! {foo = "bar"}.into(), ptr: "/foo/-", assign: "baz".into(), expected_data: toml! {foo = ["baz"]}.into(), - expected_result: Ok(Some("bar".into())), + expected: Ok(Some("bar".into())), }, Test { data: Table::new().into(), ptr: "/0", assign: "foo".into(), expected_data: toml! {"0" = "foo"}.into(), - expected_result: Ok(None), + expected: Ok(None), }, Test { data: 21.into(), ptr: "/1", assign: "foo".into(), expected_data: toml! {"1" = "foo"}.into(), - expected_result: Ok(Some(21.into())), + expected: Ok(Some(21.into())), }, Test { data: Value::Array(vec![]), ptr: "/0", expected_data: vec![Value::from("foo")].into(), assign: "foo".into(), - expected_result: Ok(None), + expected: Ok(None), }, Test { ptr: "/foo/-/bar", assign: "baz".into(), data: Table::new().into(), - expected_result: Ok(None), + expected: Ok(None), expected_data: toml! { "foo" = [{"bar" = "baz"}] }.into(), }, Test { ptr: "/foo/-/bar", assign: "qux".into(), data: toml! {"foo" = [{"bar" = "baz"}] }.into(), - expected_result: Ok(None), + expected: Ok(None), expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(), }, Test { ptr: "/foo/-/bar", data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}]}.into(), assign: "quux".into(), - expected_result: Ok(None), + expected: Ok(None), expected_data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]} .into(), }, @@ -879,7 +879,7 @@ mod tests { ptr: "/foo/0/bar", data: toml! {"foo" = [{"bar" = "baz"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(), assign: "grault".into(), - expected_result: Ok(Some("baz".into())), + expected: Ok(Some("baz".into())), expected_data: toml! {"foo" = [{"bar" = "grault"}, {"bar" = "qux"}, {"bar" = "quux"}]}.into(), }, @@ -887,14 +887,14 @@ mod tests { data: Value::Array(vec![]), ptr: "/-", assign: "foo".into(), - expected_result: Ok(None), + expected: Ok(None), expected_data: vec!["foo"].into(), }, Test { data: Value::Array(vec![]), ptr: "/1", assign: "foo".into(), - expected_result: Err(AssignError::OutOfBounds { + expected: Err(AssignError::OutOfBounds { offset: 0, source: OutOfBoundsError { index: 1, @@ -907,7 +907,7 @@ mod tests { data: Value::Array(vec![]), ptr: "/a", assign: "foo".into(), - expected_result: Err(AssignError::FailedToParseIndex { + expected: Err(AssignError::FailedToParseIndex { offset: 0, source: ParseIndexError { source: usize::from_str("foo").unwrap_err(), diff --git a/src/resolve.rs b/src/resolve.rs index 185a7ab..3e0749f 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -758,67 +758,67 @@ mod tests { Test { ptr: "", data, - expected_result: Ok(data), + expected: Ok(data), }, Test { ptr: "/array", data, - expected_result: Ok(data.get("array").unwrap()), // ["bar", "baz"] + expected: Ok(data.get("array").unwrap()), // ["bar", "baz"] }, Test { ptr: "/array/0", data, - expected_result: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar" + expected: Ok(data.get("array").unwrap().get(0).unwrap()), // "bar" }, Test { ptr: "/a~1b", data, - expected_result: Ok(data.get("a/b").unwrap()), // 1 + expected: Ok(data.get("a/b").unwrap()), // 1 }, Test { ptr: "/c%d", data, - expected_result: Ok(data.get("c%d").unwrap()), // 2 + expected: Ok(data.get("c%d").unwrap()), // 2 }, Test { ptr: "/e^f", data, - expected_result: Ok(data.get("e^f").unwrap()), // 3 + expected: Ok(data.get("e^f").unwrap()), // 3 }, Test { ptr: "/g|h", data, - expected_result: Ok(data.get("g|h").unwrap()), // 4 + expected: Ok(data.get("g|h").unwrap()), // 4 }, Test { ptr: "/i\\j", data, - expected_result: Ok(data.get("i\\j").unwrap()), // 5 + expected: Ok(data.get("i\\j").unwrap()), // 5 }, Test { ptr: "/k\"l", data, - expected_result: Ok(data.get("k\"l").unwrap()), // 6 + expected: Ok(data.get("k\"l").unwrap()), // 6 }, Test { ptr: "/ ", data, - expected_result: Ok(data.get(" ").unwrap()), // 7 + expected: Ok(data.get(" ").unwrap()), // 7 }, Test { ptr: "/m~0n", data, - expected_result: Ok(data.get("m~n").unwrap()), // 8 + expected: Ok(data.get("m~n").unwrap()), // 8 }, Test { ptr: "/object/bool/unresolvable", data, - expected_result: Err(ResolveError::Unreachable { offset: 12 }), + expected: Err(ResolveError::Unreachable { offset: 12 }), }, Test { ptr: "/object/not_found", data, - expected_result: Err(ResolveError::NotFound { offset: 7 }), + expected: Err(ResolveError::NotFound { offset: 7 }), }, ]); } @@ -850,7 +850,7 @@ mod tests { } = self; let ptr = Pointer::from_static(ptr); - // cloning the data & expected_result to make comparison easier + // cloning the data & expected to make comparison easier let mut data = data.clone(); let expected = expected.cloned(); From bf0e55f604644eb54cd0932fb40db5e27016568b Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 12:59:25 -0400 Subject: [PATCH 44/57] minor change to doc --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 49b3113..bc92461 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,14 @@ [code coverage](https://codecov.io/gh/chanced/jsonptr) JSON Pointers ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)) -defines a string syntax for identifying a specific location within a JSON -document. This crate provides two types, [`Pointer`] and [`PointerBuf`] (akin to -[`str`] and [`String`]), for working with them abstractly. +defines a string syntax for identifying a specific location within a JSON, or +similar, document. This crate provides two types, [`Pointer`] and [`PointerBuf`] +(akin to [`str`] and [`String`]), for working with them abstractly. A [`Pointer`] is composed of zero or more [`Token`]s, single segments which represent a field of an object or an [`index`] of an array, and are bounded by either `'/'` or the end of the string. [`Token`]s are lightly encoded, where -`'~'` is escaped as `"~0"` and `'/'` as `"~1"`. Combined, the [`Pointer`] forms -a path to a specific location within a JSON, or similar, document. +`'~'` is escaped as `"~0"` and `'/'` as `"~1"`. [`Token`]s can be iterated over using either [`Tokens`], returned from the [`tokens`](`Pointer::tokens`) method of a pointer or [`Components`], returned @@ -63,7 +62,7 @@ use serde_json::json; let ptr = Pointer::parse("/foo/bar").unwrap(); let data = json!({"foo": { "bar": 34 }}); let bar = ptr.resolve(&data).unwrap(); -assert_eq!(*bar, json!(34)); +assert_eq!(bar, &json!(34)); ``` Values can be assigned using the [`Assign`] trait. See [`assign`] for more From e6e91af59aa2bbb784c3fa19eadabbf849c4ca78 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 14:06:07 -0400 Subject: [PATCH 45/57] utilizes rustdoc link hack --- README.md | 73 +++++++++++++++++++++++++++++++++++++++----------- src/lib.rs | 22 +++++++++++++++ src/pointer.rs | 44 ++++++++++++++++++------------ 3 files changed, 107 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index bc92461..0f8e04a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# jsonptr - JSON Pointers (RFC 6901) +
+ +# jsonptr - JSON Pointers (RFC 6901) for Rust + +
[github](https://github.com/chanced/jsonptr) [crates.io](https://crates.io/crates/jsonptr) @@ -17,15 +21,14 @@ either `'/'` or the end of the string. [`Token`]s are lightly encoded, where `'~'` is escaped as `"~0"` and `'/'` as `"~1"`. [`Token`]s can be iterated over using either [`Tokens`], returned from the -[`tokens`](`Pointer::tokens`) method of a pointer or [`Components`], returned -from the [`components`](`Pointer::components`) method. The difference being that -[`Tokens`] iterates over each [`Token`] in the [`Pointer`], while a -[`Component`] can represent the [`Root`](Component::Root) document or a single -[`Token`](Component::Token). +[`tokens`] method of a pointer or [`Components`], returned from the +[`components`] method. The difference being that [`Tokens`] iterates over each +[`Token`] in the [`Pointer`], while a [`Component`] can represent the +[`Root`](Component::Root) document or a single [`Token`](Component::Token). Operations [`resolve`], [`assign`] and [`delete`] are provided as traits with corresponding methods on [`Pointer`]. Implementations of each trait are provided -for [`serde_json::Value`] and [`toml::Value`](https://docs.rs/toml/0.8). All +for [`serde_json::Value`] and [`toml::Value`]. All operations are enabled by default but are gated by [feature flags](#feature-flags). ## Usage @@ -100,17 +103,57 @@ assert_eq!(data, json!({"secret": { "universe": 34 }})); | `"std"` | Implements `std::error::Error` for error types | | ✓ | | `"serde"` | Enables [`serde`] support for types | | ✓ | | `"json"` | Implements ops for [`serde_json::Value`] | `"serde"` | ✓ | -| `"toml"` | Implements ops for [`toml::Value`](https://docs.rs/toml/0.8) | `"std"`, `toml` | | +| `"toml"` | Implements ops for [`toml::Value`] | `"std"`, `toml` | | | `"assign"` | Enables the [`assign`] module and related pointer methods, providing a means to assign a value to a specific location within a document | | ✓ | | `"resolve"` | Enables the [`resolve`] module and related pointer methods, providing a means to resolve a value at a specific location within a document | | ✓ | | `"delete"` | Enables the [`delete`] module and related pointer methods, providing a means to delete a value at a specific location within a document | `"resolve"` | ✓ | -## Contributions / Issues - -Contributions and feedback are always welcome and appreciated. - -If you find an issue, please open a ticket or a pull request. - +
## License -MIT or Apache 2.0. +Licensed under either of + +- Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Contributions and feedback are always welcome and appreciated. If you find an +issue, please open a ticket or a pull request. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[LICENSE-APACHE]: LICENSE-APACHE +[LICENSE-MIT]: LICENSE-MIT + +
+ +[`Pointer::components`]: (https://docs.rs/jsonptr/latest/jsonptrstruct.Pointer.html#method.components) +[`Pointer::tokens`]: (https://docs.rs/jsonptr/latest/jsonptrstruct.Pointer.html#method.tokens) +[`Pointer`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html +[`PointerBuf`]: https://docs.rs/jsonptr/latest/jsonptr/struct.PointerBuf.html +[`Token`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Token.html +[`Tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Tokens.html +[`Components`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Components.html +[`Component`]: https://docs.rs/jsonptr/latest/jsonptr/enum.Component.html +[`Root`]: https://docs.rs/jsonptr/latest/jsonptr/enum.Component.html#variant.Root +[`index`]: https://doc.rust-lang.org/std/primitive.usize.html +[`tokens`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.tokens +[`components`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html#method.components +[`resolve`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/index.html +[`assign`]: https://docs.rs/jsonptr/latest/jsonptr/assign/index.html +[`delete`]: https://docs.rs/jsonptr/latest/jsonptr/delete/index.html +[`Resolve`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/trait.Resolve.html +[`ResolveMut`]: https://docs.rs/jsonptr/latest/jsonptr/resolve/trait.ResolveMut.html +[`Assign`]: https://docs.rs/jsonptr/latest/jsonptr/assign/trait.Assign.html +[`Delete`]: https://docs.rs/jsonptr/latest/jsonptr/delete/trait.Delete.html +[`serde`]: https://docs.rs/serde/1.0.120/serde/index +[`serde_json::Value`]: https://docs.rs/serde_json/1.0.120/serde_json/enum.Value.html +[`str`]: https://doc.rust-lang.org/std/primitive.str.html +[`String`]: https://doc.rust-lang.org/std/string/struct.String.html diff --git a/src/lib.rs b/src/lib.rs index 2428dc5..4dbe9f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,25 @@ +// rustdoc + README hack: https://linebender.org/blog/doc-include +//! +//! +//! [`Pointer`]: crate::Pointer +//! [`PointerBuf`]: crate::PointerBuf +//! [`Token`]: crate::Token +//! [`Tokens`]: crate::Tokens +//! [`Component`]: crate::Component +//! [`Components`]: crate::Components +//! [`Resolve`]: crate::resolve::Resolve +//! [`ResolveMut`]: crate::resolve::ResolveMut +//! [`resolve`]: crate::resolve +//! [`assign`]: crate::assign +//! [delete]: crate::delete +//! [index]: crate::index +//! [str]: str +//! [String]: String +//! [serde_json::Value]: serde_json::Value +//! [toml::Value]: https://docs.rs/toml/0.8/toml/enum.Value.html + #![doc = include_str!("../README.md")] #![warn(missing_docs)] #![deny(clippy::all, clippy::pedantic)] diff --git a/src/pointer.rs b/src/pointer.rs index 7a1b6f5..3db4009 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -18,7 +18,7 @@ use core::{borrow::Borrow, cmp::Ordering, ops::Deref, str::FromStr}; */ /// A JSON Pointer is a string containing a sequence of zero or more reference -/// tokens, each prefixed by a '/' character. +/// [`Token`]s, each prefixed by a `'/'` character. /// /// See [RFC 6901 for more /// information](https://datatracker.ietf.org/doc/html/rfc6901). @@ -252,43 +252,53 @@ impl Pointer { self.tokens().nth(index).clone() } - /// Attempts to resolve a `Value` based on the path in this `Pointer`. + /// Attempts to resolve a [`R::Value`] based on the path in this [`Pointer`]. /// /// ## Errors - /// Returns [`R::Error`](`Resolve::Error`) if an error occurs while - /// resolving. + /// Returns [`R::Error`] if an error occurs while resolving. /// /// The rules of such are determined by the `R`'s implementation of - /// [`Resolve`] but provided implementations return - /// [`ResolveError`](crate::resolve::ResolveError) if: + /// [`Resolve`] but provided implementations return [`ResolveError`] if: /// - The path is unreachable (e.g. a scalar is encountered prior to the end /// of the path) /// - The path is not found (e.g. a key in an object or an index in an array /// does not exist) - /// - A [`Token`](crate::Token) cannot be parsed as an array - /// [`Index`](crate::index::Index) - /// - An array [`Index`](crate::index::Index) is out of bounds + /// - A [`Token`] cannot be parsed as an array [`Index`] + /// - An array [`Index`] is out of bounds + /// [`R::Value`]: `crate::resolve::Resolve::Value` + /// [`R::Error`]: `crate::resolve::Resolve::Error` + /// [`Resolve`]: `crate::resolve::Resolve` + /// [`ResolveError`]: `crate::resolve::ResolveError` + /// [`Token`]: crate::Token + /// [`Index`]: crate::index::Index #[cfg(feature = "resolve")] pub fn resolve<'v, R: crate::Resolve>(&self, value: &'v R) -> Result<&'v R::Value, R::Error> { value.resolve(self) } - /// Attempts to resolve a mutable `Value` based on the path in this `Pointer`. + /// Attempts to resolve a mutable [`R::Value`] based on the path in this + /// `Pointer`. /// /// ## Errors - /// Returns [`R::Error`](`ResolveMut::Error`) if an error occurs while + /// Returns [`R::Error`] if an error occurs while /// resolving. /// /// The rules of such are determined by the `R`'s implementation of - /// [`ResolveMut`] but provided implementations return - /// [`ResolveError`](crate::resolve::ResolveError) if: + /// [`ResolveMut`] but provided implementations return [`ResolveError`] if: /// - The path is unreachable (e.g. a scalar is encountered prior to the end /// of the path) /// - The path is not found (e.g. a key in an object or an index in an array /// does not exist) - /// - A [`Token`](crate::Token) cannot be parsed as an array - /// [`Index`](crate::index::Index) - /// - An array [`Index`](crate::index::Index) is out of bounds + /// - A [`Token`] cannot be parsed as an array [`Index`] + /// - An array [`Index`] is out of bounds + /// + /// [`R::Value`]: `crate::resolve::ResolveMut::Value` + /// [`R::Error`]: `crate::resolve::ResolveMut::Error` + /// [`ResolveMut`]: `crate::resolve::ResolveMut` + /// [`ResolveError`]: `crate::resolve::ResolveError` + /// [`Token`]: crate::Token + /// [`Index`]: crate::index::Index + #[cfg(feature = "resolve")] pub fn resolve_mut<'v, R: crate::ResolveMut>( &self, @@ -684,7 +694,7 @@ impl<'a> IntoIterator for &'a Pointer { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -/// An owned, mutable Pointer (akin to String). +/// 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 From 6cb790337745b707cccc793e66c80dfac30afc0b Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 14:09:53 -0400 Subject: [PATCH 46/57] fixes some rustdoc errors --- src/pointer.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pointer.rs b/src/pointer.rs index 3db4009..1962af4 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -265,12 +265,13 @@ impl Pointer { /// does not exist) /// - A [`Token`] cannot be parsed as an array [`Index`] /// - An array [`Index`] is out of bounds + /// /// [`R::Value`]: `crate::resolve::Resolve::Value` /// [`R::Error`]: `crate::resolve::Resolve::Error` /// [`Resolve`]: `crate::resolve::Resolve` /// [`ResolveError`]: `crate::resolve::ResolveError` - /// [`Token`]: crate::Token - /// [`Index`]: crate::index::Index + /// [`Token`]: `crate::Token` + /// [`Index`]: `crate::index::Index` #[cfg(feature = "resolve")] pub fn resolve<'v, R: crate::Resolve>(&self, value: &'v R) -> Result<&'v R::Value, R::Error> { value.resolve(self) @@ -296,8 +297,8 @@ impl Pointer { /// [`R::Error`]: `crate::resolve::ResolveMut::Error` /// [`ResolveMut`]: `crate::resolve::ResolveMut` /// [`ResolveError`]: `crate::resolve::ResolveError` - /// [`Token`]: crate::Token - /// [`Index`]: crate::index::Index + /// [`Token`]: `crate::Token` + /// [`Index`]: `crate::index::Index` #[cfg(feature = "resolve")] pub fn resolve_mut<'v, R: crate::ResolveMut>( From 65127515eec1a4c77e725496ab60c6e07f1964a9 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 14:17:15 -0400 Subject: [PATCH 47/57] adds toml::Value to URLs --- README.md | 1 + src/lib.rs | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0f8e04a..644519b 100644 --- a/README.md +++ b/README.md @@ -155,5 +155,6 @@ dual licensed as above, without any additional terms or conditions. [`Delete`]: https://docs.rs/jsonptr/latest/jsonptr/delete/trait.Delete.html [`serde`]: https://docs.rs/serde/1.0.120/serde/index [`serde_json::Value`]: https://docs.rs/serde_json/1.0.120/serde_json/enum.Value.html +[`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html [`str`]: https://doc.rust-lang.org/std/primitive.str.html [`String`]: https://doc.rust-lang.org/std/string/struct.String.html diff --git a/src/lib.rs b/src/lib.rs index 4dbe9f0..e9196db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,22 +3,22 @@ //! .rustdoc-hidden { display: none; } //! //! -//! [`Pointer`]: crate::Pointer -//! [`PointerBuf`]: crate::PointerBuf -//! [`Token`]: crate::Token -//! [`Tokens`]: crate::Tokens -//! [`Component`]: crate::Component -//! [`Components`]: crate::Components -//! [`Resolve`]: crate::resolve::Resolve -//! [`ResolveMut`]: crate::resolve::ResolveMut -//! [`resolve`]: crate::resolve -//! [`assign`]: crate::assign -//! [delete]: crate::delete -//! [index]: crate::index -//! [str]: str -//! [String]: String -//! [serde_json::Value]: serde_json::Value -//! [toml::Value]: https://docs.rs/toml/0.8/toml/enum.Value.html +//! [`Pointer`]: `crate::Pointer` +//! [`PointerBuf`]: `crate::PointerBuf` +//! [`Token`]: `crate::Token` +//! [`Tokens`]: `crate::Tokens` +//! [`Component`]: `crate::Component` +//! [`Components`]: `crate::Components` +//! [`Resolve`]: `crate::resolve::Resolve` +//! [`ResolveMut`]: `crate::resolve::ResolveMut` +//! [`resolve`]: `crate::resolve` +//! [`assign`]: `crate::assign` +//! [`delete`]: `crate::delete` +//! [`index`]: `crate::index` +//! [`str`]: `str` +//! [`String`]: `String` +//! [`serde_json::Value`]: `serde_json::Value` +//! [`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html #![doc = include_str!("../README.md")] #![warn(missing_docs)] From 03b0a7f1809e03633208bb2013715e99be408921 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 14:18:59 -0400 Subject: [PATCH 48/57] adds Root to urls --- README.md | 2 +- src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 644519b..c59bcc2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ either `'/'` or the end of the string. [`Token`]s are lightly encoded, where [`tokens`] method of a pointer or [`Components`], returned from the [`components`] method. The difference being that [`Tokens`] iterates over each [`Token`] in the [`Pointer`], while a [`Component`] can represent the -[`Root`](Component::Root) document or a single [`Token`](Component::Token). +[`Root`] document or a single [`Token`](Component::Token). Operations [`resolve`], [`assign`] and [`delete`] are provided as traits with corresponding methods on [`Pointer`]. Implementations of each trait are provided diff --git a/src/lib.rs b/src/lib.rs index e9196db..cbe7a7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ //! [`assign`]: `crate::assign` //! [`delete`]: `crate::delete` //! [`index`]: `crate::index` +//! [`Root`]: `crate::Component::Root` //! [`str`]: `str` //! [`String`]: `String` //! [`serde_json::Value`]: `serde_json::Value` From f7629e4bd52df4aeefe8e0fffd536d9e2e1e58ce Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 16:22:34 -0400 Subject: [PATCH 49/57] Update src/resolve.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Mello <3285133+asmello@users.noreply.github.com> --- src/resolve.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resolve.rs b/src/resolve.rs index 3e0749f..25cfbac 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -11,8 +11,8 @@ //! [`resolve`](Pointer::resolve) and [`resolve_mut`](Pointer::resolve_mut) //! methods on [`Pointer`] and [`PointerBuf`](crate::PointerBuf). //! ```rust -//! use jsonptr::{Pointer, Resolve, ResolveMut}; -//! use serde_json::json; +//! # use jsonptr::{Pointer, Resolve, ResolveMut}; +//! # use serde_json::json; //! //! let ptr = Pointer::from_static("/foo/1"); //! let mut data = json!({"foo": ["bar", "baz"]}); From f16b4758c5ac549d79f439c07a61f650ae9eb63a Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 18:41:02 -0400 Subject: [PATCH 50/57] Update src/resolve.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Mello <3285133+asmello@users.noreply.github.com> --- src/resolve.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/resolve.rs b/src/resolve.rs index 25cfbac..09418f9 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -22,7 +22,6 @@ //! //! let value = data.resolve_mut(ptr).unwrap(); //! assert_eq!(value, &json!("baz")); -//! //! ``` //! //! ## Provided implementations From 340f314ca2583306eeda0886e14f9404f3b8a8b3 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 18:38:22 -0400 Subject: [PATCH 51/57] reduces links + fixes formatting of README.md --- README.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c59bcc2..c552fdc 100644 --- a/README.md +++ b/README.md @@ -13,23 +13,26 @@ JSON Pointers ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)) defines a string syntax for identifying a specific location within a JSON, or similar, document. This crate provides two types, [`Pointer`] and [`PointerBuf`] -(akin to [`str`] and [`String`]), for working with them abstractly. +(akin to [`Path`] and [`PathBuf`]), for working with them abstractly. -A [`Pointer`] is composed of zero or more [`Token`]s, single segments which +A pointer is composed of zero or more [`Token`]s, single segments which represent a field of an object or an [`index`] of an array, and are bounded by -either `'/'` or the end of the string. [`Token`]s are lightly encoded, where -`'~'` is escaped as `"~0"` and `'/'` as `"~1"`. +either `'/'` or the end of the string. `Token`s are lightly encoded, where `'~'` +is escaped as `"~0"` due to it signaling encoding and `'/'` is escaped as `"~1"` +because `'/'` separates tokens and would split the token into two otherwise. -[`Token`]s can be iterated over using either [`Tokens`], returned from the +`Token`s can be iterated over using either [`Tokens`], returned from the [`tokens`] method of a pointer or [`Components`], returned from the -[`components`] method. The difference being that [`Tokens`] iterates over each -[`Token`] in the [`Pointer`], while a [`Component`] can represent the -[`Root`] document or a single [`Token`](Component::Token). +[`components`] method. The difference being that `Tokens` iterates over each +token in the pointer, while `Components` iterates over [`Component`]s, which can +represent the root of the document or a single token along with its `offset` of +the token from within the pointer. Operations [`resolve`], [`assign`] and [`delete`] are provided as traits with -corresponding methods on [`Pointer`]. Implementations of each trait are provided -for [`serde_json::Value`] and [`toml::Value`]. All -operations are enabled by default but are gated by [feature flags](#feature-flags). +corresponding methods on pointer types. Implementations of each trait are +provided for value types of the crates [`serde_json`] and [`toml`]. All +operations are enabled by default but are gated by [feature +flags](#feature-flags). ## Usage @@ -109,6 +112,7 @@ assert_eq!(data, json!({"secret": { "universe": 34 }})); | `"delete"` | Enables the [`delete`] module and related pointer methods, providing a means to delete a value at a specific location within a document | `"resolve"` | ✓ |
+ ## License Licensed under either of @@ -118,7 +122,7 @@ Licensed under either of - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -at your option. +at your convenience. ## Contribution @@ -154,7 +158,7 @@ dual licensed as above, without any additional terms or conditions. [`Assign`]: https://docs.rs/jsonptr/latest/jsonptr/assign/trait.Assign.html [`Delete`]: https://docs.rs/jsonptr/latest/jsonptr/delete/trait.Delete.html [`serde`]: https://docs.rs/serde/1.0.120/serde/index -[`serde_json::Value`]: https://docs.rs/serde_json/1.0.120/serde_json/enum.Value.html -[`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html -[`str`]: https://doc.rust-lang.org/std/primitive.str.html -[`String`]: https://doc.rust-lang.org/std/string/struct.String.html +[`serde_json`]: https://docs.rs/serde_json/1.0.120/serde_json/enum.Value.html +[`toml`]: https://docs.rs/toml/0.8/toml/enum.Value.html +[`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html +[`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html From 702ca35d34a8f2191fd3a286f7fc362c1249ec4c Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 18:59:26 -0400 Subject: [PATCH 52/57] spelling, minor fixes --- src/index.rs | 4 ++-- src/pointer.rs | 47 ++++++++++++++++++++++++----------------------- src/token.rs | 5 +++-- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/index.rs b/src/index.rs index 286ca8f..e81acca 100644 --- a/src/index.rs +++ b/src/index.rs @@ -338,7 +338,7 @@ mod tests { } #[test] - fn testindex_try_from_string_next() { + fn index_try_from_string_next() { let index = Index::try_from(String::from("-")).unwrap(); assert_eq!(index, Index::Next); } @@ -351,7 +351,7 @@ mod tests { #[test] fn index_for_len_incl_out_of_bounds() { - assert!(Index::Num(2).for_len_incl(1).is_err()); + Index::Num(2).for_len_incl(1).unwrap_err(); } #[test] diff --git a/src/pointer.rs b/src/pointer.rs index 1962af4..0e56d88 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -183,7 +183,7 @@ impl Pointer { .into() } /// Splits the `Pointer` at the given index if the character at the index is - /// a seperator backslash (`'/'`), returning `Some((head, tail))`. Otherwise, + /// a separator backslash (`'/'`), returning `Some((head, tail))`. Otherwise, /// returns `None`. /// /// For the following JSON Pointer, the following splits are possible (0, 4, 8): @@ -930,7 +930,7 @@ const fn validate(value: &str) -> Result<&str, ParseError> { if bytes[0] != b'/' { return Err(ParseError::NoLeadingBackslash); } - let mut ptr_offset = 0; // offset within the pointer of the most recent '/' seperator + let mut ptr_offset = 0; // offset within the pointer of the most recent '/' separator let mut tok_offset = 0; // offset within the current token let bytes = value.as_bytes(); @@ -938,7 +938,7 @@ const fn validate(value: &str) -> Result<&str, ParseError> { while i < bytes.len() { match bytes[i] { b'/' => { - // backslashes ('/') seperate tokens + // backslashes ('/') separate tokens // we increment the ptr_offset to point to this character ptr_offset = i; // and reset the token offset @@ -951,7 +951,7 @@ const fn validate(value: &str) -> Result<&str, ParseError> { // the pointer is not properly encoded // // we use the pointer offset, which points to the last - // encountered seperator, as the offset of the error. + // encountered separator, as the offset of the error. // The source `InvalidEncodingError` then uses the token // offset. // @@ -975,7 +975,7 @@ const fn validate(value: &str) -> Result<&str, ParseError> { _ => {} } i += 1; - // not a seperator so we increment the token offset + // not a separator so we increment the token offset tok_offset += 1; } Ok(value) @@ -1023,25 +1023,23 @@ impl fmt::Display for ParseError { } impl ParseError { - /// Returns `true` if this error is `NoLeadingBackslash`; otherwise returns - /// `false`. + /// Returns `true` if this error is `NoLeadingBackslash` pub fn is_no_leading_backslash(&self) -> bool { matches!(self, Self::NoLeadingBackslash { .. }) } - /// Returns `true` if this error is `InvalidEncoding`; otherwise returns - /// `false`. + /// Returns `true` if this error is `InvalidEncoding` pub fn is_invalid_encoding(&self) -> bool { matches!(self, Self::InvalidEncoding { .. }) } - /// Offset of the partial pointer starting with the token which caused the - /// error. + /// Offset of the partial pointer starting with the token which caused the error. + /// /// ```text /// "/foo/invalid~tilde/invalid" /// ↑ - /// 4 /// ``` + /// /// ``` /// # use jsonptr::PointerBuf; /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); @@ -1056,6 +1054,7 @@ impl ParseError { /// Offset of the character index from within the first token of /// [`Self::pointer_offset`]) + /// /// ```text /// "/foo/invalid~tilde/invalid" /// ↑ @@ -1080,9 +1079,9 @@ impl ParseError { /// 12 /// ``` /// ``` - /// # use jsonptr::PointerBuf; + /// use jsonptr::PointerBuf; /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); - /// assert_eq!(err.pointer_offset(), 4) + /// assert_eq!(err.complete_offset(), 12) /// ``` pub fn complete_offset(&self) -> usize { self.source_offset() + self.pointer_offset() @@ -1255,10 +1254,9 @@ mod tests { } #[test] - fn as_pointer() { - let ptr = Pointer::from_static("/foo/bar"); - let ptr_buf = ptr.to_buf(); - assert_eq!(ptr_buf.as_ptr(), ptr); + fn pointerbuf_as_pointer_returns_pointer() { + let ptr = PointerBuf::parse("/foo/bar").unwrap(); + assert_eq!(ptr.as_ptr(), ptr); } #[test] @@ -1525,11 +1523,11 @@ mod tests { assert!(ptr.replace_token(1, "qux".into()).is_ok()); assert_eq!(ptr, PointerBuf::from_tokens(["foo", "qux", "baz"])); - assert!(ptr.replace_token(0, "norf".into()).is_ok()); - assert_eq!(ptr, PointerBuf::from_tokens(["norf", "qux", "baz"])); + assert!(ptr.replace_token(0, "corge".into()).is_ok()); + assert_eq!(ptr, PointerBuf::from_tokens(["corge", "qux", "baz"])); assert!(ptr.replace_token(2, "quux".into()).is_ok()); - assert_eq!(ptr, PointerBuf::from_tokens(["norf", "qux", "quux"])); + assert_eq!(ptr, PointerBuf::from_tokens(["corge", "qux", "quux"])); } #[test] @@ -1598,8 +1596,11 @@ mod tests { } #[test] + // `clippy::useless_asref` is tripping here because the `as_ref` is being + // called on the same type (`&Pointer`). This is just to ensure that the + // `as_ref` method is implemented correctly and stays that way. #[allow(clippy::useless_asref)] - fn as_ref() { + fn pointerbuf_as_ref_returns_pointer() { let ptr_str = "/foo/bar"; let ptr = Pointer::from_static(ptr_str); let ptr_buf = ptr.to_buf(); @@ -1899,7 +1900,7 @@ mod tests { assert!(a_ptr >= b_buf); assert!(a_ptr < c_buf); assert!(c_ptr > b_string); - // couldnt inline this + // couldn't inline this #[allow(clippy::nonminimal_bool)] let not = !(a_ptr > c_buf); assert!(not); diff --git a/src/token.rs b/src/token.rs index add1995..5f89f10 100644 --- a/src/token.rs +++ b/src/token.rs @@ -25,8 +25,9 @@ const SLASH_ENC: u8 = b'1'; ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -/// A `Token` is a segment of a JSON Pointer, preceded by `'/'` (`%x2F`). It can -/// represent a key in a JSON object or an index in a JSON array. +/// A `Token` is a segment of a JSON [`Pointer`](crate::Token), preceded by `'/'` (`%x2F`). +/// +/// `Token`s can represent a key in a JSON object or an index in an array. /// /// - Indexes should not contain leading zeros. /// - When dealing with arrays or path expansion for assignment, `"-"` represent From 572430b6976f69de5bc37ca429ea3383d41cdbdc Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 19:00:15 -0400 Subject: [PATCH 53/57] fixes typo --- src/resolve.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resolve.rs b/src/resolve.rs index 09418f9..5752088 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -10,6 +10,7 @@ //! [`Resolve`] and [`ResolveMut`] can be used directly or through the //! [`resolve`](Pointer::resolve) and [`resolve_mut`](Pointer::resolve_mut) //! methods on [`Pointer`] and [`PointerBuf`](crate::PointerBuf). +//! //! ```rust //! # use jsonptr::{Pointer, Resolve, ResolveMut}; //! # use serde_json::json; @@ -532,7 +533,7 @@ mod tests { } #[test] - fn reolve_error_is_not_found() { + fn resolve_error_is_not_found() { let err = ResolveError::FailedToParseIndex { offset: 0, source: ParseIndexError { From af879e4ddbdaa14969a8ff7d9633d2c83db1f518 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 19:07:13 -0400 Subject: [PATCH 54/57] reduces noise a bit more in README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c552fdc..c1951c3 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,15 @@ similar, document. This crate provides two types, [`Pointer`] and [`PointerBuf`] A pointer is composed of zero or more [`Token`]s, single segments which represent a field of an object or an [`index`] of an array, and are bounded by -either `'/'` or the end of the string. `Token`s are lightly encoded, where `'~'` +either `'/'` or the end of the string. Tokens are lightly encoded, where `'~'` is escaped as `"~0"` due to it signaling encoding and `'/'` is escaped as `"~1"` because `'/'` separates tokens and would split the token into two otherwise. -`Token`s can be iterated over using either [`Tokens`], returned from the +[`Token`]s can be iterated over using either [`Tokens`], returned from the [`tokens`] method of a pointer or [`Components`], returned from the [`components`] method. The difference being that `Tokens` iterates over each token in the pointer, while `Components` iterates over [`Component`]s, which can -represent the root of the document or a single token along with its `offset` of +represent the root of the document or a single token along with the offset of the token from within the pointer. Operations [`resolve`], [`assign`] and [`delete`] are provided as traits with From 5c05dc93b89aaa59e691da9e37610ed01c2ab646 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 20:55:28 -0400 Subject: [PATCH 55/57] removes unnecessary newlines --- src/assign.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index 7607e84..d2eb9e4 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -15,8 +15,6 @@ //! - All tokens not equal to `"0"` or `"-"` will be considered keys of an //! object. //! -//! -//! //! ## Usage //! [`Assign`] can be used directly or through the [`assign`](Pointer::assign) //! method of [`Pointer`]. From 75240e2f834f964bd159507432b74b5442a10039 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 20:57:00 -0400 Subject: [PATCH 56/57] removes # from use statements in doc comments for resolve & assign --- src/assign.rs | 4 ++-- src/resolve.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index d2eb9e4..5ed22d4 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -20,8 +20,8 @@ //! method of [`Pointer`]. //! //! ```rust -//! # use jsonptr::Pointer; -//! # use serde_json::json; +//! use jsonptr::Pointer; +//! use serde_json::json; //! let mut data = json!({"foo": "bar"}); //! let ptr = Pointer::from_static("/foo"); //! let replaced = ptr.assign(&mut data, "baz").unwrap(); diff --git a/src/resolve.rs b/src/resolve.rs index 5752088..17ee9e0 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -12,8 +12,8 @@ //! methods on [`Pointer`] and [`PointerBuf`](crate::PointerBuf). //! //! ```rust -//! # use jsonptr::{Pointer, Resolve, ResolveMut}; -//! # use serde_json::json; +//! use jsonptr::{Pointer, Resolve, ResolveMut}; +//! use serde_json::json; //! //! let ptr = Pointer::from_static("/foo/1"); //! let mut data = json!({"foo": ["bar", "baz"]}); From b90b5e31a0ad0c8c31e7b2f584851b7c267cc062 Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 8 Jul 2024 20:57:21 -0400 Subject: [PATCH 57/57] fixes spelling error --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50b57a6..114ac79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ This is a breaking release including: - [Quickcheck](https://docs.rs/quickcheck/latest/quickcheck/index.html)-based testing - New methods: `Pointer::split_front`, `Pointer::split_back`, `Pointer::parent`, `Pointer::strip_suffix` - Implemented `Display` and `Debug` for `ParseError` -- Adds `Pointer::split_at` which utilizes character offsets to split a pointer at a seperator +- Adds `Pointer::split_at` which utilizes character offsets to split a pointer at a separator - Adds specific error types `ParseError`, `ResolveError`, `AssignError` ### Changed