wiremock-0.6.5/.cargo_vcs_info.json0000644000000001360000000000100126640ustar { "git": { "sha1": "6b193047bf2c5626da5dc5f3a23b58ab9bd3f130" }, "path_in_vcs": "" }wiremock-0.6.5/.github/workflows/deny.yml000064400000000000000000000007431046102023000165370ustar 00000000000000name: Verify dependencies on: workflow_dispatch: push: branches: - main pull_request: branches: - main jobs: deny-check: name: cargo-deny check runs-on: ubuntu-latest strategy: matrix: checks: - advisories - licenses steps: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check ${{ matrix.checks }} arguments: --all-features wiremock-0.6.5/.github/workflows/general.yml000064400000000000000000000036041046102023000172140ustar 00000000000000name: Rust on: pull_request: push: branches: - main env: CARGO_TERM_COLOR: always jobs: test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Install Rust stable toolchain uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 - name: Test run: | cargo test fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Install Rust stable toolchain uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 - name: Format run: | cargo fmt --all -- --check clippy: name: Clippy runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 - name: Install Rust stable toolchain uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 - name: Lint run: | cargo clippy -- -D warnings docs: name: Docs runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 - name: Install Rust stable toolchain uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 - name: Document run: | cargo doc --no-deps --document-private-items coverage: runs-on: ubuntu-latest env: CARGO_TERM_COLOR: always steps: - uses: actions/checkout@v5 - name: Install Rust stable toolchain uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage run: cargo llvm-cov --all-features --workspace --codecov --output-path codecov.json - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: codecov.json fail_ci_if_error: true wiremock-0.6.5/.gitignore000064400000000000000000000000311046102023000134360ustar 00000000000000/target .idea Cargo.lock wiremock-0.6.5/CHANGELOG.md000064400000000000000000000051351046102023000132710ustar 00000000000000# Changelog # Next # `0.5.8` - Features: - Functions from `&Request` to `ResponseTemplate` can now be passed to `MockBuilder::respond_with`. You do not have to write a struct with a `Respond` implementation for simple manipulation of request data! (by [@RoGryza]) # `0.5.7` - Fixes: - `MockGuard` is now marked as `must_use`, ensuring that a compiler warning is raised if the guard for a scoped mock is not bound to a variable. # `0.5.6` - Features: - Added support for **scoped** `Mock`s! Using `MockServer::register_as_scoped` or `Mock::mount_as_scoped` you can now register `Mock`s that go "out of scope" when the returned RAII guard (`MockGuard`) is dropped. Scoped `Mock`s are recommended for usage in test helper functions to ensure proper isolation - check the documentation for more details! (by [@LukeMathWalker]) # `0.5.5` - Miscellaneous: - Added the `http` module to re-export the types from `http-types` that are part of `wiremock`'s public API (by [@LukeMathWalker]). # `0.5.4` - Fixes: - Handle URI authority properly in the mock server (by [@Tuetuopay]) # `0.5.3` - Miscellaneous: - Remove `textwrap` from the dependency tree (by [@apiraino]) # `0.5.2` - Features: - Support multi-valued headers (by [@beltram]) - Miscellaneous: - Improve README (by [@apiraino]) # `0.5.1` - Features: - Capture the port in `hyper`'s `Request` into `wiremock::Request::url` (by [@beltram]) # `0.5.0` - Breaking changes: - Removed `MockServer::start_on`. Use `MockServer::builder` and `MockServerBuilder::listener` to configure your `MockServer` to start on a pre-determined port (by [@LukeMathWalker]). - `MockServer::verify` is now asynchronous (by [@LukeMathWalker]). - Features: - Added request recording to `MockServer`, enabled by default. The recorded requests are used to display more meaningful error messages when assertions are not satisfied and can be retrieved using `MockServer::received_requests` ([by @LukeMathWalker]). - Added `MockServerBuilder` to customise the configuration of a `MockServer`. It can be used to bind a `MockServer` to an existing `TcpListener` as well as disabling request recording (by [@LukeMathWalker]). - Added `matchers::body_json_schema` to verify the structure of the body attached to an incoming request (by [@Michael-J-Ward]). [@Michael-J-Ward]: https://github.com/Michael-J-Ward [@LukeMathWalker]: https://github.com/LukeMathWalker [@beltram]: https://github.com/beltram [@apiraino]: https://github.com/apiraino [@Tuetuopay]: https://github.com/Tuetuopay [@RoGryza]: https://github.com/RoGryza wiremock-0.6.5/CODE_OF_CONDUCT.md000064400000000000000000000064261046102023000142630ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at rust@lpalmieri.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq wiremock-0.6.5/Cargo.lock0000644000001500450000000000100106440ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "actix-macros" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", "syn 2.0.106", ] [[package]] name = "actix-rt" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" dependencies = [ "actix-macros", "futures-core", "tokio", ] [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "assert-json-diff" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" dependencies = [ "serde", "serde_json", ] [[package]] name = "async-attributes" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", "syn 1.0.109", ] [[package]] name = "async-channel" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", "event-listener 2.5.3", "futures-core", ] [[package]] name = "async-channel" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "pin-project-lite", "slab", ] [[package]] name = "async-global-executor" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.5.0", "async-executor", "async-io", "async-lock", "blocking", "futures-lite", "once_cell", "tokio", ] [[package]] name = "async-io" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix", "slab", "windows-sys 0.60.2", ] [[package]] name = "async-lock" version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-std" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", "async-io", "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", "log", "memchr", "once_cell", "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "blocking" version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ "async-channel 2.5.0", "async-task", "futures-io", "futures-lite", "piper", ] [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "deadpool" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ed5957ff93768adf7a65ab167a17835c3d2c3c50d084fe305174c112f468e2f" dependencies = [ "deadpool-runtime", "num_cpus", "tokio", ] [[package]] name = "deadpool-runtime" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.60.2", ] [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener 5.4.1", "pin-project-lite", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gloo-timers" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", ] [[package]] name = "h2" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "pin-utils", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2", "system-configuration", "tokio", "tower-service", "tracing", "windows-registry", ] [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ "memchr", "serde", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ "log", ] [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" dependencies = [ "value-bag", ] [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "native-tls" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "num_cpus" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polling" version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", "windows-sys 0.60.2", ] [[package]] name = "potential_utf" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ "zerovec", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "redox_syscall" version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", "mime", "native-tls", "percent-encoding", "pin-project-lite", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-native-tls", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.60.2", ] [[package]] name = "rustls" version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "serde_json" version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[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.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.60.2", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "slab", "socket2", "tokio-macros", "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-http" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "value-bag" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-result" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.3", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "wiremock" version = "0.6.5" dependencies = [ "actix-rt", "assert-json-diff", "async-std", "base64", "deadpool", "futures", "http", "http-body-util", "hyper", "hyper-util", "log", "once_cell", "regex", "reqwest", "serde", "serde_json", "tokio", "url", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] wiremock-0.6.5/Cargo.toml0000644000000046750000000000100106760ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "wiremock" version = "0.6.5" authors = ["Luca Palmieri "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "HTTP mocking to test Rust applications." documentation = "https://docs.rs/wiremock/" readme = "README.md" keywords = [ "test", "http", "mocking", "mock", "black-box", ] categories = [ "development-tools", "asynchronous", ] license = "MIT/Apache-2.0" repository = "https://github.com/LukeMathWalker/wiremock-rs" [lib] name = "wiremock" path = "src/lib.rs" [[test]] name = "mocks" path = "tests/mocks.rs" [[test]] name = "priority" path = "tests/priority.rs" [[test]] name = "request_header_matching" path = "tests/request_header_matching.rs" [[test]] name = "timeout" path = "tests/timeout.rs" [[test]] name = "tokio" path = "tests/tokio.rs" [dependencies.assert-json-diff] version = "2.0.2" [dependencies.base64] version = "0.22" [dependencies.deadpool] version = "0.12.2" [dependencies.futures] version = "0.3.31" [dependencies.http] version = "1.3" [dependencies.http-body-util] version = "0.1" [dependencies.hyper] version = "1.7" features = ["full"] [dependencies.hyper-util] version = "0.1" features = [ "tokio", "server", "http1", "http2", ] [dependencies.log] version = "0.4" [dependencies.once_cell] version = "1" [dependencies.regex] version = "1" [dependencies.serde] version = "1" [dependencies.serde_json] version = "1" [dependencies.tokio] version = "1.47.1" features = [ "rt", "macros", "net", ] [dependencies.url] version = "2.5" [dev-dependencies.actix-rt] version = "2.10.0" [dev-dependencies.async-std] version = "1.13.2" features = [ "attributes", "tokio1", ] [dev-dependencies.reqwest] version = "0.12.23" features = ["json"] [dev-dependencies.serde] version = "1" features = ["derive"] [dev-dependencies.tokio] version = "1.47.1" features = [ "macros", "rt-multi-thread", ] wiremock-0.6.5/Cargo.toml.orig000064400000000000000000000023551046102023000143500ustar 00000000000000[package] name = "wiremock" version = "0.6.5" authors = ["Luca Palmieri "] edition = "2024" license = "MIT/Apache-2.0" repository = "https://github.com/LukeMathWalker/wiremock-rs" documentation = "https://docs.rs/wiremock/" description = "HTTP mocking to test Rust applications." keywords = ["test", "http", "mocking", "mock", "black-box"] categories = ["development-tools", "asynchronous"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] path = "src/lib.rs" [dependencies] log = "0.4" serde_json = "1" serde = "1" regex = "1" futures = "0.3.31" http = "1.3" http-body-util = "0.1" hyper = { version = "1.7", features = ["full"] } hyper-util = { version = "0.1", features = [ "tokio", "server", "http1", "http2", ] } tokio = { version = "1.47.1", features = ["rt", "macros", "net"] } deadpool = "0.12.2" once_cell = "1" assert-json-diff = "2.0.2" base64 = "0.22" url = "2.5" [dev-dependencies] async-std = { version = "1.13.2", features = ["attributes", "tokio1"] } reqwest = { version = "0.12.23", features = ["json"] } tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] } actix-rt = "2.10.0" serde = { version = "1", features = ["derive"] } wiremock-0.6.5/LICENSE-APACHE000064400000000000000000000251371046102023000134100ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. wiremock-0.6.5/LICENSE-MIT000064400000000000000000000017771046102023000131240ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wiremock-0.6.5/README.md000064400000000000000000000204571046102023000127430ustar 00000000000000

wiremock

HTTP mocking to test Rust applications.

Crates.io version Download docs.rs docs

