├── .gitignore ├── .helix └── languages.toml ├── .cargo └── config.toml ├── .github ├── codecov.yml ├── dependabot.yml ├── DOCS.md └── workflows │ ├── nostd.yml │ ├── scheduled.yml │ ├── safety.yml │ ├── check.yml │ └── test.yml ├── .taplo.toml ├── LICENSE-MIT ├── src ├── arbitrary.rs ├── component.rs ├── lib.rs ├── diagnostic.rs ├── delete.rs ├── pointer │ └── slice.rs ├── index.rs ├── token.rs └── resolve.rs ├── Cargo.toml ├── CHANGELOG.md ├── LICENSE-APACHE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | # Cargo.lock 3 | lcov.info 4 | -------------------------------------------------------------------------------- /.helix/languages.toml: -------------------------------------------------------------------------------- 1 | [language-server.rust-analyzer] 2 | config.cargo.features = "all" 3 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | cov = "llvm-cov --lcov --output-path lcov.info" 3 | 4 | [build] 5 | rustdocflags = ["--cfg", "docsrs"] 6 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference 2 | coverage: 3 | # Hold ourselves to a high bar 4 | range: 85..100 5 | round: down 6 | precision: 1 7 | status: 8 | # ref: https://docs.codecov.com/docs/commit-status 9 | project: 10 | default: 11 | # Avoid false negatives 12 | threshold: 1% 13 | 14 | # Test files aren't important for coverage 15 | ignore: 16 | - "tests" 17 | - "arbitrary.rs" 18 | - "src/arbitrary.rs" 19 | 20 | # Make comments less noisy 21 | comment: 22 | layout: "files" 23 | require_changes: true 24 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | include = ["**/*.toml", "**/Cargo.toml", "Cargo.toml"] 2 | [formatting] 3 | align_comments = true 4 | align_entries = true 5 | allowed_blank_lines = 1 6 | indent_entries = false 7 | reorder_arrays = false 8 | reorder_keys = true 9 | trailing_newline = true 10 | 11 | [[rule]] 12 | formatting.align_entries = true 13 | formatting.array_auto_expand = false 14 | formatting.reorder_arrays = true 15 | formatting.reorder_keys = true 16 | include = ["Cargo.toml", "**/Cargo.toml"] 17 | keys = [ 18 | "dependencies", 19 | "dev-dependencies", 20 | "build-dependencies", 21 | "workspace.dependencies", 22 | "workspace.dev-dependencies", 23 | "workspace.build-dependencies", 24 | ] 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: daily 11 | ignore: 12 | - dependency-name: "*" 13 | # patch and minor updates don't matter for libraries as consumers of this library build 14 | # with their own lockfile, rather than the version specified in this library's lockfile 15 | # remove this ignore rule if your package has binaries to ensure that the binaries are 16 | # built with the exact set of dependencies and those are up to date. 17 | update-types: 18 | - "version-update:semver-patch" 19 | - "version-update:semver-minor" 20 | -------------------------------------------------------------------------------- /.github/DOCS.md: -------------------------------------------------------------------------------- 1 | # Github config and workflows 2 | 3 | In this folder there is configuration for codecoverage, dependabot, and ci 4 | workflows that check the library more deeply than the default configurations. 5 | 6 | This folder can be or was merged using a --allow-unrelated-histories merge 7 | strategy from which provides a 8 | reasonably sensible base for writing your own ci on. By using this strategy 9 | the history of the CI repo is included in your repo, and future updates to 10 | the CI can be merged later. 11 | 12 | To perform this merge run: 13 | 14 | ```shell 15 | git remote add ci https://github.com/jonhoo/rust-ci-conf.git 16 | git fetch ci 17 | git merge --allow-unrelated-histories ci/main 18 | ``` 19 | 20 | An overview of the files in this project is available at: 21 | , which contains some 22 | rationale for decisions and runs through an example of solving minimal version 23 | and OpenSSL issues. 24 | -------------------------------------------------------------------------------- /.github/workflows/nostd.yml: -------------------------------------------------------------------------------- 1 | # This workflow checks whether the library is able to run without the std library (e.g., embedded). 2 | # This entire file should be removed if this crate does not support no-std. See check.yml for 3 | # information about how the concurrency cancellation and workflow triggering works 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 12 | cancel-in-progress: true 13 | name: no-std 14 | jobs: 15 | nostd: 16 | runs-on: ubuntu-latest 17 | name: ${{ matrix.target }} 18 | strategy: 19 | matrix: 20 | target: [thumbv7m-none-eabi, aarch64-unknown-none] 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | submodules: true 25 | - name: Install stable 26 | uses: dtolnay/rust-toolchain@stable 27 | - name: rustup target add ${{ matrix.target }} 28 | run: rustup target add ${{ matrix.target }} 29 | - name: cargo check 30 | run: cargo check --target ${{ matrix.target }} --no-default-features 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chance Dinkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/arbitrary.rs: -------------------------------------------------------------------------------- 1 | use crate::{PointerBuf, Token}; 2 | use alloc::{boxed::Box, string::String, vec::Vec}; 3 | use quickcheck::Arbitrary; 4 | 5 | impl Arbitrary for Token<'static> { 6 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 7 | Self::new(String::arbitrary(g)) 8 | } 9 | 10 | fn shrink(&self) -> Box> { 11 | Box::new(self.decoded().into_owned().shrink().map(Self::new)) 12 | } 13 | } 14 | 15 | impl Arbitrary for PointerBuf { 16 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 17 | let size = usize::arbitrary(g) % g.size(); 18 | Self::from_tokens((0..size).map(|_| Token::arbitrary(g)).collect::>()) 19 | } 20 | 21 | fn shrink(&self) -> Box> { 22 | let tokens: Vec<_> = self.tokens().map(Token::into_owned).collect(); 23 | Box::new((0..self.count()).map(move |i| { 24 | let subset: Vec<_> = tokens 25 | .iter() 26 | .enumerate() 27 | .filter_map(|(j, t)| (i != j).then_some(t.clone())) 28 | .collect(); 29 | Self::from_tokens(subset) 30 | })) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "chance dinkins", 4 | "André Sá de Mello ", 5 | "Oliver Wangler ", 6 | ] 7 | description = "Data structures and logic for resolving, assigning, and deleting by JSON Pointers (RFC 6901)" 8 | documentation = "https://docs.rs/jsonptr" 9 | edition = "2021" 10 | homepage = "https://github.com/chanced/jsonptr" 11 | keywords = ["json-pointer", "rfc-6901", "6901"] 12 | license = "MIT OR Apache-2.0" 13 | name = "jsonptr" 14 | repository = "https://github.com/chanced/jsonptr" 15 | rust-version = "1.79.0" 16 | version = "0.7.1" 17 | 18 | [dependencies] 19 | miette = { version = "7.4.0", optional = true, features = ["fancy"] } 20 | serde = { version = "1.0.219", optional = true, default-features = false, features = ["alloc"] } 21 | serde_json = { version = "1.0.140", optional = true, default-features = false, features = ["alloc"] } 22 | toml = { version = "0.9", optional = true } 23 | 24 | [dev-dependencies] 25 | quickcheck = "1.0.3" 26 | quickcheck_macros = "1.0.0" 27 | 28 | # quickcheck-macros requires a later version than it declares 29 | # so we use this hack to make `-Zminimal-versions` work 30 | [target.'cfg(any())'.dependencies] 31 | syn = { version = "1.0.109", optional = true } 32 | 33 | [features] 34 | assign = [] 35 | default = ["std", "serde", "json", "resolve", "assign", "delete"] 36 | delete = ["resolve"] 37 | json = ["dep:serde_json", "serde"] 38 | miette = ["dep:miette", "std"] 39 | resolve = [] 40 | std = ["serde/std", "serde_json?/std"] 41 | toml = ["dep:toml", "serde", "std"] 42 | -------------------------------------------------------------------------------- /src/component.rs: -------------------------------------------------------------------------------- 1 | use crate::{Pointer, Token, Tokens}; 2 | 3 | /// A single [`Token`] or the root of a JSON Pointer 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 5 | pub enum Component<'t> { 6 | /// The document root 7 | Root, 8 | /// A segment of a JSON Pointer 9 | Token(Token<'t>), 10 | } 11 | impl<'t> From> for Component<'t> { 12 | fn from(token: Token<'t>) -> Self { 13 | Self::Token(token) 14 | } 15 | } 16 | 17 | /// An iterator over the [`Component`]s of a JSON Pointer 18 | #[derive(Debug)] 19 | pub struct Components<'t> { 20 | tokens: Tokens<'t>, 21 | sent_root: bool, 22 | } 23 | 24 | impl<'t> Iterator for Components<'t> { 25 | type Item = Component<'t>; 26 | fn next(&mut self) -> Option { 27 | if !self.sent_root { 28 | self.sent_root = true; 29 | return Some(Component::Root); 30 | } 31 | self.tokens.next().map(Component::Token) 32 | } 33 | } 34 | 35 | impl<'t> From<&'t Pointer> for Components<'t> { 36 | fn from(pointer: &'t Pointer) -> Self { 37 | Self { 38 | sent_root: false, 39 | tokens: pointer.tokens(), 40 | } 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | 48 | #[test] 49 | fn components() { 50 | let ptr = Pointer::from_static(""); 51 | let components: Vec<_> = Components::from(ptr).collect(); 52 | assert_eq!(components, vec![Component::Root]); 53 | 54 | let ptr = Pointer::from_static("/foo"); 55 | let components = ptr.components().collect::>(); 56 | assert_eq!( 57 | components, 58 | vec![Component::Root, Component::Token("foo".into())] 59 | ); 60 | 61 | let ptr = Pointer::from_static("/foo/bar/-/0/baz"); 62 | let components = ptr.components().collect::>(); 63 | assert_eq!( 64 | components, 65 | vec![ 66 | Component::Root, 67 | Component::from(Token::from("foo")), 68 | Component::Token("bar".into()), 69 | Component::Token("-".into()), 70 | Component::Token("0".into()), 71 | Component::Token("baz".into()) 72 | ] 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yml: -------------------------------------------------------------------------------- 1 | # Run scheduled (rolling) jobs on a nightly basis, as your crate may break independently of any 2 | # given PR. E.g., updates to rust nightly and updates to this crates dependencies. See check.yml for 3 | # information about how the concurrency cancellation and workflow triggering works 4 | permissions: 5 | contents: read 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | schedule: 11 | - cron: '7 7 * * *' 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | name: rolling 16 | jobs: 17 | # https://twitter.com/mycoliza/status/1571295690063753218 18 | nightly: 19 | runs-on: ubuntu-latest 20 | name: ubuntu / nightly 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | submodules: true 25 | - name: Install nightly 26 | uses: dtolnay/rust-toolchain@nightly 27 | - name: cargo generate-lockfile 28 | if: hashFiles('Cargo.lock') == '' 29 | run: cargo generate-lockfile 30 | - name: cargo test --locked 31 | run: cargo test --locked --all-features --all-targets 32 | # https://twitter.com/alcuadrado/status/1571291687837732873 33 | update: 34 | # This action checks that updating the dependencies of this crate to the latest available that 35 | # satisfy the versions in Cargo.toml does not break this crate. This is important as consumers 36 | # of this crate will generally use the latest available crates. This is subject to the standard 37 | # Cargo semver rules (i.e cargo does not update to a new major version unless explicitly told 38 | # to). 39 | runs-on: ubuntu-latest 40 | name: ubuntu / beta / updated 41 | # There's no point running this if no Cargo.lock was checked in in the first place, since we'd 42 | # just redo what happened in the regular test job. Unfortunately, hashFiles only works in if on 43 | # steps, so we repeat it. 44 | steps: 45 | - uses: actions/checkout@v4 46 | with: 47 | submodules: true 48 | - name: Install beta 49 | if: hashFiles('Cargo.lock') != '' 50 | uses: dtolnay/rust-toolchain@beta 51 | - name: cargo update 52 | if: hashFiles('Cargo.lock') != '' 53 | run: cargo update 54 | - name: cargo test 55 | if: hashFiles('Cargo.lock') != '' 56 | run: cargo test --locked --all-features --all-targets 57 | env: 58 | RUSTFLAGS: -D deprecated 59 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // rustdoc + README hack: https://linebender.org/blog/doc-include 2 | //! 3 | //! [`Pointer`]: https://docs.rs/jsonptr/latest/jsonptr/struct.Pointer.html 4 | //! [`Pointer::tokens`]: crate::Pointer::tokens 5 | //! [`Pointer::components`]: crate::Pointer::components 6 | //! [`Pointer::parse`]: crate::Pointer::parse 7 | //! [`Pointer::resolve`]: crate::Pointer::resolve 8 | //! [`Pointer::resolve_mut`]: crate::Pointer::resolve_mut 9 | //! [`Pointer::assign`]: crate::Pointer::assign 10 | //! [`Pointer::delete`]: crate::Pointer::delete 11 | //! [`PointerBuf::parse`]: crate::PointerBuf::parse 12 | //! [`PointerBuf`]: crate::PointerBuf 13 | //! [`from_tokens`]: crate::PointerBuf::from_tokens 14 | //! [`Token`]: crate::Token 15 | //! [`Tokens`]: crate::Tokens 16 | //! [`Components`]: crate::Components 17 | //! [`Component`]: crate::Component 18 | //! [`index`]: crate::index 19 | //! [`tokens`]: crate::Pointer::tokens 20 | //! [`components`]: crate::Pointer::components 21 | //! [`resolve`]: crate::resolve 22 | //! [`assign`]: crate::asign 23 | //! [`delete`]: crate::delete 24 | //! [`Resolve`]: crate::resolve::Resolve 25 | //! [`ResolveMut`]: crate::resolve::ResolveMut 26 | //! [`Assign`]: crate::assign::Assign 27 | //! [`Delete`]: crate::delete::Delete 28 | //! [`serde`]: https://docs.rs/serde/1.0/serde/index 29 | //! [`serde_json`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html 30 | //! [`serde_json::Value`]: https://docs.rs/serde_json/1.0/serde_json/enum.Value.html 31 | //! [`toml`]: https://docs.rs/toml/0.8/toml/enum.Value.html 32 | //! [`toml::Value`]: https://docs.rs/toml/0.8/toml/enum.Value.html 33 | //! [`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html 34 | //! [`PathBuf`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html 35 | 36 | #![doc = include_str!("../README.md")] 37 | #![warn(missing_docs)] 38 | #![deny(clippy::all, clippy::pedantic)] 39 | #![cfg_attr(not(feature = "std"), no_std)] 40 | #![allow( 41 | clippy::module_name_repetitions, 42 | clippy::into_iter_without_iter, 43 | clippy::needless_pass_by_value, 44 | clippy::expect_fun_call, 45 | clippy::must_use_candidate, 46 | clippy::similar_names 47 | )] 48 | 49 | #[cfg_attr(not(feature = "std"), macro_use)] 50 | extern crate alloc; 51 | 52 | #[cfg(feature = "assign")] 53 | pub mod assign; 54 | #[cfg(feature = "assign")] 55 | pub use assign::Assign; 56 | 57 | #[cfg(feature = "delete")] 58 | pub mod delete; 59 | #[cfg(feature = "delete")] 60 | pub use delete::Delete; 61 | 62 | #[cfg(feature = "resolve")] 63 | pub mod resolve; 64 | #[cfg(feature = "resolve")] 65 | pub use resolve::{Resolve, ResolveMut}; 66 | 67 | pub mod diagnostic; 68 | pub use diagnostic::{Diagnose, Report}; 69 | 70 | mod pointer; 71 | pub use pointer::{ParseError, Pointer, PointerBuf, RichParseError}; 72 | 73 | mod token; 74 | pub use token::{EncodingError, InvalidEncoding, Token, Tokens}; 75 | 76 | #[allow(deprecated)] 77 | pub use token::InvalidEncodingError; 78 | 79 | pub mod index; 80 | 81 | mod component; 82 | pub use component::{Component, Components}; 83 | 84 | #[cfg(test)] 85 | mod arbitrary; 86 | -------------------------------------------------------------------------------- /.github/workflows/safety.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs checks for unsafe code. In crates that don't have any unsafe code, this can be 2 | # removed. Runs: 3 | # - miri - detects undefined behavior and memory leaks 4 | # - address sanitizer - detects memory errors 5 | # - leak sanitizer - detects memory leaks 6 | # See check.yml for information about how the concurrency cancellation and workflow triggering works 7 | permissions: 8 | contents: read 9 | on: 10 | push: 11 | branches: [main] 12 | pull_request: 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 15 | cancel-in-progress: true 16 | name: safety 17 | jobs: 18 | sanitizers: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | submodules: true 24 | - name: Install nightly 25 | uses: dtolnay/rust-toolchain@nightly 26 | - run: | 27 | # to get the symbolizer for debug symbol resolution 28 | sudo apt install llvm 29 | # to fix buggy leak analyzer: 30 | # https://github.com/japaric/rust-san#unrealiable-leaksanitizer 31 | # ensure there's a profile.dev section 32 | if ! grep -qE '^[ \t]*[profile.dev]' Cargo.toml; then 33 | echo >> Cargo.toml 34 | echo '[profile.dev]' >> Cargo.toml 35 | fi 36 | # remove pre-existing opt-levels in profile.dev 37 | sed -i '/^\s*\[profile.dev\]/,/^\s*\[/ {/^\s*opt-level/d}' Cargo.toml 38 | # now set opt-level to 1 39 | sed -i '/^\s*\[profile.dev\]/a opt-level = 1' Cargo.toml 40 | cat Cargo.toml 41 | name: Enable debug symbols 42 | - name: cargo test -Zsanitizer=address 43 | # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945 44 | run: cargo test --lib --tests --all-features --target x86_64-unknown-linux-gnu 45 | env: 46 | ASAN_OPTIONS: "detect_odr_violation=0:detect_leaks=0" 47 | RUSTFLAGS: "-Z sanitizer=address" 48 | - name: cargo test -Zsanitizer=leak 49 | if: always() 50 | run: cargo test --all-features --target x86_64-unknown-linux-gnu 51 | env: 52 | LSAN_OPTIONS: "suppressions=lsan-suppressions.txt" 53 | RUSTFLAGS: "-Z sanitizer=leak" 54 | miri: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v4 58 | with: 59 | submodules: true 60 | - run: | 61 | echo "NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" >> $GITHUB_ENV 62 | - name: Install ${{ env.NIGHTLY }} 63 | uses: dtolnay/rust-toolchain@master 64 | with: 65 | toolchain: ${{ env.NIGHTLY }} 66 | components: miri 67 | - name: cargo miri test 68 | run: cargo miri test -- --skip pointer::tests::qc 69 | env: 70 | MIRIFLAGS: "" 71 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | # This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs 2 | # several checks: 3 | # - fmt: checks that the code is formatted according to rustfmt 4 | # - clippy: checks that the code does not contain any clippy warnings 5 | # - doc: checks that the code can be documented without errors 6 | # - hack: check combinations of feature flags 7 | # - msrv: check that the msrv specified in the crate is correct 8 | permissions: 9 | contents: read 10 | # This configuration allows maintainers of this repo to create a branch and pull request based on 11 | # the new branch. Restricting the push trigger to the main branch ensures that the PR only gets 12 | # built once. 13 | on: 14 | push: 15 | branches: [main] 16 | pull_request: 17 | # If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that 18 | # we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 21 | cancel-in-progress: true 22 | name: check 23 | jobs: 24 | fmt: 25 | runs-on: ubuntu-latest 26 | name: stable / fmt 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | submodules: true 31 | - name: Install stable 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | components: rustfmt 35 | - name: cargo fmt --check 36 | run: cargo fmt --check 37 | clippy: 38 | runs-on: ubuntu-latest 39 | name: ${{ matrix.toolchain }} / clippy 40 | permissions: 41 | contents: read 42 | checks: write 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | # Get early warning of new lints which are regularly introduced in beta channels. 47 | toolchain: [stable, beta] 48 | steps: 49 | - uses: actions/checkout@v4 50 | with: 51 | submodules: true 52 | - name: Install ${{ matrix.toolchain }} 53 | uses: dtolnay/rust-toolchain@master 54 | with: 55 | toolchain: ${{ matrix.toolchain }} 56 | components: clippy 57 | - name: cargo clippy 58 | uses: giraffate/clippy-action@v1 59 | with: 60 | reporter: "github-pr-check" 61 | github_token: ${{ secrets.GITHUB_TOKEN }} 62 | semver: 63 | runs-on: ubuntu-latest 64 | name: semver 65 | steps: 66 | - uses: actions/checkout@v4 67 | with: 68 | submodules: true 69 | - name: Install stable 70 | uses: dtolnay/rust-toolchain@stable 71 | with: 72 | components: rustfmt 73 | - name: cargo-semver-checks 74 | uses: obi1kenobi/cargo-semver-checks-action@v2 75 | doc: 76 | # run docs generation on nightly rather than stable. This enables features like 77 | # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an 78 | # API be documented as only available in some specific platforms. 79 | runs-on: ubuntu-latest 80 | name: nightly / doc 81 | steps: 82 | - uses: actions/checkout@v4 83 | with: 84 | submodules: true 85 | - name: Install nightly 86 | uses: dtolnay/rust-toolchain@nightly 87 | - name: cargo doc 88 | run: cargo doc --no-deps --all-features 89 | env: 90 | RUSTDOCFLAGS: --cfg docsrs 91 | hack: 92 | # cargo-hack checks combinations of feature flags to ensure that features are all additive 93 | # which is required for feature unification 94 | runs-on: ubuntu-latest 95 | name: ubuntu / stable / features 96 | steps: 97 | - uses: actions/checkout@v4 98 | with: 99 | submodules: true 100 | - name: Install stable 101 | uses: dtolnay/rust-toolchain@stable 102 | - name: cargo install cargo-hack 103 | uses: taiki-e/install-action@cargo-hack 104 | # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 105 | # --feature-powerset runs for every combination of features 106 | - name: cargo hack 107 | run: cargo hack --feature-powerset check 108 | msrv: 109 | # check that we can build using the minimal rust version that is specified by this crate 110 | runs-on: ubuntu-latest 111 | # we use a matrix here just because env can't be used in job names 112 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability 113 | # TODO: would be nice to just parse this from the manifest 114 | strategy: 115 | matrix: 116 | msrv: ["1.79"] 117 | name: ubuntu / ${{ matrix.msrv }} 118 | steps: 119 | - uses: actions/checkout@v4 120 | with: 121 | submodules: true 122 | - name: Install ${{ matrix.msrv }} 123 | uses: dtolnay/rust-toolchain@master 124 | with: 125 | toolchain: ${{ matrix.msrv }} 126 | - name: cargo +${{ matrix.msrv }} check 127 | run: cargo check 128 | toml-fmt: 129 | runs-on: ubuntu-latest 130 | name: toml / fmt 131 | steps: 132 | - uses: actions/checkout@v4 133 | with: 134 | submodules: true 135 | - name: Install taplo 136 | uses: docker://docker.io/tamasfe/taplo:latest 137 | with: 138 | args: fmt --check --diff 139 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This is the main CI workflow that runs the test suite on all pushes to main and all pull requests. 2 | # It runs the following jobs: 3 | # - required: runs the test suite on ubuntu with stable and beta rust toolchains 4 | # - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the 5 | # requirements of this crate, and its dependencies 6 | # - os-check: runs the test suite on mac and windows 7 | # - coverage: runs the test suite and collects coverage information 8 | # See check.yml for information about how the concurrency cancellation and workflow triggering works 9 | permissions: 10 | contents: read 11 | on: 12 | push: 13 | branches: [main] 14 | pull_request: 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 17 | cancel-in-progress: true 18 | name: test 19 | jobs: 20 | required: 21 | runs-on: ubuntu-latest 22 | name: ubuntu / ${{ matrix.toolchain }} 23 | strategy: 24 | matrix: 25 | # run on stable and beta to ensure that tests won't break on the next version of the rust 26 | # toolchain 27 | toolchain: [stable, beta] 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | submodules: true 32 | - name: Install ${{ matrix.toolchain }} 33 | uses: dtolnay/rust-toolchain@master 34 | with: 35 | toolchain: ${{ matrix.toolchain }} 36 | - name: cargo generate-lockfile 37 | # enable this ci template to run regardless of whether the lockfile is checked in or not 38 | if: hashFiles('Cargo.lock') == '' 39 | run: cargo generate-lockfile 40 | # https://twitter.com/jonhoo/status/1571290371124260865 41 | - name: cargo test --locked 42 | run: cargo test --locked --all-features --all-targets 43 | # https://github.com/rust-lang/cargo/issues/6669 44 | - name: cargo test --doc 45 | run: cargo test --locked --all-features --doc 46 | minimal: 47 | # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure 48 | # that this crate is compatible with the minimal version that this crate and its dependencies 49 | # require. This will pickup issues where this create relies on functionality that was introduced 50 | # later than the actual version specified (e.g., when we choose just a major version, but a 51 | # method was added after this version). 52 | # 53 | # This particular check can be difficult to get to succeed as often transitive dependencies may 54 | # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There 55 | # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for 56 | # direct dependencies of this crate, while selecting the maximal versions for the transitive 57 | # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase 58 | # the minimal dependency, which you do with e.g.: 59 | # ```toml 60 | # # for minimal-versions 61 | # [target.'cfg(any())'.dependencies] 62 | # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions 63 | # ``` 64 | # The optional = true is necessary in case that dependency isn't otherwise transitively required 65 | # by your library, and the target bit is so that this dependency edge never actually affects 66 | # Cargo build order. See also 67 | # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49. 68 | # This action is run on ubuntu with the stable toolchain, as it is not expected to fail 69 | runs-on: ubuntu-latest 70 | name: ubuntu / stable / minimal-versions 71 | steps: 72 | - uses: actions/checkout@v4 73 | with: 74 | submodules: true 75 | - name: Install stable 76 | uses: dtolnay/rust-toolchain@stable 77 | - name: Install nightly for -Zminimal-versions 78 | uses: dtolnay/rust-toolchain@nightly 79 | - name: rustup default stable 80 | run: rustup default stable 81 | - name: cargo update -Zminimal-versions 82 | run: cargo +nightly update -Zminimal-versions 83 | - name: cargo test 84 | run: cargo test --locked --all-features --all-targets 85 | os-check: 86 | # run cargo test on mac and windows 87 | runs-on: ${{ matrix.os }} 88 | name: ${{ matrix.os }} / stable 89 | strategy: 90 | fail-fast: false 91 | matrix: 92 | os: [macos-latest, windows-latest] 93 | steps: 94 | # if your project needs OpenSSL, uncomment this to fix Windows builds. 95 | # it's commented out by default as the install command takes 5-10m. 96 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append 97 | # if: runner.os == 'Windows' 98 | # - run: vcpkg install openssl:x64-windows-static-md 99 | # if: runner.os == 'Windows' 100 | - uses: actions/checkout@v4 101 | with: 102 | submodules: true 103 | - name: Install stable 104 | uses: dtolnay/rust-toolchain@stable 105 | - name: cargo generate-lockfile 106 | if: hashFiles('Cargo.lock') == '' 107 | run: cargo generate-lockfile 108 | - name: cargo test 109 | run: cargo test --locked --all-features --all-targets 110 | coverage: 111 | # use llvm-cov to build and collect coverage and outputs in a format that 112 | # is compatible with codecov.io 113 | # 114 | # note that codecov as of v4 requires that CODECOV_TOKEN from 115 | # 116 | # https://app.codecov.io/gh///settings 117 | # 118 | # is set in two places on your repo: 119 | # 120 | # - https://github.com/jonhoo/guardian/settings/secrets/actions 121 | # - https://github.com/jonhoo/guardian/settings/secrets/dependabot 122 | # 123 | # (the former is needed for codecov uploads to work with Dependabot PRs) 124 | # 125 | # PRs coming from forks of your repo will not have access to the token, but 126 | # for those, codecov allows uploading coverage reports without a token. 127 | # it's all a little weird and inconvenient. see 128 | # 129 | # https://github.com/codecov/feedback/issues/112 130 | # 131 | # for lots of more discussion 132 | runs-on: ubuntu-latest 133 | name: ubuntu / stable / coverage 134 | steps: 135 | - uses: actions/checkout@v4 136 | with: 137 | submodules: true 138 | - name: Install stable 139 | uses: dtolnay/rust-toolchain@stable 140 | with: 141 | components: llvm-tools-preview 142 | - name: cargo install cargo-llvm-cov 143 | uses: taiki-e/install-action@cargo-llvm-cov 144 | - name: cargo generate-lockfile 145 | if: hashFiles('Cargo.lock') == '' 146 | run: cargo generate-lockfile 147 | - name: cargo llvm-cov 148 | run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info 149 | - name: Record Rust version 150 | run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" 151 | - name: Upload to codecov.io 152 | uses: codecov/codecov-action@v4 153 | with: 154 | fail_ci_if_error: true 155 | token: ${{ secrets.CODECOV_TOKEN }} 156 | env_vars: OS,RUST 157 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ### Deprecated 11 | 12 | - Deprecated `ResolveError` name. 13 | 14 | ### Fixed 15 | 16 | - `EncodingError` and `ParseIndexError` now implement `Diagnostic`, which 17 | unifies the API for errors originating from parse-like operations. 18 | - Fixes returning an incorrect error type when parsing a `Token` that 19 | terminates with `~`. This now correctly classifies the error as a `Tilde` 20 | error. 21 | - Disabled default features for the `serde` dependency, which enables using 22 | this library in no-std environments even with the `serde` feature enabled. 23 | 24 | ### Removed 25 | 26 | - Some methods were removed from `InvalidCharacterError`, as that type no 27 | longer holds a copy of the input string internally. This is a breaking 28 | change. To access equivalent functionality, use the `Diagnostic` API 29 | integration. 30 | 31 | ### Changed 32 | - `Token::from_encoded` now accepts owned or borrowed strings (any type that 33 | implements `Into>`). 34 | - Sealed the `Diagnose` trait. 35 | - Implementation of the `Default` trait for `Pointer` now doesn't constrain the lifetime. 36 | 37 | ## [0.7.1] 2025-02-16 38 | 39 | ### Changed 40 | 41 | - Removes accidentally enabled default features `"miette"` and `"toml"` 42 | 43 | ## [0.7.0] 2025-02-13 44 | 45 | ### Added 46 | 47 | - Adds method `into_buf` for `Box` and `impl From for Box`. 48 | - Adds unsafe associated methods `Pointer::new_unchecked` and `PointerBuf::new_unchecked` for 49 | external zero-cost construction. 50 | - Adds `Pointer::starts_with` and `Pointer::ends_with` for prefix and suffix matching. 51 | - Adds new `ParseIndexError` variant to express the presence non-digit characters in the token. 52 | - Adds `Token::is_next` for checking if a token represents the `-` character. 53 | - Adds `InvalidEncoding` to represent the two possible encoding errors when decoding a token. 54 | - Adds `diagnotic::Diagnostic` trait to facilitate error reporting and 55 | `miette` integration. All errors intended for usage with `assign::Assign` or 56 | `resolve::Resolve` must implement this trait. 57 | - Adds `diagnostic::Report` to capture the input for `PointerBuf::parse` 58 | and to facilitate `miette` integration for all errors. 59 | - Adds `"miette"` feature flag to enable `miette` integration for error reporting. 60 | 61 | ### Changed 62 | 63 | - `Pointer::get` now accepts ranges and can produce `Pointer` segments as output (similar to 64 | `slice::get`). 65 | - Bumps minimum Rust version to 1.79. 66 | - `PointerBuf::parse` now returns `RichParseError`, an alias to 67 | `Report` which contains the allocated string as well as the 68 | error. Use `Report::original` for matches or `Report:: 69 | - Renames `ParseError::NoLeadingBackslash` to `ParseError::NoLeadingSlash` 70 | (sorry for the churn, I spaced hard - @chanced). 71 | - Adds field `position` to variants of `resolve::Error` and `assign::Error` to indicate the 72 | token index of where the error occurred. 73 | - Renames `ParseError::is_no_leading_backslash` to `ParseError::is_no_leading_slash`. 74 | - Renames `assign::AssignError` to `assign::Error` 75 | - Renames `resolve::ResolveError` to `resolve::Error` 76 | - Renames `InvalidEncodingError` to `EncodingError` 77 | 78 | ### Fixed 79 | 80 | - Make validation of array indices conform to RFC 6901 in the presence of non-digit characters. 81 | 82 | ### Deprecated 83 | 84 | - `ParseError::is_no_leading_backslash` renamed to `ParseError::is_no_leading_slash`. 85 | - `assign::AssignError` renamed to `assign::Error` 86 | - `resolve::ResolveError` renamed to `resolve::Error` 87 | - `InvalidEncodingError` renamed to `EncodingError` 88 | 89 | ## [0.6.2] 2024-09-30 90 | 91 | ### Added 92 | 93 | - Adds methods `len` and `is_empty` to `Pointer` 94 | 95 | ## [0.6.1] 2024-09-26 96 | 97 | ## Added 98 | 99 | - Adds fluid methods `with_trailing_token`, `with_leading_token`, `concat` to `Pointer`. 100 | 101 | ## [0.6.0] - 2024-08-06 102 | 103 | ### Fixed 104 | 105 | - `Token::to_index` now fails if the token contains leading zeros, as mandated by the RFC. 106 | 107 | ### Changed 108 | 109 | - `ParseIndexError` is now an enum to reflect the new failure mode when parsing indices. 110 | 111 | ## [0.5.1] 112 | 113 | ### Changed 114 | 115 | - README tweak. 116 | 117 | ## [0.5.0] 118 | 119 | This is a breaking release including: 120 | 121 | - [#30](https://github.com/chanced/jsonptr/pull/30) and [#37](https://github.com/chanced/jsonptr/pull/37) by [@asmello](https://github.com/asmello) 122 | - [#41](https://github.com/chanced/jsonptr/pull/41) by [@chanced](https://github.com/chanced) & [@asmello](https://github.com/asmello) 123 | 124 | ### Added 125 | 126 | - New slice type `Pointer` that enables zero-copy usage patterns 127 | - New const constructor `const fn Pointer::from_static` for compile-time allocated `Pointer`s 128 | - Zero-allocation `Pointer::root` singleton pointer 129 | - [Quickcheck](https://docs.rs/quickcheck/latest/quickcheck/index.html)-based testing 130 | - New methods: `Pointer::split_front`, `Pointer::split_back`, `Pointer::parent`, `Pointer::strip_suffix` 131 | - Implemented `Display` and `Debug` for `ParseError` 132 | - Adds `Pointer::split_at` which utilizes character offsets to split a pointer at a separator 133 | - Adds specific error types `ParseError`, `ResolveError`, `AssignError` 134 | 135 | ### Changed 136 | 137 | - JSON Pointers with leading `"#"` are no longer accepted. Previously, the erroneous leading hashtag was allowed during parsing but discarded. 138 | - `Assign`, `Resolve`, `ResolveMut`, `Delete` all now use associated types `Value` and `Error`, allowing for more impls other than JSON 139 | - Debug implementation now preserves type information (e.g. prints `PathBuf("/foo/bar")` instead of `"/foo/bar"`) - `Display` remains the same 140 | - Original `Pointer` type renamed to `PointerBuf` 141 | - Error types now use character `offset` indexing instead of owned copies of `Pointer` and `Token`. 142 | - `Pointer::root` is now `PointerBuf::new` 143 | - `Pointer::new` is now `PointerBuf::from_tokens` (and takes an `IntoIterator` argument - arrays still work) 144 | - `Pointer::union` is now `PointerBuf::intersection` 145 | - `Token` type has been simplified and is now by default a borrowed type (use `Token::to_owned` or `Token::into_owned` to make it owned) 146 | - `Assign::assign` now returns `Result, AssignError>`, where `Option` is the replaced value 147 | 148 | ### Fixed 149 | 150 | - Fixes [#28](https://github.com/chanced/jsonptr/pull/28): `Pointer::union` is misleadingly named 151 | 152 | ### Removed 153 | 154 | - Removes `Assignment` 155 | - Removes `MaybePointer` 156 | - Removes `Error` 157 | - Removes `impl Deref` from `Pointer` 158 | - Removes optional dependencies of `url`, `fluent-uri` and `uniresid` as well as the `TryFrom` implementations for their respective types 159 | - Removed `Token::as_key` and `Token::as_str` - use `Token::decoded().as_ref()` to achieve the same effect 160 | - Several redundant or error-prone trait implementations were removed from `Token` 161 | 162 | ## [0.4.7] 2024-03-18 163 | 164 | - Fixes issue with `pop_front` on a token with an empty string leaving the pointer in an invalid state. #25 by [@wngr](https://github.com/wngr) 165 | - Fixes issue with `pop_back` on a token with an empty string. #26 by [@asmello](https://github.com/asmello) 166 | 167 | ## [0.4.6] 2024-03-24 168 | 169 | - Fixes `Pointer::last` panicking for empty/root pointers #23 by [@wngr](https://github.com/wngr) 170 | 171 | ## [0.4.5] 2024-02-23 172 | 173 | ### Fixed 174 | 175 | - Fixes issue with `Pointer::push_back` that does not allow for empty strings 176 | to be appended as tokens. #21 fixed by [@wngr](https://github.com/wngr) 177 | 178 | ## [0.4.3] 2023-08-20 179 | 180 | ### Added 181 | 182 | - Adds `parse` method to `Pointer` which calls the currently existing `FromStr` 183 | impl 184 | 185 | ## [0.4.2] 2023-06-23 186 | 187 | ### Added 188 | 189 | - implements `IntoIterator` for `&Pointer` 190 | 191 | ## [0.4.1] 2023-06-21 192 | 193 | ### Added 194 | 195 | - implements `Borrow<[u8]>` and `AsRef<[u8]>` for `Pointer` 196 | 197 | ## [0.4.0] 2023-05-31 198 | 199 | ### Added 200 | 201 | - Adds `CHANGELOG.md` which will be better upkept moving forward. 202 | - Adds `MaybePointer` to assist with deserialization which should not fail fast. 203 | 204 | ### Changed 205 | 206 | - `Pointer::new` now accepts a generic list, so `&["example"]` can be replaced by `["example"]`. For untyped, empty slices (i.e. `Pointer::new(&[])`), use `Pointer::default()`. 207 | - `std` is now enabled by default. 208 | 209 | ### Removed 210 | 211 | - Removes optional `MalformedPointerError` from `Pointer`. 212 | 213 | ## [0.3.6] 2023-05-23 214 | 215 | ### Changed 216 | 217 | - Adds quotes around `Pointer` debug output (#11) 218 | 219 | ### Fixed 220 | 221 | - Adds missing `impl std::error::Error` for `Error`, `NotFoundError`, `MalformedError` 222 | - Fixes build for `std` feature flag 223 | 224 | ## [0.3.4] 2023-05-11 225 | 226 | ### Added 227 | 228 | - Adds feature flag `fluent-uri` for `From` impl (#3) 229 | 230 | ## [0.2.0] 2023-02-24 231 | 232 | ### Changed 233 | 234 | - `std` is now optional 235 | - Adds feature flags `"uniresid"`, `"url"` to enable implementing `From`, `From` (respectively). 236 | 237 | ### Removed 238 | 239 | - Removes `Cargo.lock` 240 | - Makes `uniresid` and `uri` optional 241 | 242 | ## [0.1.0] - 2022-06-12 243 | 244 | ### Fixed 245 | 246 | - Fixes root pointer representation `""` rather than the erroneous `"/"` 247 | - Fixes an issue where encoded tokens were not being resolved properly 248 | -------------------------------------------------------------------------------- /src/diagnostic.rs: -------------------------------------------------------------------------------- 1 | //! Error reporting data structures and miette integration. 2 | //! 3 | 4 | use alloc::{boxed::Box, string::String}; 5 | use core::{fmt, ops::Deref}; 6 | 7 | /// Implemented by errors which can be converted into a [`Report`]. 8 | pub trait Diagnostic: Sized { 9 | /// The value which caused the error. 10 | type Subject: Deref; 11 | 12 | /// Combine the error with its subject to generate a [`Report`]. 13 | fn into_report(self, subject: impl Into) -> Report { 14 | Report::new(self, subject.into()) 15 | } 16 | 17 | /// The docs.rs URL for this error 18 | fn url() -> &'static str; 19 | 20 | /// Returns the label for the given [`Subject`] if applicable. 21 | fn labels(&self, subject: &Self::Subject) -> Option>>; 22 | } 23 | 24 | /// A label for a span within a JSON Pointer or malformed string. 25 | #[derive(Debug, PartialEq, Eq, Clone)] 26 | pub struct Label { 27 | text: String, 28 | offset: usize, 29 | len: usize, 30 | } 31 | 32 | impl Label { 33 | /// Creates a new instance of a [`Label`] from its parts 34 | // NOTE: this is deliberately public, so that users can use 35 | // the `Assign` and `Resolve` traits with custom types and errors, 36 | // and then implement `Diagnostic` for those errors. 37 | pub fn new(text: String, offset: usize, len: usize) -> Self { 38 | Self { text, offset, len } 39 | } 40 | } 41 | 42 | #[cfg(feature = "miette")] 43 | impl From