diff --git a/.github/DOCS.md b/.github/DOCS.md new file mode 100644 index 0000000..e932784 --- /dev/null +++ b/.github/DOCS.md @@ -0,0 +1,23 @@ +# Github config and workflows + +In this folder there is configuration for codecoverage, dependabot, and ci +workflows that check the library more deeply than the default configurations. + +This folder can be or was merged using a --allow-unrelated-histories merge +strategy from which provides a +reasonably sensible base for writing your own ci on. By using this strategy +the history of the CI repo is included in your repo, and future updates to +the CI can be merged later. + +To perform this merge run: + +```shell +git remote add ci https://github.com/jonhoo/rust-ci-conf.git +git fetch ci +git merge --allow-unrelated-histories ci/main +``` + +An overview of the files in this project is available at: +, which contains some +rationale for decisions and runs through an example of solving minimal version +and OpenSSL issues. diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..cd5ce8f --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,21 @@ +# 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% + +# Test files aren't important for coverage +ignore: + - "tests" + +# Make comments less noisy +comment: + layout: "files" + require_changes: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d0f091e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + - package-ecosystem: cargo + directory: / + schedule: + interval: daily + ignore: + - dependency-name: "*" + # patch and minor updates don't matter for libraries as consumers of this library build + # with their own lockfile, rather than the version specified in this library's lockfile + # remove this ignore rule if your package has binaries to ensure that the binaries are + # built with the exact set of dependencies and those are up to date. + update-types: + - "version-update:semver-patch" + - "version-update:semver-minor" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..2fc8abd --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,126 @@ +# This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs +# several checks: +# - fmt: checks that the code is formatted according to rustfmt +# - clippy: checks that the code does not contain any clippy warnings +# - doc: checks that the code can be documented without errors +# - hack: check combinations of feature flags +# - msrv: check that the msrv specified in the crate is correct +permissions: + contents: read +# This configuration allows maintainers of this repo to create a branch and pull request based on +# the new branch. Restricting the push trigger to the main branch ensures that the PR only gets +# built once. +on: + push: + branches: [main] + pull_request: +# If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that +# we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +name: check +jobs: + fmt: + runs-on: ubuntu-latest + name: stable / fmt + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: cargo fmt --check + run: cargo fmt --check + clippy: + runs-on: ubuntu-latest + name: ${{ matrix.toolchain }} / clippy + permissions: + contents: read + checks: write + strategy: + fail-fast: false + matrix: + # Get early warning of new lints which are regularly introduced in beta channels. + toolchain: [stable, beta] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + - name: cargo clippy + uses: giraffate/clippy-action@v1 + with: + reporter: "github-pr-check" + github_token: ${{ secrets.GITHUB_TOKEN }} + semver: + runs-on: ubuntu-latest + name: semver + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: cargo-semver-checks + uses: obi1kenobi/cargo-semver-checks-action@v2 + doc: + # run docs generation on nightly rather than stable. This enables features like + # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an + # API be documented as only available in some specific platforms. + runs-on: ubuntu-latest + name: nightly / doc + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + - name: cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + hack: + # cargo-hack checks combinations of feature flags to ensure that features are all additive + # which is required for feature unification + runs-on: ubuntu-latest + name: ubuntu / stable / features + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: cargo install cargo-hack + uses: taiki-e/install-action@cargo-hack + # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 + # --feature-powerset runs for every combination of features + - name: cargo hack + run: cargo hack --feature-powerset check + msrv: + # check that we can build using the minimal rust version that is specified by this crate + runs-on: ubuntu-latest + # we use a matrix here just because env can't be used in job names + # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability + strategy: + matrix: + msrv: ["1.76"] + name: ubuntu / ${{ matrix.msrv }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install ${{ matrix.msrv }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.msrv }} + - name: cargo +${{ matrix.msrv }} check + run: cargo check diff --git a/.github/workflows/nostd.yml b/.github/workflows/nostd.yml new file mode 100644 index 0000000..c12227a --- /dev/null +++ b/.github/workflows/nostd.yml @@ -0,0 +1,30 @@ +# This workflow checks whether the library is able to run without the std library (e.g., embedded). +# This entire file should be removed if this crate does not support no-std. See check.yml for +# information about how the concurrency cancellation and workflow triggering works +permissions: + contents: read +on: + push: + branches: [main] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +name: no-std +jobs: + nostd: + runs-on: ubuntu-latest + name: ${{ matrix.target }} + strategy: + matrix: + target: [thumbv7m-none-eabi, aarch64-unknown-none] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: rustup target add ${{ matrix.target }} + run: rustup target add ${{ matrix.target }} + - name: cargo check + run: cargo check --target ${{ matrix.target }} --no-default-features diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 31000a2..0000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Rust - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/.github/workflows/safety.yml b/.github/workflows/safety.yml new file mode 100644 index 0000000..9481dea --- /dev/null +++ b/.github/workflows/safety.yml @@ -0,0 +1,70 @@ +# This workflow runs checks for unsafe code. In crates that don't have any unsafe code, this can be +# removed. Runs: +# - miri - detects undefined behavior and memory leaks +# - address sanitizer - detects memory errors +# - leak sanitizer - detects memory leaks +# See check.yml for information about how the concurrency cancellation and workflow triggering works +permissions: + contents: read +on: + push: + branches: [main] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +name: safety +jobs: + sanitizers: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + - run: | + # to get the symbolizer for debug symbol resolution + sudo apt install llvm + # to fix buggy leak analyzer: + # https://github.com/japaric/rust-san#unrealiable-leaksanitizer + # ensure there's a profile.dev section + if ! grep -qE '^[ \t]*[profile.dev]' Cargo.toml; then + echo >> Cargo.toml + echo '[profile.dev]' >> Cargo.toml + fi + # remove pre-existing opt-levels in profile.dev + sed -i '/^\s*\[profile.dev\]/,/^\s*\[/ {/^\s*opt-level/d}' Cargo.toml + # now set opt-level to 1 + sed -i '/^\s*\[profile.dev\]/a opt-level = 1' Cargo.toml + cat Cargo.toml + name: Enable debug symbols + - name: cargo test -Zsanitizer=address + # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945 + run: cargo test --lib --tests --all-features --target x86_64-unknown-linux-gnu + env: + ASAN_OPTIONS: "detect_odr_violation=0:detect_leaks=0" + RUSTFLAGS: "-Z sanitizer=address" + - name: cargo test -Zsanitizer=leak + if: always() + run: cargo test --all-features --target x86_64-unknown-linux-gnu + env: + LSAN_OPTIONS: "suppressions=lsan-suppressions.txt" + RUSTFLAGS: "-Z sanitizer=leak" + miri: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - run: | + echo "NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" >> $GITHUB_ENV + - name: Install ${{ env.NIGHTLY }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.NIGHTLY }} + components: miri + - name: cargo miri test + run: cargo miri test -- --skip pointer::tests::qc + env: + MIRIFLAGS: "" diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml new file mode 100644 index 0000000..02aa275 --- /dev/null +++ b/.github/workflows/scheduled.yml @@ -0,0 +1,58 @@ +# Run scheduled (rolling) jobs on a nightly basis, as your crate may break independently of any +# given PR. E.g., updates to rust nightly and updates to this crates dependencies. See check.yml for +# information about how the concurrency cancellation and workflow triggering works +permissions: + contents: read +on: + push: + branches: [main] + pull_request: + schedule: + - cron: '7 7 * * *' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +name: rolling +jobs: + # https://twitter.com/mycoliza/status/1571295690063753218 + nightly: + runs-on: ubuntu-latest + name: ubuntu / nightly + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo test --locked + run: cargo test --locked --all-features --all-targets + # https://twitter.com/alcuadrado/status/1571291687837732873 + update: + # This action checks that updating the dependencies of this crate to the latest available that + # satisfy the versions in Cargo.toml does not break this crate. This is important as consumers + # of this crate will generally use the latest available crates. This is subject to the standard + # Cargo semver rules (i.e cargo does not update to a new major version unless explicitly told + # to). + runs-on: ubuntu-latest + name: ubuntu / beta / updated + # There's no point running this if no Cargo.lock was checked in in the first place, since we'd + # just redo what happened in the regular test job. Unfortunately, hashFiles only works in if on + # steps, so we repeat it. + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install beta + if: hashFiles('Cargo.lock') != '' + uses: dtolnay/rust-toolchain@beta + - name: cargo update + if: hashFiles('Cargo.lock') != '' + run: cargo update + - name: cargo test + if: hashFiles('Cargo.lock') != '' + run: cargo test --locked --all-features --all-targets + env: + RUSTFLAGS: -D deprecated diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6826f77 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,156 @@ +# This is the main CI workflow that runs the test suite on all pushes to main and all pull requests. +# It runs the following jobs: +# - required: runs the test suite on ubuntu with stable and beta rust toolchains +# - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the +# requirements of this crate, and its dependencies +# - os-check: runs the test suite on mac and windows +# - coverage: runs the test suite and collects coverage information +# See check.yml for information about how the concurrency cancellation and workflow triggering works +permissions: + contents: read +on: + push: + branches: [main] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +name: test +jobs: + required: + runs-on: ubuntu-latest + name: ubuntu / ${{ matrix.toolchain }} + strategy: + matrix: + # run on stable and beta to ensure that tests won't break on the next version of the rust + # toolchain + toolchain: [stable, beta] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + - name: cargo generate-lockfile + # enable this ci template to run regardless of whether the lockfile is checked in or not + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + # https://twitter.com/jonhoo/status/1571290371124260865 + - name: cargo test --locked + run: cargo test --locked --all-features --all-targets + # https://github.com/rust-lang/cargo/issues/6669 + - name: cargo test --doc + run: cargo test --locked --all-features --doc + minimal: + # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure + # that this crate is compatible with the minimal version that this crate and its dependencies + # require. This will pickup issues where this create relies on functionality that was introduced + # later than the actual version specified (e.g., when we choose just a major version, but a + # method was added after this version). + # + # This particular check can be difficult to get to succeed as often transitive dependencies may + # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There + # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for + # direct dependencies of this crate, while selecting the maximal versions for the transitive + # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase + # the minimal dependency, which you do with e.g.: + # ```toml + # # for minimal-versions + # [target.'cfg(any())'.dependencies] + # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions + # ``` + # The optional = true is necessary in case that dependency isn't otherwise transitively required + # by your library, and the target bit is so that this dependency edge never actually affects + # Cargo build order. See also + # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49. + # This action is run on ubuntu with the stable toolchain, as it is not expected to fail + runs-on: ubuntu-latest + name: ubuntu / stable / minimal-versions + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: Install nightly for -Zminimal-versions + uses: dtolnay/rust-toolchain@nightly + - name: rustup default stable + run: rustup default stable + - name: cargo update -Zminimal-versions + run: cargo +nightly update -Zminimal-versions + - name: cargo test + run: cargo test --locked --all-features --all-targets + os-check: + # run cargo test on mac and windows + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} / stable + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + steps: + # if your project needs OpenSSL, uncomment this to fix Windows builds. + # it's commented out by default as the install command takes 5-10m. + # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + # if: runner.os == 'Windows' + # - run: vcpkg install openssl:x64-windows-static-md + # if: runner.os == 'Windows' + - uses: actions/checkout@v4 + with: + submodules: true + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo test + run: cargo test --locked --all-features --all-targets + coverage: + # use llvm-cov to build and collect coverage and outputs in a format that + # is compatible with codecov.io + # + # note that codecov as of v4 requires that CODECOV_TOKEN from + # + # https://app.codecov.io/gh///settings + # + # is set in two places on your repo: + # + # - https://github.com/jonhoo/guardian/settings/secrets/actions + # - https://github.com/jonhoo/guardian/settings/secrets/dependabot + # + # (the former is needed for codecov uploads to work with Dependabot PRs) + # + # PRs coming from forks of your repo will not have access to the token, but + # for those, codecov allows uploading coverage reports without a token. + # it's all a little weird and inconvenient. see + # + # https://github.com/codecov/feedback/issues/112 + # + # for lots of more discussion + runs-on: ubuntu-latest + name: ubuntu / stable / coverage + steps: + - 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: Upload to codecov.io + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + env_vars: OS,RUST diff --git a/.gitignore b/.gitignore index 96ef6c0..a3d6b48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target -Cargo.lock +# Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..156b406 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,314 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +dependencies = [ + "memchr", +] + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" + +[[package]] +name = "env_logger" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + +[[package]] +name = "getrandom" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3e61cf687687b30c9e6ddf0fc36cf15f035e66d491e6da968fa49ffa9a378" + +[[package]] +name = "jsonptr" +version = "0.5.0" +dependencies = [ + "quickcheck", + "quickcheck_macros", + "serde", + "serde_json", + "syn 1.0.109", + "toml", +] + +[[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" + +[[package]] +name = "libc" +version = "0.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "proc-macro2" +version = "1.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76330fb486679b4ace3670f117bbc9e16204005c4bde9c4bd372f45bed34f12" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc98360d9e6ad383647702acc90f80b0582eac3ea577ab47d96325d3575de908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" + +[[package]] +name = "ryu" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.46", +] + +[[package]] +name = "serde_json" +version = "1.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "toml" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winnow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 954d978..5efddef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,24 +8,29 @@ keywords = ["json-pointer", "rfc-6901", "6901"] license = "MIT OR Apache-2.0" name = "jsonptr" repository = "https://github.com/chanced/jsonptr" -version = "0.4.7" +rust-version = "1.76.0" +version = "0.5.0" [dependencies] -serde = { version = "1.0", optional = true, features = ["alloc"] } -serde_json = { version = "1.0", optional = true, features = ["alloc"] } +serde = { version = "1.0.203", optional = true, features = ["alloc"] } +serde_json = { version = "1.0.119", optional = true, features = ["alloc"] } toml = { version = "0.8", optional = true } [dev-dependencies] quickcheck = "1.0.3" quickcheck_macros = "1.0.0" +# quickcheck-macros requires a later version than it declares +# so we use this hack to make `-Zminimal-versions` work +[target.'cfg(any())'.dependencies] +syn = { version = "1.0.109", optional = true } + [features] assign = [] -default = ["std", "serde", "json", "toml", "resolve", "assign", "delete"] -delete = [] +default = ["std", "serde", "json", "resolve", "assign", "delete"] +delete = ["resolve"] impl = [] json = ["dep:serde_json", "serde"] resolve = [] -serde = [] -std = ["serde/std", "serde_json/std"] +std = ["serde/std", "serde_json?/std"] toml = ["dep:toml", "serde", "std"] diff --git a/src/assign.rs b/src/assign.rs index b30f521..7890b16 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -202,9 +202,15 @@ enum Assigned<'v, V> { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ +#[cfg(feature = "json")] mod json { use super::{Assign, AssignError, Assigned}; use crate::{Pointer, Token}; + use alloc::{ + string::{String, ToString}, + vec::Vec, + }; + use core::mem; use serde_json::{map::Entry, Map, Value}; @@ -374,6 +380,7 @@ mod json { mod toml { use super::{Assign, AssignError, Assigned}; use crate::{Pointer, Token}; + use alloc::{string::String, vec, vec::Vec}; use core::mem; use toml::{map::Entry, map::Map, Value}; @@ -542,8 +549,8 @@ mod toml { mod tests { use super::{Assign, AssignError}; use crate::{OutOfBoundsError, ParseIndexError, Pointer}; + use alloc::str::FromStr; use core::fmt::{Debug, Display}; - use std::str::FromStr; #[derive(Debug)] struct Test { @@ -592,6 +599,7 @@ mod tests { #[test] #[cfg(feature = "json")] fn test_assign_json() { + use alloc::vec; use serde_json::json; Test::all([ Test { @@ -750,8 +758,8 @@ mod tests { #[test] #[cfg(feature = "toml")] fn test_assign_toml() { + use alloc::vec; use toml::{toml, Table, Value}; - Test::all([ Test { data: Value::Table(toml::Table::new()), diff --git a/src/delete.rs b/src/delete.rs index dfe49a2..8db5850 100644 --- a/src/delete.rs +++ b/src/delete.rs @@ -194,7 +194,7 @@ mod tests { let ptr = Pointer::from_static(ptr); let deleted = ptr.delete(&mut data); assert_eq!( - expected_data, + 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" ); @@ -215,20 +215,20 @@ mod tests { fn test_delete_json() { Test::all([ // 0 - Test { + Test { ptr: "/foo", data: json!({"foo": "bar"}), expected_data: json!({}), expected_deleted: Some(json!("bar")), }, // 1 - Test { + Test { ptr: "/foo/bar", data: json!({"foo": {"bar": "baz"}}), expected_data: json!({"foo": {}}), expected_deleted: Some(json!("baz")), }, - // 2 + // 2 Test { ptr: "/foo/bar", data: json!({"foo": "bar"}), @@ -236,13 +236,13 @@ mod tests { expected_deleted: None, }, // 3 - Test { + Test { ptr: "/foo/bar", data: json!({"foo": {"bar": "baz"}}), expected_data: json!({"foo": {}}), expected_deleted: Some(json!("baz")), }, - // 4 + // 4 Test { ptr: "/foo/bar/0", data: json!({"foo": {"bar": ["baz", "qux"]}}), @@ -250,27 +250,27 @@ mod tests { expected_deleted: Some(json!("baz")), }, // 5 - Test { + Test { ptr: "/foo/0", data: json!({"foo": "bar"}), expected_data: json!({"foo": "bar"}), expected_deleted: None, }, - // 6 + // 6 Test { ptr: "/foo/bar/0/baz", data: json!({"foo": { "bar": [{"baz": "qux", "remaining": "field"}]}}), expected_data: json!({"foo": { "bar": [{"remaining": "field"}]} }), expected_deleted: Some(json!("qux")), }, - // 7 - // issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18 - Test { + // 7 + // issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18 + Test { ptr: "/Example", data: json!({"Example": 21, "test": "test"}), expected_data: json!({"test": "test"}), expected_deleted: Some(json!(21)), - } + }, ]); } /* @@ -281,66 +281,66 @@ mod tests { #[test] #[cfg(feature = "toml")] fn test_delete_toml() { - use toml::{Value,Table,toml}; + use toml::{toml, Table, Value}; Test::all([ // 0 - Test { - data: toml!{"foo" = "bar"}.into(), + Test { + data: toml! {"foo" = "bar"}.into(), ptr: "/foo", expected_data: Value::Table(Table::new()), expected_deleted: Some("bar".into()), }, // 1 - Test { - data: toml!{"foo" = {"bar" = "baz"}}.into(), + Test { + data: toml! {"foo" = {"bar" = "baz"}}.into(), ptr: "/foo/bar", - expected_data: toml!{"foo" = {}}.into(), + expected_data: toml! {"foo" = {}}.into(), expected_deleted: Some("baz".into()), }, // 2 - Test { - data: toml!{"foo" = "bar"}.into(), + Test { + data: toml! {"foo" = "bar"}.into(), ptr: "/foo/bar", - expected_data: toml!{"foo" = "bar"}.into(), + expected_data: toml! {"foo" = "bar"}.into(), expected_deleted: None, }, // 3 - Test { - data: toml!{"foo" = {"bar" = "baz"}}.into(), + Test { + data: toml! {"foo" = {"bar" = "baz"}}.into(), ptr: "/foo/bar", - expected_data: toml!{"foo" = {}}.into(), + expected_data: toml! {"foo" = {}}.into(), expected_deleted: Some("baz".into()), }, - // 4 + // 4 Test { - data: toml!{"foo" = {"bar" = ["baz", "qux"]}}.into(), + data: toml! {"foo" = {"bar" = ["baz", "qux"]}}.into(), ptr: "/foo/bar/0", - expected_data: toml!{"foo" = {"bar" = ["qux"]}}.into(), + expected_data: toml! {"foo" = {"bar" = ["qux"]}}.into(), expected_deleted: Some("baz".into()), }, // 5 - Test { - data: toml!{"foo" = "bar"}.into(), + Test { + data: toml! {"foo" = "bar"}.into(), ptr: "/foo/0", - expected_data: toml!{"foo" = "bar"}.into(), + expected_data: toml! {"foo" = "bar"}.into(), expected_deleted: None, }, - // 6 + // 6 Test { - data: toml!{"foo" = { "bar" = [{"baz" = "qux", "remaining" = "field"}]}}.into(), + data: toml! {"foo" = { "bar" = [{"baz" = "qux", "remaining" = "field"}]}}.into(), ptr: "/foo/bar/0/baz", - expected_data: toml!{"foo" = { "bar" = [{"remaining" = "field"}]} }.into(), + expected_data: toml! {"foo" = { "bar" = [{"remaining" = "field"}]} }.into(), expected_deleted: Some("qux".into()), }, - // 7 - // issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18 + // 7 + // issue #18 - unable to delete root token https://github.com/chanced/jsonptr/issues/18 Test { - data: toml!{"Example" = 21 "test" = "test"}.into(), + data: toml! {"Example" = 21 "test" = "test"}.into(), ptr: "/Example", - expected_data: toml!{"test" = "test"}.into(), + expected_data: toml! {"test" = "test"}.into(), expected_deleted: Some(21.into()), - } + }, ]); } } diff --git a/src/index.rs b/src/index.rs index 1531ece..2d770f2 100644 --- a/src/index.rs +++ b/src/index.rs @@ -34,6 +34,7 @@ //! ```` use crate::{OutOfBoundsError, ParseIndexError, Token}; +use alloc::string::String; use core::fmt::Display; /// Represents an abstract index into an array. @@ -127,7 +128,7 @@ impl Index { /// assert_eq!(Index::Num(42).for_len_unchecked(30), 42); /// assert_eq!(Index::Next.for_len_unchecked(30), 30); /// ```` - #[must_use] + pub fn for_len_unchecked(&self, length: usize) -> usize { match *self { Self::Num(idx) => idx, diff --git a/src/lib.rs b/src/lib.rs index 2865c3d..76b55fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,12 +6,14 @@ clippy::module_name_repetitions, clippy::into_iter_without_iter, clippy::needless_pass_by_value, - clippy::expect_fun_call + clippy::expect_fun_call, + clippy::must_use_candidate )] + +#[cfg_attr(not(feature = "std"), macro_use)] extern crate alloc; use core::{fmt, num::ParseIntError}; - pub mod prelude; #[cfg(feature = "assign")] @@ -88,14 +90,12 @@ impl fmt::Display for ParseError { impl ParseError { /// Returns `true` if this error is `NoLeadingBackslash`; otherwise returns /// `false`. - #[must_use] pub fn is_no_leading_backslash(&self) -> bool { matches!(self, Self::NoLeadingBackslash { .. }) } /// Returns `true` if this error is `InvalidEncoding`; otherwise returns /// `false`. - #[must_use] pub fn is_invalid_encoding(&self) -> bool { matches!(self, Self::InvalidEncoding { .. }) } @@ -112,7 +112,6 @@ impl ParseError { /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); /// assert_eq!(err.pointer_offset(), 4) /// ``` - #[must_use] pub fn pointer_offset(&self) -> usize { match *self { Self::NoLeadingBackslash { .. } => 0, @@ -132,7 +131,6 @@ impl ParseError { /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); /// assert_eq!(err.source_offset(), 8) /// ``` - #[must_use] pub fn source_offset(&self) -> usize { match self { Self::NoLeadingBackslash { .. } => 0, @@ -151,7 +149,6 @@ impl ParseError { /// let err = PointerBuf::parse("/foo/invalid~tilde/invalid").unwrap_err(); /// assert_eq!(err.pointer_offset(), 4) /// ``` - #[must_use] pub fn complete_offset(&self) -> usize { self.source_offset() + self.pointer_offset() } @@ -224,7 +221,6 @@ pub struct InvalidEncodingError { impl InvalidEncodingError { /// The byte offset of the first invalid `~`. - #[must_use] pub fn offset(&self) -> usize { self.offset } @@ -242,25 +238,6 @@ impl fmt::Display for InvalidEncodingError { #[cfg(feature = "std")] impl std::error::Error for InvalidEncodingError {} -/// Indicates that the `Token` could not be parsed as valid RFC 6901 index. -#[derive(Debug, PartialEq, Eq)] -pub struct IndexError { - source: ParseIntError, -} - -impl core::fmt::Display for IndexError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "failed to parse token as an integer") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for IndexError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.source) - } -} - /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ diff --git a/src/pointer.rs b/src/pointer.rs index a2191f0..b8501e2 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -4,7 +4,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use core::{borrow::Borrow, cmp::Ordering, fmt, ops::Deref, slice, str::FromStr}; +use core::{borrow::Borrow, cmp::Ordering, ops::Deref, str::FromStr}; /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ @@ -56,9 +56,12 @@ impl Pointer { } /// Constant reference to a root pointer - #[must_use] pub const fn root() -> &'static Self { - unsafe { &*(core::ptr::from_ref::("") as *const Self) } + // unsafe { &*(core::ptr::from_ref::("") as *const Self) } + #[allow(clippy::ref_as_ptr)] + unsafe { + &*("" as *const str as *const Self) + } } /// Attempts to parse a string into a `Pointer`. @@ -88,26 +91,22 @@ impl Pointer { /// let bar = data.resolve(POINTER).unwrap(); /// assert_eq!(bar, "baz"); /// ```` - #[must_use] pub const fn from_static(s: &'static str) -> &'static Self { assert!(validate(s).is_ok(), "invalid json pointer"); - unsafe { &*(std::ptr::from_ref::(s) as *const Self) } + unsafe { &*(core::ptr::from_ref::(s) as *const Self) } } /// The encoded string representation of this `Pointer` - #[must_use] pub fn as_str(&self) -> &str { &self.0 } /// Converts into an owned [`PointerBuf`] - #[must_use] pub fn to_buf(&self) -> PointerBuf { PointerBuf(self.0.to_string()) } /// Returns an iterator of `Token`s in the `Pointer`. - #[must_use] pub fn tokens(&self) -> Tokens { let mut s = self.0.split('/'); // skipping the first '/' @@ -116,26 +115,22 @@ impl Pointer { } /// Returns the number of tokens in the `Pointer`. - #[must_use] pub fn count(&self) -> usize { self.tokens().count() } /// Returns `true` if the JSON Pointer equals `""`. - #[must_use] pub fn is_root(&self) -> bool { self.0.is_empty() } /// Returns a `serde_json::Value` representation of this `Pointer` - #[must_use] #[cfg(feature = "json")] pub fn to_json_value(&self) -> serde_json::Value { serde_json::Value::String(self.0.to_string()) } /// Returns the last `Token` in the `Pointer`. - #[must_use] pub fn back(&self) -> Option { self.0 .rsplit_once('/') @@ -145,13 +140,11 @@ impl Pointer { /// Returns the last token in the `Pointer`. /// /// alias for `back` - #[must_use] pub fn last(&self) -> Option { self.back() } /// Returns the first `Token` in the `Pointer`. - #[must_use] pub fn front(&self) -> Option { if self.is_root() { return None; @@ -168,26 +161,21 @@ impl Pointer { /// Returns the first `Token` in the `Pointer`. /// /// alias for `front` - #[must_use] pub fn first(&self) -> Option { self.front() } /// Splits the `Pointer` into the first `Token` and a remainder `Pointer`. - #[must_use] pub fn split_front(&self) -> Option<(Token, &Self)> { if self.is_root() { return None; } self.0[1..] - .split_once('/') + .find('/') .map_or_else( || (Token::from_encoded_unchecked(&self.0[1..]), Self::root()), - |(front, back)| { - // We want to include the delimiter character too! - // SAFETY: if split was successful, then the delimiter - // character exists before the start of the second `str`. - let back = unsafe { extend_one_before(back) }; + |idx| { + let (front, back) = self.0[1..].split_at(idx); (Token::from_encoded_unchecked(front), Self::new(back)) }, ) @@ -215,7 +203,6 @@ impl Pointer { /// assert_eq!(tail, Pointer::from_static("/bar/baz")); /// assert_eq!(ptr.split_at(3), None); /// ``` - #[must_use] pub fn split_at(&self, idx: usize) -> Option<(&Self, &Self)> { if self.0.as_bytes().get(idx).copied() != Some(b'/') { return None; @@ -225,7 +212,6 @@ impl Pointer { } /// Splits the `Pointer` into the parent path and the last `Token`. - #[must_use] pub fn split_back(&self) -> Option<(&Self, Token)> { self.0 .rsplit_once('/') @@ -233,7 +219,6 @@ impl Pointer { } /// A pointer to the parent of the current path. - #[must_use] pub fn parent(&self) -> Option<&Self> { self.0.rsplit_once('/').map(|(front, _)| Self::new(front)) } @@ -258,7 +243,6 @@ impl Pointer { /// let ptr = Pointer::root(); /// assert_eq!(ptr.get(0), None); /// ``` - #[must_use] pub fn get(&self, index: usize) -> Option { self.tokens().nth(index).clone() } @@ -309,7 +293,6 @@ impl Pointer { } /// Finds the commonality between this and another `Pointer`. - #[must_use] pub fn intersection<'a>(&'a self, other: &Self) -> &'a Self { if self.is_root() || other.is_root() { return Self::root(); @@ -427,7 +410,7 @@ impl<'de: 'p, 'p> serde::Deserialize<'de> for &'p Pointer { impl<'a> Visitor<'a> for PointerVisitor { type Value = &'a Pointer; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { formatter.write_str("a borrowed Pointer") } @@ -587,7 +570,6 @@ pub struct PointerBuf(String); impl PointerBuf { /// Creates a new `PointerBuf` pointing to a document root. - #[must_use] pub fn new() -> Self { Self(String::new()) } @@ -614,7 +596,6 @@ impl PointerBuf { } /// Coerces to a Pointer slice. - #[must_use] pub fn as_ptr(&self) -> &Pointer { self } @@ -864,13 +845,6 @@ const fn validate(value: &str) -> Result<&str, ParseError> { Ok(value) } -unsafe fn extend_one_before(s: &str) -> &str { - let ptr = s.as_ptr().offset(-1); - let len = s.len() + 1; - let slice = slice::from_raw_parts(ptr, len); - core::str::from_utf8_unchecked(slice) -} - /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ diff --git a/src/resolve.rs b/src/resolve.rs index e6c402a..f11bbd9 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -156,7 +156,6 @@ pub enum ResolveError { impl ResolveError { /// Offset of the partial pointer starting with the token which caused the /// error. - #[must_use] pub fn offset(&self) -> usize { match self { Self::FailedToParseIndex { offset, .. } @@ -168,28 +167,24 @@ impl ResolveError { /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns /// `false`. - #[must_use] pub fn is_unreachable(&self) -> bool { matches!(self, Self::Unreachable { .. }) } /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns /// `false`. - #[must_use] pub fn is_not_found(&self) -> bool { matches!(self, Self::NotFound { .. }) } /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns /// `false`. - #[must_use] pub fn is_out_of_bounds(&self) -> bool { matches!(self, Self::OutOfBounds { .. }) } /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns /// `false`. - #[must_use] pub fn is_failed_to_parse_index(&self) -> bool { matches!(self, Self::FailedToParseIndex { .. }) } diff --git a/src/token.rs b/src/token.rs index 4b20862..bafa5b1 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,8 +2,8 @@ use crate::{index::Index, InvalidEncodingError, ParseIndexError}; use alloc::{ borrow::Cow, string::{String, ToString}, + vec::Vec, }; -use serde::{Deserialize, Serialize}; const ENCODED_TILDE: &[u8] = b"~0"; const ENCODED_SLASH: &[u8] = b"~1"; @@ -143,7 +143,6 @@ impl<'a> Token<'a> { /// /// If the token is not already owned, this will clone the referenced string /// slice. - #[must_use] pub fn into_owned(self) -> Token<'static> { Token { inner: Cow::Owned(self.inner.into_owned()), @@ -157,7 +156,6 @@ impl<'a> Token<'a> { /// /// This method is like [`Self::into_owned`], except it doesn't take /// ownership of the original `Token`. - #[must_use] pub fn to_owned(&self) -> Token<'static> { Token { inner: Cow::Owned(self.inner.clone().into_owned()), @@ -172,7 +170,6 @@ impl<'a> Token<'a> { /// # use jsonptr::Token; /// assert_eq!(Token::new("~bar").encoded(), "~0bar"); /// ``` - #[must_use] pub fn encoded(&self) -> &str { &self.inner } @@ -185,7 +182,6 @@ impl<'a> Token<'a> { /// # use jsonptr::Token; /// assert_eq!(Token::new("~bar").decoded(), "~bar"); /// ``` - #[must_use] pub fn decoded(&self) -> Cow<'_, str> { if let Some(i) = self.inner.bytes().position(|b| b == ENC_PREFIX) { let input = self.inner.as_bytes(); @@ -250,7 +246,8 @@ impl<'a> Token<'a> { } } -impl Serialize for Token<'_> { +#[cfg(feature = "serde")] +impl serde::Serialize for Token<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -259,7 +256,8 @@ impl Serialize for Token<'_> { } } -impl<'de> Deserialize<'de> for Token<'de> { +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Token<'de> { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -311,8 +309,8 @@ impl<'a> From<&Token<'a>> for Token<'a> { } } -impl core::fmt::Display for Token<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +impl alloc::fmt::Display for Token<'_> { + fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { write!(f, "{}", self.decoded()) } }