`wiremock` provides HTTP mocking to perform black-box testing of Rust applications that interact with third-party APIs. It provides mocking of HTTP responses using request matching and response templating. *The name `wiremock` is a reference to [`WireMock.Net`](https://github.com/WireMock-Net/WireMock.Net), a .NET port of the original [`Wiremock`](http://wiremock.org/) from Java.*
Documentation - Crates.io
# Table of Contents 0. [How to install](#how-to-install) 1. [Getting started](#getting-started) 2. [Matchers](#matchers) 3. [Spying](#spying) 4. [Responses](#spying) 5. [Test isolation](#test-isolation) 6. [Runtime compatibility](#runtime-compatibility) 7. [Efficiency](#efficiency) 8. [Prior art](#prior-art) 9. [Future evolution](#future-evolution) 10. [Related projects](#related-projects) 11. [License](#license) ## How to install Add `wiremock` to your development dependencies by editing the `Cargo.toml` file: ```toml [dev-dependencies] # ... wiremock = "0.6" ``` Or by running: ```bash cargo add wiremock --dev ``` ## Getting started ```rust use wiremock::{MockServer, Mock, ResponseTemplate}; use wiremock::matchers::{method, path}; #[async_std::main] async fn main() { // Start a background HTTP server on a random local port let mock_server = MockServer::start().await; // Arrange the behaviour of the MockServer adding a Mock: // when it receives a GET request on '/hello' it will respond with a 200. Mock::given(method("GET")) .and(path("/hello")) .respond_with(ResponseTemplate::new(200)) // Mounting the mock on the mock server - it's now effective! .mount(&mock_server) .await; // If we probe the MockServer using any HTTP client it behaves as expected. let status = reqwest::get(format!("{}/hello", &mock_server.uri())) .await .unwrap() .status(); assert_eq!(status.as_u16(), 200); // If the request doesn't match any `Mock` mounted on our `MockServer` a 404 is returned. let status = reqwest::get(format!("{}/missing", &mock_server.uri())) .await .unwrap() .status(); assert_eq!(status.as_u16(), 404); } ``` ## Matchers `wiremock` provides a set of matching strategies out of the box - check the [`matchers`] module for a complete list. You can define your own matchers using the [`Match`] trait, as well as using `Fn` closures. Check [`Match`]'s documentation for more details and examples. ## Spying `wiremock` empowers you to set expectations on the number of invocations to your [`Mock`]s - check the [`expect`] method for more details. Expectations can be used to verify that a side-effect has (or has not) taken place! Expectations are automatically verified during the shutdown of each [`MockServer`] instance, at the end of your test. A failed verification will trigger a panic. By default, no expectations are set on your [`Mock`]s. ## Responses `wiremock` lets you specify pre-determined responses using [`ResponseTemplate`] and [`respond_with`]. You are also given the option to have [`Mock`]s return different responses based on the matched [`Request`] using the [`Respond`] trait. Check [`Respond`]'s documentation for more details and examples. ## Test isolation Each instance of [`MockServer`] is fully isolated: [`start`] takes care of finding a random port available on your local machine which is assigned to the new [`MockServer`]. To ensure full isolation and no cross-test interference, [`MockServer`]s shouldn't be shared between tests. Instead, [`MockServer`]s should be created in the test where they are used. When a [`MockServer`] instance goes out of scope (e.g. the test finishes), the corresponding HTTP server running in the background is shut down to free up the port it was using. ## Runtime compatibility `wiremock` can be used (and it is tested to work) with both [`async_std`] and [`tokio`] as futures runtimes. If you encounter any compatibility bug, please open an issue on our [GitHub repository]. ## Efficiency `wiremock` maintains a pool of mock servers in the background to minimise the number of connections and the time spent starting up a new [`MockServer`]. Pooling reduces the likelihood of you having to tune your OS configurations (e.g. ulimit). The pool is designed to be invisible: it makes your life easier and your tests faster. If you end up having to worry about it, it's a bug: open an issue! ## Prior art [`mockito`] and [`httpmock`] provide HTTP mocking for Rust. Check the table below to see how `wiremock` compares to them across the following dimensions: - Test execution strategy (do tests have to be executed sequentially or can they be executed in parallel?); - How many APIs can I mock in a test? - Out-of-the-box request matchers; - Extensible request matching (i.e. you can define your own matchers); - Sync/Async API; - Spying (e.g. verify that a mock has/hasn't been called in a test); - Standalone mode (i.e. can I launch an HTTP mock server outside of a test suite?). | | Test execution strategy | How many APIs can I mock? | Out-of-the-box request matchers | Extensible request matching | API | Spying | Standalone mode | |-----------|-------------------------|---------------------------|---------------------------------|-----------------------------|------------|--------|-----------------| | mockito | ✔ Parallel | ✔ Unbounded | ✔ | ❌ | Async/Sync | ✔ | ❌ | | httpmock | ✔ Parallel | ✔ Unbounded | ✔ | ✔ | Async/Sync | ✔ | ✔ | | wiremock | ✔ Parallel ️ | ✔ Unbounded | ✔ | ✔ | Async | ✔ | ❌ | ## Future evolution More request matchers can be added to those provided out-of-the-box to handle common usecases. ## Related projects * [`stubr`](https://github.com/beltram/stubr) for mounting [`Wiremock`](http://wiremock.org/) json stubs in a [`MockServer`]. Also works as a cli. ## License Licensed under either of Apache License, Version 2.0 or MIT license at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [`MockServer`]: https://docs.rs/wiremock/latest/wiremock/struct.MockServer.html [`Mock`]: https://docs.rs/wiremock/latest/wiremock/struct.Mock.html [`ResponseTemplate`]: https://docs.rs/wiremock/latest/wiremock/struct.ResponseTemplate.html [`Request`]: https://docs.rs/wiremock/latest/wiremock/struct.Request.html [`Match`]: https://docs.rs/wiremock/latest/wiremock/trait.Match.html [`Respond`]: https://docs.rs/wiremock/latest/wiremock/trait.Respond.html [`start`]: https://docs.rs/wiremock/latest/wiremock/struct.MockServer.html#method.start [`expect`]: https://docs.rs/wiremock/latest/wiremock/struct.Mock.html#method.expect [`respond_with`]: https://docs.rs/wiremock/latest/wiremock/struct.MockBuilder.html#method.respond_with [`matchers`]: https://docs.rs/wiremock/latest/wiremock/matchers/index.html [GitHub repository]: https://github.com/LukeMathWalker/wiremock-rs [`mockito`]: https://docs.rs/mockito/ [`httpmock`]: https://docs.rs/httpmock/ [`async_std`]: https://docs.rs/async-std/ [`tokio`]: https://docs.rs/tokio/ wiremock-0.6.5/deny.toml000064400000000000000000000007641046102023000133170ustar 00000000000000[licenses] # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ "MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "MPL-2.0", "ISC", "CC0-1.0", "Unicode-DFS-2016", "Unicode-3.0", "BSD-3-Clause", "BSL-1.0", "OpenSSL", "Zlib", "CDLA-Permissive-2.0", ] unused-allowed-license = "allow" confidence-threshold = 0.8 wiremock-0.6.5/src/http.rs000064400000000000000000000002401046102023000135640ustar 00000000000000//! Convenient re-exports of http types that are part of `wiremock`'s public API. pub use http::{HeaderMap, HeaderName, HeaderValue, Method}; pub use url::Url; wiremock-0.6.5/src/lib.rs000064400000000000000000000155001046102023000133600ustar 00000000000000#![allow(clippy::needless_doctest_main)] //! `wiremock` provides HTTP mocking to perform black-box testing of Rust applications that //! interact with third-party APIs. //! //! It provides mocking of HTTP responses using request matching and response templating. //! //! ## How to install //! //! Add `wiremock` to your development dependencies: //! ```toml //! [dev-dependencies] //! # ... //! wiremock = "0.5" //! ``` //! If you are using [`cargo-edit`](https://github.com/killercup/cargo-edit), run //! ```bash //! cargo add wiremock --dev //! ``` //! //! ## Getting started //! //! ```rust //! use wiremock::{MockServer, Mock, ResponseTemplate}; //! use wiremock::matchers::{method, path}; //! //! #[async_std::main] //! async fn main() { //! // Start a background HTTP server on a random local port //! let mock_server = MockServer::start().await; //! //! // Arrange the behaviour of the MockServer adding a Mock: //! // when it receives a GET request on '/hello' it will respond with a 200. //! Mock::given(method("GET")) //! .and(path("/hello")) //! .respond_with(ResponseTemplate::new(200)) //! // Mounting the mock on the mock server - it's now effective! //! .mount(&mock_server) //! .await; //! //! // If we probe the MockServer using any HTTP client it behaves as expected. //! let status = reqwest::get(format!("{}/hello", &mock_server.uri())) //! .await //! .unwrap() //! .status(); //! assert_eq!(status, 200); //! //! // If the request doesn't match any `Mock` mounted on our `MockServer` a 404 is returned. //! let status = reqwest::get(format!("{}/missing", &mock_server.uri())) //! .await //! .unwrap() //! .status(); //! assert_eq!(status, 404); //! } //! ``` //! //! ## Matchers //! //! `wiremock` provides a set of matching strategies out of the box - check the [`matchers`] module //! for a complete list. //! //! You can define your own matchers using the [`Match`] trait, as well as using `Fn` closures. //! Check [`Match`]'s documentation for more details and examples. //! //! ## Spying //! //! `wiremock` empowers you to set expectations on the number of invocations to your [`Mock`]s - //! check the [`expect`] method for more details. //! //! Expectations can be used to verify that a side-effect has (or has not) taken place! //! //! Expectations are automatically verified during the shutdown of each [`MockServer`] instance, //! at the end of your test. A failed verification will trigger a panic. //! By default, no expectations are set on your [`Mock`]s. //! //! ## Responses //! //! `wiremock` lets you specify pre-determined responses using [`ResponseTemplate`] and //! [`respond_with`]. //! //! You also given the option to have [`Mock`]s that return different responses based on the matched //! [`Request`] using the [`Respond`] trait. //! Check [`Respond`]'s documentation for more details and examples. //! //! ## Test isolation //! //! Each instance of [`MockServer`] is fully isolated: [`start`] takes care of finding a random port //! available on your local machine which is assigned to the new [`MockServer`]. //! //! To ensure full isolation and no cross-test interference, [`MockServer`]s shouldn't be //! shared between tests. Instead, [`MockServer`]s should be created in the test where they are used. //! //! When a [`MockServer`] instance goes out of scope (e.g. the test finishes), the corresponding //! HTTP server running in the background is shut down to free up the port it was using. //! //! ## Runtime compatibility //! //! `wiremock` can be used (and it is tested to work) with both [`async_std`] and [`tokio`] as //! futures runtimes. //! If you encounter any compatibility bug, please open an issue on our [GitHub repository]. //! //! ## Efficiency //! //! `wiremock` maintains a pool of mock servers in the background to minimise the number of //! connections and the time spent starting up a new [`MockServer`]. //! Pooling reduces the likelihood of you having to tune your OS configurations (e.g. ulimit). //! //! The pool is designed to be invisible: it makes your life easier and your tests faster. If you //! end up having to worry about it, it's a bug: open an issue! //! //! ## Prior art //! //! [`mockito`] and [`httpmock`] provide HTTP mocking for Rust. //! //! Check the table below to see how `wiremock` compares to them across the following dimensions: //! - Test execution strategy (do tests have to be executed sequentially or can they be executed in parallel?); //! - How many APIs can I mock in a test? //! - Out-of-the-box request matchers; //! - Extensible request matching (i.e. you can define your own matchers); //! - Sync/Async API; //! - Spying (e.g. verify that a mock has/hasn't been called in a test); //! - Standalone mode (i.e. can I launch an HTTP mock server outside of a test suite?). //! //! | | Test execution strategy | How many APIs can I mock? | Out-of-the-box request matchers | Extensible request matching | API | Spying | Standalone mode | //! |-----------|-------------------------|---------------------------|---------------------------------|----------------------------|-------|----------|-----------------| //! | mockito | ❌ Sequential | ❌ 1 | ✔ | ❌ | Sync | ✔ | ❌ | //! | httpmock | ✔ Parallel | ✔ Unbounded | ✔ | ✔ | Async/Sync | ✔ | ✔ | //! | wiremock | ✔ Parallel ️ | ✔ Unbounded | ✔ | ✔ | Async | ✔ | ❌ | //! //! //! ## Future evolution //! //! More request matchers can be added to those provided out-of-the-box to handle common usecases. //! //! ## Related projects //! //! * [`stubr`](https://github.com/beltram/stubr) for mounting [`Wiremock`](http://wiremock.org/) json stubs in a [`MockServer`]. Also works as a cli. //! //! [`MockServer`]: MockServer //! [`start`]: MockServer::start //! [`expect`]: Mock::expect //! [`respond_with`]: MockBuilder::respond_with //! [GitHub repository]: https://github.com/LukeMathWalker/wiremock-rs //! [`mockito`]: https://docs.rs/mockito/ //! [`httpmock`]: https://docs.rs/httpmock/ //! [`async_std`]: https://docs.rs/async-std/ //! [`tokio`]: https://docs.rs/tokio/ pub mod http; pub mod matchers; mod mock; mod mock_server; mod mock_set; mod mounted_mock; mod request; mod respond; mod response_template; mod verification; pub type ErrorResponse = Box; pub use mock::{Match, Mock, MockBuilder, Times}; pub use mock_server::{MockGuard, MockServer, MockServerBuilder}; pub use request::{BodyPrintLimit, Request}; pub use respond::Respond; pub use response_template::ResponseTemplate; wiremock-0.6.5/src/matchers.rs000064400000000000000000001002251046102023000144170ustar 00000000000000//! A collection of different matching strategies provided out-of-the-box by `wiremock`. //! //! If the set of matchers provided out-of-the-box is not enough for your specific testing needs //! you can implement your own thanks to the [`Match`] trait. //! //! Furthermore, `Fn` closures that take an immutable [`Request`] reference as input and return a boolean //! as input automatically implement [`Match`] and can be used where a matcher is expected. //! //! Check [`Match`]'s documentation for examples. use crate::{Match, Request}; use assert_json_diff::{CompareMode, assert_json_matches_no_panic}; use base64::prelude::{BASE64_STANDARD, Engine as _}; use http::{HeaderName, HeaderValue, Method}; use log::debug; use regex::Regex; use serde::Serialize; use serde_json::Value; use std::convert::TryInto; use std::str::{self, FromStr}; use url::Url; /// Implement the `Match` trait for all closures, out of the box, /// if their signature is compatible. impl Match for F where F: Fn(&Request) -> bool, F: Send + Sync, { fn matches(&self, request: &Request) -> bool { // Just call the closure itself! self(request) } } #[derive(Debug)] /// Match **exactly** the method of a request. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200); /// let mock = Mock::given(method("GET")).respond_with(response); /// /// mock_server.register(mock).await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct MethodExactMatcher(Method); /// Shorthand for [`MethodExactMatcher::new`]. pub fn method(method: T) -> MethodExactMatcher where T: AsRef, { MethodExactMatcher::new(method) } impl MethodExactMatcher { pub fn new(method: T) -> Self where T: AsRef, { let method = Method::from_str(&method.as_ref().to_ascii_uppercase()) .expect("Failed to convert to HTTP method."); Self(method) } } impl Match for MethodExactMatcher { fn matches(&self, request: &Request) -> bool { request.method == self.0 } } #[derive(Debug)] /// Match all incoming requests, regardless of their method, path, headers or body. /// /// You can use it to verify that a request has been fired towards the server, without making /// any other assertion about it. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::any; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200); /// // Respond with a `200 OK` to all requests hitting /// // the mock server /// let mock = Mock::given(any()).respond_with(response); /// /// mock_server.register(mock).await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct AnyMatcher; /// Shorthand for [`AnyMatcher`]. pub fn any() -> AnyMatcher { AnyMatcher } impl Match for AnyMatcher { fn matches(&self, _request: &Request) -> bool { true } } #[derive(Debug)] /// Match **exactly** the path of a request. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::path; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200).set_body_string("world"); /// let mock = Mock::given(path("/hello")).respond_with(response); /// /// mock_server.register(mock).await; /// /// // Act /// let status = reqwest::get(format!("{}/hello", &mock_server.uri())) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` /// /// ### Example: /// /// The path matcher ignores query parameters: /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::path; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200).set_body_string("world"); /// let mock = Mock::given(path("/hello")).respond_with(response); /// /// mock_server.register(mock).await; /// /// // Act /// let status = reqwest::get(format!("{}/hello?a_parameter=some_value", &mock_server.uri())) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct PathExactMatcher(String); /// Shorthand for [`PathExactMatcher::new`]. pub fn path(path: T) -> PathExactMatcher where T: Into, { PathExactMatcher::new(path) } impl PathExactMatcher { pub fn new>(path: T) -> Self { let path = path.into(); if path.contains('?') { panic!( "Wiremock can't match the path `{}` because it contains a `?`. You must use `wiremock::matchers::query_param` to match on query parameters (the part of the path after the `?`).", path ); } if let Ok(url) = Url::parse(&path) && let Some(host) = url.host_str() { panic!( "Wiremock can't match the path `{}` because it contains the host `{}`. You don't have to specify the host - wiremock knows it. Try replacing your path with `path(\"{}\")`", path, host, url.path() ); } // Prepend "/" to the path if missing. if path.starts_with('/') { Self(path) } else { Self(format!("/{}", path)) } } } impl Match for PathExactMatcher { fn matches(&self, request: &Request) -> bool { request.url.path() == self.0 } } #[derive(Debug)] /// Match the path of a request against a regular expression. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::path_regex; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200).set_body_string("world"); /// let mock = Mock::given(path_regex(r"^/hello/\d{3}$")).respond_with(response); /// /// mock_server.register(mock).await; /// /// // Act /// let status = reqwest::get(format!("{}/hello/123", &mock_server.uri())) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::path_regex; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200).set_body_string("world"); /// let mock = Mock::given(path_regex(r"^/users/[a-z0-9-~_]{1,}/posts$")).respond_with(response); /// /// mock_server.register(mock).await; /// /// // Act /// let status = reqwest::get(format!("{}/users/da2854ea-b70f-46e7-babc-2846eff4d33c/posts", &mock_server.uri())) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct PathRegexMatcher(Regex); /// Shorthand for [`PathRegexMatcher::new`]. pub fn path_regex(path: T) -> PathRegexMatcher where T: Into, { PathRegexMatcher::new(path) } impl PathRegexMatcher { pub fn new>(path: T) -> Self { let path = path.into(); Self(Regex::new(&path).expect("Failed to create regex for path matcher")) } } impl Match for PathRegexMatcher { fn matches(&self, request: &Request) -> bool { self.0.is_match(request.url.path()) } } #[derive(Debug)] /// Match **exactly** the header of a request. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::{header, headers}; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(header("custom", "header")) /// .and(headers("cache-control", vec!["no-cache", "no-store"])) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let client = reqwest::Client::new(); /// let status = client /// .get(&mock_server.uri()) /// .header("custom", "header") /// .header("cache-control", "no-cache, no-store") /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct HeaderExactMatcher(HeaderName, Vec); /// Shorthand for [`HeaderExactMatcher::new`]. pub fn header(key: K, value: V) -> HeaderExactMatcher where K: TryInto, >::Error: std::fmt::Debug, V: TryInto, >::Error: std::fmt::Debug, { HeaderExactMatcher::new(key, vec![value]) } /// Shorthand for [`HeaderExactMatcher::new`] supporting multi valued headers. pub fn headers(key: K, values: Vec) -> HeaderExactMatcher where K: TryInto, >::Error: std::fmt::Debug, V: TryInto, >::Error: std::fmt::Debug, { HeaderExactMatcher::new(key, values) } impl HeaderExactMatcher { pub fn new(key: K, values: Vec) -> Self where K: TryInto, >::Error: std::fmt::Debug, V: TryInto, >::Error: std::fmt::Debug, { let key = key.try_into().expect("Failed to convert to header name."); let values = values .into_iter() .map(|value| { value .try_into() .expect("Failed to convert to header value.") }) .collect(); Self(key, values) } } impl Match for HeaderExactMatcher { fn matches(&self, request: &Request) -> bool { let values = request .headers .get_all(&self.0) .iter() .filter_map(|v| v.to_str().ok()) .flat_map(|v| { v.split(',') .map(str::trim) .filter_map(|v| HeaderValue::from_str(v).ok()) }) .collect::>(); values == self.1 // order matters } } #[derive(Debug)] /// Match **exactly** the header name of a request. It checks that the /// header is present but does not validate the value. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::header; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// use wiremock::matchers::header_exists; /// let mock_server = MockServer::start().await; /// /// Mock::given(header_exists("custom")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let client = reqwest::Client::new(); /// let status = client /// .get(&mock_server.uri()) /// .header("custom", "header") /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct HeaderExistsMatcher(HeaderName); /// Shorthand for [`HeaderExistsMatcher::new`]. pub fn header_exists(key: K) -> HeaderExistsMatcher where K: TryInto, >::Error: std::fmt::Debug, { HeaderExistsMatcher::new(key) } impl HeaderExistsMatcher { pub fn new(key: K) -> Self where K: TryInto, >::Error: std::fmt::Debug, { let key = key.try_into().expect("Failed to convert to header name."); Self(key) } } impl Match for HeaderExistsMatcher { fn matches(&self, request: &Request) -> bool { request.headers.get(&self.0).is_some() } } #[derive(Debug)] /// Match the value of a header using a regular expression. /// If the header is multi-valued, all values must satisfy the regular expression. /// If the header is missing, the mock will not match. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::header_regex; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(header_regex("custom", "header")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let client = reqwest::Client::new(); /// let status = client.get(&mock_server.uri()) /// .header("custom", "headers are fun to match on with a regex") /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct HeaderRegexMatcher(HeaderName, Regex); /// Shorthand for [`HeaderRegexMatcher::new`]. pub fn header_regex(key: K, value: &str) -> HeaderRegexMatcher where K: TryInto, >::Error: std::fmt::Debug, { HeaderRegexMatcher::new(key, value) } impl HeaderRegexMatcher { pub fn new(key: K, value: &str) -> Self where K: TryInto, >::Error: std::fmt::Debug, { let key = key.try_into().expect("Failed to convert to header name."); let value_matcher = Regex::new(value).expect("Failed to create regex for value matcher"); Self(key, value_matcher) } } impl Match for HeaderRegexMatcher { fn matches(&self, request: &Request) -> bool { let mut it = request .headers .get_all(&self.0) .iter() .filter_map(|v| v.to_str().ok()) .peekable(); if it.peek().is_some() { it.all(|v| self.1.is_match(v)) } else { false } } } #[derive(Debug)] /// Match **exactly** the body of a request. /// /// ### Example (string): /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::body_string; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(body_string("hello world!")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let client = reqwest::Client::new(); /// let status = client.post(&mock_server.uri()) /// .body("hello world!") /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` /// /// ### Example (json): /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::body_json; /// use serde_json::json; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let expected_body = json!({ /// "hello": "world!" /// }); /// Mock::given(body_json(&expected_body)) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let client = reqwest::Client::new(); /// let status = client.post(&mock_server.uri()) /// .json(&expected_body) /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct BodyExactMatcher(Body); #[derive(Debug)] enum Body { Bytes(Vec), Json(Value), } impl BodyExactMatcher { /// Specify the expected body as a string. pub fn string>(body: T) -> Self { let body = body.into(); Self(Body::Bytes(body.into_bytes())) } /// Specify the expected body as a vector of bytes. pub fn bytes>>(body: T) -> Self { let body = body.into(); Self(Body::Bytes(body)) } /// Specify something JSON-serializable as the expected body. pub fn json(body: T) -> Self { let bytes = serde_json::to_vec(&body).expect("Failed to serialize JSON body"); Self::json_string(bytes) } /// Specify a JSON string as the expected body. pub fn json_string(body: impl AsRef<[u8]>) -> Self { let body = serde_json::from_slice(body.as_ref()).expect("Failed to parse JSON string"); Self(Body::Json(body)) } } /// Shorthand for [`BodyExactMatcher::string`]. pub fn body_string(body: T) -> BodyExactMatcher where T: Into, { BodyExactMatcher::string(body) } /// Shorthand for [`BodyExactMatcher::bytes`]. pub fn body_bytes(body: T) -> BodyExactMatcher where T: Into>, { BodyExactMatcher::bytes(body) } /// Shorthand for [`BodyExactMatcher::json`]. pub fn body_json(body: T) -> BodyExactMatcher where T: Serialize, { BodyExactMatcher::json(body) } /// Shorthand for [`BodyExactMatcher::json_string`]. pub fn body_json_string(body: impl AsRef<[u8]>) -> BodyExactMatcher { BodyExactMatcher::json_string(body) } impl Match for BodyExactMatcher { fn matches(&self, request: &Request) -> bool { match &self.0 { Body::Bytes(bytes) => request.body == *bytes, Body::Json(json) => { if let Ok(body) = serde_json::from_slice::(&request.body) { body == *json } else { false } } } } } #[derive(Debug)] /// Match part of the body of a request. /// /// ### Example (string): /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::body_string_contains; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(body_string_contains("hello world")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let client = reqwest::Client::new(); /// let status = client.post(&mock_server.uri()) /// .body("this is a hello world example!") /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct BodyContainsMatcher(Vec); impl BodyContainsMatcher { /// Specify the part of the body that should be matched as a string. pub fn string>(body: T) -> Self { Self(body.into().as_bytes().into()) } } /// Shorthand for [`BodyContainsMatcher::string`]. pub fn body_string_contains(body: T) -> BodyContainsMatcher where T: Into, { BodyContainsMatcher::string(body) } impl Match for BodyContainsMatcher { fn matches(&self, request: &Request) -> bool { let body = match str::from_utf8(&request.body) { Ok(body) => body.to_string(), Err(err) => { debug!("can't convert body from byte slice to string: {}", err); return false; } }; let part = match str::from_utf8(&self.0) { Ok(part) => part, Err(err) => { debug!( "can't convert expected part from byte slice to string: {}", err ); return false; } }; body.contains(part) } } #[derive(Debug)] /// Match part JSON body of a request. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::body_partial_json; /// use serde_json::json; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let expected_body = json!({ /// "hello": "world!" /// }); /// Mock::given(body_partial_json(&expected_body)) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let body = json!({ /// "hello": "world!", /// "foo": "bar" /// }); /// let client = reqwest::Client::new(); /// let status = client.post(&mock_server.uri()) /// .json(&body) /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct BodyPartialJsonMatcher(Value); impl BodyPartialJsonMatcher { /// Specify the part of the body that should be matched as a JSON value. pub fn json(body: T) -> Self { Self(serde_json::to_value(body).expect("Can't serialize to JSON")) } /// Specify the part of the body that should be matched as a string. pub fn json_string(body: impl AsRef) -> Self { Self(serde_json::from_str(body.as_ref()).expect("Can't deserialize JSON")) } } /// Shorthand for [`BodyPartialJsonMatcher::json`]. pub fn body_partial_json(body: T) -> BodyPartialJsonMatcher { BodyPartialJsonMatcher::json(body) } /// Shorthand for [`BodyPartialJsonMatcher::json_string`]. pub fn body_partial_json_string(body: impl AsRef) -> BodyPartialJsonMatcher { BodyPartialJsonMatcher::json_string(body) } impl Match for BodyPartialJsonMatcher { fn matches(&self, request: &Request) -> bool { if let Ok(body) = serde_json::from_slice::(&request.body) { let config = assert_json_diff::Config::new(CompareMode::Inclusive); assert_json_matches_no_panic(&body, &self.0, config).is_ok() } else { false } } } #[derive(Debug)] /// Match **exactly** the query parameter of a request. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::query_param; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(query_param("hello", "world")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let status = reqwest::get(format!("{}?hello=world", &mock_server.uri())) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct QueryParamExactMatcher(String, String); impl QueryParamExactMatcher { /// Specify the expected value for a query parameter. pub fn new, V: Into>(key: K, value: V) -> Self { let key = key.into(); let value = value.into(); Self(key, value) } } /// Shorthand for [`QueryParamExactMatcher::new`]. pub fn query_param(key: K, value: V) -> QueryParamExactMatcher where K: Into, V: Into, { QueryParamExactMatcher::new(key, value) } impl Match for QueryParamExactMatcher { fn matches(&self, request: &Request) -> bool { request .url .query_pairs() .any(|q| q.0 == self.0.as_str() && q.1 == self.1.as_str()) } } #[derive(Debug)] /// Match when a query parameter contains the specified value as a substring. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::query_param_contains; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// // It matches since "world" is a substring of "some_world". /// Mock::given(query_param_contains("hello", "world")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let status = reqwest::get(format!("{}?hello=some_world", &mock_server.uri())) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct QueryParamContainsMatcher(String, String); impl QueryParamContainsMatcher { /// Specify the substring that the query parameter should contain. pub fn new, V: Into>(key: K, value: V) -> Self { let key = key.into(); let value = value.into(); Self(key, value) } } /// Shorthand for [`QueryParamContainsMatcher::new`]. pub fn query_param_contains(key: K, value: V) -> QueryParamContainsMatcher where K: Into, V: Into, { QueryParamContainsMatcher::new(key, value) } impl Match for QueryParamContainsMatcher { fn matches(&self, request: &Request) -> bool { request .url .query_pairs() .any(|q| q.0 == self.0.as_str() && q.1.contains(self.1.as_str())) } } #[derive(Debug)] /// Only match requests that do **not** contain a specified query parameter. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::{method, query_param_is_missing}; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(method("GET")) /// .and(query_param_is_missing("unexpected")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Act /// let ok_status = reqwest::get(mock_server.uri().to_string()) /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(ok_status, 200); /// /// // Act /// let err_status = reqwest::get(format!("{}?unexpected=foo", mock_server.uri())) /// .await. /// unwrap().status(); /// /// // Assert /// assert_eq!(err_status, 404); /// } /// ``` pub struct QueryParamIsMissingMatcher(String); impl QueryParamIsMissingMatcher { /// Specify the query parameter that is expected to not exist. pub fn new>(key: K) -> Self { let key = key.into(); Self(key) } } /// Shorthand for [`QueryParamIsMissingMatcher::new`]. pub fn query_param_is_missing(key: K) -> QueryParamIsMissingMatcher where K: Into, { QueryParamIsMissingMatcher::new(key) } impl Match for QueryParamIsMissingMatcher { fn matches(&self, request: &Request) -> bool { !request.url.query_pairs().any(|(k, _)| k == self.0) } } /// Match an incoming request if its body is encoded as JSON and can be deserialized /// according to the specified schema. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::body_json_schema; /// use serde_json::json; /// use serde::{Deserialize, Serialize}; /// /// // The schema we expect the body to conform to. /// #[derive(Deserialize, Serialize)] /// struct Greeting { /// hello: String, /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(body_json_schema::) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// // Both JSON objects have the same fields, /// // therefore they'll match. /// let success_cases = vec![ /// json!({"hello": "world!"}), /// json!({"hello": "everyone!"}), /// ]; /// let client = reqwest::Client::new(); /// for case in &success_cases { /// let status = client.post(&mock_server.uri()) /// .json(case) /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// /// // This JSON object cannot be deserialized as `Greeting` /// // because it does not have the `hello` field. /// // It won't match. /// let failure_case = json!({"world": "hello!"}); /// let status = client.post(&mock_server.uri()) /// .json(&failure_case) /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 404); /// } /// ``` pub fn body_json_schema(request: &Request) -> bool where for<'de> T: serde::de::Deserialize<'de>, { serde_json::from_slice::(&request.body).is_ok() } #[derive(Debug)] /// Match an incoming request if it contains the basic authentication header with the username and password /// as per [RFC 7617](https://datatracker.ietf.org/doc/html/rfc7617). /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::basic_auth; /// use serde::{Deserialize, Serialize}; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// /// Mock::given(basic_auth("username", "password")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// let client = reqwest::Client::new(); /// /// // Act /// let status = client /// .get(&mock_server.uri()) /// .header("Authorization", "Basic dXNlcm5hbWU6cGFzc3dvcmQ=") /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct BasicAuthMatcher(HeaderExactMatcher); impl BasicAuthMatcher { /// Match basic authentication header using the given username and password. pub fn from_credentials(username: impl AsRef, password: impl AsRef) -> Self { Self::from_token(BASE64_STANDARD.encode(format!( "{}:{}", username.as_ref(), password.as_ref() ))) } /// Match basic authentication header with the exact token given. pub fn from_token(token: impl AsRef) -> Self { Self(header( "Authorization", &*format!("Basic {}", token.as_ref()), )) } } /// Shorthand for [`BasicAuthMatcher::from_credentials`]. pub fn basic_auth(username: U, password: P) -> BasicAuthMatcher where U: AsRef, P: AsRef, { BasicAuthMatcher::from_credentials(username, password) } impl Match for BasicAuthMatcher { fn matches(&self, request: &Request) -> bool { self.0.matches(request) } } #[derive(Debug)] /// Match an incoming request if it contains the bearer token header /// as per [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750). /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::bearer_token; /// use serde::{Deserialize, Serialize}; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(bearer_token("token")) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// let client = reqwest::Client::new(); /// /// // Act /// let status = client.get(&mock_server.uri()) /// .header("Authorization", "Bearer token") /// .send() /// .await /// .unwrap() /// .status(); /// /// // Assert /// assert_eq!(status, 200); /// } /// ``` pub struct BearerTokenMatcher(HeaderExactMatcher); impl BearerTokenMatcher { pub fn from_token(token: impl AsRef) -> Self { Self(header( "Authorization", &*format!("Bearer {}", token.as_ref()), )) } } impl Match for BearerTokenMatcher { fn matches(&self, request: &Request) -> bool { self.0.matches(request) } } /// Shorthand for [`BearerTokenMatcher::from_token`]. pub fn bearer_token(token: T) -> BearerTokenMatcher where T: AsRef, { BearerTokenMatcher::from_token(token) } wiremock-0.6.5/src/mock.rs000064400000000000000000000733411046102023000135520ustar 00000000000000use crate::respond::{Respond, RespondErr}; use crate::{ErrorResponse, MockGuard, MockServer, Request, ResponseTemplate}; use std::fmt::{Debug, Formatter}; use std::ops::{ Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, }; /// Anything that implements `Match` can be used to constrain when a [`Mock`] is activated. /// /// `Match` can be used to extend the set of matchers provided out-of-the-box by `wiremock` to /// cater to your specific testing needs: /// ```rust /// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate}; /// use wiremock::matchers::HeaderExactMatcher; /// use std::convert::TryInto; /// /// // Check that a header with the specified name exists and its value has an odd length. /// pub struct OddHeaderMatcher(http::HeaderName); /// /// impl Match for OddHeaderMatcher { /// fn matches(&self, request: &Request) -> bool { /// match request.headers.get(&self.0) { /// // We are ignoring multi-valued headers for simplicity /// Some(value) => value.to_str().unwrap_or_default().len() % 2 == 1, /// None => false /// } /// } /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(OddHeaderMatcher("custom".try_into().unwrap())) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// let client = reqwest::Client::new(); /// /// // Even length /// let status = client /// .get(&mock_server.uri()) /// .header("custom", "even") /// .send() /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// /// // Odd length /// let status = client /// .get(&mock_server.uri()) /// .header("custom", "odd") /// .send() /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// } /// ``` /// /// Anonymous functions that take a reference to a [`Request`] as input and return a boolean /// as output automatically implement the `Match` trait. /// /// The previous example could be rewritten as follows: /// ```rust /// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate}; /// use wiremock::matchers::HeaderExactMatcher; /// use std::convert::TryInto; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let header_name = http::HeaderName::from_static("custom"); /// // Check that a header with the specified name exists and its value has an odd length. /// let matcher = move |request: &Request| { /// match request.headers.get(&header_name) { /// Some(value) => value.to_str().unwrap_or_default().len() % 2 == 1, /// None => false /// } /// }; /// /// Mock::given(matcher) /// .respond_with(ResponseTemplate::new(200)) /// .mount(&mock_server) /// .await; /// /// let client = reqwest::Client::new(); /// /// // Even length /// let status = client /// .get(&mock_server.uri()) /// .header("custom", "even") /// .send() /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// /// // Odd length /// let status = client /// .get(&mock_server.uri()) /// .header("custom", "odd") /// .send() /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// } /// ``` pub trait Match: Send + Sync { /// Given a reference to a [`Request`], determine if it should match or not given /// a specific criterion. fn matches(&self, request: &Request) -> bool; } /// Wrapper around a `Match` trait object. /// /// We need the wrapper to provide a (fake) implementation of `Debug`, /// thus allowing us to pass this struct around as a `bastion` message. /// This is because Rust's closures do not implement `Debug`. /// /// We wouldn't need this if `bastion` didn't require `Debug` as a trait bound for its Message trait /// or if Rust automatically implemented `Debug` for closures. pub(crate) struct Matcher(Box); impl Match for Matcher { fn matches(&self, request: &Request) -> bool { self.0.matches(request) } } impl Debug for Matcher { fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { // Dummy `Debug` implementation to allow us to pass `Matcher` as a message in `bastion`. // It's needed because closures do not implement `Debug` and we really want to enable // closures as matchers from an API perspective. // Might re-think this in the future. Ok(()) } } /// Given a set of matchers, a `Mock` instructs an instance of [`MockServer`] to return a pre-determined response if the matching conditions are satisfied. /// /// `Mock`s have to be mounted (or registered) with a [`MockServer`] to become effective. /// You can use: /// /// - [`MockServer::register`] or [`Mock::mount`] to activate a **global** `Mock`; /// - [`MockServer::register_as_scoped`] or [`Mock::mount_as_scoped`] to activate a **scoped** `Mock`. /// /// Check the respective documentations for more details (or look at the following examples!). /// /// # Example (using [`register`]): /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200); /// /// let mock = Mock::given(method("GET")).respond_with(response.clone()); /// // Registering the mock with the mock server - it's now effective! /// mock_server.register(mock).await; /// /// // We won't register this mock instead. /// let unregistered_mock = Mock::given(method("POST")).respond_with(response); /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// /// // This would have matched `unregistered_mock`, but we haven't registered it! /// // Hence it returns a 404, the default response when no mocks matched on the mock server. /// let client = reqwest::Client::new(); /// let status = client.post(&mock_server.uri()) /// .send() /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// # Example (using [`mount`]): /// /// If you prefer a fluent style, you can use the [`mount`] method on the `Mock` itself /// instead of [`register`]. /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .up_to_n_times(1) /// // Mounting the mock on the mock server - it's now effective! /// .mount(&mock_server) /// .await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// } /// ``` /// /// # Example (using [`mount_as_scoped`]): /// /// Sometimes you will need a `Mock` to be active within the scope of a function, but not any longer. /// You can use [`Mock::mount_as_scoped`] to precisely control how long a `Mock` stays active. /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// async fn my_test_helper(mock_server: &MockServer) { /// let mock_guard = Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .expect(1) /// .named("my_test_helper GET /") /// .mount_as_scoped(mock_server) /// .await; /// /// reqwest::get(&mock_server.uri()) /// .await /// .unwrap(); /// /// // `mock_guard` is dropped, expectations are verified! /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// my_test_helper(&mock_server).await; /// /// // Act /// /// // This would have returned 200 if the `Mock` in /// // `my_test_helper` had not been scoped. /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// [`register`]: MockServer::register /// [`mount`]: Mock::mount /// [`mount_as_scoped`]: Mock::mount_as_scoped #[must_use = "`Mock`s have to be mounted or registered with a `MockServer` to become effective"] pub struct Mock { pub(crate) matchers: Vec, pub(crate) response: Result, Box>, /// Maximum number of times (inclusive) we should return a response from this Mock on /// matching requests. /// If `None`, there is no cap and we will respond to all incoming matching requests. /// If `Some(max_n_matches)`, when `max_n_matches` matching incoming requests have been processed, /// [`crate::mounted_mock::MountedMock::matches`] should start returning `false`, regardless of the incoming request. pub(crate) max_n_matches: Option, /// Allows prioritizing a Mock over another one. /// `1` is the highest priority, `255` the lowest, default to `5`. /// When priority is the same, it fallbacks to insertion order. pub(crate) priority: u8, /// The friendly mock name specified by the user. /// Used in diagnostics and error messages if the mock expectations are not satisfied. pub(crate) name: Option, /// The expectation is satisfied if the number of incoming requests falls within `expectation_range`. pub(crate) expectation_range: Times, } /// A fluent builder to construct a [`Mock`] instance given matchers and a [`ResponseTemplate`]. #[derive(Debug)] pub struct MockBuilder { pub(crate) matchers: Vec, } impl Mock { /// Start building a [`Mock`] specifying the first matcher. /// /// It returns an instance of [`MockBuilder`]. pub fn given(matcher: M) -> MockBuilder { MockBuilder { matchers: vec![Matcher(Box::new(matcher))], } } /// Specify an upper limit to the number of times you would like this [`Mock`] to respond to /// incoming requests that satisfy the conditions imposed by your [`matchers`]. /// /// ### Example: /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// // Default behaviour will have this Mock responding to any incoming request /// // that satisfied our matcher (e.g. being a GET request). /// // We can opt out of the default behaviour by setting a cap on the number of /// // matching requests this Mock should respond to. /// // /// // In this case, once one matching request has been received, the mock will stop /// // matching additional requests and you will receive a 404 if no other mock /// // matches on those requests. /// .up_to_n_times(1) /// .mount(&mock_server) /// .await; /// /// // Act /// /// // The first request matches, as expected. /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// /// // The second request does NOT match given our `up_to_n_times(1)` setting. /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// [`matchers`]: crate::matchers pub fn up_to_n_times(mut self, n: u64) -> Mock { assert!(n > 0, "n must be strictly greater than 0!"); self.max_n_matches = Some(n); self } /// Specify a priority for this [`Mock`]. /// Use this when you mount many [`Mock`] in a [`MockServer`] /// and those mocks have interlaced request matching conditions /// e.g. `mock A` accepts path `/abcd` and `mock B` a path regex `[a-z]{4}` /// It is recommended to set the highest priority (1) for mocks with exact conditions (`mock A` in this case) /// `1` is the highest priority, `255` the lowest, default to `5` /// If two mocks have the same priority, priority is defined by insertion order (first one mounted has precedence over the others). /// /// ### Example: /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::{method, path, path_regex}; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(method("GET")) /// .and(path("abcd")) /// .respond_with(ResponseTemplate::new(200)) /// .with_priority(1) // highest priority /// .mount(&mock_server) /// .await; /// /// Mock::given(method("GET")) /// .and(path_regex("[a-z]{4}")) /// .respond_with(ResponseTemplate::new(201)) /// .with_priority(2) /// .mount(&mock_server) /// .await; /// /// // Act /// /// // The request with highest priority, as expected. /// let status = reqwest::get(&format!("{}/abcd", mock_server.uri())) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// } /// ``` /// /// [`matchers`]: crate::matchers pub fn with_priority(mut self, p: u8) -> Mock { assert!(p > 0, "priority must be strictly greater than 0!"); self.priority = p; self } /// Set an expectation on the number of times this [`Mock`] should match in the current /// test case. /// Expectations are verified when the [`MockServer`] is shutting down: if the expectation /// is not satisfied, the [`MockServer`] will panic and the `error_message` is shown. /// /// By default, no expectation is set for [`Mock`]s. /// /// ### When is this useful? /// /// `expect` can turn out handy when you'd like to verify that a certain side-effect has /// (or has not!) taken place. /// /// For example: /// - check that a 3rd party notification API (e.g. email service) is called when an event /// in your application is supposed to trigger a notification; /// - check that a 3rd party API is NOT called when the response of a call is expected /// to be retrieved from a cache (`.expect(0)`). /// /// This technique is also called [spying](https://martinfowler.com/bliki/TestDouble.html). /// /// ### Example: /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .up_to_n_times(2) /// // We expect the mock to be called at least once. /// // If that does not happen, the `MockServer` will panic on shutdown, /// // causing the whole test to fail. /// .expect(1..) /// // We assign a name to the mock - it will be shown in error messages /// // if our expectation is not verified! /// .named("Root GET") /// .mount(&mock_server) /// .await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// /// // Assert /// // We made at least one matching request, the expectation is satisfied. /// // The `MockServer` will shutdown peacefully, without panicking. /// } /// ``` pub fn expect>(mut self, r: T) -> Self { let range = r.into(); self.expectation_range = range; self } /// Assign a name to your mock. /// /// The mock name will be used in error messages (e.g. if the mock expectation /// is not satisfied) and debug logs to help you identify what failed. /// /// ### Example: /// /// ```should_panic /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// // We have two mocks in the same test - how do we find out /// // which one failed when the test panics? /// // Assigning a name to each mock with `named` gives us better error /// // messages and makes it much easier to debug why a test is failing! /// Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .up_to_n_times(2) /// .expect(1..) /// // We assign a name to the mock - it will be shown in error messages /// // if our expectation is not verified! /// .named("Root GET") /// .mount(&mock_server) /// .await; /// /// Mock::given(method("POST")) /// .respond_with(ResponseTemplate::new(200)) /// .up_to_n_times(2) /// .expect(1..) /// // We assign a name to the mock - it will be shown in error messages /// // if our expectation is not verified! /// .named("Root POST") /// .mount(&mock_server) /// .await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// /// // Assert /// // We did not make a POST request, therefore the expectation on `Root POST` /// // is not satisfied and the test will panic. /// } /// ``` pub fn named>(mut self, mock_name: T) -> Self { self.name = Some(mock_name.into()); self } /// Mount a [`Mock`] on an instance of [`MockServer`]. /// The [`Mock`] will remain active until [`MockServer`] is shut down. If you want to control or limit how /// long your [`Mock`] stays active, check out [`Mock::mount_as_scoped`]. /// /// Be careful! [`Mock`]s are not effective until they are [`mount`]ed or [`register`]ed on a [`MockServer`]. /// [`mount`] is an asynchronous method, make sure to `.await` it! /// /// [`register`]: MockServer::register /// [`mount`]: Mock::mount pub async fn mount(self, server: &MockServer) { server.register(self).await; } /// Mount a [`Mock`] as **scoped** on an instance of [`MockServer`]. /// /// When using [`mount`], your [`Mock`]s will be active until the [`MockServer`] is shut down. /// When using `mount_as_scoped`, your [`Mock`]s will be active as long as the returned [`MockGuard`] is not dropped. /// When the returned [`MockGuard`] is dropped, [`MockServer`] will verify that the expectations set on the scoped [`Mock`] were /// verified - if not, it will panic. /// /// `mount_as_scoped` is the ideal solution when you need a [`Mock`] within a test helper /// but you do not want it to linger around after the end of the function execution. /// /// # Limitations /// /// When expectations of a scoped [`Mock`] are not verified, it will trigger a panic - just like a normal [`Mock`]. /// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's [`Drop`] trait, /// the panic message will not include the filename and the line location /// where the corresponding [`MockGuard`] was dropped - it will point into `wiremock`'s source code. /// /// This can be an issue when you are using more than one scoped [`Mock`] in a single test - which of them panicked? /// To improve your debugging experience it is strongly recommended to use [`Mock::named`] to assign a unique /// identifier to your scoped [`Mock`]s, which will in turn be referenced in the panic message if their expectations are /// not met. /// /// # Example: /// /// - The behaviour of the scoped mock is invisible outside of `my_test_helper`. /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// async fn my_test_helper(mock_server: &MockServer) { /// let mock_guard = Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .expect(1) /// .named("my_test_helper GET /") /// .mount_as_scoped(mock_server) /// .await; /// /// reqwest::get(&mock_server.uri()) /// .await /// .unwrap(); /// /// // `mock_guard` is dropped, expectations are verified! /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// my_test_helper(&mock_server).await; /// /// // Act /// /// // This would have returned 200 if the `Mock` in /// // `my_test_helper` had not been scoped. /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// - The expectations for the scoped mock are not verified, it panics at the end of `my_test_helper`. /// /// ```rust,should_panic /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// async fn my_test_helper(mock_server: &MockServer) { /// let mock_guard = Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .expect(1) /// .named("my_test_helper GET /") /// .mount_as_scoped(mock_server) /// .await; /// // `mock_guard` is dropped, expectations are NOT verified! /// // Panic! /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// my_test_helper(&mock_server).await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// [`mount`]: Mock::mount pub async fn mount_as_scoped(self, server: &MockServer) -> MockGuard { server.register_as_scoped(self).await } /// Given a [`Request`] build an instance a [`ResponseTemplate`] using /// the responder associated with the `Mock`. pub(crate) fn response_template( &self, request: &Request, ) -> Result { match &self.response { Ok(responder) => Ok(responder.respond(request)), Err(responder_err) => Err(responder_err.respond_err(request)), } } } impl MockBuilder { /// Add another request matcher to the mock you are building. /// /// **All** specified [`matchers`] must match for the overall [`Mock`] to match an incoming request. /// /// [`matchers`]: crate::matchers pub fn and(mut self, matcher: M) -> Self { self.matchers.push(Matcher(Box::new(matcher))); self } /// Establish what [`ResponseTemplate`] should be used to generate a response when an incoming /// request matches. /// /// `respond_with` finalises the `MockBuilder` and returns you a [`Mock`] instance, ready to /// be [`register`]ed or [`mount`]ed on a [`MockServer`]! /// /// [`register`]: MockServer::register /// [`mount`]: Mock::mount pub fn respond_with(self, responder: R) -> Mock { Mock { matchers: self.matchers, response: Ok(Box::new(responder)), max_n_matches: None, priority: 5, name: None, expectation_range: Times(TimesEnum::Unbounded(RangeFull)), } } /// Instead of response with an HTTP reply, return a Rust error. /// /// This can simulate lower level errors, e.g., a [`ConnectionReset`] IO Error. /// /// [`ConnectionReset`]: std::io::ErrorKind::ConnectionReset pub fn respond_with_err(self, responder_err: R) -> Mock { Mock { matchers: self.matchers, response: Err(Box::new(responder_err)), max_n_matches: None, priority: 5, name: None, expectation_range: Times(TimesEnum::Unbounded(RangeFull)), } } } /// Specify how many times we expect a [`Mock`] to match via [`expect`]. /// It is used to set expectations on the usage of a [`Mock`] in a test case. /// /// You can either specify an exact value, e.g. /// ```rust /// use wiremock::Times; /// /// let times: Times = 10.into(); /// ``` /// or a range /// ```rust /// use wiremock::Times; /// /// // Between 10 and 15 (not included) times /// let times: Times = (10..15).into(); /// // Between 10 and 15 (included) times /// let times: Times = (10..=15).into(); /// // At least 10 times /// let times: Times = (10..).into(); /// // Strictly less than 15 times /// let times: Times = (..15).into(); /// // Strictly less than 16 times /// let times: Times = (..=15).into(); /// ``` /// /// [`expect`]: Mock::expect #[derive(Clone, Debug)] pub struct Times(TimesEnum); impl Times { pub(crate) fn contains(&self, n_calls: u64) -> bool { match &self.0 { TimesEnum::Exact(e) => e == &n_calls, TimesEnum::Unbounded(r) => r.contains(&n_calls), TimesEnum::Range(r) => r.contains(&n_calls), TimesEnum::RangeFrom(r) => r.contains(&n_calls), TimesEnum::RangeTo(r) => r.contains(&n_calls), TimesEnum::RangeToInclusive(r) => r.contains(&n_calls), TimesEnum::RangeInclusive(r) => r.contains(&n_calls), } } } impl std::fmt::Display for Times { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.0 { TimesEnum::Exact(e) => write!(f, "== {}", e), TimesEnum::Unbounded(_) => write!(f, "0 <= x"), TimesEnum::Range(r) => write!(f, "{} <= x < {}", r.start, r.end), TimesEnum::RangeFrom(r) => write!(f, "{} <= x", r.start), TimesEnum::RangeTo(r) => write!(f, "0 <= x < {}", r.end), TimesEnum::RangeToInclusive(r) => write!(f, "0 <= x <= {}", r.end), TimesEnum::RangeInclusive(r) => write!(f, "{} <= x <= {}", r.start(), r.end()), } } } // Implementation notes: this has gone through a couple of iterations before landing to // what you see now. // // The original draft had Times itself as an enum with two variants (Exact and Range), with // the Range variant generic over `R: RangeBounds`. // // We switched to a generic struct wrapper around a private `R: RangeBounds` when we realised // that you would have had to specify a range type when creating the Exact variant // (e.g. as you do for `Option` when creating a `None` variant). // // We achieved the same functionality with a struct wrapper, but exact values had to converted // to ranges with a single element (e.g. 15 -> 15..16). // Not the most expressive representation, but we would have lived with it. // // We changed once again when we started to update our `MockActor`: we are storing all `Mock`s // in a vector. Being generic over `R`, the range type leaked into the overall `Mock` (and `MountedMock`) // type, thus making those generic as well over `R`. // To store them in a vector all mocks would have had to use the same range internally, which is // obviously an unreasonable restrictions. // At the same time, we can't have a Box> because `contains` is a generic // method hence the requirements for object safety are not satisfied. // // Thus we ended up creating this master enum that wraps all range variants with the addition // of the Exact variant. // If you can do better, please submit a PR. // We keep them enum private to the crate to allow for future refactoring. #[derive(Clone, Debug)] pub(crate) enum TimesEnum { Exact(u64), Unbounded(RangeFull), Range(Range), RangeFrom(RangeFrom), RangeTo(RangeTo), RangeToInclusive(RangeToInclusive), RangeInclusive(RangeInclusive), } impl From for Times { fn from(x: u64) -> Self { Times(TimesEnum::Exact(x)) } } impl From for Times { fn from(x: RangeFull) -> Self { Times(TimesEnum::Unbounded(x)) } } // A quick macro to help easing the implementation pain. macro_rules! impl_from_for_range { ($type_name:ident) => { impl From<$type_name> for Times { fn from(r: $type_name) -> Self { Times(TimesEnum::$type_name(r)) } } }; } impl_from_for_range!(Range); impl_from_for_range!(RangeTo); impl_from_for_range!(RangeFrom); impl_from_for_range!(RangeInclusive); impl_from_for_range!(RangeToInclusive); wiremock-0.6.5/src/mock_server/bare_server.rs000064400000000000000000000316751046102023000174430ustar 00000000000000use crate::mock_server::hyper::run_server; use crate::mock_set::MockId; use crate::mock_set::MountedMockSet; use crate::request::BodyPrintLimit; use crate::{ErrorResponse, Request, mock::Mock, verification::VerificationOutcome}; use http_body_util::Full; use hyper::body::Bytes; use std::fmt::{Debug, Write}; use std::net::{SocketAddr, TcpListener, TcpStream}; use std::pin::pin; use std::sync::Arc; use std::sync::atomic::AtomicBool; use tokio::sync::Notify; use tokio::sync::RwLock; /// An HTTP web-server running in the background to behave as one of your dependencies using `Mock`s /// for testing purposes. /// /// `BareMockServer` is the actual mock server behind the publicly-exposed `MockServer`, which /// is instead a thin facade over a `BareMockServer` retrieved from a pool - see `get_pooled_server` /// for more details. pub(crate) struct BareMockServer { state: Arc>, server_address: SocketAddr, // When `_shutdown_trigger` gets dropped the listening server terminates gracefully. _shutdown_trigger: tokio::sync::watch::Sender<()>, } /// The elements of [`BareMockServer`] that are affected by each incoming request. /// By bundling them together, we can expose a unified `handle_request` that ensures /// they are kept in sync without having to leak logic across multiple corners of the `wiremock`'s codebase. pub(super) struct MockServerState { mock_set: MountedMockSet, received_requests: Option>, body_print_limit: BodyPrintLimit, } impl MockServerState { pub(super) async fn handle_request( &mut self, request: Request, ) -> Result<(hyper::Response>, Option), ErrorResponse> { // If request recording is enabled, record the incoming request // by adding it to the `received_requests` stack if let Some(received_requests) = &mut self.received_requests { received_requests.push(request.clone()); } self.mock_set.handle_request(request).await } } impl BareMockServer { /// Start a new instance of a `BareMockServer` listening on the specified /// [`TcpListener`]. pub(super) async fn start( listener: TcpListener, request_recording: RequestRecording, body_print_limit: BodyPrintLimit, ) -> Self { let (shutdown_trigger, shutdown_receiver) = tokio::sync::watch::channel(()); let received_requests = match request_recording { RequestRecording::Enabled => Some(Vec::new()), RequestRecording::Disabled => None, }; let state = Arc::new(RwLock::new(MockServerState { mock_set: MountedMockSet::new(body_print_limit), received_requests, body_print_limit, })); let server_address = listener .local_addr() .expect("Failed to get server address."); let server_state = state.clone(); std::thread::spawn(move || { let server_future = run_server(listener, server_state, shutdown_receiver); let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("Cannot build local tokio runtime"); runtime.block_on(server_future); }); for _ in 0..40 { if TcpStream::connect_timeout(&server_address, std::time::Duration::from_millis(25)) .is_ok() { break; } tokio::time::sleep(std::time::Duration::from_millis(25)).await; } Self { state, server_address, _shutdown_trigger: shutdown_trigger, } } /// Register a `Mock` on an instance of `BareMockServer`. /// /// Be careful! `Mock`s are not effective until they are `mount`ed or `register`ed on a /// `BareMockServer`. pub(crate) async fn register(&self, mock: Mock) { self.state.write().await.mock_set.register(mock); } /// Register a **scoped** `Mock` on an instance of `MockServer`. /// /// When using `register`, your `Mock`s will be active until the `MockServer` is shut down. /// When using `register_as_scoped`, your `Mock`s will be active as long as the returned `MockGuard` is not dropped. /// When the returned `MockGuard` is dropped, `MockServer` will verify that the expectations set on the scoped `Mock` were /// verified - if not, it will panic. pub async fn register_as_scoped(&self, mock: Mock) -> MockGuard { let (notify, mock_id) = self.state.write().await.mock_set.register(mock); MockGuard { notify, mock_id, server_state: self.state.clone(), } } /// Drop all mounted `Mock`s from an instance of `BareMockServer`. /// Delete all recorded requests. /// /// It *must* be called if you plan to reuse a `BareMockServer` instance (i.e. in our /// `MockServerPoolManager`). pub(crate) async fn reset(&self) { let mut state = self.state.write().await; state.mock_set.reset(); if let Some(received_requests) = &mut state.received_requests { received_requests.clear(); } } /// Verify that all mounted `Mock`s on this instance of `BareMockServer` have satisfied /// their expectations on their number of invocations. pub(crate) async fn verify(&self) -> VerificationOutcome { let mock_set = &self.state.read().await.mock_set; mock_set.verify_all() } /// Return the base uri of this running instance of `BareMockServer`, e.g. `http://127.0.0.1:4372`. /// /// Use this method to compose uris when interacting with this instance of `BareMockServer` via /// an HTTP client. pub(crate) fn uri(&self) -> String { format!("http://{}", self.server_address) } /// Return the socket address of this running instance of `BareMockServer`, e.g. `127.0.0.1:4372`. /// /// Use this method to interact with the `BareMockServer` using `TcpStream`s. pub(crate) fn address(&self) -> &SocketAddr { &self.server_address } /// Return the body print limit of this running instance of `BareMockServer`. pub(crate) async fn body_print_limit(&self) -> BodyPrintLimit { self.state.read().await.body_print_limit } /// Return a vector with all the requests received by the `BareMockServer` since it started. /// If no request has been served, it returns an empty vector. /// /// If request recording was disabled, it returns `None`. pub(crate) async fn received_requests(&self) -> Option> { let state = self.state.read().await; state.received_requests.clone() } } impl Debug for BareMockServer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "BareMockServer {{ address: {} }}", self.address()) } } pub(super) enum RequestRecording { Enabled, Disabled, } /// You get a `MockGuard` when registering a **scoped** [`Mock`] using [`MockServer::register_as_scoped`](crate::MockServer::register_as_scoped) /// or [`Mock::mount_as_scoped`]. /// /// When the [`MockGuard`] is dropped, the [`MockServer`](crate::MockServer) verifies that the expectations set on the /// scoped [`Mock`] were verified - if not, it will panic. /// /// # Limitations /// /// When expectations of a scoped [`Mock`] are not verified, it will trigger a panic - just like a normal [`Mock`]. /// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's `Drop` trait, /// the panic message will not include the filename and the line location /// where the corresponding `MockGuard` was dropped - it will point into `wiremock`'s source code. /// /// This can be an issue when you are using more than one scoped [`Mock`] in a single test - which of them panicked? /// To improve your debugging experience it is strongly recommended to use [`Mock::named`] to assign a unique /// identifier to your scoped [`Mock`]s, which will in turn be referenced in the panic message if their expectations are /// not met. #[must_use = "All *_scoped methods return a `MockGuard`. This guard MUST be bound to a variable (e.g. _mock_guard), \ otherwise the mock will immediately be unmounted (and its expectations checked). Check `wiremock`'s documentation on scoped mocks for more details."] pub struct MockGuard { mock_id: MockId, server_state: Arc>, notify: Arc<(Notify, AtomicBool)>, } impl MockGuard { /// Return all the requests that have been matched by the corresponding /// scoped [`Mock`] since it was mounted. /// The requests are returned in the order they were received. /// /// It returns an empty vector if no request has been matched. pub async fn received_requests(&self) -> Vec { let state = self.server_state.read().await; let (mounted_mock, _) = &state.mock_set[self.mock_id]; mounted_mock.received_requests() } /// This method doesn't return until the expectations set on the /// corresponding scoped [`Mock`] are satisfied. /// /// It can be useful when you are testing asynchronous flows (e.g. a /// message queue consumer) and you don't have a good event that can be used /// to trigger the verification of the expectations set on the scoped [`Mock`]. /// /// # Timeouts /// /// There is no default timeout for this method, so it will end up waiting /// **forever** if your expectations are never met. Probably not what you /// want. /// /// It is strongly recommended that you set your own timeout using the /// appropriate timers from your chosen async runtime. /// Since `wiremock` is runtime-agnostic, it cannot provide a default /// timeout mechanism that would work for all users. /// /// ```rust /// use std::time::Duration; /// use wiremock::{Mock, MockServer, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[tokio::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200); /// let mock = Mock::given(method("GET")).respond_with(response); /// let mock_guard = mock_server.register_as_scoped(mock).await; /// /// // Act /// let waiter = mock_guard.wait_until_satisfied(); /// // Here we wrap the waiter in a tokio timeout /// let outcome = tokio::time::timeout(Duration::from_millis(10), waiter).await; /// /// // Assert /// assert!(outcome.is_err()); /// } /// ``` pub async fn wait_until_satisfied(&self) { let (notify, flag) = &*self.notify; let mut notification = pin!(notify.notified()); // listen for events of satisfaction. notification.as_mut().enable(); // check if satisfaction has previously been recorded if flag.load(std::sync::atomic::Ordering::Acquire) { return; } // await event notification.await; } } impl Drop for MockGuard { fn drop(&mut self) { let future = async move { let MockGuard { mock_id, server_state, .. } = self; let mut state = server_state.write().await; let report = state.mock_set.verify(*mock_id); if !report.is_satisfied() { let received_requests_message = if let Some(received_requests) = &state.received_requests { if received_requests.is_empty() { "The server did not receive any request.".into() } else { received_requests.iter().enumerate().fold( "Received requests:\n".to_string(), |mut message, (index, request)| { _ = write!(message, "- Request #{}\n\t", index + 1,); _ = request.print_with_limit(&mut message, state.body_print_limit); message }, ) } } else { "Enable request recording on the mock server to get the list of incoming requests as part of the panic message.".into() }; let verifications_error = format!("- {}\n", report.error_message()); let error_message = format!( "Verification failed for a scoped mock:\n{}\n{}", verifications_error, received_requests_message ); if std::thread::panicking() { log::debug!("{}", &error_message); } else { panic!("{}", &error_message); } } else { state.mock_set.deactivate(*mock_id); } }; futures::executor::block_on(future); } } wiremock-0.6.5/src/mock_server/builder.rs000064400000000000000000000104701046102023000165600ustar 00000000000000use crate::MockServer; use crate::mock_server::bare_server::{BareMockServer, RequestRecording}; use crate::mock_server::exposed_server::InnerServer; use crate::request::{BODY_PRINT_LIMIT, BodyPrintLimit}; use std::env; use std::net::TcpListener; /// A builder providing a fluent API to assemble a [`MockServer`] step-by-step. /// Use [`MockServer::builder`] to get started. pub struct MockServerBuilder { listener: Option, record_incoming_requests: bool, body_print_limit: BodyPrintLimit, } impl MockServerBuilder { pub(super) fn new() -> Self { let body_print_limit = match env::var("WIREMOCK_BODY_PRINT_LIMIT") .ok() .and_then(|x| x.parse::().ok()) { Some(limit) => BodyPrintLimit::Limited(limit), None => BodyPrintLimit::Limited(BODY_PRINT_LIMIT), }; Self { listener: None, record_incoming_requests: true, body_print_limit, } } /// Each instance of [`MockServer`] is, by default, running on a random /// port available on your local machine. /// With `MockServerBuilder::listener` you can choose to start the `MockServer` /// instance on a specific port you have already bound. /// /// ### Example: /// ```rust /// use wiremock::MockServer; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); /// let expected_server_address = listener /// .local_addr() /// .expect("Failed to get server address."); /// /// // Act /// let mock_server = MockServer::builder().listener(listener).start().await; /// /// // Assert /// assert_eq!(&expected_server_address, mock_server.address()); /// } /// ``` pub fn listener(mut self, listener: TcpListener) -> Self { self.listener = Some(listener); self } /// By default, [`MockServer`] will record all incoming requests to display /// more meaningful error messages when your expectations are not verified. /// /// This can sometimes be undesirable (e.g. a long-lived server serving /// high volumes of traffic) - you can disable request recording using /// `MockServerBuilder::disable_request_recording`. /// /// ### Example (Request recording disabled): /// /// ```rust /// use wiremock::MockServer; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::builder().disable_request_recording().start().await; /// /// // Act /// let received_requests = mock_server.received_requests().await; /// /// // Assert /// assert!(received_requests.is_none()); /// } /// ``` pub fn disable_request_recording(mut self) -> Self { self.record_incoming_requests = false; self } /// The mock server prints the requests it received when one or more mocks have expectations that have not been satisfied. /// By default, the size of the printed body is limited. /// /// You may want to change this if you're working with services with very large /// bodies, or when printing wiremock output to a file where size matters /// less than in a terminal window. You can configure this limit with /// `MockServerBuilder::body_print_limit`. pub fn body_print_limit(mut self, limit: BodyPrintLimit) -> Self { self.body_print_limit = limit; self } /// Finalise the builder to get an instance of a [`BareMockServer`]. pub(super) async fn build_bare(self) -> BareMockServer { let listener = if let Some(listener) = self.listener { listener } else { TcpListener::bind("127.0.0.1:0").expect("Failed to bind an OS port for a mock server.") }; let recording = if self.record_incoming_requests { RequestRecording::Enabled } else { RequestRecording::Disabled }; BareMockServer::start(listener, recording, self.body_print_limit).await } /// Finalise the builder and launch the [`MockServer`] instance! pub async fn start(self) -> MockServer { MockServer::new(InnerServer::Bare(self.build_bare().await)) } } wiremock-0.6.5/src/mock_server/exposed_server.rs000064400000000000000000000443461046102023000202000ustar 00000000000000use crate::mock_server::MockServerBuilder; use crate::mock_server::bare_server::BareMockServer; use crate::mock_server::pool::{PooledMockServer, get_pooled_mock_server}; use crate::{MockGuard, Request, mock::Mock, verification::VerificationOutcome}; use log::debug; use std::fmt::{Debug, Write}; use std::net::SocketAddr; use std::ops::Deref; /// An HTTP web-server running in the background to behave as one of your dependencies using [`Mock`]s /// for testing purposes. /// /// Each instance of `MockServer` is fully isolated: [`MockServer::start`] takes care of finding a random port /// available on your local machine which is assigned to the new `MockServer`. /// /// You can use [`MockServer::builder`] if you need to specify custom configuration - e.g. /// run on a specific port or disable request recording. /// /// ## Best practices /// /// You should use one instance of `MockServer` for each REST API that your application interacts /// with and needs mocking for testing purposes. /// /// To ensure full isolation and no cross-test interference, `MockServer`s shouldn't be /// shared between tests. Instead, `MockServer`s should be created in the test where they are used. /// /// When using a [`Mock`] within a test helper function, consider using [`MockServer::register_as_scoped`] /// instead of [`MockServer::register`]. /// /// You can register as many [`Mock`]s as your scenario requires on a `MockServer`. #[derive(Debug)] pub struct MockServer(InnerServer); /// `MockServer` is either a wrapper around a `BareMockServer` retrieved from an /// object pool or a wrapper around an exclusive `BareMockServer`. /// We use the pool when the user does not care about the port the mock server listens to, while /// we provision a dedicated one if they specify their own `TcpListener` with `start_on`. /// /// `InnerServer` implements `Deref`, so we never actually have to match /// on `InnerServer` in `MockServer` - the compiler does all the boring heavy-lifting for us. #[derive(Debug)] pub(super) enum InnerServer { Bare(BareMockServer), Pooled(PooledMockServer), } impl Deref for InnerServer { type Target = BareMockServer; fn deref(&self) -> &Self::Target { match self { InnerServer::Bare(b) => b, InnerServer::Pooled(p) => p, } } } impl MockServer { pub(super) fn new(server: InnerServer) -> Self { Self(server) } /// You can use `MockServer::builder` if you need to specify custom configuration - e.g. /// run on a specific port or disable request recording. /// /// If this is not your case, use [`MockServer::start`]. pub fn builder() -> MockServerBuilder { MockServerBuilder::new() } /// Start a new instance of a `MockServer` listening on a random port. /// /// Each instance of `MockServer` is fully isolated: `start` takes care of finding a random port /// available on your local machine which is assigned to the new `MockServer`. /// /// You should use one instance of `MockServer` for each REST API that your application interacts /// with and needs mocking for testing purposes. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server_one = MockServer::start().await; /// let mock_server_two = MockServer::start().await; /// /// assert!(mock_server_one.address() != mock_server_two.address()); /// /// let mock = Mock::given(method("GET")).respond_with(ResponseTemplate::new(200)); /// // Registering the mock with the first mock server - it's now effective! /// // But it *won't* be used by the second mock server! /// mock_server_one.register(mock).await; /// /// // Act /// /// let status = reqwest::get(&mock_server_one.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// /// // This would have matched our mock, but we haven't registered it for `mock_server_two`! /// // Hence it returns a 404, the default response when no mocks matched on the mock server. /// let status = reqwest::get(&mock_server_two.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` pub async fn start() -> Self { Self(InnerServer::Pooled(get_pooled_mock_server().await)) } /// Register a [`Mock`] on an instance of `MockServer`. /// The [`Mock`] will remain active until `MockServer` is shut down. If you want to control or limit how /// long your [`Mock`] stays active, check out [`MockServer::register_as_scoped`]. /// /// Be careful! [`Mock`]s are not effective until they are [`mount`]ed or `register`ed on a `MockServer`. /// `register` is an asynchronous method, make sure to `.await` it! /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200); /// /// let mock = Mock::given(method("GET")).respond_with(response.clone()); /// // Registering the mock with the mock server - it's now effective! /// mock_server.register(mock).await; /// /// // We won't register this mock instead. /// let unregistered_mock = Mock::given(method("GET")).respond_with(response); /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// /// // This would have matched `unregistered_mock`, but we haven't registered it! /// // Hence it returns a 404, the default response when no mocks matched on the mock server. /// let client = reqwest::Client::new(); /// let status = client.post(&mock_server.uri()) /// .send() /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// [`mount`]: Mock::mount pub async fn register(&self, mock: Mock) { self.0.register(mock).await; } /// Register a **scoped** [`Mock`] on an instance of `MockServer`. /// /// When using `register`, your [`Mock`]s will be active until the `MockServer` is shut down. /// When using `register_as_scoped`, your [`Mock`]s will be active as long as the returned [`MockGuard`] is not dropped. /// When the returned [`MockGuard`] is dropped, `MockServer` will verify that the expectations set on the scoped [`Mock`] were /// verified - if not, it will panic. /// /// `register_as_scoped` is the ideal solution when you need a [`Mock`] within a test helper /// but you do not want it to linger around after the end of the function execution. /// /// # Limitations /// /// When expectations of a scoped [`Mock`] are not verified, it will trigger a panic - just like a normal [`Mock`]. /// Due to [limitations](https://internals.rust-lang.org/t/should-drop-glue-use-track-caller/13682) in Rust's `Drop` trait, /// the panic message will not include the filename and the line location /// where the corresponding [`MockGuard`] was dropped - it will point into `wiremock`'s source code. /// /// This can be an issue when you are using more than one scoped [`Mock`] in a single test - which of them panicked? /// To improve your debugging experience it is strongly recommended to use [`Mock::named`] to assign a unique /// identifier to your scoped [`Mock`]s, which will in turn be referenced in the panic message if their expectations are /// not met. /// /// # Example: /// /// - The behaviour of the scoped mock is invisible outside of `my_test_helper`. /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// async fn my_test_helper(mock_server: &MockServer) { /// let mock = Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .expect(1) /// .named("my_test_helper GET /"); /// let mock_guard = mock_server.register_as_scoped(mock).await; /// /// reqwest::get(&mock_server.uri()) /// .await /// .unwrap(); /// /// // `mock_guard` is dropped, expectations are verified! /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// my_test_helper(&mock_server).await; /// /// // Act /// /// // This would have returned 200 if the `Mock` in /// // `my_test_helper` had not been scoped. /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// - The expectations for the scoped mock are not verified, it panics at the end of `my_test_helper`. /// /// ```rust,should_panic /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// async fn my_test_helper(mock_server: &MockServer) { /// let mock = Mock::given(method("GET")) /// .respond_with(ResponseTemplate::new(200)) /// .expect(1) /// .named("my_test_helper GET /"); /// let mock_guard = mock_server.register_as_scoped(mock).await; /// // `mock_guard` is dropped, expectations are NOT verified! /// // Panic! /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// my_test_helper(&mock_server).await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` pub async fn register_as_scoped(&self, mock: Mock) -> MockGuard { self.0.register_as_scoped(mock).await } /// Drop all mounted [`Mock`]s from an instance of [`MockServer`]. /// It also deletes all recorded requests. /// /// ### Example /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// let response = ResponseTemplate::new(200); /// Mock::given(method("GET")).respond_with(response).mount(&mock_server).await; /// /// // Act /// let status = reqwest::get(&mock_server.uri()) /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 200); /// /// // Reset the server /// mock_server.reset().await; /// /// // This would have matched our mock, but we have dropped it resetting the server! /// let client = reqwest::Client::new(); /// let status = client.post(&mock_server.uri()) /// .send() /// .await /// .unwrap() /// .status(); /// assert_eq!(status, 404); /// } /// ``` /// /// ### Example (Recorded requests are reset) /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// // Act /// reqwest::get(&mock_server.uri()).await.unwrap(); /// /// // We have recorded the incoming request /// let received_requests = mock_server.received_requests().await.unwrap(); /// assert!(!received_requests.is_empty()); /// /// // Reset the server /// mock_server.reset().await; /// /// // All received requests have been forgotten after the call to `.reset` /// let received_requests = mock_server.received_requests().await.unwrap(); /// assert!(received_requests.is_empty()) /// } /// ``` pub async fn reset(&self) { self.0.reset().await; } /// Verify that all mounted [`Mock`]s on this instance of `MockServer` have satisfied /// their expectations on their number of invocations. Panics otherwise. pub async fn verify(&self) { debug!("Verify mock expectations."); let body_print_limit = self.0.body_print_limit().await; if let VerificationOutcome::Failure(failed_verifications) = self.0.verify().await { let received_requests_message = if let Some(received_requests) = self.0.received_requests().await { if received_requests.is_empty() { "The server did not receive any request.".into() } else { received_requests.iter().enumerate().fold( "Received requests:\n".to_string(), |mut message, (index, request)| { _ = write!(message, "- Request #{}\n\t", index + 1,); _ = request.print_with_limit(&mut message, body_print_limit); message }, ) } } else { "Enable request recording on the mock server to get the list of incoming requests as part of the panic message.".into() }; let verifications_errors: String = failed_verifications.iter().fold(String::new(), |mut s, m| { _ = writeln!(s, "- {}", m.error_message()); s }); let error_message = format!( "Verifications failed:\n{verifications_errors}\n{received_requests_message}", ); if std::thread::panicking() { debug!("{}", &error_message); } else { panic!("{}", &error_message); } } } /// Return the base uri of this running instance of `MockServer`, e.g. `http://127.0.0.1:4372`. /// /// Use this method to compose uris when interacting with this instance of `MockServer` via /// an HTTP client. /// /// ### Example: /// ```rust /// use wiremock::MockServer; /// /// #[async_std::main] /// async fn main() { /// // Arrange - no mocks mounted /// /// let mock_server = MockServer::start().await; /// // Act /// let uri = format!("{}/health_check", &mock_server.uri()); /// let status = reqwest::get(uri).await.unwrap().status(); /// /// // Assert - default response /// assert_eq!(status, 404); /// } /// ``` pub fn uri(&self) -> String { self.0.uri() } /// Return the socket address of this running instance of `MockServer`, e.g. `127.0.0.1:4372`. /// /// Use this method to interact with the `MockServer` using [`TcpStream`]s. /// /// ### Example: /// ```rust /// use wiremock::MockServer; /// use std::net::TcpStream; /// /// #[async_std::main] /// async fn main() { /// // Act - the server is started /// let mock_server = MockServer::start().await; /// /// // Assert - we can connect to it /// assert!(TcpStream::connect(mock_server.address()).is_ok()); /// } /// ``` /// /// [`TcpStream`]: std::net::TcpStream pub fn address(&self) -> &SocketAddr { self.0.address() } /// Return a vector with all the requests received by the `MockServer` since it started. /// If no request has been served, it returns an empty vector. /// /// If request recording has been disabled using [`MockServerBuilder::disable_request_recording`], /// it returns `None`. /// /// ### Example: /// /// ```rust /// use wiremock::MockServer; /// use http::Method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// // Act /// reqwest::get(&mock_server.uri()).await.unwrap(); /// /// // Assert /// let received_requests = mock_server.received_requests().await.unwrap(); /// assert_eq!(received_requests.len(), 1); /// /// let received_request = &received_requests[0]; /// assert_eq!(received_request.method, Method::GET); /// assert_eq!(received_request.url.path(), "/"); /// assert!(received_request.body.is_empty()); /// } /// ``` /// /// ### Example (No request served): /// /// ```rust /// use wiremock::MockServer; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// /// // Assert /// let received_requests = mock_server.received_requests().await.unwrap(); /// assert_eq!(received_requests.len(), 0); /// } /// ``` /// /// ### Example (Request recording disabled): /// /// ```rust /// use wiremock::MockServer; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::builder().disable_request_recording().start().await; /// /// // Assert /// let received_requests = mock_server.received_requests().await; /// assert!(received_requests.is_none()); /// } /// ``` pub async fn received_requests(&self) -> Option> { self.0.received_requests().await } } impl Drop for MockServer { // Clean up when the `MockServer` instance goes out of scope. fn drop(&mut self) { futures::executor::block_on(self.verify()); // The sender half of the channel, `shutdown_trigger`, gets dropped here // Triggering the graceful shutdown of the server itself. } } wiremock-0.6.5/src/mock_server/hyper.rs000064400000000000000000000065121046102023000162630ustar 00000000000000use crate::mock_server::bare_server::MockServerState; use hyper::service::service_fn; use hyper_util::rt::TokioIo; use std::sync::Arc; use tokio::net::TcpListener; use tokio::sync::RwLock; /// Work around a lifetime error where, for some reason, /// `Box` can't be converted to a /// `Box` struct ErrorLifetimeCast(Box); impl From for Box { fn from(value: ErrorLifetimeCast) -> Self { value.0 } } /// The actual HTTP server responding to incoming requests according to the specified mocks. pub(super) async fn run_server( listener: std::net::TcpListener, server_state: Arc>, mut shutdown_signal: tokio::sync::watch::Receiver<()>, ) { listener .set_nonblocking(true) .expect("Cannot set non-blocking mode on TcpListener"); let listener = TcpListener::from_std(listener).expect("Cannot upgrade TcpListener"); let request_handler = move |request| { let server_state = server_state.clone(); async move { let wiremock_request = crate::Request::from_hyper(request).await; let (response, delay) = server_state .write() .await .handle_request(wiremock_request) .await .map_err(ErrorLifetimeCast)?; // We do not wait for the delay within the handler otherwise we would be // holding on to the write-side of the `RwLock` on `mock_set`. // Holding on the lock while waiting prevents us from handling other requests until // we have waited the whole duration specified in the delay. // In particular, we cannot perform even perform read-only operation - // e.g. check that mock assumptions have been verified. // Using long delays in tests without handling the delay as we are doing here // caused tests to hang (see https://github.com/seanmonstar/reqwest/issues/1147) if let Some(delay) = delay { delay.await; } Ok::<_, ErrorLifetimeCast>(response) } }; loop { let (stream, _) = tokio::select! { biased; accepted = listener.accept() => { match accepted { Ok(accepted) => accepted, Err(_) => break, } }, _ = shutdown_signal.changed() => { log::info!("Mock server shutting down"); break; } }; let io = TokioIo::new(stream); let request_handler = request_handler.clone(); let mut shutdown_signal = shutdown_signal.clone(); tokio::task::spawn(async move { let http_server = hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new()); let conn = http_server.serve_connection_with_upgrades(io, service_fn(request_handler)); tokio::pin!(conn); loop { tokio::select! { _ = conn.as_mut() => break, _ = shutdown_signal.changed() => conn.as_mut().graceful_shutdown(), } } }); } } wiremock-0.6.5/src/mock_server/mod.rs000064400000000000000000000017251046102023000157140ustar 00000000000000//! All bits and pieces concerning the HTTP mock server are in this module. //! //! `bare_server::BareMockServer` is the "front-end" to drive behaviour for the `hyper` HTTP //! server running in the background, defined in the `hyper` sub-module. //! //! `bare_server::BareMockServer` is not exposed directly: crate users only get to interact with //! `exposed_server::MockServer`. //! `exposed_server::MockServer` is either a wrapper around a `BareMockServer` retrieved from an //! object pool or a wrapper around an exclusive `BareMockServer`. //! We use the pool when the user does not care about the port the mock server listens to, while //! we provision a dedicated one if they specify their own `TcpListener` with `start_on`. //! Check the `pool` submodule for more details on our pooling strategy. mod bare_server; mod builder; mod exposed_server; mod hyper; mod pool; pub use bare_server::MockGuard; pub use builder::MockServerBuilder; pub use exposed_server::MockServer; wiremock-0.6.5/src/mock_server/pool.rs000064400000000000000000000052111046102023000161000ustar 00000000000000use crate::MockServer; use crate::mock_server::bare_server::BareMockServer; use deadpool::managed::{Metrics, Object, Pool}; use once_cell::sync::Lazy; use std::convert::Infallible; /// A pool of `BareMockServer`s. /// /// ## Design constraints /// /// `wiremock`'s pooling is designed to be an invisible optimisation: users of the crate, if /// we are successful, should never have to reason about it. /// /// ## Motivation /// /// Why are we pooling `BareMockServer`s? /// Mostly to reduce the number of `TcpListener`s that are being opened and closed, therefore /// mitigating risk of our users having to fight/raise OS limits for the maximum number of open /// connections (e.g. ulimit on Linux). /// /// It is also marginally faster to get a pooled `BareMockServer` than to create a new one, but /// the absolute time is so small (<1 ms) that it does not make a material difference in a real /// world test suite. static MOCK_SERVER_POOL: Lazy> = Lazy::new(|| { // We are choosing an arbitrarily high max_size because we never want a test to "wait" for // a `BareMockServer` instance to become available. // // We might expose in the future a way for a crate user to tune this value. Pool::builder(MockServerPoolManager) .max_size(1000) .build() .expect("Building a server pool is not expected to fail. Please report an issue") }); pub(crate) type PooledMockServer = Object; /// Retrieve a `BareMockServer` from the pool. /// The operation should never fail. pub(crate) async fn get_pooled_mock_server() -> PooledMockServer { MOCK_SERVER_POOL .get() .await .expect("Failed to get a MockServer from the pool") } /// The `BareMockServer` pool manager. /// /// It: /// - creates a new `BareMockServer` if there is none to borrow from the pool; /// - "cleans up" used `BareMockServer`s before making them available again for other tests to use. #[derive(Debug)] pub(crate) struct MockServerPoolManager; impl deadpool::managed::Manager for MockServerPoolManager { type Error = Infallible; type Type = BareMockServer; async fn create(&self) -> Result { // All servers in the pool use the default configuration Ok(MockServer::builder().build_bare().await) } async fn recycle( &self, mock_server: &mut BareMockServer, _metrics: &Metrics, ) -> deadpool::managed::RecycleResult { // Remove all existing settings - we want to start clean when the mock server // is picked up again from the pool. mock_server.reset().await; Ok(()) } } wiremock-0.6.5/src/mock_set.rs000064400000000000000000000212321046102023000144150ustar 00000000000000use crate::request::BodyPrintLimit; use crate::{ ErrorResponse, mounted_mock::MountedMock, verification::{VerificationOutcome, VerificationReport}, }; use crate::{Mock, Request}; use http_body_util::Full; use hyper::body::Bytes; use log::debug; use std::{ ops::{Index, IndexMut}, sync::{Arc, atomic::AtomicBool}, }; use tokio::sync::Notify; use tokio::time::{Sleep, sleep}; /// The collection of mocks used by a `MockServer` instance to match against /// incoming requests. /// /// New mocks are added to `MountedMockSet` every time [`MockServer::register`](crate::MockServer::register), /// [`MockServer::register_as_scoped`](crate::MockServer::register_as_scoped) or /// [`Mock::mount`](crate::Mock::mount) are called. pub(crate) struct MountedMockSet { mocks: Vec<(MountedMock, MountedMockState)>, /// A counter that keeps track of how many times [`MountedMockSet::reset`] has been called. /// It starts at `0` and gets incremented for each invocation. /// /// We need `generation` to know if a [`MockId`] points to an [`MountedMock`] that has been /// removed via [`MountedMockSet::reset`]. generation: u16, body_print_limit: BodyPrintLimit, } /// A `MockId` is an opaque index that uniquely identifies an [`MountedMock`] inside an [`MountedMockSet`]. /// /// The only way to create a `MockId` is calling [`MountedMockSet::register`]. #[derive(Copy, Clone)] pub(crate) struct MockId { index: usize, /// The generation of [`MountedMockSet`] when [`MountedMockSet::register`] was called. /// It allows [`MountedMockSet`] to check that the [`MountedMock`] our [`MockId`] points to is still in /// the set (i.e. the set has not been wiped by a [`MountedMockSet::reset`] call). generation: u16, } impl MountedMockSet { /// Create a new instance of `MountedMockSet`. pub(crate) fn new(body_print_limit: BodyPrintLimit) -> MountedMockSet { MountedMockSet { mocks: vec![], generation: 0, body_print_limit, } } pub(crate) async fn handle_request( &mut self, request: Request, ) -> Result<(hyper::Response>, Option), ErrorResponse> { debug!("Handling request."); let mut response_template: Option<_> = None; self.mocks.sort_by_key(|(m, _)| m.specification.priority); for (mock, mock_state) in &mut self.mocks { if *mock_state == MountedMockState::OutOfScope { continue; } if mock.matches(&request) { response_template = Some(mock.response_template(&request)); break; } } if let Some(response_template) = response_template { match response_template { Ok(response_template) => { let delay = response_template.delay().map(sleep); Ok((response_template.generate_response(), delay)) } Err(err) => Err(err), } } else { let mut msg = "Got unexpected request:\n".to_string(); _ = request.print_with_limit(&mut msg, self.body_print_limit); debug!("{}", msg); let not_found_response = hyper::Response::builder() .status(hyper::StatusCode::NOT_FOUND) .body(Full::default()) .unwrap(); Ok((not_found_response, None)) } } pub(crate) fn register(&mut self, mock: Mock) -> (Arc<(Notify, AtomicBool)>, MockId) { let n_registered_mocks = self.mocks.len(); let active_mock = MountedMock::new(mock, n_registered_mocks); let notify = active_mock.notify(); self.mocks.push((active_mock, MountedMockState::InScope)); ( notify, MockId { index: self.mocks.len() - 1, generation: self.generation, }, ) } pub(crate) fn reset(&mut self) { self.mocks = vec![]; self.generation += 1; } /// Mark one of the mocks in the set as out of scope. /// /// It will stop matching against incoming requests, regardless of its specification. pub(crate) fn deactivate(&mut self, mock_id: MockId) { let mock = &mut self[mock_id]; mock.1 = MountedMockState::OutOfScope; } /// Verify that expectations have been met for **all** [`MountedMock`]s in the set. pub(crate) fn verify_all(&self) -> VerificationOutcome { let failed_verifications: Vec = self .mocks .iter() .filter(|(_, state)| *state == MountedMockState::InScope) .map(|(m, _)| m.verify()) .filter(|verification_report| !verification_report.is_satisfied()) .collect(); if failed_verifications.is_empty() { VerificationOutcome::Success } else { VerificationOutcome::Failure(failed_verifications) } } /// Verify that expectations have been met for the [`MountedMock`] corresponding to the specified [`MockId`]. pub(crate) fn verify(&self, mock_id: MockId) -> VerificationReport { let (mock, _) = &self[mock_id]; mock.verify() } } impl IndexMut for MountedMockSet { fn index_mut(&mut self, index: MockId) -> &mut Self::Output { if index.generation != self.generation { panic!( "The mock you are trying to access is no longer active. It has been deleted from the active set via `reset` - you should not hold on to a `MockId` after you call `reset`!." ) } &mut self.mocks[index.index] } } impl Index for MountedMockSet { type Output = (MountedMock, MountedMockState); fn index(&self, index: MockId) -> &Self::Output { if index.generation != self.generation { panic!( "The mock you are trying to access is no longer active. It has been deleted from the active set via `reset` - you should not hold on to a `MockId` after you call `reset`!." ) } &self.mocks[index.index] } } /// A [`MountedMock`] can either be global (i.e. registered using [`crate::MockServer::register`]) or /// scoped (i.e. registered using [`crate::MockServer::register_as_scoped`]). /// /// [`MountedMock`]s must currently be in scope to be matched against incoming requests. /// Out of scope [`MountedMock`]s are skipped when trying to match an incoming request. /// /// # Implementation Rationale /// /// An alternative approach would be removing a [`MountedMock`] from the [`MountedMockSet`] when it goes /// out of scope. /// This would create an issue for the stability of [`MockId`]s: removing an element from the vector /// of [`MountedMock`]s in [`MountedMockSet`] would invalidate the ids of all mocks registered after /// the removed one. /// /// Attaching a state to the mocks in the vector, instead, allows us to ensure id stability while /// achieving the desired behaviour. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum MountedMockState { InScope, OutOfScope, } #[cfg(test)] mod tests { use crate::matchers::path; use crate::mock_set::{MountedMockSet, MountedMockState}; use crate::request::BodyPrintLimit; use crate::{Mock, ResponseTemplate}; fn test_mock_set() -> MountedMockSet { MountedMockSet::new(BodyPrintLimit::Unlimited) } #[test] fn generation_is_incremented_for_every_reset() { let mut set = test_mock_set(); assert_eq!(set.generation, 0); for i in 1..10 { set.reset(); assert_eq!(set.generation, i); } } #[test] #[should_panic] fn accessing_a_mock_id_after_a_reset_triggers_a_panic() { // Assert let mut set = test_mock_set(); let mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200)); let (_, mock_id) = set.register(mock); // Act set.reset(); // Assert let _ = &set[mock_id]; } #[test] fn deactivating_a_mock_does_not_invalidate_other_ids() { // Assert let mut set = test_mock_set(); let first_mock = Mock::given(path("/")).respond_with(ResponseTemplate::new(200)); let second_mock = Mock::given(path("/hello")).respond_with(ResponseTemplate::new(500)); let (_, first_mock_id) = set.register(first_mock); let (_, second_mock_id) = set.register(second_mock); // Act set.deactivate(first_mock_id); // Assert let first_mock = &set[first_mock_id]; assert_eq!(first_mock.1, MountedMockState::OutOfScope); let second_mock = &set[second_mock_id]; assert_eq!(second_mock.1, MountedMockState::InScope); } } wiremock-0.6.5/src/mounted_mock.rs000064400000000000000000000070101046102023000152730ustar 00000000000000use std::sync::{Arc, atomic::AtomicBool}; use tokio::sync::Notify; use crate::{ ErrorResponse, Match, Mock, Request, ResponseTemplate, verification::VerificationReport, }; /// Given the behaviour specification as a [`Mock`], keep track of runtime information /// concerning this mock - e.g. how many times it matched on a incoming request. pub(crate) struct MountedMock { pub(crate) specification: Mock, n_matched_requests: u64, /// The position occupied by this mock within the parent [`MountedMockSet`](crate::mock_set::MountedMockSet) /// collection of `MountedMock`s. /// /// E.g. `0` if this is the first mock that we try to match against an incoming request, `1` /// if it is the second, etc. position_in_set: usize, // matched requests: matched_requests: Vec, notify: Arc<(Notify, AtomicBool)>, } impl MountedMock { pub(crate) fn new(specification: Mock, position_in_set: usize) -> Self { Self { specification, n_matched_requests: 0, position_in_set, matched_requests: Vec::new(), notify: Arc::new((Notify::new(), AtomicBool::new(false))), } } /// This is NOT the same of `matches` from the `Match` trait! /// Key difference: we are talking a mutable reference to `self` in order to capture /// additional information (e.g. how many requests we matched so far) or change behaviour /// after a certain threshold has been crossed (e.g. start returning `false` for all requests /// once enough requests have been matched according to `max_n_matches`). pub(crate) fn matches(&mut self, request: &Request) -> bool { if Some(self.n_matched_requests) == self.specification.max_n_matches { // Skip the actual check if we are already at our maximum of matched requests. false } else { let matched = self .specification .matchers .iter() .all(|matcher| matcher.matches(request)); if matched { // Increase match count self.n_matched_requests += 1; // Keep track of request self.matched_requests.push(request.clone()); // notification of satisfaction if self.verify().is_satisfied() { // always set the satisfaction flag **before** raising the event self.notify .1 .store(true, std::sync::atomic::Ordering::Release); self.notify.0.notify_waiters(); } } matched } } /// Verify if this mock has verified the expectations set at creation time /// over the number of invocations. pub(crate) fn verify(&self) -> VerificationReport { VerificationReport { mock_name: self.specification.name.clone(), n_matched_requests: self.n_matched_requests, expectation_range: self.specification.expectation_range.clone(), position_in_set: self.position_in_set, } } pub(crate) fn response_template( &self, request: &Request, ) -> Result { self.specification.response_template(request) } pub(crate) fn received_requests(&self) -> Vec { self.matched_requests.clone() } pub(crate) fn notify(&self) -> Arc<(Notify, AtomicBool)> { self.notify.clone() } } wiremock-0.6.5/src/request.rs000064400000000000000000000113261046102023000143040ustar 00000000000000use std::fmt; use http::{HeaderMap, Method}; use http_body_util::BodyExt; use serde::de::DeserializeOwned; use url::Url; pub const BODY_PRINT_LIMIT: usize = 10_000; /// Specifies limitations on printing request bodies when logging requests. For some mock servers /// the bodies may be too large to reasonably print and it may be desirable to limit them. #[derive(Debug, Copy, Clone)] pub enum BodyPrintLimit { /// Maximum length of a body to print in bytes. Limited(usize), /// There is no limit to the size of a body that may be printed. Unlimited, } /// An incoming request to an instance of [`MockServer`]. /// /// Each matcher gets an immutable reference to a `Request` instance in the [`matches`] method /// defined in the [`Match`] trait. /// /// [`MockServer`]: crate::MockServer /// [`matches`]: crate::Match::matches /// [`Match`]: crate::Match /// /// ### Implementation notes: /// We can't use `http_types::Request` directly in our `Match::matches` signature: /// it requires having mutable access to the request to extract the body (which gets /// consumed when read!). /// It would also require `matches` to be async, which is cumbersome due to the lack of async traits. /// /// We introduce our `Request` type to perform this extraction once when the request /// arrives in the mock serve, store the result and pass an immutable reference to it /// to all our matchers. #[derive(Debug, Clone)] pub struct Request { pub url: Url, pub method: Method, pub headers: HeaderMap, pub body: Vec, } impl Request { pub fn body_json(&self) -> Result { serde_json::from_slice(&self.body) } pub(crate) async fn from_hyper(request: hyper::Request) -> Request { let (parts, body) = request.into_parts(); let url = match parts.uri.authority() { Some(_) => parts.uri.to_string(), None => format!("http://localhost{}", parts.uri), } .parse() .unwrap(); let body = body .collect() .await .expect("Failed to read request body.") .to_bytes(); Self { url, method: parts.method, headers: parts.headers, body: body.to_vec(), } } pub(crate) fn print_with_limit( &self, mut buffer: impl fmt::Write, body_print_limit: BodyPrintLimit, ) -> fmt::Result { writeln!(buffer, "{} {}", self.method, self.url)?; for name in self.headers.keys() { let values = self .headers .get_all(name) .iter() .map(|value| String::from_utf8_lossy(value.as_bytes())) .collect::>(); let values = values.join(","); writeln!(buffer, "{}: {}", name, values)?; } match body_print_limit { BodyPrintLimit::Limited(limit) if self.body.len() > limit => { let mut written = false; for end_byte in limit..(limit + 4).max(self.body.len()) { if let Ok(truncated) = std::str::from_utf8(&self.body[..end_byte]) { written = true; writeln!(buffer, "{}", truncated)?; if end_byte < self.body.len() { writeln!( buffer, "We truncated the body because it was too large: {} bytes (limit: {} bytes)", self.body.len(), limit )?; writeln!( buffer, "Increase this limit by setting `WIREMOCK_BODY_PRINT_LIMIT`, or calling `MockServerBuilder::body_print_limit` when building your MockServer instance" )?; } break; } } if !written { writeln!( buffer, "Body is likely binary (invalid utf-8) size is {} bytes", self.body.len() ) } else { Ok(()) } } _ => { if let Ok(body) = std::str::from_utf8(&self.body) { writeln!(buffer, "{}", body) } else { writeln!( buffer, "Body is likely binary (invalid utf-8) size is {} bytes", self.body.len() ) } } } } } wiremock-0.6.5/src/respond.rs000064400000000000000000000131341046102023000142650ustar 00000000000000use crate::{ErrorResponse, Request, ResponseTemplate}; /// Anything that implements `Respond` can be used to reply to an incoming request when a /// [`Mock`] is activated. /// /// ## Fixed responses /// /// The simplest `Respond` is [`ResponseTemplate`]: no matter the request, it will /// always return itself. /// /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// let correlation_id = "1311db4f-fe65-4cb2-b514-1bb47f781aa7"; /// let template = ResponseTemplate::new(200).insert_header( /// "X-Correlation-ID", /// correlation_id /// ); /// Mock::given(method("GET")) /// .respond_with(template) /// .mount(&mock_server) /// .await; /// /// // Act /// let response = reqwest::get(&mock_server.uri()) /// .await /// .unwrap(); /// /// // Assert /// assert_eq!(response.status(), 200); /// assert_eq!(response.headers().get("X-Correlation-ID").unwrap().to_str().unwrap(), correlation_id); /// } /// ``` /// /// ## Dynamic responses /// /// You can use `Respond`, though, to implement responses that depend on the data in /// the request matched by a [`Mock`]. /// /// Functions from `Request` to `ResponseTemplate` implement `Respond`, so for simple cases you /// can use a closure to build a response dynamically, for instance to echo the request body back: /// /// ```rust /// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate}; /// use wiremock::matchers::path; /// /// #[async_std::main] /// async fn main() { /// let mock_server = MockServer::start().await; /// let body = "Mock Server!".to_string(); /// /// Mock::given(path("/echo")) /// .respond_with(|req: &Request| { /// let body_string = String::from_utf8(req.body.clone()).unwrap(); /// ResponseTemplate::new(200).set_body_string(body_string) /// }) /// .mount(&mock_server) /// .await; /// /// let client = reqwest::Client::new(); /// let response = client.post(format!("{}/echo", &mock_server.uri())) /// .body(body.clone()) /// .send() /// .await /// .unwrap(); /// assert_eq!(response.status(), 200); /// assert_eq!(response.text().await.unwrap(), body); /// } /// ``` /// /// For more complex cases you may want to implement `Respond` yourself. As an example, this is a /// `Respond` that propagates back a request header in the response: /// /// ```rust /// use http::HeaderName; /// use wiremock::{Match, MockServer, Mock, Request, ResponseTemplate, Respond}; /// use wiremock::matchers::path; /// use std::convert::TryInto; /// use std::str::FromStr; /// /// /// Responds using the specified `ResponseTemplate`, but it dynamically populates the /// /// `X-Correlation-Id` header from the request data. /// pub struct CorrelationIdResponder(pub ResponseTemplate); /// /// impl Respond for CorrelationIdResponder { /// fn respond(&self, request: &Request) -> ResponseTemplate { /// const HEADER: HeaderName = HeaderName::from_static("x-correlation-id"); /// let mut response_template = self.0.clone(); /// if let Some(correlation_id) = request.headers.get(&HEADER) { /// response_template = response_template.insert_header( /// HEADER, /// correlation_id.to_owned() /// ); /// } /// response_template /// } /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// let correlation_id = "1241-1245-1548-4567"; /// /// Mock::given(path("/hello")) /// .respond_with(CorrelationIdResponder(ResponseTemplate::new(200))) /// .mount(&mock_server) /// .await; /// /// let client = reqwest::Client::new(); /// let response = client /// .get(format!("{}/hello", &mock_server.uri())) /// .header("X-Correlation-Id", correlation_id) /// .send() /// .await /// .unwrap(); /// assert_eq!(response.status(), 200); /// assert_eq!(response.headers().get("X-Correlation-Id").unwrap().to_str().unwrap(), correlation_id); /// } /// ``` /// /// [`Mock`]: crate::Mock /// [`ResponseTemplate`]: crate::ResponseTemplate pub trait Respond: Send + Sync { /// Given a reference to a [`Request`] return a [`ResponseTemplate`] that will be used /// by the [`MockServer`] as blueprint for the response returned to the client. /// /// [`Request`]: crate::Request /// [`MockServer`]: crate::MockServer /// [`ResponseTemplate`]: crate::ResponseTemplate fn respond(&self, request: &Request) -> ResponseTemplate; } /// A `ResponseTemplate` is the simplest `Respond` implementation: it returns a clone of itself /// no matter what the incoming request contains! impl Respond for ResponseTemplate { fn respond(&self, _request: &Request) -> ResponseTemplate { self.clone() } } impl Respond for F where F: Send + Sync + Fn(&Request) -> ResponseTemplate, { fn respond(&self, request: &Request) -> ResponseTemplate { (self)(request) } } /// Like [`Respond`], but it only allows returning an error through a function. pub trait RespondErr: Send + Sync { fn respond_err(&self, request: &Request) -> ErrorResponse; } impl RespondErr for F where F: Send + Sync + Fn(&Request) -> Err, Err: std::error::Error + Send + Sync + 'static, { fn respond_err(&self, request: &Request) -> ErrorResponse { Box::new((self)(request)) } } wiremock-0.6.5/src/response_template.rs000064400000000000000000000264151046102023000163520ustar 00000000000000use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode}; use http_body_util::Full; use hyper::body::Bytes; use serde::Serialize; use std::convert::TryInto; use std::time::Duration; /// The blueprint for the response returned by a [`MockServer`] when a [`Mock`] matches on an incoming request. /// /// [`Mock`]: crate::Mock /// [`MockServer`]: crate::MockServer #[derive(Clone, Debug)] pub struct ResponseTemplate { mime: String, status_code: StatusCode, headers: HeaderMap, body: Option>, delay: Option, } // `wiremock` is a crate meant for testing - failures are most likely not handled/temporary mistakes. // Hence we prefer to panic and provide an easier API than to use `Result`s thus pushing // the burden of "correctness" (and conversions) on the user. // // All methods try to accept the widest possible set of inputs and then perform the fallible conversion // internally, bailing if the fallible conversion fails. // // Same principle applies to allocation/cloning, freely used where convenient. impl ResponseTemplate { /// Start building a `ResponseTemplate` specifying the status code of the response. pub fn new(s: S) -> Self where S: TryInto, >::Error: std::fmt::Debug, { let status_code = s.try_into().expect("Failed to convert into status code."); Self { status_code, headers: HeaderMap::new(), mime: String::new(), body: None, delay: None, } } /// Append a header `value` to list of headers with `key` as header name. /// /// Unlike `insert_header`, this function will not override the contents of a header: /// - if there are no header values with `key` as header name, it will insert one; /// - if there are already some values with `key` as header name, it will append to the /// existing list. pub fn append_header(mut self, key: K, value: V) -> Self where K: TryInto, >::Error: std::fmt::Debug, V: TryInto, >::Error: std::fmt::Debug, { let key = key.try_into().expect("Failed to convert into header name."); let value = value .try_into() .expect("Failed to convert into header value."); self.headers.append(key, value); self } /// Insert a header `value` with `key` as header name. /// /// This function will override the contents of a header: /// - if there are no header values with `key` as header name, it will insert one; /// - if there are already some values with `key` as header name, it will drop them and /// start a new list of header values, containing only `value`. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// let correlation_id = "1311db4f-fe65-4cb2-b514-1bb47f781aa7"; /// let template = ResponseTemplate::new(200).insert_header( /// "X-Correlation-ID", /// correlation_id /// ); /// Mock::given(method("GET")) /// .respond_with(template) /// .mount(&mock_server) /// .await; /// /// // Act /// let res = reqwest::get(&mock_server.uri()) /// .await /// .unwrap(); /// /// // Assert /// assert_eq!(res.headers().get("X-Correlation-ID").unwrap().to_str().unwrap(), correlation_id); /// } /// ``` pub fn insert_header(mut self, key: K, value: V) -> Self where K: TryInto, >::Error: std::fmt::Debug, V: TryInto, >::Error: std::fmt::Debug, { let key = key.try_into().expect("Failed to convert into header name."); let value = value .try_into() .expect("Failed to convert into header value."); self.headers.insert(key, value); self } /// Append multiple header key-value pairs. /// /// Existing header values will not be overridden. /// /// # Example /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// let headers = vec![ /// ("Set-Cookie", "name=value"), /// ("Set-Cookie", "name2=value2; Domain=example.com"), /// ]; /// let template = ResponseTemplate::new(200).append_headers(headers); /// Mock::given(method("GET")) /// .respond_with(template) /// .mount(&mock_server) /// .await; /// /// // Act /// let res = reqwest::get(&mock_server.uri()) /// .await /// .unwrap(); /// /// // Assert /// assert_eq!(res.headers().get_all("Set-Cookie").iter().count(), 2); /// } /// ``` pub fn append_headers(mut self, headers: I) -> Self where K: TryInto, >::Error: std::fmt::Debug, V: TryInto, >::Error: std::fmt::Debug, I: IntoIterator, { let headers = headers.into_iter().map(|(key, value)| { ( key.try_into().expect("Failed to convert into header name."), value .try_into() .expect("Failed to convert into header value."), ) }); // The `Extend<(HeaderName, T)>` impl uses `HeaderMap::append` internally: https://docs.rs/http/1.0.0/src/http/header/map.rs.html#1953 self.headers.extend(headers); self } /// Set the response body with bytes. /// /// It sets "Content-Type" to "application/octet-stream". /// /// To set a body with bytes but a different "Content-Type" /// [`set_body_raw`](#method.set_body_raw) can be used. pub fn set_body_bytes(mut self, body: B) -> Self where B: TryInto>, >>::Error: std::fmt::Debug, { let body = body.try_into().expect("Failed to convert into body."); self.body = Some(body); self } /// Set the response body from a JSON-serializable value. /// /// It sets "Content-Type" to "application/json". pub fn set_body_json(mut self, body: B) -> Self { let body = serde_json::to_vec(&body).expect("Failed to convert into body."); self.body = Some(body); self.mime = "application/json".to_string(); self } /// Set the response body to a string. /// /// It sets "Content-Type" to "text/plain". pub fn set_body_string(mut self, body: T) -> Self where T: TryInto, >::Error: std::fmt::Debug, { let body = body.try_into().expect("Failed to convert into body."); self.body = Some(body.into_bytes()); self.mime = "text/plain".to_string(); self } /// Set a raw response body. The mime type needs to be set because the /// raw body could be of any type. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// /// mod external { /// // This could be a method of a struct that is /// // implemented in another crate and the struct /// // does not implement Serialize. /// pub fn body() -> Vec{ /// r#"{"hello": "world"}"#.as_bytes().to_owned() /// } /// } /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// let template = ResponseTemplate::new(200).set_body_raw( /// external::body(), /// "application/json" /// ); /// Mock::given(method("GET")) /// .respond_with(template) /// .mount(&mock_server) /// .await; /// /// // Act /// let mut res = reqwest::get(&mock_server.uri()) /// .await /// .unwrap(); /// let content_type = res.headers().get(reqwest::header::CONTENT_TYPE).cloned(); /// let body = res.text() /// .await /// .unwrap(); /// /// // Assert /// assert_eq!(body, r#"{"hello": "world"}"#); /// assert_eq!(content_type, Some("application/json".parse().unwrap())); /// } /// ``` pub fn set_body_raw(mut self, body: B, mime: &str) -> Self where B: TryInto>, >>::Error: std::fmt::Debug, { let body = body.try_into().expect("Failed to convert into body."); self.body = Some(body); self.mime = mime.to_string(); self } /// By default the [`MockServer`] tries to fulfill incoming requests as fast as possible. /// /// You can use `set_delay` to introduce an artificial delay to simulate the behaviour of /// a real server with a non-negligible latency. /// /// In particular, you can use it to test the behaviour of your timeout policies. /// /// ### Example: /// ```rust /// use wiremock::{MockServer, Mock, ResponseTemplate}; /// use wiremock::matchers::method; /// use std::time::Duration; /// use async_std::prelude::FutureExt; /// /// #[async_std::main] /// async fn main() { /// // Arrange /// let mock_server = MockServer::start().await; /// let delay = Duration::from_secs(1); /// let template = ResponseTemplate::new(200).set_delay(delay); /// Mock::given(method("GET")) /// .respond_with(template) /// .mount(&mock_server) /// .await; /// /// // Act /// let mut res = async_std::future::timeout( /// // Shorter than the response delay! /// delay / 3, /// reqwest::get(&mock_server.uri()) /// ) /// .await; /// /// // Assert - Timeout error! /// assert!(res.is_err()); /// } /// ``` /// /// [`MockServer`]: crate::mock_server::MockServer pub fn set_delay(mut self, delay: Duration) -> Self { self.delay = Some(delay); self } /// Generate a response from the template. pub(crate) fn generate_response(&self) -> Response> { let mut response = Response::builder().status(self.status_code); let mut headers = self.headers.clone(); // Set content-type, if needed if !self.mime.is_empty() { headers.insert(http::header::CONTENT_TYPE, self.mime.parse().unwrap()); } *response.headers_mut().unwrap() = headers; let body = self.body.clone().unwrap_or_default(); response.body(body.into()).unwrap() } /// Retrieve the response delay. pub(crate) fn delay(&self) -> &Option { &self.delay } } wiremock-0.6.5/src/verification.rs000064400000000000000000000036061046102023000153000ustar 00000000000000use crate::mock::Times; /// A report returned by an `MountedMock` detailing what the user expectations were and /// how many calls were actually received since the mock was mounted on the server. #[derive(Clone)] pub(crate) struct VerificationReport { /// The mock name specified by the user. pub(crate) mock_name: Option, /// What users specified pub(crate) expectation_range: Times, /// Actual number of received requests that matched the specification pub(crate) n_matched_requests: u64, /// The position occupied by the mock that generated the report within its parent /// [`MountedMockSet`](crate::mock_set::MountedMockSet) collection of `MountedMock`s. /// /// E.g. `0` if it is the first mock that we try to match against an incoming request, `1` /// if it is the second, etc. pub(crate) position_in_set: usize, } impl VerificationReport { pub(crate) fn error_message(&self) -> String { if let Some(ref mock_name) = self.mock_name { format!( "{}.\n\tExpected range of matching incoming requests: {}\n\tNumber of matched incoming requests: {}", mock_name, self.expectation_range, self.n_matched_requests ) } else { format!( "Mock #{}.\n\tExpected range of matching incoming requests: {}\n\tNumber of matched incoming requests: {}", self.position_in_set, self.expectation_range, self.n_matched_requests ) } } pub(crate) fn is_satisfied(&self) -> bool { self.expectation_range.contains(self.n_matched_requests) } } pub(crate) enum VerificationOutcome { /// The expectations set on all active mocks were satisfied. Success, /// The expectations set for one or more of the active mocks were not satisfied. /// All failed expectations are returned. Failure(Vec), } wiremock-0.6.5/tests/mocks.rs000064400000000000000000000342471046102023000143120ustar 00000000000000use futures::FutureExt; use reqwest::StatusCode; use serde::Serialize; use serde_json::json; use std::fmt::{Display, Formatter}; use std::io::ErrorKind; use std::iter; use std::net::TcpStream; use std::time::Duration; use wiremock::matchers::{PathExactMatcher, body_json, body_partial_json, method, path}; use wiremock::{Mock, MockServer, Request, ResponseTemplate}; #[async_std::test] async fn new_starts_the_server() { // Act let mock_server = MockServer::start().await; // Assert assert!(TcpStream::connect(mock_server.address()).is_ok()) } #[async_std::test] async fn returns_404_if_nothing_matches() { // Arrange - no mocks mounted let mock_server = MockServer::start().await; // Act let status = reqwest::get(&mock_server.uri()).await.unwrap().status(); // Assert assert_eq!(status, 404); } #[async_std::test] #[should_panic] async fn panics_if_the_expectation_is_not_satisfied() { // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200); Mock::given(method("GET")) .respond_with(response) .expect(1..) .named("panics_if_the_expectation_is_not_satisfied expectation failed") .mount(&mock_server) .await; // Act - we never call the mock } #[async_std::test] #[should_panic(expected = "Verifications failed: - Mock #0. \tExpected range of matching incoming requests: 1 <= x \tNumber of matched incoming requests: 0 The server did not receive any request.")] async fn no_received_request_line_is_printed_in_the_panic_message_if_expectations_are_not_verified() { // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200); Mock::given(method("GET")) .respond_with(response) .expect(1..) .mount(&mock_server) .await; // Act - we never call the mock } #[async_std::test] #[should_panic(expected = "Verifications failed: - Mock #0. \tExpected range of matching incoming requests: 1 <= x \tNumber of matched incoming requests: 0 Received requests: - Request #1 \tGET http://localhost/")] async fn received_request_are_printed_as_panic_message_if_expectations_are_not_verified() { // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200); Mock::given(method("POST")) .respond_with(response) .expect(1..) .mount(&mock_server) .await; // Act - we sent a request that does not match (GET) reqwest::get(&mock_server.uri()).await.unwrap(); // Assert - verified on drop } #[async_std::test] #[should_panic] async fn panic_during_expectation_does_not_crash() { // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200); Mock::given(method("GET")) .respond_with(response) .expect(1..) .named("panic_during_expectation_does_not_crash expectation failed") .mount(&mock_server) .await; // Act - start a panic panic!("forced panic") } #[async_std::test] async fn simple_route_mock() { // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200).set_body_bytes("world"); let mock = Mock::given(method("GET")) .and(PathExactMatcher::new("hello")) .respond_with(response); mock_server.register(mock).await; // Act let response = reqwest::get(format!("{}/hello", &mock_server.uri())) .await .unwrap(); // Assert assert_eq!(response.status(), 200); assert_eq!(response.text().await.unwrap(), "world"); } #[async_std::test] async fn two_route_mocks() { // Arrange let mock_server = MockServer::start().await; // First let response = ResponseTemplate::new(200).set_body_bytes("aaa"); Mock::given(method("GET")) .and(PathExactMatcher::new("first")) .respond_with(response) .named("/first") .mount(&mock_server) .await; // Second let response = ResponseTemplate::new(200).set_body_bytes("bbb"); Mock::given(method("GET")) .and(PathExactMatcher::new("second")) .respond_with(response) .named("/second") .mount(&mock_server) .await; // Act let first_response = reqwest::get(format!("{}/first", &mock_server.uri())) .await .unwrap(); let second_response = reqwest::get(format!("{}/second", &mock_server.uri())) .await .unwrap(); // Assert assert_eq!(first_response.status(), 200); assert_eq!(second_response.status(), 200); assert_eq!(first_response.text().await.unwrap(), "aaa"); assert_eq!(second_response.text().await.unwrap(), "bbb"); } #[async_std::test] async fn body_json_matches_independent_of_key_ordering() { #[derive(Serialize)] struct X { b: u8, a: u8, } // Arrange let expected_body = json!({ "a": 1, "b": 2 }); let body = serde_json::to_string(&X { a: 1, b: 2 }).unwrap(); let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200); let mock = Mock::given(method("POST")) .and(body_json(expected_body)) .respond_with(response); mock_server.register(mock).await; // Act let client = reqwest::Client::new(); let response = client .post(mock_server.uri()) .body(body) .send() .await .unwrap(); // Assert assert_eq!(response.status(), 200); } #[async_std::test] async fn body_json_partial_matches_a_part_of_response_json() { // Arrange let expected_body = json!({ "a": 1, "c": { "e": 2 } }); let body = json!({ "a": 1, "b": 2, "c": { "d": 1, "e": 2 } }); let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200); let mock = Mock::given(method("POST")) .and(body_partial_json(expected_body)) .respond_with(response); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let response = client .post(mock_server.uri()) .json(&body) .send() .await .unwrap(); // Assert assert_eq!(response.status(), StatusCode::OK); } #[should_panic(expected = "\ Wiremock can't match the path `abcd?` because it contains a `?`. You must use `wiremock::matchers::query_param` to match on query parameters (the part of the path after the `?`).")] #[async_std::test] async fn query_parameter_is_not_accepted_in_path() { Mock::given(method("GET")).and(path("abcd?")); } #[should_panic(expected = "\ Wiremock can't match the path `https://domain.com/abcd` because it contains the host `domain.com`. You don't have to specify the host - wiremock knows it. Try replacing your path with `path(\"/abcd\")`")] #[async_std::test] async fn host_is_not_accepted_in_path() { Mock::given(method("GET")).and(path("https://domain.com/abcd")); } #[async_std::test] async fn use_mock_guard_to_verify_requests_from_mock() { // Arrange let mock_server = MockServer::start().await; let first = mock_server .register_as_scoped( Mock::given(method("POST")) .and(PathExactMatcher::new("first")) .respond_with(ResponseTemplate::new(200)), ) .await; let second = mock_server .register_as_scoped( Mock::given(method("POST")) .and(PathExactMatcher::new("second")) .respond_with(ResponseTemplate::new(200)), ) .await; let client = reqwest::Client::new(); // Act let uri = mock_server.uri(); let response = client .post(format!("{uri}/first")) .json(&json!({ "attempt": 1})) .send() .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); let response = client .post(format!("{uri}/first")) .json(&json!({ "attempt": 2})) .send() .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); let response = client .post(format!("{uri}/second")) .json(&json!({ "attempt": 99})) .send() .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); // Assert let all_requests_to_first = first.received_requests().await; assert_eq!(all_requests_to_first.len(), 2); let value: serde_json::Value = second.received_requests().await[0].body_json().unwrap(); assert_eq!(value, json!({"attempt": 99})); } #[async_std::test] async fn use_mock_guard_to_await_satisfaction_readiness() { // Arrange let mock_server = MockServer::start().await; let satisfy = mock_server .register_as_scoped( Mock::given(method("POST")) .and(PathExactMatcher::new("satisfy")) .respond_with(ResponseTemplate::new(200)) .expect(1), ) .await; let eventually_satisfy = mock_server .register_as_scoped( Mock::given(method("POST")) .and(PathExactMatcher::new("eventually_satisfy")) .respond_with(ResponseTemplate::new(200)) .expect(1), ) .await; // Act one let uri = mock_server.uri(); let client = reqwest::Client::new(); let response = client.post(format!("{uri}/satisfy")).send().await.unwrap(); assert_eq!(response.status(), StatusCode::OK); // Assert satisfy .wait_until_satisfied() .now_or_never() .expect("should be satisfied immediately"); eventually_satisfy .wait_until_satisfied() .now_or_never() .ok_or(()) .expect_err("should not be satisfied yet"); // Act two async_std::task::spawn(async move { async_std::task::sleep(Duration::from_millis(100)).await; let client = reqwest::Client::new(); let response = client .post(format!("{uri}/eventually_satisfy")) .send() .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); }); // Assert eventually_satisfy .wait_until_satisfied() .now_or_never() .ok_or(()) .expect_err("should not be satisfied yet"); async_std::io::timeout( Duration::from_millis(1000), eventually_satisfy.wait_until_satisfied().map(Ok), ) .await .expect("should be satisfied"); } #[async_std::test] async fn debug_prints_mock_server_variants() { let pooled_mock_server = MockServer::start().await; let pooled_debug_str = format!("{:?}", pooled_mock_server); assert!(pooled_debug_str.starts_with("MockServer(Pooled(Object {")); assert!( pooled_debug_str .find( format!( "BareMockServer {{ address: {} }}", pooled_mock_server.address() ) .as_str() ) .is_some() ); let bare_mock_server = MockServer::builder().start().await; assert_eq!( format!( "MockServer(Bare(BareMockServer {{ address: {} }}))", bare_mock_server.address() ), format!("{:?}", bare_mock_server) ); } #[tokio::test] async fn io_err() { // Act let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")).respond_with_err(|_: &Request| { std::io::Error::new(ErrorKind::ConnectionReset, "connection reset") }); mock_server.register(mock).await; // Assert let err = reqwest::get(&mock_server.uri()).await.unwrap_err(); // We're skipping the original error since it can be either `error sending request` or // `error sending request for url (http://127.0.0.1:/)` let actual_err: Vec = iter::successors(std::error::Error::source(&err), |err| err.source()) .map(|err| err.to_string()) .collect(); let expected_err = vec![ "client error (SendRequest)".to_string(), "connection closed before message completed".to_string(), ]; assert_eq!(actual_err, expected_err); } #[tokio::test] async fn custom_err() { // Act #[derive(Debug)] struct CustomErr; impl Display for CustomErr { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str("custom error") } } impl std::error::Error for CustomErr {} let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")).respond_with_err(|_: &Request| CustomErr); mock_server.register(mock).await; // Assert let err = reqwest::get(&mock_server.uri()).await.unwrap_err(); // We're skipping the original error since it can be either `error sending request` or // `error sending request for url (http://127.0.0.1:/)` let actual_err: Vec = iter::successors(std::error::Error::source(&err), |err| err.source()) .map(|err| err.to_string()) .collect(); let expected_err = vec![ "client error (SendRequest)".to_string(), "connection closed before message completed".to_string(), ]; assert_eq!(actual_err, expected_err); } #[async_std::test] async fn method_matcher_is_case_insensitive() { // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200).set_body_bytes("world"); let mock = Mock::given(method("Get")) .and(PathExactMatcher::new("hello")) .respond_with(response); mock_server.register(mock).await; // Act let response = reqwest::get(format!("{}/hello", &mock_server.uri())) .await .unwrap(); // Assert assert_eq!(response.status(), 200); assert_eq!(response.text().await.unwrap(), "world"); } #[async_std::test] async fn http_crate_method_can_be_used_directly() { use http::Method; // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200).set_body_bytes("world"); let mock = Mock::given(method(Method::GET)) .and(PathExactMatcher::new("hello")) .respond_with(response); mock_server.register(mock).await; // Act let response = reqwest::get(format!("{}/hello", &mock_server.uri())) .await .unwrap(); // Assert assert_eq!(response.status(), 200); assert_eq!(response.text().await.unwrap(), "world"); } wiremock-0.6.5/tests/priority.rs000064400000000000000000000053031046102023000150460ustar 00000000000000use wiremock::{ Mock, MockServer, ResponseTemplate, matchers::{method, path, path_regex}, }; #[async_std::test] async fn should_prioritize_mock_with_highest_priority() { // Arrange let mock_server = MockServer::start().await; let exact = Mock::given(method("GET")) .and(path("abcd")) .respond_with(ResponseTemplate::new(200)) .with_priority(2); mock_server.register(exact).await; let regex = Mock::given(method("GET")) .and(path_regex("[a-z]{4}")) .respond_with(ResponseTemplate::new(201)) .with_priority(1); mock_server.register(regex).await; // Act let should_match = reqwest::get(format!("{}/abcd", mock_server.uri())) .await .unwrap(); // Assert assert_eq!(should_match.status(), 201); } #[async_std::test] async fn should_not_prioritize_mock_with_lower_priority() { // Arrange let mock_server = MockServer::start().await; let exact = Mock::given(method("GET")) .and(path("abcd")) .respond_with(ResponseTemplate::new(200)) .with_priority(u8::MAX); mock_server.register(exact).await; let regex = Mock::given(method("GET")) .and(path_regex("[a-z]{4}")) .respond_with(ResponseTemplate::new(201)); mock_server.register(regex).await; // Act let should_match = reqwest::get(format!("{}/abcd", mock_server.uri())) .await .unwrap(); // Assert assert_eq!(should_match.status(), 201); } #[async_std::test] async fn by_default_should_use_insertion_order() { // Arrange let mock_server = MockServer::start().await; let exact = Mock::given(method("GET")) .and(path("abcd")) .respond_with(ResponseTemplate::new(200)); let regex = Mock::given(method("GET")) .and(path_regex("[a-z]{4}")) .respond_with(ResponseTemplate::new(201)); mock_server.register(exact).await; mock_server.register(regex).await; // Act let should_match = reqwest::get(format!("{}/abcd", mock_server.uri())) .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); // Insert mocks in the opposite order // Arrange let mock_server = MockServer::start().await; let exact = Mock::given(method("GET")) .and(path("abcd")) .respond_with(ResponseTemplate::new(200)); let regex = Mock::given(method("GET")) .and(path_regex("[a-z]{4}")) .respond_with(ResponseTemplate::new(201)); mock_server.register(regex).await; mock_server.register(exact).await; // Act let should_match = reqwest::get(format!("{}/abcd", mock_server.uri())) .await .unwrap(); // Assert assert_eq!(should_match.status(), 201); } wiremock-0.6.5/tests/request_header_matching.rs000064400000000000000000000241031046102023000200360ustar 00000000000000use wiremock::matchers::{basic_auth, bearer_token, header, header_regex, headers, method}; use wiremock::{Mock, MockServer, ResponseTemplate}; #[async_std::test] async fn should_match_simple_request_header() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header("content-type", "application/json")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; // Act let client = reqwest::Client::new(); let should_match = client .get(mock_server.uri()) .header("content-type", "application/json") .send() .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); } #[async_std::test] async fn should_not_match_simple_request_header_upon_wrong_key() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header("content-type", "application/json")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; // Act let client = reqwest::Client::new(); let should_fail_wrong_key = client .get(mock_server.uri()) .header("accept", "application/json") .send() .await .unwrap(); // Assert assert_eq!(should_fail_wrong_key.status(), 404); } #[async_std::test] async fn should_not_match_simple_request_header_upon_wrong_value() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header("content-type", "application/json")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; // Act let client = reqwest::Client::new(); let should_fail_wrong_value = client .get(mock_server.uri()) .header("content-type", "application/xml") .send() .await .unwrap(); // Assert assert_eq!(should_fail_wrong_value.status(), 404); } #[async_std::test] async fn should_match_multi_request_header() { // Arrange let mock_server = MockServer::start().await; let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]); let mock = Mock::given(method("GET")) .and(header_matcher) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; // Act let client = reqwest::Client::new(); let should_match = client .get(mock_server.uri()) .header("cache-control", "no-cache, no-store") .send() .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); } #[tokio::test] async fn should_match_multi_request_header_x() { // Arrange let mock_server = MockServer::start().await; let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]); let mock = Mock::given(method("GET")) .and(header_matcher) .respond_with(ResponseTemplate::new(200)) .expect(1); mock_server.register(mock).await; // Act let should_match = reqwest::Client::new() .get(mock_server.uri()) // TODO: use a dedicated headers when upgrade reqwest v0.12 .header("cache-control", "no-cache") .header("cache-control", "no-store") .send() .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); } #[async_std::test] async fn should_not_match_multi_request_header_upon_wrong_values() { // Arrange let mock_server = MockServer::start().await; let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]); let mock = Mock::given(method("GET")) .and(header_matcher) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; // Act let client = reqwest::Client::new(); let should_fail_wrong_values = client .get(mock_server.uri()) .header("cache-control", "no-cache, no-transform") .send() .await .unwrap(); // Assert assert_eq!(should_fail_wrong_values.status(), 404); } #[async_std::test] async fn should_not_match_multi_request_header_upon_incomplete_values() { // Arrange let mock_server = MockServer::start().await; let header_matcher = headers("cache-control", vec!["no-cache", "no-store"]); let mock = Mock::given(method("GET")) .and(header_matcher) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; // Act let client = reqwest::Client::new(); let should_fail_incomplete_values = client .get(mock_server.uri()) .header("cache-control", "no-cache") .send() .await .unwrap(); // Assert assert_eq!(should_fail_incomplete_values.status(), 404); } #[async_std::test] async fn should_match_regex_single_header_value() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header_regex("cache-control", r"no-(cache|store)")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_match = client .get(mock_server.uri()) .header("cache-control", "no-cache") .send() .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); } #[async_std::test] async fn should_match_regex_multiple_header_values() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header_regex("cache-control", r"no-(cache|store)")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_match = client .get(mock_server.uri()) .header("cache-control", "no-cache") .header("cache-control", "no-store") .send() .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); } #[async_std::test] async fn should_not_match_regex_with_wrong_header_value() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header_regex("cache-control", r"no-(cache|store)")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_fail_wrong_value = client .get(mock_server.uri()) .header("cache-control", "no-junk") .send() .await .unwrap(); // Assert assert_eq!(should_fail_wrong_value.status(), 404); } #[async_std::test] async fn should_not_match_regex_with_at_least_one_wrong_header_value() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header_regex("cache-control", r"no-(cache|store)")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_fail_wrong_value = client .get(mock_server.uri()) .header("cache-control", "no-cache") .header("cache-control", "no-junk") .send() .await .unwrap(); // Assert assert_eq!(should_fail_wrong_value.status(), 404); } #[async_std::test] async fn should_not_match_regex_with_no_values_for_header() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(header_regex("cache-control", r"no-(cache|store)")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; // Act let should_fail_wrong_value = reqwest::get(mock_server.uri()).await.unwrap(); // Assert assert_eq!(should_fail_wrong_value.status(), 404); } #[async_std::test] async fn should_match_basic_auth_header() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(basic_auth("Aladdin", "open sesame")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_match = client .get(mock_server.uri()) .header("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==") .send() .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); } #[async_std::test] async fn should_not_match_bad_basic_auth_header() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(basic_auth("Aladdin", "close sesame")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_not_match = client .get(mock_server.uri()) .header("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==") .send() .await .unwrap(); // Assert assert_eq!(should_not_match.status(), 404); } #[async_std::test] async fn should_match_bearer_token_header() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(bearer_token("delightful")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_match = client .get(mock_server.uri()) .header("Authorization", "Bearer delightful") .send() .await .unwrap(); // Assert assert_eq!(should_match.status(), 200); } #[async_std::test] async fn should_not_match_bearer_token_header() { // Arrange let mock_server = MockServer::start().await; let mock = Mock::given(method("GET")) .and(bearer_token("expired")) .respond_with(ResponseTemplate::new(200)); mock_server.register(mock).await; let client = reqwest::Client::new(); // Act let should_not_match = client .get(mock_server.uri()) .header("Authorization", "Bearer delightful") .send() .await .unwrap(); // Assert assert_eq!(should_not_match.status(), 404); } wiremock-0.6.5/tests/timeout.rs000064400000000000000000000015311046102023000146520ustar 00000000000000use reqwest::Client; use wiremock::matchers::any; use wiremock::{Mock, MockServer, ResponseTemplate}; async fn test_body() { // Arrange let mock_server = MockServer::start().await; let response = ResponseTemplate::new(200).set_delay(std::time::Duration::from_secs(60)); Mock::given(any()) .respond_with(response) .mount(&mock_server) .await; // Act let outcome = Client::builder() .timeout(std::time::Duration::from_secs(1)) .build() .unwrap() .get(&mock_server.uri()) .send() .await; // Assert assert!(outcome.is_err()); } #[actix_rt::test] async fn request_times_out_if_the_server_takes_too_long_with_actix() { test_body().await } #[tokio::test] async fn request_times_out_if_the_server_takes_too_long_with_tokio() { test_body().await } wiremock-0.6.5/tests/tokio.rs000064400000000000000000000030431046102023000143110ustar 00000000000000use reqwest::Client; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; // regression tests for https://github.com/LukeMathWalker/wiremock-rs/issues/7 // running both tests will _sometimes_ trigger a hang if the runtimes aren't separated correctly #[tokio::test] async fn hello_reqwest() { let mock_server = MockServer::start().await; Mock::given(method("GET")) .and(path("/")) .respond_with(ResponseTemplate::new(200)) .mount(&mock_server) .await; let resp = Client::new().get(&mock_server.uri()).send().await.unwrap(); assert_eq!(resp.status(), 200); } #[actix_rt::test] async fn hello_reqwest_actix() { let mock_server = MockServer::start().await; Mock::given(method("GET")) .and(path("/")) .respond_with(ResponseTemplate::new(200)) .mount(&mock_server) .await; let resp = Client::new().get(&mock_server.uri()).send().await.unwrap(); assert_eq!(resp.status(), 200); } #[tokio::test] async fn hello_reqwest_http2() { let mock_server = MockServer::start().await; Mock::given(method("GET")) .and(path("/")) .respond_with(ResponseTemplate::new(200)) .mount(&mock_server) .await; let resp = Client::builder() .http2_prior_knowledge() .build() .expect("http client") .get(&mock_server.uri()) .send() .await .expect("response"); assert_eq!(resp.status(), 200); assert_eq!(resp.version(), reqwest::Version::HTTP_2); }