From eeb3fab483c073fd80f1e7207a21e60a931ebe2f Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 14:26:48 +0200 Subject: [PATCH 01/13] v0.8.0 alpha --- .gitignore | 11 +- CHANGELOG.md | 15 +- CODE_OF_CONDUCT.md | 128 + Cargo.toml | 63 +- Dockerfile | 32 +- Makefile | 81 +- README.md | 21 +- certs/ca.key | 28 + certs/ca.pem | 19 + deny.toml | 237 + docker-compose.yml | 13 + docs/.gitignore | 23 + docs/.vscode/extensions.json | 4 + docs/.vscode/launch.json | 11 + docs/assets/Logo-design.afdesign | Bin 0 -> 464134 bytes docs/assets/Logo.afdesign | Bin 0 -> 12342 bytes docs/{ => assets}/diff.png | Bin docs/website/README.md | 54 + docs/website/astro.config.mjs | 86 + docs/website/generated/code_examples.json | 104 + docs/website/generated/docs.json | 104 + docs/website/generated/example_tests.json | 7 + docs/website/generated/groups.json | 398 + docs/website/package-lock.json | 8807 +++++++++++++++++ docs/website/package.json | 25 + docs/website/public/favicon.svg | 1 + docs/website/src/assets/houston.webp | Bin 0 -> 98506 bytes docs/website/src/assets/landing.css | 45 + docs/website/src/assets/logo-dark.svg | 45 + docs/website/src/assets/logo-light.svg | 45 + docs/website/src/assets/logo.png | Bin 0 -> 32762 bytes docs/website/src/assets/logo_color.png | Bin 0 -> 16611 bytes docs/website/src/assets/logo_color_round.png | Bin 0 -> 119882 bytes .../src/assets/logo_color_round_sm.png | Bin 0 -> 112381 bytes docs/website/src/assets/logo_color_sqare.png | Bin 0 -> 49165 bytes docs/website/src/assets/logo_round_sm.png | Bin 0 -> 111167 bytes docs/website/src/assets/logo_square.png | Bin 0 -> 37094 bytes docs/website/src/content/config.ts | 6 + .../docs/getting_started/contributing.md | 6 + .../docs/getting_started/fundamentals.mdx | 122 + .../getting_started/quick_introduction.mdx | 89 + .../content/docs/getting_started/resources.md | 30 + .../docs/getting_started/why_httpmock.md | 11 + docs/website/src/content/docs/index.mdx | 56 + .../src/content/docs/miscellaneous/faq.mdx | 13 + .../docs/record-and-playback/playback.mdx | 39 + .../docs/record-and-playback/recording.mdx | 82 + .../src/content/docs/server/debugging.mdx | 81 + .../website/src/content/docs/server/https.mdx | 77 + .../src/content/docs/server/standalone.mdx | 116 + docs/website/src/env.d.ts | 2 + .../templates/matching_requests/body.md | 82 + .../templates/matching_requests/cookies.md | 44 + .../templates/matching_requests/custom.md | 15 + .../templates/matching_requests/headers.md | 42 + .../templates/matching_requests/host.md | 34 + .../templates/matching_requests/method.md | 13 + .../templates/matching_requests/path.md | 33 + .../templates/matching_requests/port.md | 13 + .../templates/matching_requests/query.md | 42 + .../templates/matching_requests/scheme.md | 15 + .../templates/mocking_responses/all.md | 24 + .../templates/mocking_responses/delay.md | 9 + docs/website/tools/generate-docs.cjs | 56 + docs/website/tsconfig.json | 3 + gitlab-ci.yml | 10 + rustfmt.toml | 2 + scripts/generate_tarpaulin_config.sh | 52 + scripts/run_tarpaulin_tests.sh | 46 + scripts/run_tarpaulin_tests_limited.sh | 46 + src/api/adapter/local.rs | 228 +- src/api/adapter/mod.rs | 126 +- src/api/adapter/remote.rs | 557 ++ src/api/adapter/standalone.rs | 298 - src/api/mock.rs | 742 +- src/api/mod.rs | 15 +- src/api/output.rs | 382 + src/api/proxy.rs | 351 + src/api/server.rs | 1233 ++- src/api/spec.rs | 5381 ++++++++-- src/common/data.rs | 1844 +++- src/common/http.rs | 90 + src/common/mod.rs | 6 +- src/common/runtime.rs | 35 + src/common/util.rs | 224 +- src/lib.rs | 196 +- src/main.rs | 32 +- src/server/builder.rs | 507 + src/server/handler.rs | 553 ++ src/server/matchers/comparators.rs | 790 +- src/server/matchers/comparison.rs | 1773 ++++ src/server/matchers/generic.rs | 576 +- src/server/matchers/mod.rs | 1222 ++- src/server/matchers/readers.rs | 710 ++ src/server/matchers/sources.rs | 378 - src/server/matchers/targets.rs | 214 - src/server/matchers/transformers.rs | 84 - src/server/mod.rs | 785 +- src/server/persistence.rs | 112 + src/server/server.rs | 466 + src/server/state.rs | 744 ++ src/server/tls.rs | 311 + src/server/util.rs | 14 +- src/server/web/handlers.rs | 733 -- src/server/web/mod.rs | 2 - src/server/web/routes.rs | 196 - src/standalone.rs | 157 - tarpaulin.full.toml | 514 + tarpaulin.toml | 12 + tests/examples/binary_body_tests.rs | 11 +- tests/examples/cookie_tests.rs | 12 +- .../examples/custom_request_matcher_tests.rs | 12 +- tests/examples/delay_tests.rs | 5 +- tests/examples/delete_mock_tests.rs | 11 +- tests/examples/file_body_tests.rs | 3 +- tests/examples/forwarding_tests.rs | 70 + tests/examples/getting_started_tests.rs | 41 +- tests/examples/headers_tests.rs | 35 +- tests/examples/https_tests.rs | 62 + tests/examples/json_body_tests.rs | 25 +- tests/examples/mod.rs | 9 +- tests/examples/multi_server_tests.rs | 13 +- tests/examples/proxy_tests.rs | 46 + tests/examples/query_param_tests.rs | 22 +- tests/examples/record_and_playback_tests.rs | 206 + tests/examples/reset_tests.rs | 15 +- tests/examples/showcase_tests.rs | 18 +- tests/examples/standalone_tests.rs | 103 +- tests/examples/string_body_tests.rs | 12 +- tests/examples/url_matching_tests.rs | 10 +- tests/examples/x_www_form_urlencoded_tests.rs | 19 +- tests/internal/runtimes_test.rs | 42 - tests/lib.rs | 54 +- tests/matchers/body.rs | 419 + tests/matchers/cookies.rs | 252 + tests/matchers/headers.rs | 229 + tests/matchers/host.rs | 147 + tests/matchers/method.rs | 96 + tests/matchers/mod.rs | 775 ++ tests/matchers/path.rs | 179 + tests/matchers/port.rs | 113 + tests/matchers/query_param.rs | 222 + tests/matchers/scheme.rs | 95 + tests/matchers/urlencoded_body.rs | 244 + tests/{internal => misc}/extensions_test.rs | 5 +- tests/{internal => misc}/large_body_test.rs | 20 +- tests/{internal => misc}/loop_test.rs | 19 +- tests/{internal => misc}/mod.rs | 1 + tests/misc/runtimes_test.rs | 72 + tests/resources/simple_static_mock.yaml | 6 + tools/Cargo.toml | 27 + tools/src/extract_code.rs | 78 + tools/src/extract_docs.rs | 72 + tools/src/extract_example_tests.rs | 49 + tools/src/extract_groups.rs | 72 + 155 files changed, 33334 insertions(+), 5025 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 certs/ca.key create mode 100644 certs/ca.pem create mode 100644 deny.toml create mode 100644 docker-compose.yml create mode 100644 docs/.gitignore create mode 100644 docs/.vscode/extensions.json create mode 100644 docs/.vscode/launch.json create mode 100644 docs/assets/Logo-design.afdesign create mode 100644 docs/assets/Logo.afdesign rename docs/{ => assets}/diff.png (100%) create mode 100644 docs/website/README.md create mode 100644 docs/website/astro.config.mjs create mode 100644 docs/website/generated/code_examples.json create mode 100644 docs/website/generated/docs.json create mode 100644 docs/website/generated/example_tests.json create mode 100644 docs/website/generated/groups.json create mode 100644 docs/website/package-lock.json create mode 100644 docs/website/package.json create mode 100644 docs/website/public/favicon.svg create mode 100644 docs/website/src/assets/houston.webp create mode 100644 docs/website/src/assets/landing.css create mode 100644 docs/website/src/assets/logo-dark.svg create mode 100644 docs/website/src/assets/logo-light.svg create mode 100644 docs/website/src/assets/logo.png create mode 100644 docs/website/src/assets/logo_color.png create mode 100644 docs/website/src/assets/logo_color_round.png create mode 100644 docs/website/src/assets/logo_color_round_sm.png create mode 100644 docs/website/src/assets/logo_color_sqare.png create mode 100644 docs/website/src/assets/logo_round_sm.png create mode 100644 docs/website/src/assets/logo_square.png create mode 100644 docs/website/src/content/config.ts create mode 100644 docs/website/src/content/docs/getting_started/contributing.md create mode 100644 docs/website/src/content/docs/getting_started/fundamentals.mdx create mode 100644 docs/website/src/content/docs/getting_started/quick_introduction.mdx create mode 100644 docs/website/src/content/docs/getting_started/resources.md create mode 100644 docs/website/src/content/docs/getting_started/why_httpmock.md create mode 100644 docs/website/src/content/docs/index.mdx create mode 100644 docs/website/src/content/docs/miscellaneous/faq.mdx create mode 100644 docs/website/src/content/docs/record-and-playback/playback.mdx create mode 100644 docs/website/src/content/docs/record-and-playback/recording.mdx create mode 100644 docs/website/src/content/docs/server/debugging.mdx create mode 100644 docs/website/src/content/docs/server/https.mdx create mode 100644 docs/website/src/content/docs/server/standalone.mdx create mode 100644 docs/website/src/env.d.ts create mode 100644 docs/website/templates/matching_requests/body.md create mode 100644 docs/website/templates/matching_requests/cookies.md create mode 100644 docs/website/templates/matching_requests/custom.md create mode 100644 docs/website/templates/matching_requests/headers.md create mode 100644 docs/website/templates/matching_requests/host.md create mode 100644 docs/website/templates/matching_requests/method.md create mode 100644 docs/website/templates/matching_requests/path.md create mode 100644 docs/website/templates/matching_requests/port.md create mode 100644 docs/website/templates/matching_requests/query.md create mode 100644 docs/website/templates/matching_requests/scheme.md create mode 100644 docs/website/templates/mocking_responses/all.md create mode 100644 docs/website/templates/mocking_responses/delay.md create mode 100644 docs/website/tools/generate-docs.cjs create mode 100644 docs/website/tsconfig.json create mode 100644 gitlab-ci.yml create mode 100644 rustfmt.toml create mode 100755 scripts/generate_tarpaulin_config.sh create mode 100755 scripts/run_tarpaulin_tests.sh create mode 100755 scripts/run_tarpaulin_tests_limited.sh create mode 100644 src/api/adapter/remote.rs delete mode 100644 src/api/adapter/standalone.rs create mode 100644 src/api/output.rs create mode 100644 src/api/proxy.rs create mode 100644 src/common/http.rs create mode 100644 src/common/runtime.rs create mode 100644 src/server/builder.rs create mode 100644 src/server/handler.rs create mode 100644 src/server/matchers/comparison.rs create mode 100644 src/server/matchers/readers.rs delete mode 100644 src/server/matchers/sources.rs delete mode 100644 src/server/matchers/targets.rs delete mode 100644 src/server/matchers/transformers.rs create mode 100644 src/server/persistence.rs create mode 100644 src/server/server.rs create mode 100644 src/server/state.rs create mode 100644 src/server/tls.rs delete mode 100644 src/server/web/handlers.rs delete mode 100644 src/server/web/mod.rs delete mode 100644 src/server/web/routes.rs delete mode 100644 src/standalone.rs create mode 100644 tarpaulin.full.toml create mode 100644 tarpaulin.toml create mode 100644 tests/examples/forwarding_tests.rs create mode 100644 tests/examples/https_tests.rs create mode 100644 tests/examples/proxy_tests.rs create mode 100644 tests/examples/record_and_playback_tests.rs delete mode 100644 tests/internal/runtimes_test.rs create mode 100644 tests/matchers/body.rs create mode 100644 tests/matchers/cookies.rs create mode 100644 tests/matchers/headers.rs create mode 100644 tests/matchers/host.rs create mode 100644 tests/matchers/method.rs create mode 100644 tests/matchers/mod.rs create mode 100644 tests/matchers/path.rs create mode 100644 tests/matchers/port.rs create mode 100644 tests/matchers/query_param.rs create mode 100644 tests/matchers/scheme.rs create mode 100644 tests/matchers/urlencoded_body.rs rename tests/{internal => misc}/extensions_test.rs (92%) rename tests/{internal => misc}/large_body_test.rs (52%) rename tests/{internal => misc}/loop_test.rs (72%) rename tests/{internal => misc}/mod.rs (66%) create mode 100644 tests/misc/runtimes_test.rs create mode 100644 tests/resources/simple_static_mock.yaml create mode 100644 tools/Cargo.toml create mode 100644 tools/src/extract_code.rs create mode 100644 tools/src/extract_docs.rs create mode 100644 tools/src/extract_example_tests.rs create mode 100644 tools/src/extract_groups.rs diff --git a/.gitignore b/.gitignore index d80d35be..518b2731 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ +/tools/target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html @@ -13,4 +14,12 @@ Cargo.lock *.iml # OSX -.DS_Store \ No newline at end of file +.DS_Store + +# LLVM coverage and profiling tool files +*.profraw + +# Coverage report files +cobertura.xml +tarpaulin-report.html + diff --git a/CHANGELOG.md b/CHANGELOG.md index 84eb0c5e..36192755 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,17 @@ # Changelog -## Version 0.7.1 - -- A new [MockServer::reset](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html#method.reset) method was added that resets a mock server. Thanks for providing the [pull request](https://github.com/alexliesenfeld/httpmock/pull/100) for this feature, [@dax](https://github.com/dax). +## Version 0.8.0 + +### BREAKING CHANGES +- A new [MockServer::reset](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html#method.reset) method was added that resets a mock server. Thanks for providing the [pull request](https://github.com/alexliesenfeld/httpmock/pull/100) for this feature, [@dax](https://github.com/dax). +- The default port for standalone server was changed from `5000` to `5050` due to conflicts with system services on macOS. +- [Custom matcher functions](https://docs.rs/httpmock/latest/httpmock/struct.When.html#method.matches) are now closures rather than functions. +- [When::json_body_partial](https://docs.rs/httpmock/0.7.0/httpmock/struct.When.html#method.json_body_partial) was renamed to `json_body_includes`. +- [When::x_www_form_urlencoded_tuple](https://docs.rs/httpmock/0.7.0/httpmock/struct.When.html#method.x_www_form_urlencoded) was renamed to `form_urlencoded_tuple`. +- [When::x_www_form_urlencoded_key_exists](https://docs.rs/httpmock/0.7.0/httpmock/struct.When.html#method.x_www_form_urlencoded) was renamed to `form_urlencoded_key_exists`. + +### Improvements +- The algorithm to find the most similar request in case of mock assertion failures has been improved. ## Version 0.7.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f95d6907 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible 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. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders 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, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 8f97ff83..848c8788 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "httpmock" -version = "0.7.0" +version = "0.8.0-alpha.1" authors = ["Alexander Liesenfeld "] edition = "2018" description = "HTTP mocking library for Rust" @@ -18,45 +18,64 @@ lazy_static = "1.4" base64 = "0.22" regex = "1.10" log = "0.4" -url = "2.4" +url = "2.5" +stringmetrics = "2" assert-json-diff = "2.0" async-trait = "0.1" async-object-pool = "0.1" crossbeam-utils = "0.8" futures-util = "0.3" -similar = "2.3" -levenshtein = "1.0" +similar = "2.6" form_urlencoded = "1.2" - -hyper = { version = "0.14", features = ["server", "http1", "tcp"] } -tokio = { version = "1.33", features = ["sync", "macros", "rt-multi-thread", "signal"] } - -isahc = { version = "1.7", optional = true } -basic-cookies = { version = "0.1", optional = true } -colored = { version = "2.0", optional = true } -clap = { version = "4.4", features = ["derive", "env"], optional = true } +thiserror = "1.0" +path-tree = "0.8" +http = "1" +bytes = { version = "1", features = ["serde"] } +hyper = { version = "1.4", features = ["server", "http1", "client"] } +hyper-util = { version = "0.1", features = ["tokio", "server", "http1", "server-auto"] } +http-body-util = "0.1" +tokio = { version = "1.36", features = ["sync", "macros", "rt-multi-thread", "signal"] } +tabwriter = "1.4" +colored = { version = "2.1", optional = true } +clap = { version = "4.5", features = ["derive", "env"], optional = true } env_logger = { version = "0.11", optional = true } serde_yaml = { version = "0.9", optional = true } async-std = { version = "1.12", features = ["attributes", "unstable"] } +headers = { version = "0.4", optional = true } + +### TLS / HTTPS / PROXY +rustls = { version = "0.23", default-features = false, features = ["std", "tls12"], optional = true } +rcgen = { version = "0.12", features = ["pem", "x509-parser"], optional = true } +tokio-rustls = { version = "0.26", optional = true } +rustls-pemfile = { version = "2", optional = true } +tls-detect = { version = "0.1", optional = true } +hyper-rustls = { version = "0.27", optional = true } +futures-timer = "3" [dev-dependencies] env_logger = "0.11" tokio-test = "0.4" quote = "1.0" actix-rt = "2.9" -colored = "2.0" -ureq = "2.8" - -isahc = { version = "1.7", features = ["json"] } +colored = "2.1" +reqwest = { version = "0.12", features = ["blocking", "cookies", "rustls-tls", "rustls-tls-native-roots"] } syn = { version = "2.0", features = ["full"] } +urlencoding = "2.1.2" -reqwest = "0.11.22" [features] -default = ["cookies"] -standalone = ["clap", "env_logger", "serde_yaml", "remote"] -color = ["colored"] -cookies = ["basic-cookies"] -remote = ["isahc"] +default = ["cookies", "http2", "record"] +standalone = ["clap", "env_logger", "static-mock", "remote", "http2", "https", "cookies", "color"] # enables standalone mode +color = ["colored"] # enables colorful output in standalone mode +cookies = ["headers"] # enables support for matching cookies +remote = ["hyper-util/client-legacy", "hyper-util/http2"] # allows to connect to remote mock servers +remote-https = ["remote", "rustls", "hyper-rustls", "hyper-rustls/http2"] # allows to connect to remote mock servers via HTTPS +proxy = ["remote-https", "hyper-util/client-legacy", "hyper-util/http2", "hyper-rustls", "hyper-rustls/http2"] # enables proxy functionality +static-mock = ["serde_yaml"] # allows to read mock definitions from the file system +https = ["rustls", "rcgen", "tokio-rustls", "rustls-pemfile", "rustls/ring", "tls-detect"] # enables httpmock server support for TLS/HTTPS +http2 = ["hyper/http2", "hyper-util/http2"] # enables httpmocks server support for HTTP2 +record = ["static-mock", "proxy"] +experimental = [] # marker feature for experimental features +full = ["default", "standalone", "color", "cookies", "remote", "remote-https", "proxy", "static-mock", "https", "http2", "record", "experimental"] [[bin]] name = "httpmock" diff --git a/Dockerfile b/Dockerfile index be79fbf6..addcc892 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,15 @@ -# ================================================================================ -# Builder -# ================================================================================ +# First stage: build the application FROM rust:1.74 as builder -WORKDIR /usr/src/httpmock -COPY Cargo.toml . - -COPY src/ ./src/ - -RUN cargo install --features="standalone" --path . - -# ================================================================================ -# Runner -# ================================================================================ -FROM debian:bullseye-slim -RUN apt-get update && apt-get install -y openssl && rm -rf /var/lib/apt/lists/* -COPY --from=builder /usr/local/cargo/bin/httpmock /usr/local/bin/httpmock +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* # Log level (refer to env_logger crate for more information) ENV RUST_LOG httpmock=info # The TCP port on which the mock server will listen to. -ENV HTTPMOCK_PORT 5000 +ENV HTTPMOCK_PORT 5050 # Container internal directory path that contains file bases mock specs (YAML-fies). # ENV HTTPMOCK_MOCK_FILES_DIR /mocks @@ -32,6 +20,16 @@ ENV HTTPMOCK_PORT 5000 # Request history limit. ENV HTTPMOCK_REQUEST_HISTORY_LIMIT 100 +WORKDIR /httpmock + +COPY Cargo.toml . +COPY Cargo.lock . + +COPY src/ ./src/ +COPY certs/ ./certs/ + +RUN cargo install --all-features --path . + ENTRYPOINT ["httpmock", "--expose"] EXPOSE ${HTTPMOCK_PORT} \ No newline at end of file diff --git a/Makefile b/Makefile index ccf9a77a..f98794ac 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,68 @@ -.PHONY: build -build: - cargo build +.PHONY: setup +setup: + cargo install cargo-audit + cargo install --locked cargo-deny + cargo install cargo-tarpaulin + cargo install cargo-hack -.PHONY: test-local -test-local: - cargo test +.PHONY: test-full +test-full: + docker compose up -d + HTTPMOCK_TESTS_DISABLE_SIMULATED_STANDALONE_SERVER=1 cargo hack test --feature-powerset --exclude-features https -.PHONY: test-remote -test-local: - cargo test --features=remote +.PHONY: check +check: + cargo fmt --check + cargo clippy + cargo audit + cargo deny check -.PHONY: test-standalone -test-standalone: - cargo test --features standalone +.PHONY: coverage +coverage: + cargo tarpaulin --out -.PHONY: test-all -test-all: - cargo test --all-features +.PHONY: coverage-full +coverage-full: + cargo tarpaulin --config tarpaulin.full.toml --out -.PHONY: build-docker -build-docker: - docker build . \ No newline at end of file +.PHONY: coverage-debug +coverage-debug: + RUST_BACKTRACE=1 RUST_LOG=trace cargo tarpaulin --out -- --nocapture + +.PHONY: clean-coverage +clean-coverage: + rm -f *.profraw + rm -f cobertura.xml + rm -f tarpaulin-report.html + +.PHONY: clean-coverage +clean: clean-coverage + cargo clean + +.PHONY: certs +certs: + rm -rf certs + mkdir certs + cd certs && openssl genrsa -out ca.key 2048 + cd certs && openssl req -x509 -new -nodes -key ca.key -sha256 -days 36525 -out ca.pem -subj "/CN=httpmock" + +.PHONY: docker +docker: + docker-compose build --no-cache + docker-compose up + +.PHONY: docs +docs: + rm -rf tools/target/generated && mkdir -p tools/target/generated + cd tools && cargo run --bin extract_docs + cd tools && cargo run --bin extract_code + cd tools && cargo run --bin extract_groups + cd tools && cargo run --bin extract_example_tests + rm -rf docs/website/generated && cp -r tools/target/generated docs/website/generated + cd docs/website && npm run generate-docs + + +.PHONY: fmt +fmt: + cargo fmt + cargo fix --allow-dirty \ No newline at end of file diff --git a/README.md b/README.md index 42da9b1d..d82d24e8 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,16 @@ [![crates.io](https://img.shields.io/crates/d/httpmock.svg)](https://crates.io/crates/httpmock) [![Mentioned in Awesome](https://awesome.re/badge.svg)](https://github.com/rust-unofficial/awesome-rust#testing) [![Rust](https://img.shields.io/badge/rust-1.70%2B-blue.svg?maxAge=3600)](https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1700-2023-06-01) +[![Discord](https://img.shields.io/badge/Chat-Discord-%235865F2.svg)](https://discord.gg/QrjhRh7A)

- Documentation + Online Documentation + · + API Reference + · + Chat · Crate · @@ -33,12 +38,16 @@ * Simple, expressive, fluent API. * Many built-in helpers for easy request matching ([Regex](https://docs.rs/regex/), JSON, [serde](https://crates.io/crates/serde), cookies, and more). * Parallel test execution. -* Extensible request matching. -* Fully asynchronous core with synchronous and asynchronous APIs. -* [Advanced verification and debugging support](https://alexliesenfeld.github.io/posts/mocking-http--services-in-rust/#creating-mocks) (including diff generation between actual and expected HTTP request values) +* Custom request matchers. +* Record and Playback +* Forward and Proxy Mode +* HTTPS support * Fault and network delay simulation. -* Support for [Regex](https://docs.rs/regex/) matching, JSON, [serde](https://crates.io/crates/serde), cookies, and more. * Standalone mode with an accompanying [Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock). +* Helpful error messages +* [Advanced verification and debugging support](https://alexliesenfeld.github.io/posts/mocking-http--services-in-rust/#creating-mocks) (including diff generation between actual and expected HTTP request values) +* Fully asynchronous core with synchronous and asynchronous APIs. +* Support for [Regex](https://docs.rs/regex/) matching, JSON, [serde](https://crates.io/crates/serde), cookies, and more. * Support for [mock configuration using YAML files](https://github.com/alexliesenfeld/httpmock/tree/master#file-based-mock-specification). ## Getting Started @@ -47,7 +56,7 @@ Add `httpmock` to `Cargo.toml`: ```toml [dev-dependencies] -httpmock = "0.7.0" +httpmock = "0.8.0-alpha.1" ``` You can then use `httpmock` as follows: diff --git a/certs/ca.key b/certs/ca.key new file mode 100644 index 00000000..72bb8fbf --- /dev/null +++ b/certs/ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/XRExWtLyoTUW +ZuDro/XhD4qguNA3nrcFWbxTeg7uuFkS5HvXhnh35awAWx3PpwGzxSknJCMGKoWF +kThR1aWbkcK/veudcBmtlVxk8fYy8YD87Q1I+Or73uQiqgkuJHHWYi8vk/p5hjEZ +q4Ce6+81wNsZWx7yBcX8YZ9E6BdByCGic6gnt3wuLQ3UErHgtFjWOV/hhgZPmYq5 +hfI8Tg5U42+mZ+3RVG9dpLUG/3YaxT3HpguGT2Un8iK0jgeh+LU/XGu1DOxyPo6C +Mg+MTRIKfTteMriE/AoY7GmKjtUYeWovs3QQqUq8VramlgO9i7zMM+IPcPlPscrr +sajissOTAgMBAAECggEAPNtyHkoOEA9ofKlXGllYVqzEn3xm62lUNyVkmP+WRCDo +YvO61r3zDd5CpxJTFri799nZzpLVeJ6JPAME8DGLqz/duXDCv5zo7aU0bv3sGCNp +rAYPVYej41ntH4EHzl7UvSMYcn8TBxbAArPiAahyuJuOki/CVaG0ZyD8r8NHsilQ +SNmhWJpr3tebwkLXpzIHDRNv4CNN7eOkdHXVvuqH5NEL+aqrg0dRY4/mQ8Dya3YF +Om4M7lS7si8sDbEn0DbDJskVVxdVzjqvEc24NBmydKWrXGJXhzqdvqtVVsrsrwNF ++iEmVgkmeD9uQyH4JTHLcZi5C6BqSs8ZGAmcuUB+8QKBgQDr10+37K4YUXTeToS9 +KV6JIMn8k181Jk5WiIUsWwh3Ln6jfM9Ui39ROqGwDnqDDNPcwU2rmXUx4oJK52OE +MEuW64jQpEmC9jwi/1Mqksd6joVZHmofg8r27A7TZf68FGAamq/zvxgvdkCY09px +Qx7ahZU0GrCxm5DWcnMnEDBtlwKBgQDPuH76Ddan6x8VlPMrAY1mlAUH7QEItuVs +ch+Tb+wDfjq7LcOsMGajIAYEwQKmgKKWjE+vr4QF76R8CaHV5QFpVROZGRt+ccJv +SdG6Iln0wZxaAbWiS7j1+a6K/QiGjjuXsKLnIuw2/cpfuesdp1USYhUUJTnbbVy5 +hr8mFUORZQKBgQCbuqomLf/riOYd4UUfT1DgRal6walthCTYWP9vAZF+eVIgDEsv +bYmdjpSzl2voWzEOpQnvlL5hOUuFwHLjF6ziNBc8hi8Qbh3ZkjVNeGyGDdQZu86h +jroYAFnt13y0ntOy3Y/v6LBErtYK1GF6xrJ54xlZtYIVVT73i81j7vm7cwKBgQC7 +wYJUt8l9QpN4SIh8KQ0M2WKqxVmX7On3WjicZiApECI6KqWhsKY1cK7AAU5J/h/4 +gJ9OqBFn5DMDQxmbY0IhWZs7WWx2oJElUs5VttMk3xRabw0kw9lNzQAt9YWNSmcn +N6wnzHNDSadxW3Xf+e51jV6MNRHU+0dDEz8YR0Qp2QKBgH3eXlWq3tBQrwccxUjh +aN3qziwJan0dWFUrclXdu2EgY8T8aOR8D4iHkGPaGR2BPMOxJZO9whFmNex8UYOa +JSvx/Zpno9UheVQkYOeVGyStdD/biN1BJ0rQp72cBqKawaqujzmkuuQhdK0PHodG +W6oGu2c+Yw1vuF12lyWuGXaM +-----END PRIVATE KEY----- diff --git a/certs/ca.pem b/certs/ca.pem new file mode 100644 index 00000000..c7ca12ea --- /dev/null +++ b/certs/ca.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUNR4A1PGeh9qcMWItrv3HvzfkR10wDQYJKoZIhvcNAQEL +BQAwEzERMA8GA1UEAwwIaHR0cG1vY2swIBcNMjQwMzE5MTk1OTMyWhgPMjEyNDAz +MjAxOTU5MzJaMBMxETAPBgNVBAMMCGh0dHBtb2NrMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv10RMVrS8qE1Fmbg66P14Q+KoLjQN563BVm8U3oO7rhZ +EuR714Z4d+WsAFsdz6cBs8UpJyQjBiqFhZE4UdWlm5HCv73rnXAZrZVcZPH2MvGA +/O0NSPjq+97kIqoJLiRx1mIvL5P6eYYxGauAnuvvNcDbGVse8gXF/GGfROgXQcgh +onOoJ7d8Li0N1BKx4LRY1jlf4YYGT5mKuYXyPE4OVONvpmft0VRvXaS1Bv92GsU9 +x6YLhk9lJ/IitI4Hofi1P1xrtQzscj6OgjIPjE0SCn07XjK4hPwKGOxpio7VGHlq +L7N0EKlKvFa2ppYDvYu8zDPiD3D5T7HK67Go4rLDkwIDAQABo1MwUTAdBgNVHQ4E +FgQUaU+BjH/1Nv4tQUFUcLtoab/zLu4wHwYDVR0jBBgwFoAUaU+BjH/1Nv4tQUFU +cLtoab/zLu4wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAbP+j +e38tpmk5W2aaVuFfGrVAY9XmNVhpAS6Ma7K8REVeSHvPFlZbNXSgkeIhkPKw2f9b +Y5hr9tvTxykL+ZuL6XgULSkTP8R2Ds1s3POGKuJKZ8czQxo1vbeuSS2quGFx8c3a +blMtXMbrhFWlq4qeTSZ94qJR6aXnSJJT0veZcaTNXg93VKmUnQDsAfF7Cg7b41P5 +WWBGJVLHtvfN4MoHLHHM+Uj730lnCN3Td7mIfeiwLpzcsws5aI+5aeDST2Hpv+De +iofRlpJMC6aGtsbE5PpyOJowTp1n2A3XgaFaIDZ1+yonqVnUTkGeNf603ctruEit +a+EcK/GxLDywZUeRKQ== +-----END CERTIFICATE----- diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..db146b38 --- /dev/null +++ b/deny.toml @@ -0,0 +1,237 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[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", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 1.0 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + { allow = ["BSD-3-Clause"], crate = "instant" }, + { allow = ["CC0-1.0"], crate = "tiny-keccak" }, + { allow = ["Unicode-DFS-2016"], crate = "unicode-ident" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "deny" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +# github = [""] +# 1 or more gitlab.com organizations to allow git sources for +# gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +# bitbucket = [""] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..c77757bf --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + server: + build: + context: . + dockerfile: Dockerfile + ports: + - "5050:5050" + volumes: + - ./tests/resources/simple_static_mock.yaml:/static-mocks/simple_static_mock.yaml + environment: + - HTTPMOCK_MOCK_FILES_DIR=/static-mocks diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..a8e405d4 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,23 @@ +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store +/website/src/content/docs/matching_requests +/website/src/content/docs/mocking_responses diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json new file mode 100644 index 00000000..22a15055 --- /dev/null +++ b/docs/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/docs/.vscode/launch.json b/docs/.vscode/launch.json new file mode 100644 index 00000000..d6422097 --- /dev/null +++ b/docs/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/docs/assets/Logo-design.afdesign b/docs/assets/Logo-design.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..abdb0efdd3a19af9d41253cebe758e0edcdcdc56 GIT binary patch literal 464134 zcmX6^1yoee_outNyBnmDhNWv+5RjAxX;zT#?oO#)UTQ~_MJKJ&D{LlJ8w}CI?DJcI4GWezJ}~to=!!V-xh&_q8xWasqb0peZJ zkMN5JjIKufLC^KTG`BTtvzdQ)L&~P2@hBk+Vj+z!R-#lUDKaG#n{|zEw{AOp0(0Py}qR`=$Yna$?NH%I0~Dp zqxKI_yeLnp4Ma7Zup8V=e)}HRh)<5U;?;~wHGao>-^ON@bU*;xE1-H)))^%YCD9?= z7oWMSr)j$+Udrp|EyPmpzqMuiKI!@#MPLSucYKNAp&Z|^(l9xk*xZ~=OPks(MVZ|! zO{+^uPW7wtlfCZjnFgwH9pf{J0&0#KK#>mdJ=9%qKs5;m*qB&}JrDYBDRuksXX8U2 z2~58LId!2k_{S)}fEwzviOr|0H;kc?T|LYc1=&3hC}|Wjy%y@P_em-jM{%4^JG0nv zn6>^RgMyD*NiYmDF&zRLR*?XyPY`ZQ;vmD1*p3Dt$NQ}3%WQ>m>;Oa^5#lmVM1`n$ zh8wdm!p|s{C@;Ntxn828WRr;mI7ivdZW{1(^*jJ*b1Qb?@YYvN2^hMCukccPn3j?b zf(-dh{R7yolA;o{(+*;RJDol3-H=EhqLD)n72_|s z#)hUgl7ilFb6%-jLZ{QgH*Cz_RTx=}aD4}2j*MT@Xp?CHH(6oSbHi?F$hV5rghgM6#-0cABY4~6FC;^G zuPu<=l@kX{7CZxTbwud}q|N2DC5^o8G8T1kk*DH|zrhGZy zwbCVrOVkPy>`Vr6AHmtBNlSXz=g|(asOeYlUhxJ<0Y^5qr2i9C0UJ&sCsqoH*H7;V z$7^5I$S!-?f7iYNHoMJVfPlHpWLtC0OkSA)S-!J9xLU|agQZR9qS5#4#g?X5{>11b zVr~_&Xbip9YH;{qUOowQSO4hBO@`5=uqvU;zGl^=tB2nQ4*!@$8Ao`SR%+cS#@e}f zb~O|>@#P)a%ziXx3eFO1guE|?Xspt3KGuK;P8M}VUM2-e3I3vrV}NkwBAMU*#uNHX zV^0s`3Q4+OfUKaX1{}^Loo?a$-koa8U2%BzInF{M_xoe_*RFHI?!2med|7Kd%CsIi zYmcfdWV?fG?E^7~>m2Y{r-{hEEU?9lFy_6MahV_5l*hNjt=zR}CZ5A1s<)sY-%MSj zB$=lOFhJ|K;A2Z09d+F6?8$E~HKvFELi2_H95ik5`5Zk;hSaRz`{Z@|ax%;0q1N%K(C>RNWf>mpj_qMcfM}eZ{VKXJrUC0 zaQM6CUkz)&YzGC|;2)y*;c#no5}jPpfTJqEbFBZQm(cYf1cYsDp0h1aZuaiK)AvZ| z>M6BSg2OdRdU|?3C#Cj0Y<8-Tv_&4_ER3blMoSy1eHl{2z@&f_pwDir*E({HUJa8e zEY&JgyeNo@P|k>a?OWha6G?%7JMUd!d6n_v>XtQ*e16_)5HIT?5$B;iqU#~?nCAfg z&P>V9dm~DwaM0(M+Q{A>+;gNfAq_aU$=I6Grt2g0vVkRFTwhJ&rMjlKi0wpYxnIC% z%(FFtjm@r}7ooU=n~W>CzFM}P#UX`UCmriL7AIc!Q(OBTy(8e$+M1sF3@DCH_3p-| zhbY|-^S6?-o11w8s*?EmPzi@s>ELU;@pgt@%Zzx+9A7xxe+kbQ>CrYe?bu96iaES~ zeo;>4wIKBg7?)F5H14)s!Be!-Z{p=HH<_dAISBIDd54)(@t^TpQ9YqW@A=Q+6QuP# z2ra0*_08D$oX5^WgzVcHDv&a;OtMs$evYOa5~;CN0tLbG!rZLH_-_r|{RS_}R^Ud1 zn@=^4uZ%ZBCk^nMv(P1L?hn$p52;Y;qJKQXGX23f$C4Kw+3D{|gbHYUZyH1zPFxVA*qcJ?ro zEfttdZc25k*Oh1WuulTp+;TTI9hEod`0U`bOg=tci$k2tQSjNwo)4hugEihbDJJ@- zBG=0osn^bWK1;r`K4fDjEU=827k`IwklXVcT4TW?o83&G^MQND*ej8Ke?!|kwjO*V z(Qxi8JXPc4)zs2)kS9BZZQZRjm@v`Bj!7yxNKzz8?+qQF<$4!2?_NS{OH7*(%~H zb2CIZ*=;PVyimLyP$dpr5bQA;rdw`u2W;(fhcDnNM*nalb#e{sRCZFvM5VHgNg<8L zO2ihG`*qW%;R;~hWb(`lTQLjEJ2_|n8vHE~Y#2etPhX3>n^Gv2rb}c$GJt>fbH6@w z;agxUv6R4;6A!b#H1pU(B00DIPz>&ip=qd8=kILF1w3Y(*NQ*+!&Qr)*2U^~oJ5b{ zB3o<;n8a(5BuDUO>6`+)2^6`2_$V&Rj%}hNqLd1}a+WyUc$t8B)=Ug_T2(D>Z#a=3 zc@{|r4ngx=%t?Zn63ZB~YoB+R8q@3uIt|_NFpl&eXp@r^a7?neKLspWn6(^RG6(8O=q$;cGD_l!xm;lwCjJGCUsJB*yW1O#!veXT;0o8*q zS_K5F(kHV*v6p)}|C+cCzST{J`|CFz97PxA1xDb?SevnyRzwO=j1p@|Is6;baIcoX zEzUa`PC0PTbrY_y_M0vqw(q9U^C7ag$Cs@}LEEHrXyb`Vp{s4-V87VIh#RBw*G0vU z9akd75JBZa4Jas%YG{5h{jzOmgCfRQ-6gI36FN3tId!CW;Y)A?3&Bw}3y6UTLrQa_ zFv`r`(l%1YBBF~|GoDM2CQ}2SOWZQ0uoAy!2zg77m?vR7g_=v;N*4u-1_eDq&X|NO zTp*JxDIb@xm|~@tMVkcqw(>RL)J!dxxUI#IJHp>VZG>e*`xB~n0 zm~;T*4ePAi2k8a5GN)i0FQCGbb*P?mO1sA9=0$;!y73>HRja{Gs~qVKJ5RFv@)&mX z&Y@J?R64)Ffnn-!>G2}z0IqTxNHZFigh&AuiDU7buT*X!i9O*gsPRaDm(Y_ERPmvQ z=_Qxk2Hjo~r?6(X(#}w9Jmvm|6|raPci)ll5-Ay(3m>t4v%w%s5tO|729Krf6;N2( zzvniK=P!Z5yjx$}e`<^J{8(#_iv`WG%8^7ZVo*DG1iJ2Y&2F|T%{TJu<>-O@e51a`-Zm_u}22};-N&cTd7{b(0@l$t}JKvBmL&A%-QtO(0 zznim$ex&}t+t^H)JTQAdh>1?2G@X2&0&*jJ4qNIAGwWOGQTRyheCrEit62A!tC8HW zpchpy|2P2Q_I27G#h@^iGcWkkNjepsDrm8PTY-ryn4&}uiJ;0e)lnUep-@jwlPW>6 zlg%E9WV;Gn7*QosYAa8Z7V>7>z3_nxZ6aeRm`!%q2mV1Opuf^l7k_*++ZGNFG*cFm zEa-V)Z8d?z=RGL-rZu;1DEO$VN|~W7M_PK$rN#wj`Fd zYZmvBD9xVs{v>Lz!OcZtRIN0v;1xBiuJeYd&ayg=G3|?ifj$2bVq-e&wT!3=$&Vtv zpW0du(aDZ&cOz2>d1>72zk1ScQi=q`oA(^RpW*Q{XL$a?_sWrjJ^?q(R2kraCZyU* zb~|`)Y=#P*nfM0Gj5{Aw7CrX-efFrYrvA;81>qfjPpsAbg%vZoa{VpS2Mj?wj`8V< z244*#QvOa9X|!XSiar{Z!pW^;r+!b9=aj&(53i}DG)wVBL86s*ggNvPnLs=uOl)-& zRBt`~PRhfQb3U&y58+U&`Vi@hf0qX`RU=$&l7Aq&7!k(1kQlDn&8Pk0{rQGN0flbI zf6in7_Tny&Iu5oDv)dL@FpdBd(hIchwI?^UPXC~rm+Yet=OIDUU0MfK>W~sc4`WBwSmm_@fcZ> z)i|cei-{)eccP~LH(3~d%$~>}S}u#CLVBc4+J#I9daRKs>t-9BZ5BO#bF&mx6Nl!{ zK?dK(Xe_O`Wx4qPc>B;BkfAH!TwPynGQP#rYrvgb_BTZM_bA!$$%^r|r1xy@ z2B|Xy>!aqV+xXkK51Z6#+>z?UA-+tm45?OgNRdeOdtfJ4w4U*dgROWi z*1Y=}A29v-Gd#UGPC{n`n>qM6GrpK|o6)Z~%ewR_=Ud;3#dApEa|d+eDl~AzPJ_|& zL$&K{ka5V)V&S=0(ZQC$A~Wpk+El`Om^fB7TGx4_*bpA`l!uKCFT#!Zc|&Z;bE7h3 z8;XB;tciLEClJ7x+(onO^x;rm5D}{;5jNc3*fdbkJ1p>CUsLIvciz$u?VsW<(>_-S zr5J*_&g1^v6n1KKE9h;yUnwh?%6}hS*8aZntAG<4>4cH%iU`sY|A3dTuH!CSLw~>a zXw{N>jYgJ1tIWU_!H!|E&Xsp1Z+SOX!B)=(v3Eq5<*pI~3?^>Y~LxkJ1Ut0HB+dKNJI?q|WEUi~HKmn&#B z(#1!6`NU9KoV&NpD`A~%QuHM>hS61pc2+!c@BGXBq1%$`=D6__}3gzyIOSszy!PcY)At=roSK z^ksRek2S=TIi%A#R=2V>8n3gH*A17!ikQgoxT<3AN-WTer|D3>6 zZ>ug2Qf^MNFK^cWeoh4j2RQDHVsvls;^9s(o=m@uU7rop&_w`wY~ZOjZuw4y%Ytna zXvzE?Ar?8`A#^Kc;FTu2oN$j&gBJwtX~pE+cG^%O{H~P#k$fA4+*BAU&W6?W)#CG5OwqMW+YE~iSfBL* z`sfwf+Qa^}J?5X^U4Lgh`?_Sf!?M{hbKwfR>DT=2PM_n|l4{Txmks!}zaK5b;)++m zpBm^^9CLTJ4tMW5aF*_``5sH}TtuN_BoMiq$iZ0>;kyX0Hdr)9Y^*=`0H2}XLmPK! zD%uHMRB*|4cv@W3Xx(ae1jBx!Id4!pY|v4oI>#0dDcjDtkMb$T?l`_x`&W`O#;Q49wT|(nP)a`mL|@v+hHk;|&;k zc~;%NYQ0=p@TGj3^t4RqVX^${?sT zc+JX-$35il9m)MkFS}=qK#$j=hui((-*y7LDxKy8RZE{9I>+wmo$utF zzaRB_%?J;dXjY<{{gCIM*U0_OrB zp4y&dppQMu=YGrYR&mn3*(XHGaoFCp$yO2f_3q?1EyT}H#e;sXdaau;Q|Qq^7jRT@ zDtc>rAIfJb`&afuQ(7L~ZytK33Oy{UvG6P5dar~3rj(rog{unm&(eoG1zwK=2}z)A zZW}ag(RlWc%_sRMpH1jL{}TLq-70T4NjT;S=}VM7yZ zp5k7E%~`vo=bA2^g09(@hy=X-N74O-&dgzAR_Vy(BpLtvT2{39rDi}6X6Fkcy@oa? zbI-*-L66|R~OC3WX=HN@iKGxM!7GnJxO0k}AHxs5G6dr6C*0C4-D7FN&m^rYR6<+38 zYMZ^TnK@N0J}|ERyRPf{_2G?lC$S+#7it2%bQmNcM($L)D!0i|2jOJY2XGFlw|m^3 zDewy@2vEibP;g<)Zrb?gT4prI`#E2t;=TY} zl}D0taq?v3Z@gi_q2F`@*3(Kv)I zd*};qchPhye9<%6*oCn3v~%wlB32n0wd8gd(wJ(Bn^?ppm+wT z$n?E8!0V}4;@(H`ZvEZw*U|xyC0tRXVYCb;E0*{A1Rond+M-7XPmRJHFSIn3+3C2p&PD&s)ks!SJsb^prh-t|&bIRCvw&ir>cGWlO=`Z{rVY+I_)Ja=s9$ zeyyE{>Al3l!ym`XiVy!J8mAa5{tiaH0~8Dp3VFM-5Xmnz+r{I5?c5cB-)%pBsR{LP zyDQwdwj2GP=ZHTt;($)A7*RmLibg6T^?e`~hi|qpf)z6sMOTz?XBF7BkBmB7R`W|H^zf#sRZVd4|D$4|RhNfytM9(Z8kmoIl<#H{0J|j^#_C z4W<+fJlgA$y)0R6elfxT7h(HwY{(VM`OX`zYxC1Z742>(-lHmd2b&0 zK0kbSyQ7C(1vq)Fygdv&ODU<{{z5bepNgYioKn$868;cmDG>#!bp+!e~OEb8GeZyTs zf|h4OQkWGD9DTpdU<%QsVac1vO-m}?_2$ZzYc*8p-r9Tm^-3_Vpw`$CN?DcTKZc@$ zf`fzdQkd+1!U7UL*QS*&m`}_E= z%;H~B7}Nn?HV2xEP6Pp+0I!nnYt$b)kFDcyKg&w#JtQ`dd-qf_Rq61sZMf+F?H9>l zwJmwl9Ok*)DIHwIOwXUv@ySG+2sdeK@+C3J#M*vo=j%Sp#%(5uvz`FyRQ6}w?7Ab- z!O09&%@-VLnn%Mr$1;1HRV%HIci_3o!|2G$rK+*V5^ifx#TL493@|-!^Y7#0v-Ve; z5z6R5(V|JFa1ScZn2PHzkgE_-IoJYfb0~ZR)-J{J*4>k_iEh2hBA$P{*dNLGsUbBU z-0xJfVPLC%m|f!^F%SPPl!WrS(V)y|voFDu@90y0^c~sE%fp(+)m2ILbFk@W;biC? z$~tL&X6XV~c`@m)&3sPCP^|sitB8U~ZyuDF$*dOVh4K}`l;sjSXTN8{C^ zYW_NV+xd6Vqa^pioGgT8pV~7w`x-Oq9t)}L~dRFzRS1>jyrl5Y4q}fwaDF0N;Ll}zp-u$Ednq~)YBLv)S%y^mtW!jp+6$=bDfF{{5sfc~f_= zoIAw6!5tN#tvB)^u3u^O22UXXXfRU~69}nyX<;T6b3u>W*VP)=v8!sLmxbG&*f8Ay!J-i${Fd6qYa5>_uik z5LM6B_#CzFkJvNoN>QKL?GV_7j**$e5qC-9=>m72tGTW<0{Okgj!hZY4wQ*xc=tF) zu<#BE{;NOii}OU-zp_(U`;_7*Zv#@XQ47TAI<0>mdUK*=36Y}U_gF8;KZ`mH`B%E| zn8gFqW3Jo>xsss6Nf+8G&R$2nqmHZ`s&YalCwdh_tDdsKhMcfHW-wZ-nUnRpY(b2$I%nrrU8b{iw|9riJ-${#nvT&7UHFg-nogWywc!^6gv5WCMV(bToy9;^5>%q^ zu1_6$nmu#_(aG4Aef~&pX_XsY$tmF@l-*`)`fVe9 zVDRVbZz*p7+&i%?Pc0OPs_6tZ$Zv6frHg5>ODl`Tq0Fl>8%}~2y<+GvU^nO+B4ad_ zFWPlbLq(@QKBGL8ZRUtwX#2kpl)5H5>It~;GNc9Fg;1+ORrGF^_zCa!)Jk0K;_rXV z;au28$xIVXXDLpl8(uR;m<{kxqt|Ngv+G1C#o=H}qtlv4`GCuIgL?3&W%9;UG5j(9 zE~+SrrcLU@1U@VsR{IV|42^sL%q9J3%5%{h1jXBsJBT6gj&WMG&lyTIUX?$j0 z5c}q17?um3T9~x7DN6{L;}D!j2rP8{y3s`)#PKMhweM&PFNoGgH6b&<7B3&##Y|D| z;d$GK!w9PUeWNbHdJS@ve^>7g$PEcD!2ZHAg&~ISdlhUs8EUiGB7G&eyH=AR`~{5{ zhLu{B$7mbbRv}jT+(pOy0#(f&!U3QICT$W)Jb$Ou!DUmCDI?dcC|RivVfiwnr;RDa zimCI*^q?LO<9yT3f;RMs1CV5@qWy0DtMbd!;m@+V1e0>hU9M@)6FXr=Az$e>5m^XpIc9a@He|Mhdca! zO*H0GBC4TKxo#)mdm5f`tPXdo5)uOr@wdqWQJ`LtKH@eJQKCZT*XAz zDbf_iWy&1Hv4#~B`pA*z-3@fx+RVR9vF2WYiDT2kTh<=N%byrJQ+j(R4T^&4L`k$A za!8d9Qc0@GX<2+51&=yT;3JJazq&Rmo&|PGL+pJd3~y7< z5a#xIIP*&Sk=PVDeyR6~Ua3^KZ711yT4A52Z~ZdmzdVOkYCk>9pW?zNM{UIjmDl&1 zCfQmH{+U9bwPfjrqdVv*E-+nhJY+h0Uy91UCHV!IH-((yzl-xz)polj{zOHnv-i^H>J1W>Fd**&bFOCxtbuCd)DLNr$4 z3N5-tcpR2-#@Nd!6ENjVOXE{JYiCS>YK({$S0KawuPYk7M zXYpLsePkluVUUm zJ7jO_bZVV3YWr)>cPpdB`Hm=x*htfbGGxVdhm6 zrq<+}M3()Z<Om3fL=k9-)CRN zMzX{4xW6#E9ss5Yta#jW{!qnH33b$|@EbJvQ%Decsq8~{`-RZSV8!=2lP(_GXg?ti z`XA~K&!KO(@x8v7tGcZ;dI)+1=Xvw4Q9mTN@K=u|gNb!jM9EWrE|0$7VB~bnIN%09 zpycbBrZNvE1abSwU!&#S;eOqiQ7r>YwVb&`HxDDHk(R! zb9HM32MXN=l83&miBCR$UCs=4vx-3(RfpoT;2+m5&4BPYYohj*5d88;A1yPMV~ZrY zjuBzb&Qo0!;`b5_YLUr^cJ=9i+hYl5fIixw|8L1bFNWJZ+zkpY?T=W4*4~4x9hp6p zSSXm}G|OHY6)*UXKUw1i;R}5_-25{M(iq9uGV<*sNC6?l@uG{$#`!GA$kSjcCw&b$ zH1)}T+P{r5n_Qz)xgQiP&Z+iJlmdvCtW@lGd* ztIE4Mx9{`GQf(B|)O*yoo1>3@h{r|AOfo!NRkf1&elXX@%{6Fxi@`9uvJN01%Y3TB z*v8Uu9vA3;UmlzHQ9==l}K^!s%0_5bv3Otz4N#>+hu^krVQID)tQ}WGVJ9%+`(I7^V8tU(h;-d_T~I5UIk-gN)&q?{>WJ(7-Bj*b?#Cxm@JFcp1XlPzfz~3#%BV_EMZOA<=K4=cd^*Lc zs#U{7mt~+Z)L$x8%Fkd^W<*s{VuH!V(a-G)iYD9e;SSs}0tTF#wrmv};a^Q`(bd2?SOb)jvuSN!2b=W$Lo zbjf+^q@n3w@_hpFOWd1iIrSiqNqTdKv~_fw4++pz(`M(w*a_35Rn>% zmyD1bFy3x8*Ed$c!46pvV%W}{hk*XLhA1rWGFNHwqIQPM`XDr2#k5g`c=Z6d15d5I z&&<$C9x4iiu=5Qfib>f-J5_9JggB7EX1j%gCdjvl)gC+bL?Za$KEO6-o$!%osF+85F6Cd#6D6sFQlDA@|TEwzn0$htSz@wt~f7Dg8<3973dF zBEUhsyy(x?uf)mi{;t9B*9n0U;V2&vp06#mpC>VHwoi5NG=?`zZV&vc&VH01xaD_*IUh1Qkb*Yzvh zbcLP8KhAlDE?h9*@haTiOZDH)X@vc)u?dAR2c7iaLdW~!5QskrUFo(;ipOxD1S)?1 z5d?E8!u(YotAVj9;`hL^^0x?Dps%gmfd=zHWtIMCo($7l5!k~LAuieQd7%>ic-brk`h!wFoes8i4o>N2iWuZ$xR;b#*3!BJDWPMfTv zO3D1{OF70=3kc?BQ#;p+<~K%#NeF~SM?f4E#LjK3NJ9KbujO)hqX63CsWM%YBc$-arGp@NB+Fa*^5WO zaLG&q##1#wx(<)8){GI*j+lrbXj{b1Z$LkhjSFPy`n|LOORULUadgE%!Y)O7fFjif zGS34t2P?q7K~0*N3JpT*-U?kGQQ$7uFS#0PsViXOl8uaZjviy`QuB%Prt;P3IbQNC z7cX*vGyoMky(bNP*9SgRy0ZC`Fltj-6>I`o8}a9P*>5!TgcfO4!pd>1F8lAP%APu~ z)rcen()#=#5#jmMd2a|qyV4;^c}4LvDu#EoO0bBtj*KkoGg4(Tyh>#W zm7f(;=s=thGz7wkqt+u*V`yQZ=oaCu67_@1j2Yi z6Y)n8^tq6Q^F#!KyN2}@<}<=3(GAL%sjgaH`5dk!kt_=-x)wJ9R1`e-8)>%`Ni3_- zA(EFHYM=-y0IzoaC-VtJI4?fiwm8JofmEsWja?5~*c5)Tio3g}NrpRUg*SNTzIbtNliPLr`*Qa)hPRN={$*Phq`Uz(O0aOcp4vNdXk@}??)-T!tjLG|l_@sdze zCkk5iA9L6vN(t;$QmnHPbN>Q$+s%esaLX65HkL3OqH#ojq*#<1?~5ib6@y@)c>Qc0 zMqaQA7;56?s1F5lYNhBG8T;egl&Ufh zvS~elFs&M)T*<2YOc|@wS?5KVaq$#vX;PPpcV&C(O{NA`ihaDgDxZZNDSE78(9qy6 zLPMI2mkJXd_3v}#JYOqcJ73}4u~E8yKCrhCX@`(p+2ukV?(4gkVoxcf;r7guXj|x? zMd1T9wg$q&LH=x+2K*)`OQl-8pm3oYo!&`HKJdQLNt=G@)W`VtR`H;14EucIQN)`c zx1f_<>d7evkCPZ58rbw-%V4L7wC+(UN7Aju4^d-9n z5-|^NX0Uu`?*%66mkZ5CfG1&8!o1Py@VYxD*}$!aJ1Wn<+WV8VZl%j#m1sbgK^}P;Ots6dlH$2 z!@CJT&gjI-3zsJ-+Y|Uoe&L6FY-_C?a&tE5qc=Ed>22@Or40w$X%Q|S!)`GhBPr~S zjG2t0E0m*(7Wfkbu_I--nJfB)8k}B#k&G@9ms6XQ*RYW5XIY?qg=9ULhS!#ewpPQU zXIK>cr*gDW#9@KK6_-3jMyBG+ye1fux_iVfIF_*+KA#Y)RyDYLV9A z!A!xyidua;ij<(tNBK`e8tV!#UI$$&h%o=2{iw+;YNW+zL0y39t6x8+e?^`D!r$*3h+(y&-x9WQvom5t)^^+%=lOWkr*_^_Z(>Vt}*y~24 zy0j3B>=lcCUb{l^410TnSu}sLIuotPb3ZEk_X4wv(dDGr$8hwq6RH(T*RQ@^`f4q8 z)%pT5$|kIV)vRU`x=Y)>bWz&~n+zC{z!!k|_mTyR>#KRV=X^*O=CPbMwL{-^RCG20Xe7%wdbgK)oCuU-+SGQMkgpXt7*j;tI-_gzEHmHOPYNjJw)Q3m2F) zIIrG2STxSM*1g2*C~>f}nNYT#;O`S;|sR;M87qPd<$qfCxBL$T>`R*|k`Rd;S(&Z#NC zAB?}u2nH|V%b()9ejklHltl=ok!a9xy)Jlb0oC9Jg^Ohy$(vgVfq8R_mKrtP|K35k z<_lQav2yNmB#XZ}_)RS}YPo@G*y?o{yB!q3T$HXJlJ(>0*hCD+^HuSrCtB@7RnrHg ziAxpp4xk#zDxK2rB`RPZ^13#QGCi85194tG3+VJ|7EB7c5Njw>EyU?!Pv`lJ>l!Sw zg_9y`f6>PC(llBLuynr^AnBJ(3#4{!M?%Ope`twC{oKB>jFn1u_b0sAQ$wUet2Ua{ z_B9BlPuF~M69<+Wx|z6@BAtpGFkZF>_{6I&1U?||UaHbQHqzFU6c!Prtr3JQvtPQF zDr6eMm^{R)WZSf@PM52|b+7V4oW@0O0bTEZ7HNA+i}}tEO{#RISkx~bWS+DMT{|xD zE;ni%0BcO^L8X%*VB?}II2P}C@gzhj*BE*wovV^7d({&uTe2_oVW3+7L=4hJ#?NqFLDOZ;tl+IV7vpE?f3FdA+ao~f3_Au=+*<%gJ zT&AgpQT%_JU0Kjc4E?#{sXa&;t?8sn;jPo=0lv>+EB)qdTq9X;P+C_abPfA^yHvB_ z@JY+V6nyX;cY?A#Pee}4r?!(#pIB?ml}ig1$~R)H%h3kAE_5vwPdI`!wDTvAa`+@F z11rn@sF+zMJU>nYYGdhY$ys%!VT&g|T`_>NZxGK$CS=C--STvNL zRZ+tsVkwz&5uT)-BJ6zDABa{@FJPvTysSE3p>h3-$y*`Ff0WdJh(wdJm~eVbv03nXJCy?C8gXNR8q@f9eQEmn-aJ)c?A z2jfQy8P1$^5E)6lu(lue16rlQstYYU4Xc2adewN{LZSl@+Km+0sg}DhOs8fN64nN+ zFmg3Hc{7sF>){IaIAv>gu6=J9Qb&fDs#2rFZK5cdj?5z=Le~!2xXaLq>Ps1U1=z3J zgD{l6u@F+$$n$}OfiV4qzk*SgNL|dtEaP^-MK|1}(k^iNWTo=ObEBFxXM|P^wT|eO;|2 zbG2(Ef|iQ4I6>)RxjLJ)sa#+e;~ynl_0#y&AJHf(jCsZG9P}$djBe`M;8Wx7CFruN z$txsqI3+iipvyF<{dD@4tI;&HJ46LsE#AG9ZNdvOP$qlFvU$b(2fg5_N;cim*Dt7@&WTH?gOV$MutC)erC`aYZyNY-DD-;?! z7;Eqq8a}z|A(Oi{ndP!&j%yq+0O<=b4GAz@zLmMPPeQmFbs_&5gh@R}5s;}h2*za-xzWc@;COnTlK zJk`brHK?}+OIF-xBi;9kV^-a(>}lT1I}p_>#SX{d;+v=o<2P5%0_T?ifj$^8SEIK5 zdmEt9pj5V90qlU}XQl?s__JxFLG_X>W0ewtrnA;8*UWc}RMCqmLQJ7Al5baPd8H6I>-?mK|!E!rmwVay-%>B@l*>~*$5-G>mSY)#mr zD~0i6RU2L^Q{k+=iH1cDu=LJS2<(y-v{d?SzvTx+DAg|M^?+f8@xBjku4#pA?vI?) zaU`IWtDmX~%dx1^JGnr9cV^iEnjd;=(P(h&TL`i!yw5U^J)PY5hrKQ{tbsQG)2AJl z3o)lD`AsSfYkl!NfKX6rVjJMvF{f)O-INdLESWxg_z9*f&_~t{0aj@o_|+NY8`Y-8 z@a1=3uj!sH*!!PAv~xK$y}t_@|1%DY?_ba6m)6?A**9YmbI*7`O>@p@<91Y_4czW$y`B6 zFSW}+wac!{FvA05MP$@vOiBTQ>%?XBn9`}cwURXP(pl<_-CEzrn8N026>=4z-EDkU zr;`U{bGP6TZgoCG*@NuLm+!-OrQ1uu+(M0P79i#p?L;HEQ7@lKI^fTJon0+?O~|{bG%I>c7(0 zB(s{cDSQLc!Alw{MC&?VKA;VDsZd)(MYkt6m5aZ+87F~E3%fzMsi&q;KbS_gHdNnH zELRgk=6ZuFyCD{lm}9s97&6L{`Q7O5qn_h=S14>(BEs1Q`bjv}s4wV>hT{a~Lv-ix zB<(MhlynJlkpfJhtMj%BG}r_0u~B*VE6y*qc8PIRf;Y@Vttd`*aliafsamC|FwiyZ z*v7qy1BnZG&*XnOlEetmVtvxJ#lm?6O+^4O{|`|>uD_hDx?A%DPZ-1B{=hQkN9MYK z>+prZ8w!hQvd4E0FA$@|juIa=hyhm<_Q2c|j|)$*K_wOrtFWq2<`ga>hd8SWkI$~u z;^Cy9oHj7w5KrWBEyO~`EJTZ9DfIx%pYfJak`F?A~0SEB;RkY=&(1%x{bd)4x z+Nv88N}sHXLIqd2*}^L(z68jIxv_H_*cw8}^0sEHgleFJ+P=Y&29NCv&K8wl6R$+I z*B~J6g||Plq$OY|hsl&I7fS*vGqO!@M!e~^*9a%nq_|$7bJ)Oa!*V3WQP>7@5!p?@ z4A)S5U9Sjmg#fK+2^sP!Y@yA{vLT6KRtb)h_Y^mW1U@ojw@ii9{ECa!6hW-bv}anD0%d5)aU{5#x=<6kzzMtB6;OGF?vxH9G9Fwc zSL-}%d#7!f_T}p=x7DV9v5jEGiFrMjxViyec;uW>tG#Fx$n3Khiv7S4CReJZTaS6{CdWavY07>_RL z2VSRE#JZ|$1gwpn$d%XGa67qQlGp&#+fseW#z${DPf7YN3H-@_hI z;|gD{SCsMDyCI%LCe)p9C!oA{yOzKy7wD=8bH^O#`oPlwoJC9_gOBev?8OMr%LLrV z2Lq-O_$`FA_w}#@t1H zNXq5dQ!~%li2y{jO5w#3>dB!;PwWVklpH@NOsZNZL?BE-QvQ8s{|8CexO;;@fEgJN z!G}0*fA(51-{24d%~r6u7KMm8igYEA#E|m3yV0borQ1WBLZ5n1*Rufj+E*)Z!GrKODZqOO>mI#i< zEa=HS#31|8w`M7WtVr-_JuB-oEa!;N!IG4a!jLBCL`my`jSH0x=HGR&|J~41y~G1v z_|P5?4`;w|_%&IEfKMRe!HN_&JG9DyCyYzU2B`vL+b24-=dyVs)ckvu{f`5kw)f)z zn>7+2kq1I*sXro180!*)A*>kKX+^wpOtr5|hNLdMv7iLeQhf1_+^outoJVwiuY!2W+Dmg6^N zpe;z5dK;01C&ca6?7oImMke-dcbP(MyO|Z^ZnxKBjCzfJ}CkYp@-wU<>GqkC3 zSPsr^czXvn8hYc9>X z3CJZm&_Y8KS6D(%O+_7;MS(T3ymitUwk56ZpVbhh`1L$I7wt-Mg9^9$%^3_lyeaw>tSSUuQ7+Sged;I z+x>4`3#b#*V9go^U7r$SYpc6&A%YFKr=rG)HX86s2$|amVH~a?U?7gb*{|Yvh~xP0 zOWgl<%r)%#8YG!x)PXQ@!OR11sl^U+8{b=vJ!Wm-7$IcNY=uC&NTdxQc`>$A%b8S( z|4zvLPu-c^of{;@Qg%N~vyLO3lEJZ?z1_qa z_WShxpTN5#PpW}cx9pD21RE|^FM$gtZk=D3lALW7kvJ#A?e~zvmC9C2k`81G+#yc( zg#8Zo{)dAPrgf-b!mUUxtf&s}*>+~?ws!{(Fvps{40U=`gMn{(47%mQtREHjoFix= zVbFee^8QbQS5TW!0qKTTQ!8SVBWn{h^_!CscP0nYj$0RpDlad9UQ>5W9~Zw5ZMWbT zA~Di_M*#Mp2Dek2P=SOR9@2@sQ&-{W*i%wX5K24fX(*Fg$W~nkj!A zAm2kswe~v|_`d+O2DfpACT2QqfsTphCva)1%X62G!;U)VI6NFzcF-95u6&WyW&+XP zUDi|z{jB}275_inSG@UESRyv#hJ`GP`QoswS20vw;SM&jJ8#d`f_;GB6ZpuEC@2M! z8xI=NvHe~m>>r8u3Wujc0k504IY_H6pWGcQ7cP|DlgAXdtp|mb~8Hau7v>`zP0rp{gx^@u`!g(?08EHaC zR|W)eLY`sWhaC=A{)n*}WQiN*z=0_9Yp$7dGEXDpuFgwZksxgFV6;WKzTsOCH$8g; zGt^x$%N9E2N$oQ17vd%UXe)3^vkeCg4hZcP_DiD>`qUqJxYx(OH)#*qJV^EolgCgpJKp0O%te(hGyS z&fF38drjz$jh3R_g6~Y;?_gv96~<*}Zyjdr1JJTi5W857sm6#st<7%8kUc_N0A6Tz zF?8Ye*_aI;Ra%)`qR)!n6!8|a|E}*G&BV~#+Xt_M)XJQHecxP2vEMvV;6~a|5K!kMO7vOynTM@3dG@r^_qcKywE+R;)_g zGcqFr`v7c#HYM&vNM3sqH)w>ATZ6$WGKP0pfW)~QTrb28HmrCJ7v;8HUUp*%Zq8U) zRhBt7vnk*wcWY=^VI26FQF2Wctf6XtNRG3ShTQQqPR%gAS7TLR<=OA^--K_?y#TQV||f5pP! zNHGS6Ou$vUjBcJoY;=Z~Mo6wyi^GP1j;aj@Tv;I?Sq>i*AWZu!43G$Ddnq>_A>GTd zy&PfhwK4#%l<=gpAUiZ{hgu=Au2@{#AUXiZGQ@oixsV0pMFM)DpK`DfaL#t*MvkP= zcgEOFB+@_3@?e=zS3tWOYpo}&g&-@osoW~qA-dkhCiusr<7vn)_LdaOg2-qf+sgZfSowyq-tC0&VLMIlB;RzV@-6OV#1cEQz z7?Tvr3s^?9gG={$wx)IM8c>r^u68xFWhtZ4rGnFd*X?4NGde=We5R&Q;83k<{XHSo z!@%xBjqvww2k?O>*nAV}3N!=(PfDMY`@o@he#5VJ{H}02X$pgNVN%rQif|Y!@V8yUq)grf!#Qo;E)XRR z0$T-&Eo_49ziNcHqdUS6xLlj}5f;s^3mi|)V9$Vjy=LZbACvM&XK;j>loO@%4EC4; zE6m2&d?@^wE!$Bm3A8nL;SUgNOklhGGqm#v(Yl)lLdT9y7ylsKl%w$gj(QribFO)Oq z4(>h!RoaPdGeiNjrQClnmfl+4;vj{<(C`3Wb>i2sBJHKv5wSWqD`YLo)(C>l-^}fz zz&XEoGY_nx-R?Tw4SnK0hXEwb238hwLk%SE;cLC_Ay4D}!=8_|c|&xIFw-!&2X^Lc zixdOpG_WfN){L9p+!W)npLZAuZLEcVoxqHzVJNmT$TY+Py#Oz~p} zaHL7g)s04ro3wy4Ujh|xaOM!msJa2?^o%b&GUq-}0{Rls(l3%ApY$8KghL=}3U^3K z0<;5>KzTyN9k&IkHal}rV6>LtyoT&+3vyOK;|*LB%@$>#?wCdZmdop*Mj}S%hq%^r zH(+q8El)C6MEQ<$fvaVp?G)%SW1oqVfaYb+L}-Xft3&IM^3f)jRs)X4c~J|Febu>O z_rZHTHSIJdQ2hShbzKUnMWmA!ED6(q4wFojZ=u0SI+=^Kj43&0Nwu3Wf;6hN7BVaw z7j_7}5pY{~mL(JDVt~c*VqTX}OJK0Ujfh@T7IH^O_o!h6%uSk!3P~J}y23t49)Zol z%!~}(9`AYQ$#)7S@-zeR7hsB@bF2d~7?9?sy_Q~SD(M(XUp1ZDstRx&xW+La(ayu( z=V#nQ+=KY)fPDa4YZ4;t zy58h&^`TKN$|+z4Ab1L9~ysi$4kK8sRZh0M||725h+PY`9R6 zhMmwDs>1-Wcz2@8!-T}qry=1b;x*tIVK%XNUKJh_y8)Sm=fp;dH^f!Mf^B~P{Rp=D{0=) zV9!|1U?_8EOcOC_VX$Ckd?Dc_Ko>Myl4xnmvSS%?<%N|KRTL!Vnc_tXk|=ag;7__v zq?#--IYK-x8B+2fi2x*jjMOQz=5UPRmclB8Q3jn5K@?mPxC9XKuZbU&J}P`z_PXee z$y0+T=WdVPpgIF}6X-t79p#MTYQP0~>vAi%m9gQiB~jy&CI_vEX5p-8{=!U1*_JG9 z3{R|y+)UV?IE1(ke*yON2D|6&K=zfj9Q6S8P3lW6%XVbM@tB>Dc!5|%*abMp zSIdj;vT+BuRleAG1)znXHIY5S=D1>Yk-5sY@U)O*X~Al9^~WlK6^E)Lm2~QclvLCf zYCR~=vokA(PYII6>6Y)p2g<_=Z9vJ zNnPSlP9?cW_mQpWFGUmm3y~ZYgHAX&A;JW-L=p+85nU1S5Og4peAwba&d?84=s?6F zx&em4C?`9UXSI>ZIwU&C!!{7^exG#5Y*nXlav$O_eoJ)F4R{h^Fq$+)TPmmWX~|}}fhBpxft5OmQ>f5QAxi-g1qx-tlRQnVFu_AiFNsnzP2vGb z@kp8?h=+29=|U_*8-q_GLxKka_xzplOYAGcm$#>;2bM?1+umu}N!10R+cAejZYfR) zuEtvpH>I{EHXLg^)PST7L9;sJns3eavNf4}OigS~-WO&VX9BMR2Ij5CU8n7bZDcE0 zS5SLMqd+syF3d2>Hpo22PJ5MjE9?dSe#zc+*Ub&vl6{?dt!dq|aoD-5UDq8~-d3Jg zURE9!-W8tJU6mbG-4vaaTvQy?+Yq;>Hm5ZqX+h>d%6^FX2Kf}LHjVZSFd6X}V@PH&)Ly(<>8g5Ez$u~hNVLfj6{9UK#w1^n zWNkWj;d+XCOu7!5(AmLRgV{uxNsf+b>^j6y;Vp3Yd-le=Y23MO-@cHaOjDQ~UvPA}VTB3Gk`fz-N`f`|lJIEI&4`oH7Gn)54F=kaxm8|$Q`gh8lpGZw zfsMW{zAe5i!Yjlo$k6>Mc8Z%~=6G3FqLXXn`xsV4mI6&*BVt40b^^`>T?srAd?Nxy z6ox}57Qj?+Lt)PZK@tQ=ND#pR1PKo+IJnS2 z0|O2VG$^2uKmdX_29gvo6CfH;7XVK2zfd6_g#-yDAmn3EouD}Zm763IqO!mmp(?E|h4-PzDcgpNAse^~k20Ae2bjd-Bvjh&DHw@k=-88lFvSC|O zL=BiUe9$;RLv-eanWr*fGV?KjVyc931Lnb4_=5C;)WwMlcS~GLT^1x*x?GuKrD(-M zl@y6oDm$iNNeM>@gA(_Ih!ZGH#4w?QNN94L z>kxS&5ClU6HUVG!?eM$m1M|W6-t+tSw)fc)z?*Mx)iYXse#Bp$B=cV5&duRA^VjW}91 zzpx>0trS5hT}s*xhD!18zyD`pjp~jJSaQumSA@g%DpprizPp6rol$qLID2_Fy0(t| z{SB$oQbHt67gkmw!>joB-~aun-O8p5+-Ek*qKIe$*I}Ln4J79Ttx$|3bbZ$1dMLRg zzmZ*?*i+6K>5~26*acYs{`R3 zd3$qpM2Id@}3&@NCF>8)MlchLY1NBj6Chbjzh4~;h7(T3M zP;eu#^3?v!9eIb^*zWAszyJR4M@`~Px}XQ#Xx?K4AFwiKNflY(XSD+}pkZ%qhd$`Y z2gJ>>s*IJ<@p+q+Me!W4f5))@w@CXyGg?qcjXWD0K}Wp=?X6TAC!rt87*6EGAt?g~3_j(`dO<&;tT6+i?X1)7I||g$>>rd#|=`% z6~OMtR)sBZUQ_tBmVIjO-`o3t`mG>tl;Fh%hBMGr%s~h%iZZA>AtuPgcZWw7jk4}- z=O4UG{9UE3K;sd7V$+iSj%NSYdz<4>324&dz;);j*vMh&sEsxotl7+brMMPrvir53DF z&nA3bX)B>-Aq|Jq&}J?B-OBji`j#MWwS6i39gF;bw9X(lMM9Rf0y$O+Cfh3}NQ$97Cd-0Dhue=?cIwFc z4eiBN3jeN6)n(#ex3JmoCCdN1*~M6a2`J2knW4tSrfbaDvOS0S9A=(G#e_zu zS>RIBL)aFqW2mFvV$6RRVE#8C-jdyz04H2v?5QYBSc+@~6-&5lW^F+ z4M7)(7~Cdjy+n+LOK|r4pa02lxs=gOsKY%$Ca0*&C6|? zhHZzpZ|m@lMcb@3Ms5l1cQg5aN^W88%guthM9=GJyI1``?+x0D}()W%>`T!GrU z@fhR_acuG>u@rP-#>R2K*D(Kw!S%2~Xn+!T!k#nv!p^j(#Gn%6@%{pWCc^*^(PhSr zK^TIv4YVS$1!GUtw#b%pzaP*4YuH%Ya1DHHcl$&W+@5oXWdyn@fLVhm!N|b-bg6-3 z38PYu3!@`uWvojqMOlKm-&dgjrOsvCf;40}huun=WaqLsbVd{%0iHofgLIX6pX&lO z6!{Z#hqatBM6x4x(+q&Q->>QaJz_%duMJ+rpb&>UTe@kqW{;sRg5$#2 zxlXjI8Z)uKgtv_4?Evy0WaC?boE&l+42eh;*sLC7X>K3gMMn`&N+i4SLe!gNz| zngy|(MF|<4{pfx^|13=L>rjnsp z_l&Q|Hh|n9ACl8qEo7o{nDw>2E3@G^mmcbBJeW}Wa zFAjXw$~}VAc>$I2v`t>Exj;LESHT(ECZ}8EEC;Z#yQ8g6gm7!1$rxDL@-aKnaeT3v z1z|9~$e4WyNuYgkCiJp{&2rA*7IVF#El3h}!Ya#(z zKEdnZ=teBD>q3UPjH#PrNZ6SdZj7!QH7o8l-sLudt7oo*9G7k2p_aO|ZJ?Ds>8~$h z0bE{8TOkO?_1?Q@-7fNg8Avb*aDt=}%0f;X5BG=@J7^ zDD|*=8XgEDmX`*Ji!2v?YfZ>>f7ej{w=&O}OA|N}Lu0qquthCL-n(cAo3?N2NJ5*F zAO$6&wJ*dJ&vU2 z3Wy+3dVwb7&<~J6H91LMWNF*XPAYhR-%$P!pq!?T&42`PI=l%QieXyxoQmkg?fBI= z>J!$5NB|`;u?|K20Ev^KDY=ow#!SQ$sP}gc<$uF+h`aHCV;f7}nGAYtGJ8rzSJ-F% zY8=hA5eJP*aNGin5CXD;aV?3;ii*>D(!=|Ehw{HxISmiO0P}gn-d=_?W@BHBBICIj z2;}H|=0Ye!X$t+|NQEG5X)Gc*OTros6fW=Y9?JjP9ZAnjK%Q962hQ*%e&MH7lqFUn z2tINXCooJ>YOuFJ@*)VBosEfER+upaDOd0BAIksMZxLSt0YSP*zZV&DbnX6@iWYR! zM9z-HaFGtVl)`YYk^l+vpo`cfFH7A$nnLCMJw*9`{YArXE5McZ&mVydP+AXx)gmg{ zGlOhLB(mfpSS2*9Sc#4xPVAtQv#dc^Bo!_1?@s3b2zMF|LaK$x+zJpVJ^rG4+MfXx;P2W zHf=!a&|pNp1?Dx_(I@W8?+F!$Ivzy2e39Wj9nwJmj%NN3lOB6hOAuE!e>(($Hz$&V zI>*fE)FHySLUW-9-NC1J#}5E1s5UV~4s3(KnK?|Oe@8U`CrOXNWfQ;&yMki^;z`&R zxgSm(arGhB!kr`T3y89| z-W=Si!JTwxgrt14?J3zo2Nnf1S3Sik5YBCd$PpNEk^bGp`QOI5z+8m{7SKK9^nmDc zUZcC>)X`DAF{Cf(F&_~RF6j?}Z>ln^0U#`L=j=B|%+kMKo&QIS%XDWDjP2vO2qCa` zy*dof>^>u}4-%0b-dDyW#dw4u5Gy|8L4{A;IAlFX%%p!GJpVs^Q`og37<-}6EkjJb zUF?E9?R2&DYQz}q=K1mAq1R8ph22sJ ztCs{kv1eM#w#SZ2aAcL&s7%KhO$50HikM zOw5Txw-#zvT)f}1+BVpb64xPa`?Q9RsBQ#yi-(n$pvHCr>fj=IW@hGT^%82`QiFMED_*{~n0`Z@TrQIe-vB zEJ4~43MTR$Yf?sS#KzpL*w`bB$`uXiV)nG-f|Y?LrAMU4u0cVLTRWPTsL#aKE67Dokk@|0L2b7St++6@!MD~?EI%Xz!2f(DU-#w`R50H)8 zj37*n1=os*dW_Mu9XS$@3&d?Pff?V?34)3ui{bfcmt`h=?lY13LSUD%-)599)1D)R!?fknl=YLe=*E~ z*oX_+wiIFyk__NxkRh>-#i20+U`Llu3>plq33wpb_<$Yo$-TJ&TLMn!D75|lRsU}Y zgA-G_-~eq+>?tXR21s_|MGv*4xfYK-Y}T|WSOnnMY+vE2xdCufa6NW!b8BtC7g+zx ziRTzIUs#K+7%P%&BL*#Vdx0hsGylV5&`eBPjw~h`g*HLqJ!)&XI&d>>ALvxL{oc3! zujAIqqFg9?8_7l}le`C&5y9vKwmwtq(H2*$8MVZ6#I`#z+88s=EbgX^3*8#G-@n-Z zPc??J2pG2MEM#xWG-)ShRAA((cc9_$0l22FjnN{+-rc}^+1)2@NLUdVS)Je8??T%D zPskU{vM`_^1C|jjspPmh7fDTKk=Ee{Au^H;a7(T%^@cS*o7u=2$<=4J9Tx6)EcgE$ z%$#Gvpo90!k}S=9)6PD`F#AH)#Qfm;F5A4guwO2?5ahJ)o4EvHYrbW7$G8V#w)a1s z?bX>)hN~@-c@q=v%MQ&*3@2VO*04YXX{&Anx@u^@aerhZSvquWV$f_D-T}GpKBpFZ zFESTyBpssY_}qe?Kk{aPb(5;>LE~*taDQ95A8n4oCqts%DuTpp3)-3zH8cr&`<`Ka z>otE1=1OT+gLBa0dg+~S z9p%Z8QF!ARGNxI*5utg~5Wux+NU;@)9}Wq>sSp$KMM#d2492+OzDAou$prTu;qFTw>au zJ+-|>+szk&Wr0=jK;~TsHbsRUFlBsDi5$-$M@C`gQDcGIx-m6n*R}vNu`{nV_8FI2 z7rwjw$Q|ed)iK~?9-!(iTx-Q1^?)_@(5DO_K}Q`JLncP({c*|%R-}KL&llb zjPx1qTki+O6=i0e!v{NLruYUH$a0!6r6sj;8LA@%Z4Q6)Ir~WCwlPRs z*^2m;5hsEX6>o%lfRDn)uvi0Yk%wTC0)rW&Gi3uD!VJ$l1gv-V1)GDf)WB|T zz(U&m{tmx8;xUR`xJdFT5DK_e`CE(RR*V=NCZ?}AWPem{uNHHvuNo~BG#2nAt$Gc{ z8j2V?C7BNBM5r|r0a4d`8n5WuJi~FvW4tRhg)}#u>eZ4 z!0PG_oqXdM4*QVl&O!u)K=Xpe8$kx{D7|*{MXpI*qUmTiw&r31(PPjSFmCWS#SLie z2akX??TSUN#N`erlv$wR0+7scq!W-JOS?;7h?dumsGl^=-2||SvX*k;wvsWC9rG9BE@X5-0-}+@a}`gt0JURmPHDFklLpo7zALC}3>VOwy(Job}O;sJuiwjMjM!&rMbi zVCDpNW(Ihc6u*LZR-2N)IPzBhq7E|PjXG+LSK`5%ZGB;?fo;b=@{nrtY0ueO93{XW^~36>3&k?bhIG%8^wF(y7DF__GV618t3_Zw;K32h}VPYo;GR4As+Pdy@$5KYH?g6z_wvli{aHy zG(&kx@Wwj-aj$@-Kv#i_iHk^&n5!U$r2aG1lEK&EDv0F}XbuUeW)ElTvCKT;43}X& z-<{PAiGbFD*3(^(KBkMpoN5$mJvy~@khO;*8QOPjHCA}+?KGVPb1?=q6X`?D0X;Uu zhL{f>092D$E8XE67UNJ;$*qWoti{y1*w5HsBT)7{Z5CL#-w1PtDTO!Ry zqsw)Y9v}s{HB%$O2ex4Auvqx(jve5Rz}K8Mkt1?18}B7q4%K^KI0|OK5q=MoWqM}c z8%btnG2OF~GcLY|7dDFKAxC#yZ-SM7M!~25Oe*gVniXFfg%7rrQ5MT|;zxD<2mluEB z48Q>J_yW5C)`Qg`t^$l8OcoXe4gqFI9tTs`e*gVn!Ffj*iebT78U|FJos;1(7%|Ws z!d`l8NTUd|$tsjq3KtMxQG>%F;NZ1icm><ilM|~ ze3%h)#Syjw>~-P_`-i=ZSQ!_wU)ZJERD^&3C+eb&of&F$_v{0hY`9 zO11&U5V%lmSB!s*+h6LjR9F|UFypqHVfXicDeYD{N`|KvOjg2Nk>x?IAVUE5ip&HE zlCCx~vnz`W4tXaZ*s7N6$t4toI&7$6p1<)QqOO{n-Yv3{2Erf@PiDx)lBo8RifwoXo1b_x_rIhBR zzkI1d{o3g3?@I(*(5ub*sC~j~DDB8?rj^P&6lVd;DCXFqx^6tg?JBzP6>WASTpl(? zj7f0$qORkzZJ_OyF@>$OErLA%!m3S?9efq)HGNTp!NnV1<5%s&4DXtki$$Go2d~tU zS68jeM(VpGx-z<9+(9l9_XREuE}FHlZI|r^zOF5wc(B;!B?UYMvB8T7{A?jX+xKN+ zT7y;b-7|G}?c&vXsk*w}Gw%poB)rNtH?1sProLi!YV4;t;+iM^5DmJzgdezI+R728 z-iXS|113Z@b)^O_`!-eG06&0lnA!lQ;id;w=*#8TqWtD-ih%an1TH8d7QFFsSumQ4 zbF$R{haS4F)|Dz3c|}=Iz<>G(U4aNA6qI6B7#G6usVWDN9p$$#J_6K~$>Bm8vFGeZ z7oCw)O^YiIhf~{!tuHGAaaXdu!hh((u$+Xw!JBdALe7luOcfO(-*SvE5(7ZU&2$+V z@f7QDb#at~EJc@)9DvN`Y`wU0gNCMsBp$$aBTFt1@n!%^kemd$G%NC8UD=G+)PNlB z0In`bKzK*MsyQMAqi{9R)E5uKb;J@Q58ImVAjoKF!Ns!73Sse?Lpv+G+7e=wp}qtM zaM9GP84@efde$zwq+Vh%=rFJSbXl{`unW1c#!Bw*R+2F$@1z!qIur0LSbT-Z{k&~J zAjOza8&;xitff6m8IZSG5<7Ns4eVBn5IMv33%RUJhYMMzGy`s-*-bJ3&@yp|_4UH- z5-|EEed#cf{Ce@iPU(2rXsJBNyjQYIwxr%ctdSW|7wx*mY_NOna)Ec%O~Iw4h=gqq zF8~DMUjw+$6KuSGY`v&wv|V3TJ&t6tUJSbCSTF71nd_>}@;F=3C54TXhi9wB)p`Vs zr2yUwT)>{`20&4yt3j8kYG;dx%fm+iS<-ET>!2$hUr5k*s=B4&;VI>cpCv z+z}KPzX*1NoCcX3NSnK;5IAwW(W=r-fYE7@Unt`qvgUxCmqMV0yUu=YcoG+8oluWS%>NTcB5kRqx^+!6uS#9g*(&<=yW zsmg6P;$JBfflIj+%EFVvJq_!XV9xlgD^7|xc|g|e_ z=LA+6-jvV?s1OB}(rTiLKq-IiWIZSl07ij*QX~Mpb=?ue%(oC1geSajj;*jQ_z-(=q(^4I^-WXD)vapn@{Qmp@?|^3i3fg7491cFr$K?J%Xr(2ni=z~w zmWEd*{jL$=v!R6479kMCk2!8|!a!8N|Nj3wjQPJH_KrCt2Z>@p=F~t8jzOtoqclK9 zBTp{HQMRLxs^rFuEwI9m9cOz87gYWJ`~UA}=KpcnJLo<*sDdTw>OiDi6IOe~+L27ozj(+%!;bVmGfyXOBPhXnxe)Z{*#_|3L zOe=BtN)bB!xcXj;*j4@h`~QdUTJ3*?R}?)B4fwp&d0`;FUhzE}%L`t0{6m7=f5Qj6;gusT)Sk`4l( z1V#&bc;!b@s|AueRlooK?}ru8_P^!|#-9fUa9A$-I)LDF1;_8i@`=>|K@d}}sxrbg zN(`uuidgv}C>0<{((3o$|G#86nO=Y>aXn~REQqBcNt=UW&zcD~FAFJbKi&#uU~v;9 zmx9NII3#w0Qx)dIdMUEM|Naj}W-40;FpNJ*D`LT`OiXPK3L<56*v44InGt{kBO}Kd zdM?RhMVfy1H)UM=qW2^F`|tl!vXxnm06W0Q)WldQ5MQx&B(eiXYvaPgOneW{!i=dL z1f3R-DPl|R+)T9~i`pB@{{H)a2lC(?M1b;oa7~K^sH)i5%}0-fB*eIfbj~nI>sbrKWt$vT$Ph;aUysb>h0QMPdQ7Da7NmE zrEWlv6lWcIVyZM}K73T|@4x@|g#)GO8I6E>xV^EE1#IPph3EQCg8ETC5C$5AGCSP;t+-L~- zdVy#3k>Lx>o{%ayUf(|jUcdkT@1%9Q5h_}-b#ZGfSYeUkROm*oK<2nylG7&6!iN2_kSaLMc5hx2+F|7&xXEig2ciob+-j&#_HVL z-aOEaq#RvZ3Me{sglyoek2@o8S;W8p{!ak?aySOWWLsiShMeYLV-=QkbFwm}>JyFV z%m$7uwF+usC_}akZA`9P+$6wB*nj{1--u=)rp5rAIES$b9(shaAp2VC3A15FSEV5) z=4=gzk<3o55Xwr~S(~40dLD<%+JFE3f6rOSSfYWab_g;Q9`djYlto#p+YsgvtZ?@n zZ9)zzZ>O~sQ5WDzZdBMHaT#%7_TPX1&r&`^X2gJmv$Twd2jjVcSq7$$bD(*dHP4xw z)|O)r&4P_c)HWUB4G;E(T7(?b`1jxc#l&qhJ_9<$TC+C~LBuBJZDy6&0-A;wWU*T< zscBH0)D}eQcNK8)tkN!>d-CtU|JQY=&ejY}=qAqRcp$``I{T&@_ePrEixylhYj>L3 zY{lF@Ou=o{aYkCIZ5dq|`S;)dk=kt9Zh(XK1saMERI*IarkaGXd1^-(6tE=P#yW_u zZ{GH%UUYepGozj2(o+Ya{QK|!LtK4pIt=jOT9XFk1K}1^4X}CDV!+mhK?rN>rbfs1 zibLE4Dl}Dn=2&<+sUqC1%fJ8r?_>jNYiQsht*_mo1ORSN+*elEfa?J!NFz)xN1P1A zt`1EerbPyi&lnSMlf4#ZMfdN&|2MJq+%g(qm_OZDCCHrBc#mMg1&s{YhNL`dRpNbs zh_wX}`>iS564{4v61g)o9PZzL|G#3dy{j5Hk%MtxDN#kP<~_$EkC+zDmSjjKB&-{R zi`iO?y!Geo3K?41Pnw1K6Zh}G|A(!WfVCU^a1Xven6PrUfr)4#Z|K2l&_+A4S=p|rZCc0=hcv=euX$@gsYM4nE5EB#v zMZJMckjrqr!|aLJjxBihV88$VkGIRiv^2Qe)xgb|K->f4CtS$copP_zh;f9mxD$7* zmzlf9jJ4+Sy#+ZD?F!iMzyFKy7KssX(B;(-w&TQ+H%%B77je2)Vl8Ztb(zS7JkjW8 znSrkmx`$`laTu-#wK@Fv-~aV!0T_Ea=z3}8TTaB$o)C*NQlK3i!(-z^Hm+>M5EWP^ za~4<|R|jb@$V0l`Ye#V`;NSm?y3CYeISA~6BNhY29~XLzjuFyUg0l3IldL;tO@=J7 zjA&=DZ>t$IfowZWP?4WWs!O^TWfRTVfY zn+!M39GEr$>qI-Mw}!LdfB%2O0nvph@DXc513C5>(~#~$MNsBJz1P%Y4rJY}B7# zgi(O=xQ>a@Su@h5Q2y_8g#$}p0K7VQdr@Y)*4_<}`fT}uy?t0|1&AkValzt-*%5=d z8X?97o>awwjE?R1-~S7YEfBM9;J)b)S7~LIJLKaCM{=eUWGDQJk}Gpyd47kTfjcB_`7 zlqKUdnAOnzKD7UrdFvPx;jjc&9ao=QIC_jn;~17_>~>O$?c}Yj53*xV8i#{`ab-$jJ^`>LfYMGsap=zDj~(dm(Q<4YsGsPudUf3g&W}4Ti=%pO1kv zInUYszD52oB+tuFI;g&kK4u(4XW*;Zh6*jX&}>4<3SOSOpge%ZoY%e*famBiYd~Lbx(nKvuaz1P z-UKh%`5(&G3f*UDyH#?2u)@d}0dxTyYP{a)t|$XuMrp%sQma61hm9lqM_oWI3TtHV z$qVr{PX8xm0Z9LOIAe#>a8`u4KBei{m~-VxS4kPzx>GCRa%F|rgW5%|b~Ye2F;z#} zVDto#fp`TOgUqXN6NLE2Vd$E*h|<~Nx|z)G?6^>>ID2KTgf9}nH#Y}-PFTm)@-}3o zj_V^(1G$WsLx82ZRaYE>jappyf{Q~oZWqIiGH=K(8LG260bRsz-(4Z@ANZ3!1zjC* zZhr~7iGffu_=^-^XzYZSPKYODF1&g!l9)m8TDhS`Un&t=_<^t0h(p*FFB=C7F)}aLDveCbs||CQ1M%90#i0T5 z?uk>d_TpvHBl?sJffJAB=0vbl(~NT)IF2;|JqAnJ38BAW24i;8 zXs{2uF6uF`5kh%5Y=08)4TFibNXWw^#D~#$_)nOtqZ}p^zLJFwg9(QqtA)>m=VXLo zKw-qXDq#j<4Tf5}3dm6eMBxhBQV|IBk|O%#>iSl*qHlxttdcN1ED=}!ol1M?Fy;^J9Cyp4-|T5*e+Zg{*YX~oY#W~ zu1jbEi~#AXz}H+d#)U`8g0t7xwg3n;C|bS~O4g)ktx(-=541;m(2yIEO?QdeQL$iX zgx!Kc6|Aj!OADkddc{qBs0^-!16Ie zlBH6cLDPe^qV{Et2>axR=+@&Jf{EBUkt=3^y}oX-h}PIc+d5X=E%gNhNSgQBD=J$B zPN+3ohY7=z6~NX<>_pb=FGAT5Y>y^iWH{Xm2bN|v?oq#E*126?U^#EML4qVf^L@c- zo21!z1!(J0lknQMDcM)r1cBZ5Ty~2SFJa|(@W7F{O}9V_te8jl3XHjUk9!tkCrw>%u!mVVibw$;g8H zv(+@@37wTzQFor_1q(==i67II0=nWLl`02CCKTyF_2P=3Is-P00tsb2t}2js%;0og zn4wH-;~D|whRwp31@9ZUa~4tV zT+*&BhqXc@><#29LTU`IhHDAjB@D#;m?R+Wg>9Nbsu^7U!rHYNmkKgG>E)S?jhPu#_Ro{+eSIcfIn`BkNsA+O#vmtqj>DeNL?}R|Zb-u*O|P!uWnlP{>8n%cM;qUCenXHDkco-#HK8ecIy8#fGFM7+y#D_C zztUI^T%KaNt>caag|piOo}CFGZmhkiiVttQzbT{}U`d03O;q3=io}Sb3ImkH#`X8# z{~L^P$b}W-=svtU6Ohm^^4Lt+Kx6rAs1Sp;1Joi7rmKWXnxd!+7zq-UfUQM|*Y)?` z|C{W8<~$S=_oH6lg!sntI={{H)a z3hD9BxW&(mYz@J)$pKb^uUXTWLi5QO1QT1Mt;6)XBRWOOW{{H*_ zn{AxC246(mq;J9m4_@NFw}~WLFTgStM_Tk?9;7Z=0Yy~kXlMVDFp1*Iel>x+{{H*F zH)|sAzmO}=Uzr5GYx&1yV$4+()T$ucqC>a^sZ9%NWP=XDEIlQ2QD(4UIO)6o{`-H4 zvy(Kp0%v1V*1S0EX@j?oA(_DK!NsL>;>dJROF!tA;F+Cr+@a{hvh_kQEwBmx{r7(c zXD>Bwf%n*#O^SmWv>0w`NUk&=aeC4_wG4IWr7CPK^5RZPZ(x03w%WwS3A6(L{`-F( z^L8z%K%G2ATbM(4%#52BBzde)oEm8pvc5VbOJ`{#u>+zI^ef^W;db*PknGyc|_kT8H z@!KE)3gjm^ISzQna&gzB1acXjl&KW?i8qBQ54i>3lPAA&7vO*{xjD%oUikOl|Kq^T zxNC*Br~|g-3PQ>%00;np0Reyq05cjO2njJ9gOMBwrgRSzK!f4H(Kr|+34~$5U@VGZ z5DY;80AK)sAV3fXU?C8~83;=cnFIi7O_+6GTq1gkQyK}<`?h=^&D49O==a=j6+_Bw z=j@Z(#<*74!nw8qP>cf`SlRTQm1MrjWl$bKgnEc{xdbmIs_SPrXz~i2OItjo_l|vC z{qOPm4br8$&)Lc$Yc^|uq)T~?DoXt3D|QwMb%QtaFXi$>Yd7^&S=KZYSR)Pp84!G1gKglWMWh)L zf~s6NEJ?j!jos|&^kM;X&*(E820}pLYe@(^dx3M297)X_(cSxTy*AH>9S*#ZQB$w9 zs5SLBTAsn^>>Bp7KX%}JZ3a6B#|cXQKF{&r+6!Mr*qCqa!b^AmgvwkOOAE}aL* zT4yIk5kmU??Q-`V-NyI=KA2vC@92shofAw2J=GIYS-t?@$xs$gI@6a`ueto-;aAsJ zi1U=27m6A=61l!C7q%m7C}0o2hosr44y?-M~`sXnEd zD9e1?%c0)(4G_tnoLlGz>km$FE5(ti|7hyp+?zR4+sLHNeto9tq=6lAP`#>wpwua> zD+pNYMtw0ag5w>#BCDrBpx&MW_3?4U9W*C9nNlG$79Ua)DJ#0cWx<@x@0&p~ z7pOcKTcH=Nh<$;UQmE{Mo3QRhK>Q{vSo6$e4v!}EB&sDC%VF%;=fWV*CamJ=eI32< zqsT6UImaE;$lo|CodRgdW>@mb&nke^>(4DM#*5CYA?jqSE?d*>Balq+m*>APY+OF2 zaOA+>qiIIoVQYlAKDAh3blkgN24opRBslGQPkNbMPgP&Nj22uCpP;%_1`G{2vFJ#} zNj?H!ty_^&7=IT_!~={9^TVoWKt(a^4yF%ls>8}M?Cf->J(hw~=)@;IHxaz7xq4Q$ z%wq<0<%!l(?0y;fHu=zHx?4m97vgN#b4BZf-m^3LDH^#3hLqIe;9A7)!KM-z zXf#%%M+!u9lF#u7Y_GX&s^4@JU4+3Cjn-yP&1AnPcQSJeLIk@iY^Ot>Zo`HrtSqfd zJbs};+sw`)*{obq7+!Kc!p8-SKwOqflWK2Lm3H2MhHfyopuax=xe~Ds>+Kt$p_0lH z$u*h%LS~ojjh|J32!29Wh&XN2dE!;7qz_u4QnqK+{y52AlWjJXjD`i!Fr?q4)x8T8 zg2S~gvt|jO@8^}Gih>THZ_xxM3uQ@_bCf>~s`gJPy6Q=99@L+bs)t$q@)Iq&uCP*g zHxn(B>gSSD`=W9Sm@B&6!T~IQOQa>$)A$HCF_$eI?ULZyN`w-4UdcZ~@E8}AaSk}4 zP+TKlR#k`N8Ot3Y>8>e_rB*6!cLgp7y(H} zz<682vY-LhP#C?x4ey1~gjb3XFN$5QITi2*xmupr+Xif)3z!w8HWVwJ@|X(|2QB6F z9@lTL;_)bA#cny>UzBOi77+Nc{j<>+dZZA7^MAl18w?pp`qe#r-R& zb{b@po>?!V0xb^W!Zu>Y@|9c(qNU&h7^&pcwPWqg@As^AL!7D;*==!RX=V#xYQe?j z^}yfC^e>ASgyQid81QIX#*#z<@?NGK@&Ibl@xaXuss?iIR^+}Bq+_h9D;x>{ ztUe-|`iJAu9wws4-NaM^FIPA0D{~j(OV8Q>&;SVTlt$!kW|odp5B=Le=M+&2-)Hu( zDz3=#rO*DE>1r9lg#O%hrge!e6}_q%4M4HX9`l=k<@bhUSojKsnb%WH7f`y8kr{qa zCT_M`=6S;fN03$%3t#pBqdrRqQG$j*>YMQ+5lat*<^czmNJtoS&YSxwZO}xt{XB$1 z)6dQOE0*HPSwQI2lX~oOU|Zo}Ua)Yt1!O78O7?s3AQKdXjuenMA>M=9#_wF?=30e~A*_fb-cdIkLQGATj$BBb+8 zseY8olRrCN%xeqhG5Gn-;8JWR6gHc@qC{YbohTIZE@U_G=??novhhrdOPzk_E48TO zXBcLfLVW$XjXX4u+$4fP@spMG`q9Wj&q_kDwX~o~cAJg_T|uEPq31{1__F%)W%yMu zT8zi%L5oGeE9xqi9v&Ja8kAr%F$y#iJt7oowa6#&vhP9hnJ`Ff%3lKo%_Q`&+0q7Y zZ4vKgAZhA)>Yskibh^q(qnDnys=C;@&=toGjyYrlT_0u{#d~beDu4$<3HxdT>H@tm z4eFt??^g(Db(`Sq>o*YA1*k4AzfBOFkk7qIarJyJN4blAv+c#dI2V_01id zT(lUSFrx`-8jbAdBKYOz#b53LNu-kDso~(NGbvSByaDY^-n@2qPmQ+~9Z|H8^Sl1;#n7KdZ)6)x8L0mk_T7iG--4UQnDcqQCp#?AzWbIq|7@ycm?N9QE zM)R(85gK8Md<$L{*?14nj1i(4LkuTiX@JX;$5T&3W(s$)E~XSazG?zzzvo$I+YxpW zK}8apE;2Is>RN-~vQEdc?MeI8d75Q*U6Vg3({$nMO|GG2mwaIE)@lNl{I{PfqlI;( znqo0OA*~>_7=;#0HG&S|o>r_Q*Q~{(VZ1ztU+x^pDI&!t;-C|V-ebK-Vu^xKfi@15 z{lRxGip~Lpn`_FQc?B^>YV*5i39`nna>$uqa%t@I*ZsL3TKGr(zEfe?#li`OVXDor z_Z0xGRQyv!dcs$Xm6nLrfJH9iA*{5T-2g!n5s!E`pPWLp?f^t^0fS&E>1Y5s*d=Wx zM{4Ycb$?0o~%?29qa=$@C zpTnLj1{%-uSLAaDI8BuIdtw-hA&gDSO-h4;Uks3AXDP~QukHKKapNG7;M@kbF(kh3 z2@TeJ6yc^FLRGpWc54y+eGXo)Q>Vd3tm#@C4FUYp435{CB4srYVA%jDilQh)S5Xv2 zQ4|tBysNgWmRXstg{_6Hg{_6Hg>7G5UR_>YUR_>YjBSddD9Y#jyk#I|N@eL1=p=MA z8YXlL!MGk9XD(uwqsTLe$N1F!ENVI8tMlB~xy6P6y=3$W9x9p%#GA{Rjw7)k{XU1N z-{7!gPJyq`)0f|gLyjJs;4S^1!VI0lG4Te)MGQy1yI@ruZ~+3Le8zStuuij z9iy!T{@?~kuEhQX$cKG~;2zUGu+cCF78M~BBaB%V#2u0CIUSAZOCjtG$qb#HP`4ICIo*vhdQug@GTe@m`NdolRTWOf(Ydj3ag-z+*zVL z6j?=d!5ox*j=QiK@uu`c0c1tKiMM0ou_GQ%!8-fvY78C1Hx6J)*BeKfL}rSJn!N}1yVe93IYJ`p^7 z7MV%&P)74}2x~Zboz0qU;_e6(z&&Nq%S8Adf+&=g6TdqOn#qv!P!7>C(mgYcUji@M?<%Xt%yg4&O!&oArJCJ5?R%TD6 zKj_cdQ*A9}(-qHkbZZTonI>=ysH;3Lyku&V6BkWn{TTB__MKhF2riZCn8tO^C)ug- zi)i_*9)mrc(u2%e^_LnLGL2vm(2khU(3I&CtjVY(tL%H0L_9z_k7;$^@jP=9oz{1dM!KN^ScwkdyhjAm6( zK`W3n;(I=@5(M0BXr#Fy&$jLY=Wzqn{;`t|33zV^bPyE;P9(qd)hX{HGHDy62WUJ| z&D-;lh~m;akY!UmCeVc@VIE5jk8j>+Sj(`V4h3M-2*iqdX*UuAe#N+<66f43yiYW$ z@y)^4B!9*`;!`YB#K-f&O%8+$(mHbSjS;lm@5GQ0^rE0TDnR;MDCO%ZJ);=JEin!1 zc!Y09JvAv~TtzM9(oLRZ9p`K$6g>}-H{H<9Z1)&Lk7YFeG)fDZ&QJ_YOO~*B_BMgs z`KZ@7WjsZ>MSdakz$_wfBF{T36gHVh=T&STkgNW941tNggI%J8fa4Aw!4kg}7&+hQ z-cLvneVN^*%0zM&?upE%xgM9#6SFXfTZ7v)M+N;woP^O0K)5FIOS$@gXUQ02IM=82Je&gxT-r|>W`2N&#+Vw zj*e&XiqLIKvM6{C zcoTFk3O5N~Y(W#x5NWI-8znHx0|z6EJic(mWWT%-bEv#?$OjzZ%-41rRY`UVfLmoq zf0Fp7IwirT!Y?jW-UlKWJI;`hQGlRdA{`{%@b7c|1EiywK3H+smI6JKKu$Esb}0w)%@m6Oc@4IlL~XD}6}^ABofSdkiZTX)Hlh zvmN!2{DV78HTiJQo3_rdSa&b6i8vAsI<-$F&yg|CNpY?m6z0A<<Ph>Fdn^Mkbyi(87xpHF*zieW$qFO%Gn`T*?}B^#a4igR~Nz* z21%Tyeq9tQ;R*3}6A!-!ycrrv-)vnTkmfupx0Lc_68y%p-*&X@QQSdF1@Ln5BQ5)3 zkNFC``lEm4 z7Ph|M7J31;u|Txn9AD!g1wa?&EQK-zG5Fafa^?dt>R`MyCN$7j>I)S!s_xo%Tv**NE?gQ(+KO{2Q~c08mjpvz%zHM$UXBT zCQ2~_dt%pHILQ6#Z3+As*n_qD+6_WUi@ivNbAc|pIf!(SLtUrE1fe)^yn3kTho6*n zkS4P{Slj)@RW#uaj3bMbg1>AfU>1oFws&}jnEw#EJD8|zEv=mX@S|XgeY;Rw_59d- z*cP1Zf`aWGUxJ1b-al@QaS#sQ3XoKi`?y0d7tlu#qIEj3XR*v7Ds*2o1$0ALxAhp& z)oe2-zCR>wC9t%??Yk}4;IIokhby`WDc9kSgY?q3g$OFa_Q*@kOv3xu2Yb7^4)=74 zD`6KpC9u!ACBb<78F@FDo8!Up8(gD8n|b5=2;vp`l;I`g6}uO63u)mG2gDp(#u~Bb z{;q2?aq`NUxOKve>S%b0xf^~7343+-=xQ{KS6<~L4^dSCw>H_vd#KsD3@Xl|Rw zhwY5@W`Z(eRuF$M7HQ5QH?j+x9qDbb)-1M*&I8STceWx9^BNEaXVDo|kSrq3T;taB zsge=ENkAnLD~YcdBMq;Zcd@esSJvCYDZYPxg7gL7``>j@>-7v&(dnam1@B=55jY-Q z^aKA3f-VuoSqPO5P?pyiB3;L$K`Ex58{2R;SgZM!M;EZw9z%d=YR8PWDA9EnP*DNy z0)6%~>6amGKq_+Zoz^QGN_?O;koSGGRBstKR_?83?lm^)*gfQnsJx9hs=)Mwn^6VM zJ3<=$W3Fq{Xi`prJ<>VK7zwv($UGH^64svNAC5ZKDeiyL1^WafPG*$$8)=m%(fe+N z0XJXz^GCuQN7_>g(XUMHX67Ogi5g4qM;^Zpmzf;Su`&M$Fru|VBaa}(?ye4clpx-0 zEKIOxq@nzT;bCeGQzYg-)LG1Q*xTy}doKQj*mdR{f&to|fLZdrJB4J?A%}NfDO*tw zo^Ii`=29yGZMJu)6Q@SJ$!p6q5pg1RmOaop6}I>MSzpmc5-7yag?j-d65y3LsE0_w zskjRlGSW2Eb)ZxAcg;iPxb9*1q5ZM3u%T4fZ5ZuCbfEjxU4&QhPVrt>8pQ#`BYfx} zFO%QE*g;#@O@Kx^fHEPUH-(fg zl?O=>qUOs8a*TLmF~bOoVQ(|1tt8-sloi*a7y_14uYZhd<+t_*Nvbjt!xNWph>0%3JjLqh@64>k1_OVkG2WT+s<#Twa#_s!aMkdo!bpFBPmYi*b_HzT#)t_H352E_)?wZ7zCVP zeSs7VZm;VtEPxrX3x%ZRM`Y8XEP|b4ELnC%w(uSte3Az|r3-=6!Pk+FCav*WAAg9w zZdd7HiX7Z3Fr;LdadC{$<6QEVArF96#BW+#3@yV^;4~fA;6v|~%Ir6;&eN&(B0-Kx zMRXT_tR$=Ni+{I*<<_PRDH%lYqs(^+zIKC%a&!7H>^t z=_Zo*dq^OP?h}OGQS{U!oMhg z6v99#aR}ytubeu`qxfpt5IQ9K24DiEJ~Ve3by>5O%U$j^9GM6mfcJb{H!j0dh?4*= zkW&!9S<0A$!;7do>P5hNIAHE-U=~gC&`al+qeb)rvzgZr^iMfR-Bj93hLbJRFvdlK zLu#h+i_0VGPw~zO73(6gkB%4Y4q?wHWiyiVild@(4s+Gl6B&Z}iJf{(E@zf|h^>&b z_&4#IY?CQKe1S|!LPpqh++zo4m~Q+cikv%ezIHPxe+M=KA^C9~LM}B4Ea5of>XGnC zMD&5MQ@k8v2XPY{UrY%cV!4ZbagZ}KDdj!R^)v=eU- zxpW0!zK4l{45n`Q2^aU{w^*9mXZVLkRgc32r%=$uE3E5v3pPF000uk!lwkKLT2Z>u zei9jUmBrnbsevpC4hP+fdlA2TIMO#c#}g4BgQ#Oj0Tg$jKC*MxY)RV~ME^8UOIR$T z6sJxid33cxEvH#ThF#-B-*IT{K+^$M#`fNb{FuYLf(oKI67&{3QO%U|i9FR$(-{L} z4y738DwA4Qb2E06jSOx^6a|f|VYYt&vU<;RBu>Y6=$#c>al~^FKS-JsWg3J5e!9p= zjXIQm#sn($uA0PP&O4c3*gm=>4k(-l;?9~AJ}NzRbvazNpAtkBBBY}4lZ9lcm+*=u-O&a{VD52%$YT?{bs+ zHUsP-G((P{5bg=JUS71p6?qShtfB@ODC6=j?{F|kClxP`!Y8BSwp${X4@3f|*dpd9 zZiw7M09Ob#!7&S~Pi1l(#w%QQ^fXDEwLrTVvoJ5oUG7Qr4F>{}nrLSY27;Ip{90$x z**9ctb|9$`w+&}#ehPILVYKOjf!j>xt@Xv5GMVI2n1g+)zXR%J9Ui!U{K8KE1e4UM z^rIUtb(CPFP+aYEX!w*xZQ3~3#a_oynyyK)IcR$6F|~O>m3-%JqSC5c0emP#4#Agn zVx5PL26O~`pb+}(0P(|9&w0{OvTKW33MB+K0cQo9ntE;L2ccGAEawRxVz_ajZ%~`? zmFKISYcrz^V6ZJTM^2Ea3ttKS1BZp9co=%0!(338 z;KklE@ywhCby0hgA_Rd81VN{q5M~v!iGeDHDI&(zL83k8V1q2Tlq=3foSK_v(I{FMosPB9oXEX;~!F^p6c5!Yg|^YIM}3Mqp!0lG!y0nNG} zh0V6lQAHp(W4xVnC%^J5QeF3X)wQVJ?mxBsF^fS!cMGG*pxHmo5Q2ZUU#k~77ZLN>v|6xyhKM28V& zHj(Z&j8mY8i*>e~JS&HYo9ce0C@Ss(k3)4hzi>!1hNMo!C@BYa?}}v0<^a^Qq>vWG z18$Dq--IVXdl=zsAMy=3I@mex0F0L0*}*uf&bNuf^q5cF2;4YIF#!3Uj(BE>z#ZgmFC)wsAQ~-t=UL9r_>f9$<}(gaa@9_ zIo||2cl=GouSB65FQh5ZO>xZ7Re(K4HqKe{P^c8oZi#7R?=#(zhmP|6ILTmp0BwqC zL(Pdk()k6fr+r3=G>V>c(9bwV$`=B+jNIfOgjZGTahBpK8K$`WSV+e}JV@Eyl7=la zo7RtE7M?GGC&F7sgE$U(KfW{UsO>Ry2}wkFpt}_-^pHysj7GrmicmnF6r~qp4RBkt z%Lp1j0-6F6&9KfxSLNEW?2Y41VaKL!MBhnT0=Rb%#3B>o1v$}nBnA?eu`Ogzz(OGqF!h z3{PZUt^-5=L=RUd8-sDy6G(AUc6)F24;*M`#s+buA2N1%%}_4(K7o z)9M;JjIj$D2AT~Sz^+c`proOAryk2}+az(mFo&V@%M9*&N}XgzK>}ML#!mXXu4pkcvBg72e{J zGk^oITbMz39d&}wJ;F0VA02WWK1qUx2rn{@DtYI)U+~cFT=5f3QUulLH@XfSV*UEw zh%iN0C36lZ9NqPn^2>DaL z8*fSuNdQ{GS~Bjy8?CdE6$-8RG)p;R+~nHk3?ma#EBB~73D2m!OPoW~5Icmr*GmT= zhr1Wt<{(cXJbO0O8vL=2Q=}Kx4C*;@LCipDDCx&&+QF#};mw7|3%|2z6;U_m@og-AEDUqx*#N5)VN@;=?9r(9Ktc@w-qX`w-{%FK*w{Jj zQoh34F@R%oVeSifDckN_ijeMp0dF5JA+*W^Eb_2CqE|YuP1VwQxK6=?TK;X};ej|mTNIva-%0=u8v z2$YaOHQdA)U16ZSz}=)`BV92Z;}L~jst%x1!`}_YGfmCF8PL3(Q;3-=em6o4xpI|$O@Cyd(j83;G1$fCC3 zj{GUW#=^N$8rqTa1ZG?A7<3rD@;i%trd$J#^!M|(CQl5+0{KI?8muDSU}sP`wL+Yh zbUXP$N;8OWkrGO)%}tomr3vdg^fJ>k{2Z8T)H8rZfHl;4!2psV)=l|z2N`SyhKVeM zoTuJXKw)MP@xdvjG6y>R>`U(2Dfe`YP27)|w_}{Z0)ba!OuQ3Gv9TJVV<~!U;SkIP z0J#PABv3nWiW*GShnQnc1FZ%m5H=p44@@X+q%;UnpEu56r0FAE!zCjLFL{mAN)`_2 zGBl*(U+Ne@h<`Y1RI0DS2<)RYHKR^@2p`*|liNkQf#&!fV~_?Y6!?ovPY;Q5cL+%l zdvp;r&fs-5fFM4Yd3R@58(fOtE+)Ca33@x1m0g z!k|yqsCw&ATLpbOjP;{8imgtYG;1dx=ei&2ww!C-f8wNH9{0bcG#>&y;5D=f}J?7d@G&^No z$prpx_j}Zln092Z5OPc-X?M87ktTZcv^lBaa2Z)qHIcae&9+9HA0;-ir_cdC$J(fN z!4r8m=zWI=l(SM>Y$(uN=pJKHmbbhaxD_bt#Y<@Y$UfwCI2JjN&ZM|Ro?!3%q$5M| zYl4E4Eab0&j~j9=XC6Xlco^qPEG~*B&ZBMtD21GLY#linplRd|I#|(gMnhBq=`5mZ zDr}^YT9nQBpzeosi3g!23^4@?K*MC=4+Wrg-r632&{eJnOF1+Qd6v#o^j0HT2eh<_ zQ6V8})Bs#WqDZi|F+>Yoi;f%0aVJCTWyQ#sp(o$W6xV15T{$EP^x~Y-@+J-BD1_5A z9ZGsO`c`Lgapl8yU4zJrH!8Zr(2{);Wqo?2Ay16+k#1^6phS~KxWqmx)0 znB3a0>@6R3+`Zad8g_wLZgCRS^j(dkD1v%~8j!25#;``5^H~e7X@q~QEwD5Ep|U-& z=_rS^*X`qjT1PMo7%ywO^N!8OoXjor$uWEnwU&@3hAN&C025 zdL_0z!o3(mM480fedEZy15VaNbPl5IR78~DBopWwY-BMOK^Jw2VEi5Lf!2wc0{1=) z2~W5bG_e>~Fk%QsKCDE?nhwx#H%bY)m2$jjh+q=(wW~)uS+ujHB)dqv$vX`EXLh z7>OX0=A;-4W`R&QF@X|8rdQ%6JS<0yiN90e;D2T|qqN^&S?xuMM?uEhsU6mkAszPi^#ff{0xrou2&ZYeGCG|&Dw%(26H_4vn zD}_AdD}#E9DaLV|1s>mH%M(f_@5YfoVlwH*NLJd~c<1+#SDb8i@)b&tQ9i?y;s;h< zajp>fgKR~JHrOVM;Cxc(MJ7sdnn*;J3NqUGLmqMkbA9A2h19zboKe9Xz`N$JJPvXJ zW+&(#I%YYr@XfAQUh;f#e=4s~0T>um)*>W3cvLpclA89SA0#!ZH_w`3cx#~ZnjRQC zhk1gUs!eFdVR+}Znhif$yxq)EEcoJd=1?L0Zg{Oou)d3t{eZZtu0t zCeB@JJM-iD0b{?$aRy8M-waR3VSdbQ>c0dpW)|up7KF^0Y6Cc6 z<|=6;K{#ehG=e1_G0fTu1NhZqKPI63=Gi|NG{1IiFAn+pV(lhW;Ojw6ULq{_=)Nc8 zmS7LUxU-sWBMCsWr|$ZNaQ5rIg5w0+r9DMR2dmSDz|^C|J-Y9MtTAs%7#mf{IFte? z)v0&@4Ap88_&q1R_l8Rjx#>lDuV9klzVk}N3t{l?7StydaNTjB7h>SrWzMT~ZtC>r zTq47{2Xn)wwDOs?NB8{_U4XhJ%#PfR`&V!zuFelHLnT}d`-Gh;Zj1J?A}(xY`_+jX zrqS!segE_J;w}S5L)!>J#s!RQHvpG3z^pw1X5a~eMgefi2y7MsQ0nQW8&#*7?t3UT z2zUZ9KxmDGRt9%xQG;AFb7h72o}L_Hc6`~9445e%2?_yL4(G}4`?xX?g<~;fV?&7H z225gNg_av=dDGs#zRUjZ#+HaH7~p3DU7hI3_X3jQzzV;lok(!@80;RUuCuCfT1;QN0_UOLnvKN(c9&9UeNG^`#M*9169BC@i=# zbzKYYEww9K_x)*{BAkc;b{vg2j|)t2QrtIon%o54rbkHDxZD?{@Y1rh+gtYRkGL;) z-`ik=P**y@$3{3W!~jC=EHA+mh9*HCiZm}vjVDbBimiwTa%J)rwaMdsU*)XFANl~= z8JwS4hN`k(|Ini+wyxJmI{MnH$5GwbVyrvqQlbt8i^-6-A*~A@>}c2ZF{qK!cG0IK z1a?dH@n}Nge&M@fMR7yz+2EiZ!EG{}=OygagZO#uYFAS~$FGz^oo6f7G$+zCi#{h< z+%pcJq}X=61-{Fm!#7&T2aDmuY-3F|JeZ{-R3TgiOTwuVY~;GlDggdk#m0VgslszI zGq!Io00>I9PFy4#ylnt(S&_a% zZ20|1GvFTG_t!lh<*f{m-WBl-2&s8zG5p35&q_G0 zh=`q8_$-PG9UxeUi;A4%m&dF}_vpT_+4ktZKeH1>%VmJ-w3w?A{^(%LWH>kE;L1-% zmT)P^W2q=N4dk+}&$YF2)>)74(R~l4uSfU2HEsaAE<*)Jw1yHv>#Z~GAjd)*6m-u~ zRxJ)4o|2!{IJ3O~jzDq0$Yft;6@w z+;Z+_Y>B)tZmw;tZnMe_jH;8|Ex{mHf8t&Ge5$+fsF(z-H{6x6q}F|PkYhvZtvMrY zPGu3-8wN1R81%#$=Hw%KL_c$6I6WCmOJt%v3t3Xe_&Y8#@HnuJ&}~Lp!?!Ry#Q+!Y zj%thsPA`}5#SaJXiRxYxw~M$3A`6mBK`%g==k^NE`{o(8W?^i*7=ULnT)8x0!Gc}E zKvJ%AYmbwuRj&_H3$9OwFUIUhwQY|L7lS*)m*x#R0LAyIt)W4!YSv~5`k{xJMS8-J zm&bB$72SzlEu0P2)#&4lJG)lElM0g$WWWP^(=#yaQoC7#ZqO;{0zuv78gQF%Nq}pj zbwgXXO=h9UO)+QfIn7~`K@TvP!xLZFKo+MCqY%R31_|DY0>D=K<Y;X^+2m5$4IHm;9~(_SHeieJk-{_4v2@;k)n;v z3SOkdlj=Af#OHEvcsF}w>B$bMD~8IM4Jh_51JyL3v`!q&`VXj z%r3x#o3>mW=#5T$fL#12-W~$SSS7)2;4LiX=k~x>^}XaL&kB*w-6};tlPMc`Kop;rnBD@S+C#`L0<3ZNa3!mG;%}s2D5f%DSf5Y+UEvAZDqjsB*&|dUA}>j0T0sbqQ6l`fJ+r2rfzLJwgW?MPm6$Q1Kd#- ziPldWk1HIS1GPP?2Ra^RO8699b%59AQq7Zngt#DZVuT!TR9_=IGi^TJ7EC_U0(4_+ znVGfNg3}myvSdY`<1O#yy$KlGNOvQt448Xp`JZzk8C&GGZQ+gZ1WM)#h z?5`QuNB}3VmN`-kHo8o7hCzK-YR*UMg(kOPiRG(Razx%h=;?Sftj@suX#@O@l_U8ea1jWw zQuWd+&ER5Bh2DZ8B6)>*P^LETblf40ebwox(~3(WCpjmfj|a{w4nsIIe2SNZt3f~k zJg~kh4S9BUeKLlU%x&NkLIokNyq891z^yWGEyqJ`^bXPbcjj65DRy@k(20JD1Srj6 zW@%_}5XY1CBhu2=<7%R6t(In$E0bY1%2ztKMGQeT0M^0RRu4}Og*WX5IZ)>pX7+WCYirG;hgD(wrQ3T1hva)!CGgs>J(Hn)H4T8D2h%=-bo_GN? z_7Q2^(hLU+QGR9Yd*(S81+%OhK`+oHBO6(#g5|*MVBM(KgrI8umnK=Gv%XQIfTXsL zL<@n%xxP|10gVD%g&3P1nBDYc*MhRWwt|?+dSeLUWfp5yYN*P0Hcuoh#Zv((Yx5zrsdgHutp=T z16K!43eR#oh0Kx**H*)Z(lq85TCg4|f)830aae3Xvu1J+l(w*pa8O&N)*!bBYX@K|d z?ERQyV*~wO#m%Kz*?Hcd$G}RJKrru}H-*!^)5&A8G=ffhr`9ziPONU+ChFEjj;{J$ zo5N*=Eudj(!*PRV^Ximf0~1uz5%NXBaT2Ga3d!{6_Oo~9wM0!R@6b)c+09)iABHgj zbtA+FzLv;gh^p(o{czymFEcF24u(G~F}0HwU;!}3uPA|lT<$k2csbTtUj)JfR2Rk> z3>iHAcaQGTeSa8N;7&MvOP4w9@GS(5P;jzio}|}J!Nrnbl%J`K5K~;n|EeR z&W)6yOr=+r8 z0f$kTK(Ij*6&w)(7Nxn6QM{~}Z zcmS);FFdXwYRjFK4=xoDEP{SeE`6ay>!4j)NJBG{{S8r}eT-SLdP183bArZ&=D{^0 zuMIuNIt25fX-rk$W^||EjPtI1)0#T(*n_(O4 zNtwy{IN%|QIU#qb3*p5?f11P8(qZ`GT34RGc=?9nGBGUf5yqj7s^AHLyVMF`N7Z(1 ziz3HBZGaWTo2MDud}aF?dte`s*8YltNTG@@k6)(X4bs77NZZlXWsexlh0dLBiM>&X zi*%z~>v3zlhMjR(lR;w`8-RwwuZC9*GA)W(b>FA0)#&X3Z#DqGEeUpPHNV1|PxjCq z3eXg`i@VK|3AW51n#yY3_h9pr`q%&zxJ8Gh;kh2IQ-c@j*&FMwBS2EUqs>L8`>THuQ%M6ZZIrYgMf0(alnX>|Yr z2f)bof}|2=WI+UW*}!Gl1c?MqVhiB6m_S?u@g=H&xF+6xzsaWn-yOik*EOg`LIFM* zcuJe7YTDqNkj~zLd=I80d8GL|D|DG`^%3uTIJ_=G^nd_(ObD?i%Ms7C9xd zAxJ#D66>1lc-Z@1&276lIRHmnqoNfBQ?|`SKb#V36%@flU$iBPSDd<(r7mJ&iLpfB z1poTpF3rN@}l`WgXM9mZU+`H(+Ys7}i7> zRDiG7Og21#C$Sd*eSk}0cUX#wU$xh)h~h={+sn8*EWmk9 z3x@l^q+UVHyD)IV%H=~iC|>>K+@Mga6?wI3uupGZg=NNvVy-0CG=}7RBG&+uxEE;O zY5-&JrCvr8L=OoaNGP4hRnKvPa)+WGAS=pl%HEF=@#5jFs0Vgm%aWnvng9GB#9KKZn!iU`2 zCIL%40a2?8G}VGa;}WPy)}tTv)!iZm)i0E6V}wo`L}2^kU({qtgTdbrZJmMP<4|T~ zQ1J9Er!XFak!4!$opBd|p?Qw*oQ14;SB%30IBy}IOzNY5agHjU`VBFFFuwYhm}%|D zx{W+Wz(RX%T(>ZSEjqsPaA89k2T%gHEs(FQT(q%_xnV2X;W6eK(OE{hPJo2DrX0gU zsTr(%$TMoLD`P2vny=+TD?l??`9_$N`3bp+tuPFFULybnoO8ChAO!s2Ow(frOE~vX zx&YR2cDibR3!LF%B)ligQ`j)c^6ikc909tmJ&kw4XN!{#(R0;8NY9tr*$e3@S6OU> z^w*fv^$6NT8yon56_4#EFu_{G9&!P(FTOVO5%cY>@t6=)v+EjIV!iKrh%5zO-kQ^` zZmT3u?EOf9OCA;X90r}d8aIHS!8;i@P}63e1ow?B!Z{(gPZ?dDlzZ__$J(R&o(mnN z9Fo8lIxO)N7);DHfj9n`#*u(iX)=5B0k_ohz{ViXW7Z{YEba$~HS6j1=)Tv1+q0ty zWO>u656EEWc4J-vh{zg#j_AY%t#ba|;!(zAF5rw%>`Ir6lS6G8FgUVr&;r%K*v(o4 zMW1#qt%Io@en7HHzLMN?vLM#ne-W^RcBt+>E*FEtcYLd@nZG@3@rnSzxm)$r2l&f& z4$Lg>r7dDJjB8~Z+{M#6x|Iy-(m-7+HFRmEE|Ma8dI;A5Rf%+!TLo-K`onew+h-bu zR`ZU4xkvYX+_=N{1_QvchHu%XZv4GxBb#xe*O01)ID;1+HYWymv27*7jNC1rkZ_Og zd&TaRFck(d-ncLdn|jwknC2)3*AXmMMP+LPb`ztby?@KxW@Kx4>GHa8dvxDtVg1NT z4H~gK#$7iqu6g6rBSNbcF_)E3Ut26T)>xGhf4yDNy9@W}^=R?6NB2EcHGURsfLE29 zlic9!T`A9ztaAr`%)1to7pz5xg=HlGz zI-seN$mYz@3$S3o2|(YKZE!<9-{Zd4*yq7&3|Z4|Y2EjDIS=kB4SM9j>>M~RaE)sl zNltA_>lIi%YE#r%W=70fsMT`gu^j3Wd|cFTFrN(E4!Q?}0^Ge_mG_|UY1I!+iht`p zJ1!1^SUoFgcEJjHY`Z!>dj}zVVZ8({lwlFCQst>o8vnt^qbGnokT1u{g`BfWgsa7T za1X53NvG25@2={Tc!1fpItgqj<6{l)Bdm~Lcdb?*66*!lQYQ{q71(VsqmpZhV6e4%Py{aPv)`^>yX!4`9+_OH3wxszAK(0 zc|tY8_A^WYoGHTr+$5W3p#gZTE&QQ>ZDgCEe0Ysu0eoTb`nWo>Ep<7qTzN=bv_1#i z9ByZLQAUWilq+drQ2T?s0mv}>!xaJ*hqc-+0PA7})0VcC`y&qKo*s89Ra|dgQ zF`|r(z7^jE7mWJK-x5Hi)l+YWC?$GHx?8}c;1TFt^>oaK zGEnjWjNuoO;9-fmL8*4-u%J|%juiaEoWw=>CV&Ha%lT5NX0gS2Kfg0!CUr>AY#Ew5 zKC_SunsZX+AEY5JkBl{?}fFM@l>!Xc-YZ>w|7}vv{zeKdhXGm+_&wQffd{!RtYscTo%?c5{Ip3--oOy*(g4IS|e6; zug62W9?-*bIs1t45~R^(`BmMuGvIkO&8lk@t#4VHq|X(=H|(%{3vE2mCHUr*jKo>#zHy~+!@-SY1FwBLZJ1fLnQ}Znzen7Jn7bcdL z<#^A@E$`F}wCYy5t4_A=LXNFzY(I)S^-AIg^R~pz{)cB{(1#PqBu!|n7OY~{2B&g! z;(apsZX7x!I#X{hx-3mh-kNfCUnt;bxWPBm_yo5SeTwq(nvpTsybz5-oGEQ3FO@-0 zy2j3Hhbe8qF43tjjYo$A#+bg6vxEDQj`HSc6`jM!@6p$>c(15I;y4cv z#*Wxn0v^^=s+iAiL0DI;Mi&>iB|IaSdDRPtzC|gMf)BJ=&9Te}yjXmI4XnIM^s+qY zE}fnQC@^;fUYD@6tqHqEIUDUIT^H;PY@Qt1*?K+lHcI~#_vpS?kae>*Z~!42$*0s) z;;N(?AxeVvY)@y5iZ7jaWDaeQe&?sv%G1{MK+oLm%yHTA-X7ieV|+%iat>hl0Iw%{ zD(q=maYTi{!|9_yhGciG#?-~<8udiCqi`Yc$kZU(M%bzFBa-dWeSZSa#5RTl0(g=PTeD-e=;r2~6(kM4UdxL4ek31jdF zaFs!+aO)dS6^%Gg+tgVU`I;IO9uFPtjOq(l4?qUx-rDoP!~}bEkM8?Mb);Mtg|vD~ zTm_*H>YBJ3RSnid+zz0r*EVbr^0KUj(s;mi>=(19cPLveR)=qJ?$LeUgj_|(NudbY z0$rvMBspi!ZgoaZ0tb^;fc)f^LGQzOZ%r!Loc$uBG9G_$<@N-APRS8L)Qn!iEVrtl< z)XX$+`gKq{Jo<6xOS5k+0-nv#+%5&bScU78L8vbv=dQw^tw%q|Lrg2SqLw;KJQp(< z@2){=@QX*WRY3B}aj4;NSI% zmqiSth~c~9u(RU;qtD3QjHP;&gPjeq1$v8fXgcs64mbo}6Ld}5-tR7PK-3-(&b&RI ziI`l$hZi;ygJch@XG8Ot*F#SP6W$I&UXU=Vxl%jIIu>s|bYWj3Hg}v9I2rAPTV`P; zS&}sum4j$JzaD)RJVQQ5f=#)UK?fS_@&$zMo&j?hg>^~px!_?AmET+^5lYt>U3d@e z$LlYlWdb7W280DP{8$etEbJL#iyEervg|G57L^)1MzO}rg_!^#XCGQFQWzrvz3f(a zqd~EZWH{QxS$=VNndDerC!VRy$_^H{US(n(f!Y4;TX~2h0pj3YyRScn_=hIqj^m(a@GD0f(&{K1KteVU-0D0o8N!+9R zzKNa>!@e(o7sN3}f-^h;jSgnQcjiV0KNIYfI7%(q=>{QvtenV^NjiaD>GtTphhWE- z*!KeFJR?se;F;@tWSg-Phx*w58G_3YdAfw;#zK;W)ef5=r3tW~Y>)1Ha@+vMz8~QB zNSRMyr7cIKdq$6qNaT_qCp83xAjr0L@EE7c3(da^Q zcGU0n>yZmP1iukekAB)afv+Nd_CKVw9PCFqj{vOqwmv!`z}sBsk#qER+wVst-l={@ zrkLPMe?VVx@HRkr8W8jadYAwu=t}zx1sCW@`;X2Nbl#825luSDy^D$|{RBApqk>ztpRE!J7(adnNEm zc5d|8g8-Bq@jm1it+v=%D5MfbRo6CzV-wOYQ1|^1yB>r;0)W~vC=9D`!0rK|7>|}N z1R-82EH|YP>B809t*I>%Nzf+d^aqAdPErs8s<3T-h+rL+Yl*5Q~x( z+W^E6SBq(CiBn@g%%CU&X)_>qQ}0Nyb7!K4;b6UQ*F%H?{DHB9K#>7@wO4eJ12?!c z(M@xw0&UQ_wtT35F`-?Zz|U{iJ9C(yo_BPmGK63VYMGg`EK2~krP zz}#+X2b*F(2llOs^fxxszHHrmH@SUd_dOCmwf#+jwNC_H2o0X^k6tfws#>UDpvu%U zQV$E`g2%!h)rNE@>L-R*XXW7T`z&~Ue|ieo@-Fy2)o8$DVV7X^Whdsxf+~q_!<~Q_ zm$(Oi8r&$~XaF(f(J&k85rFqS4jo+(Oo17lO+Pa1wL^wLdV1bO$>i->Gyt+oM}}3#{E>Jh?|}#0e_6 zc6DD54Hv5}a(dd*)mvRj+7s$3Y80{q){S>Ru25^N_9*M1{RP8#Eo}D`%GXA=0G~Q5 zn0>_w3tuCf#_O(%gl&NBQ}3ybiDv}wuf4ZjpR-i?-aRlL_JZJG6u9<;xa~e2@-E$# zPbKbhxUYCA*8RMXu#wFbzCZJfz*&H&apA}l@E*viyhyRbB0p2mvD=DE@v%VH1zrSo z7>5&w{PN*Oj(fH}S<@0n>*+unm49{J?E>GP0X6_dw`mOx@M11^D2>(1a=@?9vXP;;_iwe7IvlXBbCg(2G;@E`ESeJ z$^ik?vUOp75;(RM8k@CofVBe@z?r;TGdbRj-ZmmPc$&AUDkl!58%ef69`7EQ^C4f* zy}Rzsv)mQqwzLNEdO(fzkniHmk2DN83ef^J=C?>yoBHYtSC**`;%zbaLtXXuV11}> zbp6OZdXU^Wwj3}+4iToX!2-E5JTD3|h6;mP6^?a)d(5=PVKAMJhu8oZ3zkb9|Dwx1 z+Q1iE_8b7iOJmI40QbJf5{zbaXU1$-R=6I-KW6~AdE&u3D_R8N1X>kY$uOb$FzY^- z7$BORU{2lOAUjwFVT9xAQy#*)v+^KQK*Pb8C%3TEsh*LQwr+X|$ff51WkWtB=^q19 z@UhW&96D50oUuny^y1D2vW(zmI7>(iwPRwYaR*1I!koty5+`b2f!>CH;6bvD47h@a zVI6}*_6|oKgJhwOG>w||l`E&0q&2>+rA^@F+0xQ!UAMG(`bPRl{0}>szz$Eq3IRuR z5YB3~BqN{LH_7Fru#pc2n;4>EwLleA@w^8X{$#At%e!slVcA~65ZoaT`HL?33d2Q4jNV#ijP1s^_~$XHhq9*A&>Tf0iw$aJ?ftgz2vZWY1n|HUaDj zw*&2OP0%N959z}ii?$*8;_x1=ShndlDi~2VvF(vlS}t$pO@?Jft=pHl9GNA3Ln~Ke z4Ok)&581K)Ru6ncYUM&NT#= zGFvTsZUeD3@9O-fM+9y2 zDlFm}!RisrX9aE@`3zsqt993n)2P@plI>RacwNJHRx8-Jh4ZIWONEMX%7xWK)2#T5kkkh z5k&$FMbJ^nN<$@Ho|;BGIA}6{iqTL8U{r*y3~x?AKhi@mM|az zdfFiw=>>ME%hQs>lg_QRo#y%CwybQ12;S7rRuJKATR*u)4{3>tax|rxMNgj-+4*4z zGB0v>Y9xiu!_L9h)h>jNfm_Y}P%gk#%ij}R1vHC5)3%{I7mq~^ZZQ!3XU@^Oz;RNWATmj>xaRkzX@~JnTzw!LeM#o6w+%+fZF`&75 z)eKzdn7t!MUo?%pJQpN7$lZ}QA!yRNF4&tidCo`emZlIF2Ut#@-6$LH$Yo|OeGqZt zUX~0m#HrOwBs#I>9=(f4xJ6zUII%G49h~+ASg>wz9Rv=Ws}TtQ-heB5v;+H?AG~1c z{`4hkAa}X+rIYlyqxux9Zas$}`ma;3i@hiC2{W(!uT0l2WWM%3Uuk z_@HQy%d44p4?L#ksktXn(qMM_*lD-fq*v{078@aT~p7H0Xk zj_|z#;7CUi_k`v^opes8scnBCS0h$-N546#Qv)PqQ}JC3{-wc)iw>rlB{8f)_(II* zQgS`RYXy+#?#|8CPc;w)8&NRh1DhAe1xm0hp@@eyK5I|SBX*9 zU^5VzhU6M=4FF2UJ?`>0B*v6@Ur*I>5<41XFXJ=agS@<0Mx5GMNlf64Ugr^xz}7=C z9WH{#-Q)lc?Metx;A^KRc5~+Sp!Wnt)$M|(r3?WV)6S~rLVH7JgG|q^O@k3A%-=OGr^CI45ai|DSzIL>X;vyf4bkIdLmoeEs%T)Kh zSiRx!3BY^pvxp%A*1Bksd&9SeX_P8DvF<*0h>E~&AgR;k!)Bx`J$qaCeFU=;#4i(} zoM*HUfvC)71iXPNBRRemeU%d-Yea38cP7KSXv&$D|Llp~_d&n`7DdSDeBB%gMCkic zX)L`V`bs{^i;{SMq>Pl@tEQB9UGQ?T9Fv=++I_!8+t$dIxy*h+7KpGUYx?-(rY~z2 zX{4wFmU0qJip%xJ@<-RwbrMq(t%n%T-S^-=DUV7rWWKFrfe40tn2{53TvU@LlZ-(3 z;*~I@?s^6=x4iQ505u1AdKuq+FP3*IDI*4p_kuDBggCqplS?>DcR!V9qYia@mo!rr z=!9gJdX?qyII(G5vPR&2zeXooX%&+{eWyYCsGGYsMnTsb@9 zZDz8ZzXr{MDxjt0$xm76D}`MyIdlZXEZP~mNAW&~c#iSz`yKHMq5Xi6*eCBbgCnlY zg9a@LLlq8r$_z6d`rt|nlO+a0djhsDCZH=aeh}|_GcE-~`+*T$AB6XrIybA3fe@8# z48tx|(>4WSUYA%aM6sH7nHA8ONay$+%lkgSl@GCe0Dna_gfUa$%HZ%Ul)-9 z1*OjJD5-`+#{%-RPDY<#z3(g7d1%yPKyK$?(a20*9e~9sLiIT|#>*(XI2y+%UJ%|i zKsH$8WJ8mjvqOQ7apSxM*;B-~Jix)1h%4p9z<0{ENu7f;^aWUOgf!|M%B~ca{ov#h z9A3FQ;RAt=;jZw)NIxN`071r2QI5@-Vdu_GlREuo(Dkrh{=)VKW=4P#?wxW40_T5I zeVgbX@0b@E_>g%Qf*3q;{vyW<_tFC>L5QE$Ke33!t^CT&8s# z@I!<^CJWiSI(Xz4_6AE-WCC{>Ss2LbzwDWi@qs`IbocS0;07&c3lC1JZPERLBSYwL$*SLfmb3f*1qlASss88BB;o!|IT&@ zWElV!F=@Ga1ww7Hu&xkHkY?}O8+5Fj;&VU?WZ*sb%a9)iHWKY`cI|3py4E8~Ui2)NUMFVLig zZp+{o7pPnuA(Pq^oRvZ-cAjlG4mY!O><8&`04af+=`;q>{EgZjWJ4%IeI(^r7`ax5 znK&e6CwW_u2%`#uc&DR3H`0thEileGd7hl96`0V z8(b&8%-w;n$KcLeCd|i{qMiqc%OBS|8HVIO)^S3fay>SMsmXaz`zxk`j^iEz=b~QW zz0^myPkFDyJ$i?_iTxLViH(JA%`n5Z!j2-8Ws}>bQ&-qiZUbZQ_Rjk)PD?uiyhC4= zI}4nHxJPfAO|?mT$MR34z>a?Z^@B} zPs4z3asVdaD|n9#M!;3xk3(ay8~g?pl&^z$o2=}MCGJDc(%VE{bw$!;GEXt);V#C% z0a$Elqf8$Nb2OstQS`V0P9ktb5Wi;&Iw{vXpj5{Kn-KMF$j-F0N|+_3ynnZ`lTne2;Btbt0OMtEeR{Iv@k-VK&B*x!=ymGntev3CAI?IIsnnI#%0s0v3^HBzXp2 zk+rtw@K+L3vk-kH4DIQAq;H1R@(lksN#0b*87iD-T&34-fgVUsZd2oqz z7V(kq12q`j3D?M45y87_Mg57Bh1;pd!DN8zLH)xki&l#I`?e!HwPw=yh=p8ZAd_7M zu;1coWhz%K_YDMo*dOeu9KS1>?boEhDrc8|&GETlZ{ZC0eA-K_p?Q$)rE=z7ky}hY zBaBt;0uPX&LzRi!G>2*L&duY5lb3aK!FJe@y0PN+=rFuzTu;Phz8RRGxS8QSbEG;b zckX~*NYv%)**jF~5=C)AG{Ges^IH_-n$ZP1PT7{X@<_jJsrmvX7cG~&x&G}^anL1D zfLBM)onY^6jFN(PcW;b2ARN#&LI*Zfn0pO*SdvxEoy;Rkn=z~UMv}?YN@eod3 zkJwBNF<6cI{t^N7rJ2zfhg9YE8IFT{BocdckM8@lx*PUxA!{#@RV#w~A!T`F75)8S zo3P;o8n`~`gTgoIdvM0equ^`ub5Mw`_9^!09^Lm`@D*7n7UfoCJL#LbpZRU_g)R2z9^Lm>b$h#aAye(eB^yatFK*4Y?rT9U zsGA1%I!og^%I;zZq?y`l*Q7th_KlT~(b3$a`#z@p?Mk_DRIbrAI+{?<&~4&ELbl)OCY?ozqUuXu388-y=pWQla(coC*ODAxnqkOgt8szgataceKj7i6Pj zUWqxYNFH~cU9UU^K+t`WQLD<4aaSrcHT- zzA9lA=;A5>yUqHkUIMV1Ulu$m`IfteJM>{0a6+ApOLaO^d=o>q4sZ>kIk5)3ay8+x z`K&j@AhcufIk*ebo~izXOWk^UeDnF65bqNa3%+Q#Ad|;TCT5FSU=D(nLDOWGkiFo< zWNvcJF&1RjvnJu`m{0KG#Xsf^RULF6^PS#}q6V`X4^Z<;tR!3*a}!S0zH4}ZQ)s(5 z{eT;@OvvKCL0EAf-J5BxGA8ZL^o{L5xaMk$#VOlRJt58%U2Ql>44A8+i)LfEW#nFY z8q$Kd3CtF;Gujb)-}(VtEr<1mUHdz|`l4^#c&XRJC!(`KmcV#OX3KT$3v=_eLV8W1 zy!g^xBxX$XptzuWU%V>XJTsEI$!vl@p*b*Yp(7UD5-ZXt3w8{5f&jQe6n0h|4T?|h zyrRsEadsNcVjwZlz1Z78*%?=Aj>N2bivfh{3}rJzVusR4DHtQs9NZbiW0yBXK=R~V8-U0JmuEG*=z_M|mMlPppM<=) zoXxw3ysT6RzzTSKXhL`&bxwRf^4K{cbg3Z~xV=Ed#4|S*k#vmVwYJfxb`dSAfX=vC zuSgD~e289!DGYR^yrwi7^_V-KK2bkMI-zvvpN||?&_f{L8x6@%dpY=4wx)M}(aT@!XVkkUYZPa`I;y+^G!;40N;fnY+}DGTn1>e0 zfCAziRs}qpVXRd}6@xGgzBF4KSjOHJY~=SdZ`+pQE#CP)ovcQ zkAPbj3ib#O8CMIN!b*v@(@NPEmwmx^rCo8Av3JDXV?E8Y8@Bo6cU+k<_H62AP9J1< zyfOuPU~jfBdKO%*v?s1&@O8kBy6Np*vFdG`;MMeTfXCTARhb-u&ME25&}0G!0nY(y zPw)GiHwQ+*0$+hIjYJrH2KyYxeAt6Uk#DAS{*Ur8>jPmAriU`d!XBHbaESE2m+;om zNG@P%U(z@MgPot$BbQIy4@+d5$$QVLB+N4Zt-$mwy8=Yx%mGJK?|Zm=%h9gDOx%Yf z4+c@V5Tu*WfOrteIg@EzgC${BSzgZ!EqgHE!pQ}0NWJfs)GQux3NWe{K-yqftFt1n zKB#IalZQ+@>zd2Qto%Byd7=!;T1V$k9ICzVDU{)nNeC2@?U58PIFj*_b{|A?QDq0F zOtKp$h^$jG<|dLd8DuJ+c@fU1etnzY+lZUXPCeCX@lCd<8C_T&Uw)ye#5#S2dt{ZA#jM&KRu)(8edhi)xH)2z+X+quIvr%hojvPA0EFMQD{gfMk zmirm#73r0E%LYaU_wrk8UJyzsBsw}PbUegSWyO$Cv2ctM5wh_NZb)JyB2Q0>u20Q-K3*^LO;Ac`4#n0PbA3-zRhE$U%aAPX#=rm~X%r=7HGbMk-9C7)Npf7BlW$A%c4z zxsZ{8VD(-1<^tuEG6Ptz z)?;%VGFN~t>;f!2Ddh6Q`Jr$~;x6N<3Xuiig|9Y69Q?d(%vjIE?D06-D6CqoFVJhq zIQ`N6f-J}g)Z$0?V1`vjE8owlp zhd3=y4nq`!3tPO&i9H++@nH}zP$U8N`j!YBlhM)zFnDyKOScnW6_nSFGN_iqu4uxSDZJIqu!ERX z-x;wePDqu7B4gipPpWZC7{(a{UnAj<7$`o}!t{j^)h>iWg1+`L7^Y3h^NbwMj4|Fd zNxY21*WEBWuwv&T9q;0t_mvx8KryfMZo&bJSmfPwG?r)u&c*^-#FPJ_ooJLmUvb9< z>7pK?%a*M3TYGvhc#9T*Ac*8$L{r7n;eJOI1_R?suvp})$Ys);6z1tmcmNQm?B1@6 zL>qpcYOOa0xQPNL?4EE%8bbS2`~gy)pO?IvRpL%Lcc;w(GSyA-o&@%=L+MuGx%Zm~ zZoGJo0#$g3xDpyRxQ!eUi3|K^9zaV;JWijWHbt($4wdI*PU)xhqM&2_m9Cqlp~#sO zz*BdTC)K>wVsijVDb!MQN-d$aVcmpU<9dSMl{^9L&il?+jqL__WgjS;(q9aulGEsj zHATpR^uMGKWO|)zD^J#8mt5PD-R>;(y2xApx_l#$IRmrWwy2H~NA(FaA*xYc6nuVN& z#2{-V*}=seYyxQzji&7x3iJ%>A`CFxwWO~+Q+GSH8I=4lin>qiDzI{mH>ZS;v)ft; zd2rh>9ZERoru18agd?%i!tdRqH5FPvk7tHDfsRHk$+8w;tD~+Irfz++}JI zeoSw)yL)|v{cUi%o?)}~9ALMx5u?)V9Be9M7}|hsDs&^-Rri)Hnw#vcV9mna1rPcQ zU}ki8Xi#SMwpa9+nJe2rQds66cO@B_~XC;uE|Hyk7DPZLBWbTqsbqjJYk#=Nx3< zmf=5qptgbVzbOM+o4A@;2<(*jio3jP5qW}dmp&_V9=Dj!r@lZiO8nq@NJrrS>#8I7 z{sigUkvjlMK(@b8LE!Tsw{V8D7oRWU&E$pYPrgv>66;aifOH4^VhE1jql8H(EGC)| zZwggl1R|zzg~3sVxJz17BoVQXy&RMq##_c#%(<}=eR15GtWI#{`Ww$xhjpf`Eaz2E$;AlS@z-4kEBT26w-K%M8F%=W(r{I_~HE%g9RWqrPq;!#43VLdCUy{(W&p^63P| z$F9go*mW4p!jkR<9Jail@EtgC+br_0pE)+Ht~JupN9o6-NvsOv@2hG_-ezD5uE)DE z`~b2bVkhk=7nr_0-Xb=%w;JT5I9jfS^tGQl-v&QSqcOe7KP4xpJ1&7as@CYX;Jdm6 z<4*Hsva#^YyYGS(E>v7@10EYXZ{zUsx!L7@^70h?Au&>{>ktU(R6uTL(9CMmjf~Jo z*WOx_p-h?K*9@0oL9S&XcQQln>#JMgbmke|<9;I4bpgCN3IV5y*YLE-Hbbbz>IT}H z;oOU+%#FyS3@D9>{-7aA+7r#CCQ|(aP3G}hucaA>60o(T=a#0hU#Q`x2)HHJ;VmU@ z9Cmsv>z&0WT9cy>r1Kg$(L?DSH6+l1X(%2QXe4#mP!V(wbKJ_+Pc(*e8{exA|!9yOtx^y z8r%rT>f^UN${Gu^1vQU`dpWO;*F=$#)LD38$KSPF4P*Fss)aMN(o;EIaL?w z-pu^43@}g2yt6HeIFfynf$uxnrb1(6o5Q_;2ao-MKN+g)tH;Ysb9+wYOI2$;C%TfZ z5p{KSD>Ddlc=j|mv+#=cCWZsuE^bR28Q3~_l^q;>N#X^F2_w~ z*K736gr60&HJg_EfVcbi!utk8TYH9k(Gd1UkgwRul80jcfXH5FuFEvZ#o72_u)1rr zychTiOoITZc$T9K9ysJpFpYLTGBc_Mn`>~AtR>5xKM~pq%1@5W+<5X1D9pDQ8B11$ zI1L%Y7?@neSOFV=`Oesot_V8xd}5}Wc4C%*lO*kA2K`}EpOyn41yP%o2Skn67s^js zJ8UiF?ARt7l)T6GgPlXxLYDx|gHD{`agLv5a2C%~%rJi7&V=S1q@Z({xh9Iid4su6 zON;r%>^UaGyrS%Zt!%C5_eUg z(Y6q9fuKc@F=~gPp|tg6PoPiO!O2eQ9~zc~FQ_MP)V>YWEPkGFb=00nk6JMGlhha6 zR$tkQvwl!>%hLFWs5jln_MWK!?qtS8Yqtjc7iyipapLN&b^Od^Mb}c0ZmbR1olG;}kcxhoTwgwFlx^lMH4Hv8u_KqK*zH3`2$*%X}jxd$s z?$C{)E5_W;8;MyVoaMU=uCHwd{*$-<{= zR&;U`-78>0bWASJT8MgkTcA#cJxHtV^Gf@eaX2KY`N)Mj9M+=ZwxJmIQTPQD6gG=* z7ON%gy!R^%id)`|ucdfzx!1Za_|TR|UmkkVEW@FWhAH25Hqm)w_YezojQBzm6?7@M z>_EX0uzmc zu&Q|3#Ghgzx`N@Iwl%i}Sn4im8|8cCs!47{+ih49yU%etJicRdiJ{AGzC5I9aJz!6 zh^3gdK<Ud!VtZ8iFQh$-MTsrra#U?+_0X$u=pzl`WGojaGsnaPl1mcv=fNrv@q z{>kd$qhQ7%OVqLgPnPDsw^PYkV#GLg#gR6~HUdLCA_UUFj_GTn~!0=eK?orEcqj zNrh1BY&W~O);-ysv>fZTE1AxU9oV;K<+4YsonXrIUYSfYb*-;>gFBDX<~47o zl#tnrOXMY`yw!$CD=nj9Y3lOI60QrbPRMF~*)W<}ftid(3pvG@ucvbi7*=w$7T3QF zrtrd)T^L?(;3``_wYcw|)JR|rw4b6AjA!j;Ji@4 zdsAd?*4OLHQ#+!2wKfH7$NgYObS<#OfyL{C%woqqxA%=uOoajz6034dy-xmcLDOy-6&ZsTSB#m1n|iF#ahdUfR>PRchrMnc{~Zwbyz&w#hCYN@m9#J)U!HRRlCq_h=rp3MU_-xm+)h?>b3 z#S-Xx6zh5QW^eWpk-@cz?1_L&a(m@b;B(?uVW;G)_dcT2S@-Dj7|f=~*#Z~~wBoFS zTV-kVJes|LrFqU_9YN-KM&hfB8$FBAW5EY`e#6rQ9Gy817Q+kkLp)Zv?E^L@G53&Lq9pV~cNT42I zC|*OR1Ya?(P1aiXN4Ccd#8&ZIG>4;w_i0+|*m9LqJj6PucjS&5la^C!9DtXIH-UD# zTVNbaYHgG5VHTGwh^-E@d>@Fc06M6ukE@UCkh}!eC*$#55nuUz77jI!)^OiD!put0J-utsG7Z(!FBaI7zlbGn9swq3pf zB-hs+i<>CuVTL8O2G|(@U!l>+*}X98z}$4YMzUEoAKNZ7+Dud{K;F5=0c&wY-RdpZ zP>A>vtNh86?zmUSb?66p2P4f5C}VEx&Wl%ryKj*Qv9PJ3@rLBIGu?!`4A#S;!gjLd z4n266uPL4`#LcRgTpQ+bctmPA^-wwzcR+q!aSON&{<5^uKw|>l)v(Yn(?`H=I6~BA zu6*bKdk6afN~fK}x1meR?Uq-n*7v4ShlckDcguBh3FBUGSoF}gQ|<)6L$XnIi)=Y8S5Ckm*_d$b z0H+BY3D&0@Vz;jk^kBJ@uZsPq?)o3^y;UkH1khJeh+arrXs|6d zm#0yWddWJUYjpO6QBa$Y&q|f(*2Ke$4S)yHJ;ib3R^EzOg~+w6L6w{63eaHY*!m0E z4Iv10(l@MAl;+VYF_KJc^_5ahP>beK82eM-)g6h0uzrhsyc(|OZ4S%|*d)@J%=g$o z%5n;A&NIGT9k02H7Q+#b@|P7uRa*Iqp5@pN@(w#vxiA^PTpde+jQ1wtyg7!krl4*# zwt$vr$X1SnuR88=k!{*hkg)lhugU}WRpH14dImr6y9nu-GO5HWlp5U`Uw<8_7*T=ow z!+9>+htdyj#kpoh1n}wHdK1k>7xZ?Y13qjWM#V zD2~050@IGzES^Rr>z@08M~OSN4@=UExE4G%R-JA)w|Iu+=fJJ%PRHMZG$CEJpcmPO zc1_;?B_fcsFt0_Tu~DtZ9l`TvbFCbd&fJ-KKCl?Tspu-8k%(t_YjM||N2!UuJN3h| z7wsE%EnsoM?Olhf(vd?N30DPAkMzv7ObJk3*GtN>T8q_DqG?&X!IkkmusheBzUOw* z*{0jcEkbOLhvJ^`qVZ@wue&pNrkIyp2bJvR!M)3@C-b45R@#*^k2tnG66LzKB5+}u zLt2TqwG4!8#o$!=3pN!AFC%$<@Z=c#z!p(*6T7G`X(0$(z zyLD?faW$9jjEiu)bsCEz=^YFBCgB+bQ^`gx9q=Oc`Ore$*2vqUHkjS41MjV`BjRF+ z&3(U(gdAOcQsYvH*26RVK=~Ao^0mTK)GgXZE;(jlSm|4GRug+i*0icq?hvb8d1!Pv zWHh@Ua76UDxoaEep&T5Ow7F#wZx*nC9uL_bQD~d|Kjy z?z+kl+!)Zc>o;m#;D!=;%!ag`G4;|i>$Zj>bx)hF2EtnO&E4U$uFI{2wy?!@8%Y%0 z?R$qPvbQ-fMV1^~XRt*W75J5Ko)!a5w`S8&AsyM?s)0*$!Qplk(@FCnAu4GhJB%bd zwbNgTs3z(pK_6LcYa@e3!o+K!!qBt`=p5@C4I<7@ZVokZ=PU5G9WCZ8^C_W9^8vpf zNm=s-z#LIxW(@c(Svckwc?M!I${Y-`Ovr z->$z1N-er)K^iG7*PegVy4jK$;>C_+S0F~JT_Hzl7iE}>W5C5U3BUsATXaCXbbd`j z*{ujbA*HdbgF&nm+f}G=Q)}KQgD6F;Ms-lJz#8IsiCYAQFZfE_hlZ=WR==U+r3G@A z66(9w3BD;6k3B!^XYyN^moOXsCQYVC! z4Co|;Rh=1ZD+S2AER>nbQdb4Byp>^Y$|8+1EF7kzjd8;7oI^$w?F zpxPsdBXs2sNFGt?hEDG=Yw8b9QnAREP@7<57#WtdHpoD>5@{Xj`3!?~fOY#O%jRRV z@Hx?*;5K2D^d|DwKCOWFh~Htc2ZydyBhBccAF#7-tmx0S z2OlfC{>EWsMtkFpPm<`qIS8yDXuLX-us&_%CqxYi8uhN8)1H&oTr`}TQLuAv0GQ`) zPM@-}7XFPm$ym9NCm!;b4j_wk+_fW#EseSEPHU0kB!5rl*vv%MqZ)*e5!(W8>c;Ok zrVotxFRtK-6U;cTff9d3>uFhW-VlCslV6K0D_FI^2xDPauD~R=(Y^|JnL5t#W4qTN3C4Uw+F*8( z#20;%XshUFM_brNJ1CTmJ}h@9Ep(MAaCEFo4+>9%tp^V$FIa~`?7>aXu;pz6XS2y` z(dKL)o@ThsObsLeweUq97A3sW>Bpg=9tA1{4};Yg7|D^kL_texpe@R9Tav zW8~auD@b1nWxgfTeM^LKHnmh0;yi2Z^;KFYWA~-W{gm2TbEo$pZNjQL4Fp}d;ZnVm z9+4wst(fK#GQ!qU%aJO=B*~y8T|`6FuulM{QU; zTh;lTEoKetHX%ndly&NonK=l1NmT}OkhTw)lQI%_p4w(v$Xkuht6T~v!uMxRs{w3) zD?6>rIYHzJ>^30^V+Cz4OAW*b+*(#MVGZv^Fe&g6JXP)O>l8mihxPWz+kH{xtTl)k zqcIPAE=MWs)4mGTeYf08OqJd1JJPBEHxZ5$cGjkd$JhF7sywhxeSI=#vYHqRvG-<# zfSKA;ITpNl?z9knyZI(FX~=ScKe)nS6_b+)!{kGpcd-HP-JqM<=iVky&8I#n2Ec1od_F(Iw1B3cX4t>_sZ)UIO{Id zP-45{odXAa^@@vy+|?oS7fdAZ#PkkVM|CpxPcap7Tfe?GtecKI=3R!hH^46TQ5|!J zK+}ZHWJcpLd)v#s5ojDknGMPUeFZsgt8ND+-_2zHRFFyIfC6uj;nfv_qGS}^CF!;E z77p|Eq#2Hb;*gLBJ~TUp|BWh7>fb8&fsoPzqie55MDt}W|$5u{I? zd2k4lCOWV2e5ymuT}I*c)yxA5i!EjDxj3|&%wcD}o6TI819flBa(o=#dFDxP4B87F z%3;g7$JvaB%9(xEGjehcI}=4sIX|8IxC)@X&LGYfn`;?&|vLGEmE;McKlsD)K|eafTPN)kvmfAB@c*Og%`Ze{$`@4BUc0$wQJ#)3MYcqTSJY9 z$2#k-GGxt`wtIZ@V+5`vyg;eM+zN)QVzldgZ-kNmY`i>fWrAwmIXxnSd2UhH3E}9L z4_&8jECbe%*-XY#KY;Ewag>rEyg>Lz)u5clw?~YSe#0y1hPNZ|#?WKCgLM_VA^yVL z8fb3XfO%Z=sqT`qev+)Qh{;L~+91MHR!#0lFrwJroAj&TO%Ptii?)Z4OL=d+UvhC* zE}e$1o8f9K(H6>KKUmNs#+FJ~=|pj*6=51Nd^1KRodR6>My93!?yG<5>=%buQElpl zpiXe!IXBhZnR}G!eo&M%$xM`(%9>*bSEb|=ViaRmWCda~I2_|{ShA`%1_Hi+KZvWq zQPlY|k+XvuDq~bL-j7v$TGp^sAiRXky(;|{k-5msUc&KDoL4u)*pKf4mr@)Duh9O8 zrL3!rKcI)un1xH6u|G$=Q_OEmom??9lB+^3in0{4L~L7GN*t&arp(xfg71@@56jq# zBX?o{h(ptuGlqa>D93-(k`9sn3gLntM{`6mkag5}e;wJ-Z|3N-^mi z3^SIt6!?Xjk4ADkq(=XkuP4$$mPD)YvZgERWtHBz?{|nRD)3_vE||=mh-evxMbxC*&S&k zdL#JGrk{ zyNXWGoBsm>i$GV}0B)nsJwx6rm+}#)(kc~lg)g8y17y!O7IjSHo$6QaEanhi@WvzT z<%X}xg*5@w*l=O594q*Tu3J$L{YpDR>WN>5Ybh9GpbwwYTUS1=mx9lVVGrKVxT=Vx zjzZkpD4$as%&t4bS~+QVjn}WJ8&{!4FjYnWa7_>f!0!hin0F5L3Z6wGM#;ZLjf3S`XbfdpG5~ zcT8S_ISl-TT^C{Exo5nTlRgIkzZmJACv+81rJb`{Mlr85m1|~P*g3Q>enmSwRx6md z&V_mq=oijtJh?(E!cz93dyY)gn_d(q6B`SgKB|Z*X0iQ(hBqPTh~XZ~4*k8e^?{T`Zs0npgE!UV_aa=Ll> zDLF_soT1elK9E6T4jYd~9BL)s>JC^tb6X%O@lAP6a@8V^rRsDRWv2uy*5WSsFzN|okHrEF~jX04P_)<6iGwmtkx<^Zk%;pN?R0*@!9j3gyniN zz*^xZcXfCpSgo!z&*V!fha?F__KN>@0Em%zH7FebYi@mHRe42V8>r>E0j!I9Un{N_ z5O=c(^bp9ETqW#B1ZlofF;y;I@*9t%5FHD?zqSn?+4*J#sbOgW)Ue zV;cwR)m6Xq3wT9(Jq!cup4_cf_TV+L=1zA@7M7vJy+V2oY^|t5aU0jd@?^B^s+77W z*Cw)}9F#sU-RO-=HOnF9+JQYDmYD4$T*RJCdFWLEliP2^o{B9Ff;V>v*a!(%$d#zR zB%Zp#$=k*&Y7Xi+BAd=8Bp$HT8rxzjvYEP-J%?}_Y!?$9J*=96tBPNXyFRwQ!1r7e z-;P1}8-g;Jp@}uax@V#RG{i(Bj2p}^0lMXnPs+$XLe#g_oHlyB0 z*MpDh{)Y2V9tEVqMrPN5$4PTjyvu`?xyT%n*5GB)^ImIZOE4K|f8d+KHFlr#5ZjKt z({+t~ZgCKCfo2=>8#gDvEV`c>+hS#XhFK9!Bs8d(NKYbqfR;j27j$M{xvMS>sdwg< zBF(~1&>E!%np-@hs!iYG?p13mwmc(=^(C4VC!3k>+xw`?NVMUm5@ZwB3|=(kDtkw7 z_3;{><5j>|2^|(RUfdYh;*Ups=0?HML>P%0kxnx*4|s!5l$goZNE08p09J+<3a^-t z2d=4WL2m}O#8!#jnYv9|V=f!`lI;NJ;t}$iZmaz45hn;=`2g`c=(;&+*@Cn5FSe|Y zRYTkkD}$<*Y?m)X9_CuY9+e#s!47zJbLPl0yFEB<=#Xy#an>hq%jV_b6uu(ZUV-hX zBEa@?czH{GU9<@7>U%EO9ptLu@eHQDz1dljM6xzIr#8w!{l&J<=&cvuRBV{A!{#b_ z)@~%fa8VI_L!gwVUM?X&!z#%98w&Y)*Ebbsph4OvII6{+)0FJ088kHmI1rns^^Un| z&kp;BUtSSVn?+!grY$##ybUYxoAR*87a9(>hy!gWPU+~4Q-Pk)UN93N?FjE+W0f}3 zL3`rq`Mp~dBt0mor3p+eNC?C#hPsRRi8Z=v)*5~Xv@sTBXC+^t`| z5ySbR^*Dh;1N({qy;<^1P6WC%h-6olTL_GLU|X%}V>#_CTVWC_w{)2Mvfsc^p2R*3gh@pd_97AWoM7BRVT^J0fo=#SQrL~SEoE7)!kdaq%sH&uX6@au z#=f?Xa`4Sm=T;~94^RoHk%VBL#_cylllZA;8oh$uHA+#@Dc zlay<;(PpH`CE$1*MzN2%QV>n}#m`X*#FqjfDAm`?0l&qotIL28Lz)s7harQxpe;Eu zv??cea9h#vK>Q*uhtv5zP!|eHcfsx@l(5@&U>B-*wsP?8OT-nk5Hk&fwT)OfSLa(= z6yk#!i+IB|fcR>WYrs+Dve_>Lp=M40a3~eH9>5<}t@)}5MZakF!VVRv3BfB;yg>KC zZVO{2Us*T`T!4nDE*Jg9jb1i|9~B@WeNurjl-_%egI!f~csdGGzv$|0Ag-yQKSQC4r6T!>%*NTZ#TON(OGWeHt@uuj^jNe>xN&4cn&f{zz%aNZv*g( zbyPhYgIa!3wnpk4HJpP1c6u$igQOjx9aALw9(DUX0miRz0$Sxcm^?!nx4uP(g3IyK zu@BheyT|Zr;_jsZ)pQ)vY65Kd&f$8o%~NE_#(U#Ejc6amowSU&AI#Ot4#2t4ziC(CpKd7;(+Sd1>WHRF3`mtls(TXu($ zL(-K36jH^)oe})WY0zag%szP>*pXIA#^ zfnRea@*?q9eT#RbdHXjkocFezaq3#ek?54O1L)-<306M)CX|mo689^Y4LksWOK3>y zHsDd`2FwlRPy2M>?h1*(W@|AtfSNlu98Koq7YBllNs6Rr(`ZzveizhWV5xvH>n>Y^ zK?&?dJR*b~+IHIqhcE84`BD9dc0%*gZh}_xd3(R5XOa}-FX}l}>ikoU2v%gzuW7Kg z?~!bnygT4!8+D5#NO$8ft4J3}&!9nDXVYswKiE#vYe`@3Ml}*D)HmLm3s_Vfj1ARx zDqm}V^9s=4+(ujg^{aQ+`2-rC=A_Z46{y{OCa5vh=cG_|-kPf_Z9T;fhaq4mwM*MR zu%X;R^19koZ%J-uTM6#tTzH;QziHaeEYxd09M1N2J}HX1$`)6hFnhI`glU?`+*59a z=C=1*PixMDi*5B}W|AK=yWR*}c0Psx<)3N(h|Lw1-?B3QD0Bq)qUH*3w&NV0x<%YgK7v^!IFnM9)i932YD1Na9LAVV-Wq)c z+?CyB9gmN0u7KYfcngPkk6im$BbxIz@;8k#Qc%|1r;OCp#Ewd~kp&0bk`J6+7^EY| z(M1-*$%t4Uh@xb^GJA0ZnJ}JVy~G^1A>b}Dn*}9-LzerREazQi3R-ja0p+~227gz` zFz84FCzKOpFAPqVYm_4pmX))_jnrZ1uMHI2(@YnnHc5aZDX+Df#66UIoT1J` z%U^Z#I*qbm*2eFb*UZ*?1@l=xlU{L_vpG_Cowq?z>m%mWrX;qe`H9v~`_IgW#^XLT zBdMc#k1#K3sl20@1DyTf6wW#GA7?o9l?|2iKL-XCK%1VGoHn2t&wX1=XuL$DmTP5lm+z{EbHD+228)A*5zF|Bl- zhaDmBjZczI+Kt-#X?v;{kGD3S6X$fO}KW= z+tF^3H)yBlhI=>X7`(shN;oI)ykD1HN>;=90JkDWBMMM&3s*+%{K#yawE>Gf}$FK7*@9Cfpx@%U#0YpnEHz9(8E;3G)>Pn}I-NGc(y?6_b zfI2I@z-m=@f&s~;Y8xRU5ri5K(ymAB>kPe89yo98Jj>Kz>xy`IHQX$yE|tZs*TFf$Bb2Btbm;iN3ZY7_CQE(+Ns%5Oa}iq#uAiTxr!-UhSEI z^&nU9PIGNx8~2j2OJs%ILfBxe6aK4Ba|L|tVO~&<)7djO5qm&-F&79sVL_JZzC37R z{PefRVIOfg?_=?;DT@d)kFhCsq`=%VxmV@DVq~-#;1(pQwBCuqImu@+g&wNprgc*JTKBWUkOa5YBMe) z7vcIu*00UVhP(Rc8*A_MeRGqWLRE%eh4%qH1dKu6N$w6eADmOgA$We6r+l|enP#u< zQkUDDL~G|}XXeEIu!@-rtykzSvm{^6-<4T(kFT-FyqR~P^QbI22EtQf3#4g$Z3H|W-A(o}@-0sDY7YInr@V_kc6yiWpVJ&5 zjCJu#Nq%f^CXOl*#~lJ~-4T+m0h`C9W}FthflLu@*vTZFkZVMweQiK*sE=2Er{&?~ z3+)mgIh7|G=3gLFu_w9t^M6{ zpF-a(u3f;4#OwHu$SI%)>FIEp>^yg8ZoYdK9Tj07kS?yW@>1~4EnRvc-Q`>1=c>-( z=6Fh6M`91dJh1QjOy^v*!S;Zur6Du7Ed9UYDt&pczfZY>l_K*`XM`C z;c`K#og1@XVtD7fRg*4!uB?uE2hT(DdcawnkM5EfCP1(0_Au4ZoN;;|$jWx%^bZ5F zkuD1;D`FB_24h>osn^cB1>vARzwZ(FaL-E4_nnZ3M;!IK?Ko)4cyY|>oGOi-#Hc@= zU@Bg-D6+Qz7suF{TbI?eGt>^Tnu1wi(R_<@0oR?q0E$+9DDY-%o~u@NO+vtgd&UER znz}RKBT-SZ+U_FR6YGHO;GF}$5!ex7FTFym2s-^P%qO;ZO82$5)d?gnLEZ}yE!3H9 z?FLcy1Gy#64|fZD7f?<7F>nXcdX7ifB*mqUlc<0Te#g-$!7K;hoMe@^MnMpkYk5-Y z7kAc%%k^1Kh=bGy^OFLC;Z_oCfs}?1C`^*FlB-BWz-58nbyQ%MqW(nE$(sc~pi66B zV7lsluwhP3V6MffS|jm80uo{i5xjvU)Fx3#C1vHFD>mTTdj~xxFf+pyC!xHt#Z_F| z^F*MVxM$3yqB-YMI1bXBcM3pVX-+&1q+kl=(b-EkfHP^%|84pCF>?;OKq675Jgk%YmeY(yTxDgQNFOt)^1Si_oKo2_ zGfAGMnG23H!&Yvqv$#n>UIgs%Gej&S-vS9nI0&Lw>IWD_W0k7{*eeNVw*Kz01cMv! z#iCiGS6AkVk7_s@)7jNGV&M~kq(9;>gu)d_sNE(-Kq(}y(PJUk4sDc4OfwJJILtse z5m+`Sn;w;Mko-tPPPp!ubA$CNz_)*-Y`KOVkPfq&i-=O-YbRtWSG-TJ(uUcJs*KDV za8Y|vItlgS#BFoN3^Tkr?jLNbTkfFKj~DjHVH_!~kEanWb+}g}$;pKvuY+X}vr4-o znp@!Z=x%rt*aKR(*<=nK+B)7cJ6-tOyvk6~JjM^S>404lkO^zcJ#O&An2g|!A&}dL zK@^Ex@M;hSHS%Z+)i>B`9PVr=_!FH*H<5Xfp-uRuUuDw^d0Ai;Ruwu-UTCbwZ_{wd zjl*A)q87XofQ5}}+8}ACwhxD$wF$Tkoi*D_pE8uAEp^Y)wDisr=*5zRTjuk|F386Y z_uRVk9g0ACesm;9K&?#Lv~6XBJ)IW^)tONf)t3yBs{j2;)7n~u;3ifAc9`&S>;)SG z;-g!;&A4dI%iz}H7)1NeX711pAkH=NL7nzFfqlu)+u6zA11G7oD832S(pg)`WDM*a zA@PYD-Z{@0B9DBo@))(9VK(r_+kjf;BFE~Ol4bmw4A~fW0bFtdFy=AnlLaPrG(02g z5bjb$dxHwoXvEQr0d9)K+?vZm-UT)sj;qY4bX_##8=`RW^#D5>>+aK+eJVSLw9VB!K)L9z9xnpvNBX* zC~7Ec%+or?!8zRxi3Hm_LKjx4gqyfFT5HmbaF^TZ9tmK6^t9Io zK}o-}8a=-S#M?<4_TbU7If*%ZDft5;j&h9hkH)6f52go+;95aXDTZ`WT)bF zV7}wtueG4Z0}b5q*>?s{WL@}a5;iD*rRCUHG*W6RZoQtQbv`ZtDTf_Jx0}k)eq#6f z(%@$N%Uk2{1``C|HGo4IEYkLXn+wZjCNvdx1DY^&<-4tC53MJ!fOJRi)CZ?LpmX?r zepNx&0eP%VrJe9j?%-)4`A#ir+G{9d{$|K_4GuwOFdVGsS|*z_A*CSm*nvzHk)zxt zfQ61Z1F>vfEcg4Ut%^o8`2Q4mmd$+-?s-a z4kqV?!Cztf>Ym6i<&kmMhETS2+o8mxa|1HmJBh)Ja&5fOJcu=k@TIOeHXDB0JG>Lsaq1#9MA)V4Iv|=iP1}sgDh}$_fi*YZ zf-5OY*R{lP<`n%-%*(FyKfw9{vQHqEpAFnGZ9|QsCT8u$?gLt5yJ)AG7Ta`hrYs_E z0`WVuA@~S6ckdvKtu6Ym*F~1_pqFNO{)ry zq60va``Xws>#p7a_XXN`_sJg#GbC3>V5)orIkcUirZC+2#siUk3FCN8UETa#gcjb` zuMUVNnSJ}I*>Smk|4_AYR%_tjSp**#Z=chNN8K!Fet1pch(IB>E#!<%Rj@(m6t>W; zO6)ReL#jvaKkU|eGyv7MGk7orGoUl;V)8lkN_LNyyzz-=K=%!W*45B!s0V;2&@^6k zgJ5YgwoO9^>PS5+iU`(DzC|1puv4^`B%QFA06JN7p%HH?-(a+SXlrf)O$uddze-!Z zHUQE{cVIK)1JxS#W)P;=d0-)jVfK(atq9k~3Oul`MEl+JXfHuC4&C8Y=>bs_<}lJj zyaMZ4HBwuFJJDKlPYg(bO$96}h@d^{UL4fYJ`vcfFQm~oG}hsChar1ANg69k=S^0x zeVM{_)-T%5a~Vov{#&O;k($}q zTVQ_XpLT<{ta*Vu2OpgI!24#5WnKWs09lg9BIJl6~?o7cNBZ;026RiT~(=$|dB3$UxQ`Zc2nn*;ho3U&=P2REO@S zTP@7V*7Plgv(nChpRJnMDDeg53fB$uC-QLL9{osOPF#ciZBo4{+FGI<+Z*mIQZrdB zZy#ZQ)(deAaXEZa@}(;by}WskG9R8My>gDEPRO1}Xw}|euI7a0Q{_n`MZY&WLzP`n zhAe>$uCQ@jpUa>a9#_!DaIBB*>7vQc@!$QxWr*<+#WA~TuIA8y6P9rp^>rxZy{ag8 zGG$Lq{jvGIrEbAqF5nPf)A!0#wqAR;oLvju|H^HEpMRd^LBozjzB8nX)%eY?i_@(C7eh6hVidMtrg0>wwZ|6l0Ddtkrv{MKp zX+a1~@tv!kz4;~aulMsvrx%&fT+guX(4Tl%79 zj3IEDqI-yO+F;T#;&z`{x+?s_teK7kH~I-sGXTR_b>1nPm2LayvwJkbcsh7+U~*fz=!DIdWYOj7fBwiA&J~cwW9Q&UGdZ z+zsa^UG{9E8SqWR^~a3GzTksVzMziprOQfzJ$Y5-?jxk#hH?#0MK_hOxJReH^~E>w z1?u36dc_r5YMbC@79+7b*%y#NtV3(EWm}&Pp2;SY>ZjxpP`N$#=w>@g?ALQNkNgU0 zg1c*VXTFs1JKGL$MGAwmr&+aP-<8Jt!efv=R6KkVkFRKw@bboojCMK|zk^KQCDi}Mo1kp*jezCJi{ktCFNqYXsvIJ$vp% zFxLqxUiB^j>oL!AU*!1gj9RQMmU|ZLxa|(?Ua$Fna7!NzN;1MOA7BR&>A#+?;sScyGHsj3UO-Cc#`3$G~oWNF#5S z?LKp25@RbUO|Xslh1kv)1URwoLznw~{Ni!1yli=fElqa_B8}|drZvY{6k429F7e$w z!{@=*2AKp@7*}sh9)pqn5m)WD*92iXIH>Oh44zZj``#jnl6hUaew#&ipQ{eB_U8PC z_@UdQxKL4TcnI0aF&KwB);>D{j@9ZP*oX2+9*#Mh=HeZN#WB#hx+$GXI#`jbK*G$; z(3;DA-v$UFC7Bp;VB*zjjhtgcn$fmdc^bM&(_`fMP1L&sJE zbo~OH1f=yw>~cwg<(^uivb}njYaL$Ko!b{g3>3Ja>SeDmXdv0g+^)N$@7iI)u>|ApC2 zo;X8W36BCfrN7r|Dn^~gjWjz|C&_;4dDnPSG9|G5C<9Sz511oSvx2&hY%9?6~Z+pIy|pfX(c6J0Ld0cfPPo`#D=0i z*EDb!=NJCWV<*#D@FyGI)C24ibavKC0zn7?V2>7>F+E|&Epl2>X$OI%3uEv$vYcW& zf`e^3xEqW2fRbWM(LUgh8%EJ2>_c)!piKxq3LQa98ip}VNRu(5TH&R4L~_G`q=7EQ z*s$s;&YQaj>j$Aeu#@L|+-d_6bE2PQ4v^Ut-xfM7dme%@X()3WWmt_M$4owk6_VL7 z!)%V^YiHNp3o@0cfNY<6PJUzN2pNBe%khq74AKb+iG3h`F%<}ZIx4Yp00yIE5!UtP z%q(R~;Z@;@zT0{oM{}|W%4l=684_a6zXCah-~fb1LYQ6~MJJ{q+yRhKtiHCEl|;gz zWMMXGu%W;<0KL4saV0`pm3@!3bP*cFU?hN#IEdY}!YDxow&_I#OpaK+NElXF>$g-9 z*2gErS%M94PbT#8jz`{Ohjg}QQ)wI0=)9xkNO=C_l=e@f-mW3=EK2>S&X3@dfRA!$ zM>|2t$LL3=gqQ-->w2?@g_0}IuBF^y8USuY`3;^Ba2r2LBl6rd5G`uTUPD-e>%boz zu@bFE;1R|^I{6?_B-hmmAz0<27**Zq{-Tj&W0-&7iF9N7jT$+^tpwLmk8%%$GF_Q; z4T(I^Q0?MI6Lcf)9wNinLBJc8FvLvK5#AMO7_3F-k33fFxBI72xgABYG0KDcTFBHz z0FEOuLRwwC$FU0?2)Ui)=QRM`Y)N4(1s(a`!9gpHM}PBRrgi>CjWnoV;6p?as<#Zq zT{vq|MYpsW>j95gb#ivoWRKNqn_XhZGR|ReKMrN*q52(95i-CcK5Qf*Ki|7OBAw0JyLc1s3yrnrTDUP=1lS*w93*!OsAX5^#^82P2zrfkGdt zbk{>7l9vJQl;fNlsy0zF&n_{G!_p?3h>h9oBiF|V=4cz(;HtZeozpHIe$q&5+hqtO zwFN7)nC11us(vJ^9pMu(Ikz)Vy>ZEvh0;^934*L;N3vbiT)}s|%|Bpt4f9E*vHR9e zXoV&S;?HSpj<5#L1NTLIBrM|NVuXO$1dRA7x7eVaW9@_;S~K7s1W*5X$=mZ&rE?vL zeS{U~{RmJzRwR%R&xM=GCnyy2=@@bmd%`ymMHj=`7uHYR z{Qz90pyvQI9oz9c8vKNd{|Odi^1TRDg4h;L#rrTiwwctX?Q4yUby9r$)7RdlTT$xb zzHoQCy2ICj4ajojLx7m1t1yULS;5QOmks;rGx!Eg{kiB zrNpn6Ksc-mUctO+*$8fgLz=nzR;`G2-D;0CZW;4s(IOkb1IOt22AVtgTxa?b(!Kj@ zTxxH1@OZ4StO`N^W>{A(MDLtiU*zKBHC?^pC`O*tXw z;)Dv=7_Z{e$^)72sI54(vR~nGF~+##b={C}fk100N*8#yI~bd9U30?$es70`s?15e zb)F{u7RcFLTY+PAv>K5?PrD@^*|6wOMfXP{X`p+Z%7`;~d1tRptQp*(-Cf#lp=&$| zj`!q5pUZ`Cak1y1BT?({x8!#rk1Oc;2jw-84-%xZrYme5gzy)rCSqf;ab&yQcp*nO z4S3;`9F8-W>Z;1qb-J}MIubv^ydt|AV5c|c4nq)+{rQ)bH~b5yN7*}U{M0#bNFl}b zU%bVWBpb_|uuHNXWi%%=(NppE2^{Dw1LEX2$ukp&}HooHeF~S?@*yU zw459P)E~MBJ;o~pddm;X_R*n#O7zImK7t6qp=r%Q5^V|Ur9v%$Fw6zqcbgyOO*jjo z17)oF8dMuON1f~iV$615(8e_80m!&FL(GP60#=KayF(fU4p|&k z4WdZ2{$sslO2{9Fwxk#p{GFRg9G1cu?=kF@#JqUoS+sG%ylcM@xk;}?ys}=P7J(Cg z^Q?CiMUi{4o2Yo~h3yJsU4Kj5Sh+btHMkF~Hh(1UgJv_7&$rlN5`pSEiB!~Lbt#Uz zjk)Ghl;ll>-KN@+2f6QKZS?sYz?|LQ$J^Ntce`8%dIsPRozm}AP)EH*B8qetH75?$ znu^_H1j+tu6QPpQhT^6`cJvN|ht9R(FXB>zZc zjw3t?eN9@^%g}0B9PX27!nDW01ZgEb3i*7xNmy=}r5?*$Btlqw4Nov8S&LS}tqIYZ zI#;(S8ZyZ#js*RgHDmsi=Af2l?@S-j>v5-2Z;Iansi>jmA@CvAN{3MLiEOXs1$BgU z2OSyqRysAwv^|)n(K36-s87{+z{Tp!y{tUKnm#r_Z?OiV3)T(UJ&0ZG=QiDCCz>*C zLPsxMC2gdHK%J)U)T&hPP_x&n*4g!7)yO&nn~>bd4#L){$F)n_If(|`k#05S%y~?0 zLkH4)s#Ys8nXA@5TSdyL>mIhkvLO4)H&;Gu|COK0Hf}aMP1(UaOYxHnzymY;$_~~> zbUcg`*Ey87#8T`ITLi*=dk9;QFN!u;9}VxKThPp-%YM80A>p3kdy7f7fN{Ad;&{ng zi;f5IogKBLC8BS^;kzD;BoNdwn)rfxy-7lzda-UyPo6O!K?V8}7ie zA5Uj}P7Y+c60{r-cEJs`h_Sc{GFHVRu5`%^6jHp@p#)b&JF(;uM1CMk3+{l1(6Sh&M%H ztnp?UJali6GZ~OH z3fl2Zp+`oY#azK@ih6ARUo_S><{-IEc93%7_OA_Ew$hfnAyd|~d=ti{}c`)GDzK6-PSp_tPOO_&3m>6SUn63#as z(B%N>aE}bx5H0hBDt1G&g-wR5&`stX!Axk+uITp)%>~!tg+tRB_TD!-#ifO=C|b+( zaEv4U+0z4DPrsk$UTvCz*qIAXqo5Pi($kq<6j;slTin2vM>>88)t5ot19Pl8R>ys~ zzpdj=pBle**a?bk(8bqLY(rv~*R4+TkqP(u#j!N8ZImN&^3b+O%prO}JIEPnx|7uu zA?XGkyYShm;|Tjf2?Yv)AB^cB{NOE8ccpmOT>xHugyMG1uTXMrtBN_AS1pE40H5(J z&WL*30C|cJ5c!kX2b89tBiKF`B>41=+p!6+bYp-|AmZRgpc`lmvL#wvitM*HI)^GN zXnTyfVt2_Ke$3#d;-#o3bEEJCGAh3w-=#Y<;G}mlEQ%NG%G99{HgazmYYQYhQ@Jng0m@c;9m1-8Hpa-%& zvT2NndtR3pWbbYmCunSU;k{A~z}Ioj(0mSgwBRa&#s^p=XCIO{)~sL$%sf6NlwVKL zy#ODUj5^7K!{Fe45oQaLp&xx;P*MHq;PyZp8uZlq@HH{C#DYL4E|#!*;nN^t`c5km zRBBaMe8^{}^}=GHvYEt)L0j?AFH{v){n|yRRk@#Sr|{7Q;j))$8bbKBY!Jm^iar#U z-bDp`FF;Iq)Y?mvF|;-nBMC}#Z-GlxJ&0%ULa8CnH*lHs0kAV^QK;K@Eta0Q5ct7b z0J8*sKwpCpWkRf~;|y_HY+NOjw7vpfvl3+^@hVum;4aYBV(q@EwglKV;D6b^kWRkj z3Ydw2Ze>kGeNp%0E2@!r)-ui%nsPo3bFp$l$M_~$lcOJa8(?psuUxdYQQC)gY^a$o z%T{fQsZXzLBLJ)?Se@8_EO#K=3<;7I#hF)|V;W!o!h)B0Af2MKQLaNeJ=)fHSI2ya3ER()^8*~_<_>^nJCEhi!knTd2 z{D2Z}E5jE{lf#*qmk88|9BP+HhkW0)t9P0poPzvU&oUzE>;!fz)P)@Sx}vn)XJ-uxQ6U_J-S=)$BMCpG3-Jl1zxZ1xskn0) zJW!=XkN{#@7==O$jWKR|iAIt@^cWkNF56yp- z<_HLns$%RriTNjL3P zQSeGavMLAi6^Sdtq|9kJy4{M@RZNoYrTSfBTJbLV>vpQGwWL2hLJbhLqMh!8V?85y zPx8U$7J93UwC{=o!~(nxA6d9v!RwTWdy4VAOsX$~d5~x3+6Y=P-hw6yn$7R(!yr9H z@G!|MeP-xbMVMYMZi#J4&mn!@>S}W(mOZa^o95y7hn@2o!`?in$-y)`%tZGupBu9f zykwGM+3XNT6;S!kC@6M>92wbub3rz=%&S5nmp8L~naWR9yA{pn;#>c`~ z$slG@j&K!`N);*ivk`{aiEj(Z7HVg~?hi*LVuM;IlStlfvM4Z3RCQd}opNAH>y@YnHdwPKGY(pc>T_L1=44Y905`4J@^if%Ot>I(HKrLO5A| z4|3D-_RS`^J0@VF){h|LAQGCEyrZE6I~j+hiUGENizgg2(G(s(NZzP3V3WFK*j#uU zk^2P^$z}X*G&R+I0()pJ-Hq~Ow4uPELpD2}L7~N`ZHB_2M zFlu0s_w4W>?br!mqSKhTDv!n$7S0~Z)xhBPVhX$!-}j@6F4Z?a$+y6$OGo!>gB0vJo63MVBm8X+OqnJY)sp*BTp$Y>$Yo~=V>@J?aPl%s0^S372t`;Rsw z+lsScZhy~-)c34>fs_fnTzw0Sis9A0!W#v~tiYPSs7ULe61mB8vGB`vXp%roDvW9e zVb9@^h|_pG5@Fd($fcwl0Y%j9U_=Hf@7p&DhmwKwzOcm-gNtOnM<<1A^QuV(iS$A< zo2X5OLjzZWgA$R>d*);*9lswt60kL3hQA(fH1HP$f1gg_P(DK0ig;$oV(zyXPTW$M zUlX>YaI)N?@DxNw;HC3Wsk}LTib1+h-{PW91#op5gWP~>Bn|8 z@Yo{p>vp0xpfjg;VDC681Wdb?SZxXC;B_+`#VYxxor=dV`j+lq<=d|1wxN0B-Zbdr zlO_-!siFyCY344&4Rp0d5EWj;%(GBZK66*5cu6mNqd4xcpK$X{+Wc7# zMqY9QtwdU3pVtO-p5kxq9Hg_ckS@iom z%9A|?2|P=7Sp!)Y&`EmJXp139$xYuct}xUy<1o&M_&v^>@G1d0>!V*R{)V3cxDa6l zz#{n{hFZa$Ngir5b^#qMY&SO?DT+G{ZiH0>t|pH|lazzeo#3(QcXlo~Yxlc{`7FXt4W|_zkTF^pt}@ z;}AR>_N1xuBPt8cMs5K*Q)Mr*^2AphDmo|(Rl@}N-vE4hZ=(})m_P+<2DT_V=SgUA^1fv zkmbqKAbPllC|tYL@OF^av17naB3)e~A(&PZ;0ijFTSsWyYy4u{`}`*P+B)aYOc8Do z_r=d7c$UTu9W>)`&pG#4+Q`lueSq3=$pD}XwHp^+KExW`_J`QXeqfbsWYG?B?jT9w zUG@ig35S0jvp8YoRXoY<#&ngPJo%j($5Iyi(E60?Do}?_z-=$D)AqEI53k&NX!;`k z{r}#NR^SsJ%_hBaz|TH*uO`4#1ka&{z*IROUDtF4v1hUc-Ch7i+i}+m_#JMav=ZSA z90j1)VLT4xQA-iQd=2zyy;aZfplH9X<(M$Q$JmTrU~;zh(_0?B&fSCcv-`aps8R1Z z;2r|208+_sK;Gb4%)df!q_L=%czV`Y)*hJV*q>eG<=3v@4ge?Wc6wLHB6(xsH|wJ~ ziuee@M7h#@M`TYgLC+H{LkF(cc=phmYz$3wG>kS2FA!*%cMCWyXePJ;Emyi7?_rCk zgUGeuq11GAnaLGuEOqgyrH?Rb+RnbXbp)nmNnjgLDTZ^f->HN}&1q&AiK z0Uba-Nf@!-Lx~Sp`I$+jkOj0QdHlp-?ii;-aO9f;SQM{-_?n!xt0#}KWx%B|&)|lk z?W$kN17=h04Jxm&n)h#q8t(?@8$F4B9?U~eTL#N!wy-FPdS(@Kn~+!YU>ELk(;SCa z@Dw`B4+%+YoHJ-Pg5aW=oXWh#&QE$o)M3upr(RHonKbN1Aqz7W9e6Q^dB{s3GQ#;N z-e@^L`ye9Vv_VU1?u42^*M%V9W;`$ONu0l(N1rn9MCTtdJN(kQ2wjvyw6hU!Rxy9Z z60hPYgJyy_H%W+A+dRBfOq^62cciJH;@4U9oe!g%4BZDc78gS^jm?;6(Oh;%c11KE%z2N3 z-Z4K4v`Yg~1c9%T{_2b(gj6ROkwIMro#=yO-309e>a87<7Bc3-os`D8JA$9ni{Z&Q zkMx0h(_Br>fsqJ(in`F#ZjW5AkyxYcX&*kR=)|;KR8?9EkSJouYx+PE+JM_ zQ&F$kr`GAUVJ zx!1P$?%ir|k3L>$kGPja0c2&o3828kosj>}&mZUbOEnc5iXqKxFm8t9dNq_lz#YnK zn^xcffP$h&zHpL~A27ddrEetmzMCyN-$CUYARWIfJ1Na8HT^2NSahe$Xh1pWXynVOKESgv;aOy!Ek@mwL|hTZ zISV!xh3$cK$rIokljTb>_|rodrxBQo3%RzCG9Zwa90l0{m7+bz*ar;}aKZRnEf3a- zVbE)aIKpeeDaF3949Xx!3}H;u29i0zSYB9Jo0)Y0rQcoUJ5^r3CHFw%)jsT*ou4y&vV$k zx9)7&&g({En2-rTM6eaB@8Jip1GdS>0^K;Q z`LerB(q{ThphY}{@z4V+*Xb$3#8rn08WoY&&mDJgDPp&f;9Uv0?W!l7@HJ$MqKNIr zWbKhbwvcWLm|<-jqNwLnmVu{N2Mbv5iPBRk)-fJTZ>(_&Aqe)ZFp4W1!SolWtwv#L z*FzR-(T;n5S3<&Ur4nZ>MP!|LMs5;fw@^?B?Yr{y7}CxhQy4m%n})BjCfoynZ?usG zzuvbt1ffh93lETpx&2a9Ca!5^OLP4beVrgji9I!o`37B0VS z<5s?m+`T!b7l*uB&vv;mM*3ZGX^0L0q}dX&s0(h%<^&;v?kZ!6sHE}ZA}t~68*v@n zinS2%X?K(T`Q@Qgw(;JwBMJAp+a|35*W*HIA&+})iwN0b)=~~oG-!q+ThZDwCm55( zRWK(B>+nt~?*L=#M#?+BOdhk8fp`^^@|c5kKVNLkH@KlhKG}nLvnXfmRDQ8#8CF0B zld}UW7(4kYeQU&x_8o6lcqy`|iv_zqaB%~_W-;=%o!-$@yEX$kjLm?!(;wK!L7k4! z5XtG!ZSlpF77sx(#TRIyC#DZu2F0q+QoK*c5FL`ZVUrX>ywOhBW^x@h3E=m?G@tNX z1tA$Ji~Yu;=TsUnFZubEV9+S@0-J|I5uclQ^hBakC9by79AXjO#>SLS*7R_{022A+ z<5Y!qNOklzM#w26Kc30TuLXcEG99o1z>|Sm;?WS6mPT>s4zYyzfVNymnCI1u+jKwy zKHYa};TF<5zJ*b6ig0dYlJQGKJ*|m=jlf+d)Dq7O80UeDs{t>F$^&gfO*p?$le!KH zw9zx64QU93Wan78G+XOh(PQ8Gw|vs8K|uv3VP#!gX4Cb z5RFbBy2WC}`j+b`SnZQUl*6-(G>hZZ%u);Opd{vjgiaPJ3<*#rd^lJ#J>tOVGYSKu z%dcR_@f+yLe*rb_ACtlR>owwFKN@rIeMY_eXQIyi3#EJSl=ZM((N3NV-qY?mpgXPFK6{fMjzZVfUL~ z?8_ob((N^FrqNB3aowA9KN@QFeTG{7$3m_C^PpbeGu3Xp<2pVsY}?=WH=r6mCnTrsie7Obq;;FW ztm8`rRkM5i(Di0{!pW>NM$gyikD4q{BQsN*P%b!CZke$3_#Gf6{scewKL7_^uVH@M zX_(=DGtBTm80PrjhqC<6S+3h7&G-2sTJRmXE>zFmisiNZ(Mz^lTDyUrcGkiUYj}*P zUT~8up3pRH{D^%D(b?KeQd4y~MFZ62r_j{+A0!?A1V8pqAjkY=G;lxN#+Y8OA-4O` z5c}O{gztA$FreQjSkO)x&d(Lchwnl2qS~H@EW6v4-m`r3`t=jF!{R}#=^>i%l6!;! zrDn;4Xe_gcPuC?AoGVHz8>Aq=1*gRiA&KxS0IGiiI^?;HFJQgKH?UsAi?`G068e?k z8rns>hE~rK@P`3zPw7cgVx4jgd&xnd6e8FvlrA zTa8?7tQLO;slzWpN$@WSg8u?M;4d?y{qY>xL^%#Dqa4Q-a(=@~Ie)>2DL>`YjDPlZ zzALYwY{u*;nvm;(eE){<#>pkF>9mSF@H<8u)=$S69$ttRoFS5^w8SS#V~SURx)eVS z7UG|oD*O|c06&8v_b<@n{W3Gz|JHGRgy-m+gy-P9gy*Po!g1)0!b#|Lc$4aRXlL$$ zP)G8io)fPn#CUCrZvETh+9uDqBdd43ZR0`A^!$u?%?UQCVk4x&RA#tk=*#fyXc_(u zRD++wlHX@Q#Qp_-xL;;N_VZP?cT_doJM=!=JMucPdEBA0SJ;(Uf3Q2D_Pk4huFPXm zZmhxx7g}d<|Fk)*@vo1(GkHi{SA8U{3U2~dXP0v27MR5eZSWU~FKN<`CK3##ort1DJG{t{|Bl9;9 zbp0|Lt{-m{{p0S#8A#m-GY~ozAUwm$$Mv|%#poT*s|m-WP0n*B?tdfYxSQ*=J$WP{JxD9{a<);e+Nk8Zy?C}p&Y~x zL|F(O39^tm72_dtGQ>gPe296p9TBf+TM`E0#^ehM*QARn=VS{>2L+3GCxuT?j|!f= z@5&rnyQQt`{bkki@1k3Osy6snGMoRPulc{hivJ5x>+c{5{0#{8eHjkcFCIciB1}YX z1(-;j4Db**9$+ACMZh-Ln0!yLHSwCdHR*!BIoV>yLCHqWMX@T@O{oszRl%$8VVPrF zx3q<~-^>dBUU%nT^~U~8Xzwos2LCu&?TFTsOS;eS-HFCVUc_DZDAv0 zzgd?1z4FH2Dh~ab)YgAUjQw%8)E~oC`9EOV{sKbPFGIukxklh%Y*XKIY>Tu7;R;zh zvhAVPWZOdRiFU*qlx!(m6zpePlxyajl)gzgDpf|gDtU!ERZxOSQ=?jt|`)_ zSW(raSUapu;rnExQq63uvPb!5CC?G=iXEaHmN`6~7P$Idmo@g@FAMj5FKhN+-Fd&0 zoA?u%k-v`D_-DBC{s&Kdzrc{*m#NwN@-Gww|3X0OKlD@nML*zghP_g89CeD7X*ir1r^{ z$UfSU*LNGj`gT22zt5)We=!vOw1b>4Cs5%F!XSvE*aMhh*~^d+?PW+!RBnk%ljIVG zrpQI=jIhg;+25xsn|~`Z_Qx7Ce=RZa=K|AyuP?q2)`j=Sw&cDU7TaH=GW&2)U>{Fu z>tBbgepsjJt2GS$`2(Ae9}wX)z7XJn*#}@T+6MqZqL=Zs5juscu5(Hi-6R#OImRki za)ni};{L2;!O5>|xvjBuwUM)Yt#!4CrCqs{o#k~wBb)2e2FBJ!j_Zs|o|PDv>{AyP zu@TjlHP4ckx(1*u`34-aI0kT#C9@}BqqG-rqC_vCbVjdW@}FRGWevdutM9W(R$pZk zEj~skTUA0QTzY;_yzt&p!0ygc#^}CM%;d0A)ZX-@xUJDip*w4nq8ElHMXNO?Wh}KN zg?%(7#lE8?r4Yl9lt%>{q-@GCh~l9y5aUEoV8o1<2A%&ULgMcNA;RVDab(Lo!pN34 z^axki@<cD1$^gJEnRE(KFss#Q1NbWB^bfdRA5Gh zy&(O`qEX_4lMWdMl4<~10AxZ*gC5c=4Sq7ef*{9lKqv4A*uehz7K?d=Eg10#TSV3s zqE6Bkc1qR}LQ3Kd7EbO3`cYo*Z%gj{j45t|H`8XVXS~ZN3Rt}>MTCnxofK0e;W;&y z0z{D3ixn84Xy|~zw4#WFA`nj`2z_#aFjV;&5HWrPJog`9!}=Gp$X^Xv5LypcAXX7a zDC!}8Chi?dCiWE`Dq;u!7`uRR$8kLknSP5W;b`vV{cGJU?8^gnl#}a_Nyc_cN>x^Z z#1Pkt7#gfv0J-oq0?7p=(kT{j@gwM|{{S4+-*ClW_izOu7g2?vHqpc& zRxxxin@BmhJxt7h2?Ytsg7d?)-#YOfCpX&F-pIl5wQ}*Qr%6di2kL3oR{Fup3^fH& z*NGc6S|*6lM15egVF~m~h9t~S0SWRyC_4NJeCFfB6?Ac;3VT>F#r?ZT>IYW5^b;d8 z-pz%BfNH!Q55K$fcDeaba1CBW6lWnJvDM1VIy=tMvo}!@S7#qw6oHNCv9ooWnlnZE zq{fL;%gj>cw~*xcAs7{Y1wZj0fJ6Epu7INvRlw1SDdJ~E6ms*T2l;t1Q=U$Al(P*B z>G8q4pZyOGC^%0K8nVTPjpOZNWwtNU)9wz_6b0-uK?)%18_k9!xii{q6&69F$KG=n4;ZY1i^MMabQ=sXJsOY+)=h-iD5)Ejms2&`|w+;X5a7MbDF4;E! zVUoXjP;EBR^llH*gxVY?3@x_L9#CbVsY+g)PKK~Duk=h&ejBLApP@Ag{0oNEg)GE^ zhAhPR!xiB?q6%;!B8o2}Vu!EdVdl575HbvjKO|TF1)ekM^z~<(9q&*s+fTP${E0kk zB8;`WNFHHsl|-=6DwjlsQBFzXlI#+61^IKP82=2^;h&In{|koDKL7{wCv5R8ENt;L zG-TmTG+fbTJY2DjJp8boEbQD`<|n(Ef{WkIdwv``_dA!i;p`A)wA=(2{O2MMOL)UA zZ!t(2+~X0hG{`4ZW06;ux*q=!R^!*9Qv4gB3jYKp`)5FO{sCype=+ZZ%ShN_3rW~Q zOG(%Qn@K3*+DRyS{T#f^iUvMzOZ^VLAmo8!5$yRHMS0zJ!5!0U>}>;m$mtapNs~)l z^0me|#j31POVbzQFVb54JXVOGLsb4#SfXnq@h`HHgDn!m@g(ZF? zF2vt+W%y~N1V05P`Daj6{sCynKgRVk5XLoA5JsP8APhdzz!=rjz!!NZ07K-Z_zTrz zVaKe#m_u4+yb-fE)`49f<@q>>H;qoce&0&&to0{ucY#&P;PP9x(h9%SnBhn2GWNWCC&~4nkpwpn^K|etUWbUCZNWAk-$h$I+2)nVG ziY~k!A^&Zac;VkB*fTkb9#?Het_t6hW@rCjZh*fEP4F*?;XhDU;J2}=e+y6Rzo1C0 z2ZC-RFU0%?-U&I5I~Q^sbvWcO=7hvY%n5;as2k$uv?Ic<#4Dn9+%s}M>_ajR#47R5 zxlXX^a1=eT*@~Q%y+sXA{=wq>zSY{_XCedqL0i<+MMht19Z6`TDxnfZO8ulm2if`1E9X7Eqp-eJe0&7*EcJI7p)_Kdh9 z?GtfD*dXMJtVQQi)tq%k(3W>c%#e6U#)^ANzpyvDQUaWi2$yo1@%q;&Pdu+ z&Imem?#OwQ4oTUvF3A}3PDyw%Z;7|C+GGpZ2gNGSPSI0$t;~JHTGp)QFLt;7S#a;~ zlm`DpV(*WmrT!PD!heC$_Xnu){-C3g_Az%Oy(11ty2e{-dIg*j^a!{kXAf~l%%O5f z%A0dZ$d+|V#F2JOz>IiIx`umBw1Iq3tn}-Yy!F<~T==_X?MnV*dFh`O*Zxmx?oTw< z{y1Cce}U@z7Z`befST@ia{r~K^e*uAgu^z2|mNWI+YL-4-2BOa=!RLP!)O@vo2!9S!z{+3`W5cnB zF|q0Qp}6KhYRmnlwA6np%lxUb#NP_b`>(FFAIr-6wW{|17S-MFnwtAyQEp%CDeapv zjeRwwt`9e4_3wP1zSSn_hcW#8wF3^HKET1x!vp~0*vq)!>}5_rs10tZN(xm(q`ZC}6V}HT zNqsR7(qGT?{Q3h8pK!o}|M(&RM`s`4#D^Y$hZc8=QnKs}rD)X&PT8WfQ{kG6Q|Xdp zSNV!NTLFU;UlDurVj*kGW-(ji(}I?Ew`J`tuFKol+Nb*}`+HemKP!dx$4XG2{ZsVq z8+3l50}CHAKmsS7{eT#!{eTfHdIXW;^ly;5||5=pL56;*>VWjxRFSh_>hQLSg;5gxSm?(eH%IBrjMj; z#Yxt%TqSOOeN5!`?w;7)!AYscsZrUkkx?Owbx~m|7i~~wrCGAwq=j8wWANRwV@BQG~kc3^Bjm;c`bx(OeXRM z=BCPZ`Ht9O>yqqM-;`|Y)Rd6h#+JCpxR(4~Q87VGVroK}GxP*h!cCJ910)P_0f2#v zR00=ByA-&o`~+(9--Zpcc?uk4^idpTb4(m%^F|zJa>5*GaK9aEZ8r}$b-9P!x$EOj z+~0Q0=UTV%HZC%cra%qCZios2Gljut=c8Lq~%f6iguGjQ$5cmY)I8 z;wNzP_Y+l&x*@7CWjjPgT|4Xy!FCuK!p+byL|oo4M4g};Rqt*dVV!?TblX`I<#IKJ zduzwyo(Bg5aZRlT8nPJ94#9_ZlY2o!BGN1#pdYqZ#c627LFP}gd)LD;HLfywUZ(av6QHceU+h% zeU+4q*U7|2ZDgRMw{Z?hMqEp(2g#LkKW`?W8|!&U-W!d))|qm~y^SypLramu2@D1f zpRN`~ex^h;>1iUxlB2ZwX_6*ChNQ(G!RU`tL?E~lRq$&Wf{=STTDY~eB2Yp8yX1Q=;0-HGBj))^DZ16$3{ginx`M3jG{@Ch2qIEmrA(TzK&>22X?`Fm%8%pZ_%%2kehEl) zN>n$zGK3p`8R8AcJblA4J>T-n$#g;DnA!3URFsS&HM&?it?omi5nFn>^#Y2s9uiDmu_I#98n%gn96i)J$lJ zX+ETML@P!bsvj8v-IRxOxu)Ry9y%uvnx?ynCA!wg7k7D?JKEkliIkynF6k1}q*BFo z*@fw9@)J!x{v9dBzX59SOGt`;0yyzMDWW_`DWXhRiApxCL=`7mnuZxGImM5Yl4D88 zM6)F$UcG5(cZUkP=N{%~KoMzh;fgM{k%pcgC6G9{OCxA$mrlUOEU#R3QGTba$X`_D z?44CtT+x&EafikwxVyVMjRx1??(QVGHSX^24#C~sg9g_C!9p77!0=!1%m;Yqde-WT z)90ePtIj@Kp4$6YV806N_ecgvW)f~D38h#b3FYTJTrB4@+`EV}w1fIQ$fL2MP9Q%y z@mN7@#EJTdc)MK@gw?#Ep;9gOj(yyxOJvVmm0 zquhiyKM{_?yeJ=vb>z@VRSZ;)fbRy=B|X9$k?-wuvBZ z2Jp9>5L>p`_z2M6sHo2neLDJ$Z2{3ViWOHty?Zsq3e2f~%`AF+gLC~E6uW6F)w5|! zMZD#Eco}jR?ge)geHk;R-j5H;(IJdX*83C&+93%7y8?v%p_TPH6L3bIDRlo3r~m2~ zrgeIV)?QXm`PSGnstO+oPVYo}Pt=$7byZ#`69)`#vnW<<6b#;U9mDn!w*>e64YH3)S2-hW^IU4jEvghA5dn14i+{ z4mHOnp&G9^lXmZT-^kJQZ$S)}>MH7!QtWb+C?X``%^Gn@i2>=UMT1sFrBeWW2-+GIhgUqvCG8pY0|3@BW`>LP8k z8;(NQsG$p2l$(rvaC^mG@c4UP=>uRPdUl}$)hxG&ew#sa>90y^20<`_zu1waU1TZZ zdI>@~ZrqUyI=8WW{l8Nu;U-QL+88GapBe*RE&m|yE}_U2MC0AhyMXH6d#kOx2$A)i ztV7`&OOK5XbbsDo8ciPGh{s<47S7%sSS0wjZCW1$SKj_%Hkk9q7OJD^2wHDAVPY?; zhb*YnSM(;|PwXnoU;Hvvo5EwFpW=43Hi^&dLrG6^C=)@Xe{PskX)^ar>vepc-Y)1> zBFg{g(Ej|blKYhaCN(Ix?lG~b>Moo#Z@6^#5caOdUk%9KM(#nfgO?OiLCn&zA%(MRf*6b zFjZ<;OQr%P(^+Wx>1j$t#Z8Hq_f3nH+?%2G!JhW%KNHI>0H60iGxM)OEG)k>zb*Z1 zZTLP=QU0{oFe#R=^<&SX`(?YNLiH!sixWEE`;Ek~IGSlNvK2c;q%FAw9H^%!jZL^4 zx3T+gTBFbtE{70LdQ+D>9%q+pdb2=dI+wsTUMoK{ep^Q$i-Uu=!Orr2=V*HUx1Oe! zh3|jL;NUDt)k#@25#XUBGcfY);r$qQ;( zLmi$weFOI9!#Yv3^Hv)-D{m6|$;8?Uy4BUOvWRH>$wrxXG$EgZ|a*-~SgAU##4zJfkOK~_!9#WyX)dN*wQ3o(&`iLhEqiMkAq zGCr`Nbk-#d?wON==p*>g@4nZM5>sc4In`$hApL9Q(KcrUYkSU<9Fu8Yo6Tg%l-y&? zI&5?KvoEg<^=5;NHTh1=G9NDyL(915js!|Arwj;1Hnpq*pcj)N_MVt>GNA7cAky0mFqeDbx|ie?YGfL12Jn!0`jf`U-?@)qZY@_3QJ%P`&rDsiexW7kNVFaA?2xGkcHU>XlQ zKB*`q!JI5qmKbZI=y}wma*vAmVV9v$1_;9z!;Jo5I0x*;(|X9$-GWx4A0GXZ8$)Dh z#};num#k*)WhF$bi1NrRY86OHKUD}@PN?Xw8=LS}4g|u_mF9pS7-+;u?FBZT;8@aZ z#sTjPYq8<+$3QtL7b`2$o@dZc9QfpV*k+iUxo`|F9vs=hehH4HVX8hZ-nHlKBDVKM zoZ}xLMF>$ITu><45V@jaP;AQjZfVIApIxO%fYvgAECvkxi;D(XDFd7`Zbrt$TaU?0 zag1jrwp+%ehiW1_0AOFqF)8qKt$t{mm7I# zXMQ@BAS`M;OvhT+$u!f0u!{Oy3FE|ov=^9L06x^)c28Pi`|pza(_WRWu=<@BwME6hh;0Os8Wv7N#ek=NLhC!d^DmN>?~0!Ny1Ev_KiNnS;=(~35K z{$c}xgqH`W;3b4ey{a{xtJgnI>}6j_^Lkkzzb)R*nDW;!#6Uk?d>ZARSerGFtaf-o z!;{iBs@064T2MW$ZHI76KLIYEN7I|Fgp>E-YJOVy>nfJ>yJ^;V<}D6&hn@?yNAv?z zgyj)O@^Q79oF8@1tof7Gb5|SP0Pgycc((>bL^oV~1ZQ7$9~P`M11cn)jw>P^PbwlE zO)Dxsm|alfq_3nV;#t8|Q6!o~IFQdw<*-`=pHtU}x+v%RFT;rIgbv(=P zJugD#jjdslJ$((F+=Lwjz&ERmYA&^@7FcfOalFuf*?naBli=;_FYKroDQ?P?qE7!{ zixVH{j34Rpf+oRDk?5PLhSXi-d#v|@e9`TOJ#xUz##Tm%2`_tYl{ zQNddvOyPjbr-I{0sE*^u+mmCS>jd{4XH|a5@6=rEJ@hQ3LrgU2Q|zChSGX`vT!=&L zUordzFehIWvp)4J8M;SUq?uQ)XIa(H-i3wm2G+2R6}EYS1KMs)l;;4(pXUHZ>T>`? z%xsDlIxgu}CN}o(%namxtW;QI?1XnVL{VNH=#t;R0{gBI!~5`9xYnt)Qu3wa+<#O= zM_fDJMS)n~Y8poySGB?4+F*hrD_>RjJ>Ih^y7RLrI!m%Dw*i@zTiBW84a_X!%`8mp z?W{E9J#1t+BOLgMGu&vu?TEqPfGAOw_k*`LEXuzJskYZB#m9Z5#&$|d5OYh=9?I1% zvmaJqA?sk_!IX=zJ&}}y0Vs!59prDMzIm_}+Jdt!wXE?A20I0{RQ--wI@wiu1oQT* zv}^xQGn8fXRKi>$Og-z7EV%;r!$jQLM`ac`rmUlR@K=D^I z4LI-7GNjs=DZn>28rga_D*i@x3f5K*63XwK_ym32nCK(CNbpkvaM$OgPd={`#H{q@ zPg}{J&rtbSYX`N1eOTUZ*gAMgRA5tyjQb&9 zxa+WkD!ce4Jy3cxVRe4;FStFK9QO>WH`j0VDVw3q5u1_q!EdXlf5uk3@7fjrOf5Ia`PTnVj6R9!Z|w5F=DgY& zITxy+UXK`oz6mon?0whz(c{->yecG!%d)%Qjf7qn6_!Hbv)pcTKW>LFxpYhaTHLrYq6p{SxSH_Qb z4V7=L8cR{H`w$6qytk52I4v#3)npVH>w zJC(CG6JD>nT~=@JbzkxQuMEZlo1ziv4z6ftJM%_+3+p~-ebBo5iwk`b07>fsk6Rv` zg@}Rsd@HpNk4gkbURL%mxUBd>Vs-wO!ok`bv%}p$o_JGa)JcR4h8_V2gz6DH`>j## zvNHg9+v278^s=92`LJ3Pd@@**w^GyD+m|Fpp|zk3*}UasmPk4@3l1S=MWEpr8kInp zHz8;nk{j5w&0TQooZ1uIKXfE@yKPJ8^6^775}qAN*!`27IsUf6tsAB{;U&|y`o zgFR7_9-N?#*qPz>*QX%)jK85-_wUwus*Q)~t~QyW*Dl~r&uP++I1;YG2Ohquek!I! zcL{Hnwt%tx1EyW($}tp~h{9c|%XDu0AsHqPsAG+hP@AE2%MRjt$9UoSfbQ`8qMYyp zBG2)^`rXGezY$BnX67H%|K24WIPor9u$J84ZJ-YJ*HXp(kyB)9PbACu0Rs;;kE}pU zK!rLxt^M>SVd(-q}}&LSz6aE9C3hT|3&FYaktK09O(zK0c$0hkP8&ayAeezz`3 zuca=wH9jmnb~7Rtk6A|DYUBWuNC)Uwqjd4_2So@&Zv{WaT6U0Rt#gr+{_G9UJ0HfT zvZz)>OsXiyRnACIZYL!5dLu8Na(PGtlcO=dVHD?nD=R7SCuIQsmB#@-%fTx&7CkIG z(Qm^Fv_cLdf;y6P0;zYVlvPORfj+pigtBJUfN05qC?2jbqrzPYEZG?D5 zA6CG3N6gwkIB4q!RxwSQSf+zrD$w3xOGe&@xQ)fH<9^86PXD4}x(KR@^OD324~!tY z35hAW`+8jQiPIzHV+ZYdJ(}Bue}lwtzsb9AuN-gz>yiq_dLJeD>gU<4^|FIzV(x+Z zqHiV4qe0wOFBfmZ*PgI38;*!EgW)(OLgN5Q!g&HwxGiegvyjX@C#eO6){-chCQDF{ z_rsXKY8&FSQ`;mydfQY%mQ8wRg9@9CL1rPU!#&VQFp!!>W@UkwEWA=Eu{)bhX4jHE zGTKo%RQ{`y|KJR}>~&y%o{OZ$^rKm{%w1`WFhMJVNaDl9bN$0d-ntceZ-Xwqi(Q%3 z_DlKzGxNQY8E;@|-AItMHkcffC=gI+Hv%ZI9mUK$8^g>ypVXZ8JV6}yvSuva)_ipN zJk2-|R%CdlLX$#Zg5*r=HT#Dpr0R%6bHpJ_xy~w6<8qj#waiN;ke&5ozBUGqZ$o=W z*Dgj>Ydwro^*FMl7rbvvjYEcM-LZS|#kdacyuoAwi|y9^P*UrL{lb!iZgbodD|q!a zQGClIRd(DdO|i))P5o+=p|$Fp!W(h4nG^!1DszUmB{=+7pgbE&^nED@YiEeoQ!TH=SBFkM%FyeDg+mhYsSMfCy zfj_G&RJQs$zK;pe967-`qx+5i#f`{2`IPLsNm6*6Eoy9s;{h=>i7Tu@y(9O+3M6Kd z;-5Qv@VBt@WlX^hC0?{&#BA;S@cdEL7hsHVp1f;}-0)fB*3OVsUY8lP} z8Y$WlS}~#tIsw2O0~g{N6Vv++E7_SlA;?h}9YUz~G1?)?#M{`85=U)Vx~Mcy=VqG| zG&hf$z*~cp`w<4v*Y^LD;3+FE+XEDr?O+$@?P3>YnBWv7o8siho8#t0pWtSOpW~&4 z{K8Ls@`;lX$RDTJQU&f3gDvLxvrrnCEr-v%<>A@Tn`iVk4;rzVA@XKm7ir!N(LXFF zDYtOD1-Glm1?O##C5w(|m4nJ8ja4ixt**VT78KJ5ya=0Kq)&JM5QT~LXyW)M%h;W- zSQe!T)>=Ish95ivN;FdgLO484+^;%wE8Vy^YaM}Hu z;qp8B<$(RY@~fbH@GTw z_}i(DdKhaj+pB*$G?F%1oH`Z0QQKH1qLjqJf<~Jx3ZamSVte@H`qUZU>AxLG@-5!N zIbmF#p8c#THqmQmXh`^e$B-<*(J`^#^ABQ&A$>Tiw-TPsL|WNHQ_<*dMiWxtGi03Z z6I&iXPF}d)2cak)5nHWVrfLcDgzpvki0>WYO5hWIP2lJE2eogAJ5E&69j$+`3GJ7O z55jsueRIC9Tkx)z$n-f6#YLm8eV%HK!hY@&o*X+F+XoDYVnG0I_(QIAXko~A? zQ}KH=>xsXNF1E@cssbOSGh8VLy|Zo=@?Yc5-!43-F3qGNM@&?)UzJt3e#Di0ZF*6# zIv5EXiCUbGW|WjD3YxIJ*F_42M3(ES^&(!K?S`#;I8UF12Z5bCB*VE^C?_+luxzI| zkiARs;UOG}vZPH;k^8R>6Xu;Y@)ZRu*qW_0)J#Q@{^zDB8bg)}Rc%V@$)~^d{ULaf zWa3BX9bpIO9n$-@nX8{)@X~!S!@8Ab3hXu8DxTY%i0~<+W^yZ>bH$+Q+X16!B0jJ$4yxk(G_~<3}OU{v9W$$77 zPS$~Br9=rbAG(OjPIyV_z@veSdax^4GWGUj*sYbC%qGgkd(+4d+_I8yzw>IlBz zQzE&i346k*c6?F^zV@{2)zpK9to~aCBM>Jm#tJtx;RrW5)f_h>)e6T)@lSLoElIf0 zN~3gFYSRPrmOwS{n8+2*Qb%TXEVlgV@dsUon$=* zAf>M@ySgf_j2AzdzXVV$FoB($xu>8Ew4s6hrw@yB(VZbMZcc{qATV33+8(5n+;g~N zZteB_y^iXAWvv8Rxkn_Tyf6=*mg3u3$-d~=eo+y!xRkV(fO#pRmj$h@-}1_R?K35( zaLn*L!Eod&I`8D>)krcspcGI6%;Y6hq3?kXp@xB+aLj4sv&l>PUY7wJWL5~Vy>+*I}EMA#j z4b6~!?rI_v*UC72FP4JfCEy1k&+=2m%=ql1O#j_SnG`gS84)T)9_lHJ?xv`?*uKT# znX~m5tl%l$xQ^rO{F^;OhWJt%|D<3kvR)1MT254+@QX_+XZ`qW+8?530D6zAk)aE? z9X-i$g+F;wi#UG8k~ROjJW4@|^XKi%?i2YHRQG3@J{Dku>y^PcN1gQ+ICtd=T6dd7 zgKb4Q1%4#l{G*Jbtdr8ckDXv(73=7`W(1g-gfS31`$8Hg%T5L_{d^oZ-BAfQ-gyo? z!cBE`P^g+w^cHv3NDl_5uZrsa6->3nq%XbL;2xECt?>~~>?xgD?CD(sT72Gy$-VY9 zaB(Ll(6)d;$@D{U(aWG$*DDcJm8g+bpy^OjzqBY+6hV#fedveyoD-Kj1?+5b=dfg-QnP) zu&q5aS`V0kOGs=4?HK>W5$f$?@bz-meA!=Ya9!!|NE#^-+g3dPk<9mXBp0!8IRu)r zoDa4LoeF!nb|U7hg6Dx9lIIzOYHu)KXF%X7fE`J zE=t}0)~b{42HNYkN=D}fB86(mBZYN#rdC=Z(4#9y1?zG(p;CF zxha9$(*qK}JC_7e0UyLd;+c`|pAW^NKj5EZHe-_7O%+!CG}TU>WcBY2`NgrPU@{ho z5aiVa94(cHBZVmmDyfcqh}y(8PU0}G+k6Glq#uO)0CKN(Ab z9*?pd*zne@G)fx8AR=5CG@^Ra7_{NNEbb_HMA4n=jJc=$WJjn;D)kj4N2{9w%%dHn zSx?x(xf#8T0q+yX3GMzuc)P+aTT&bi_o$yhu31&VBm(lYq>$Wsf~91#3d0tUf4h?Z zOL>V81fG?dsryE`RrRz%rk;YIlyhtN7bteJjxgP(uaW#q$zlR1b7O~IGSKC?HIwF; zl2bKX$*I`Of-Nz#Uy#klK$8yytO|h`(kbG%nqOc3G9swI(*z}j6^IT>uaRz*UM=x5 zlJFQQo{#*GP4{ifP;eD zRX6j3(NAY4j0RemJcb6*AoQVgTc2U*~ss279J3PB4n?VT;rVf$H* z^iMNalIq`D>G;~rc#;?suF6f3n5kO;%s&hDtoInE`#U9_gf9Xb2}WodDPLBZSD5xQ zU2v|Id!oH;evSxg-Em);rDzBrKBy(iM!$TCCphLNl^8Z;NpY-IE51@l)(J_yyprOx zdTy>GGm_^kF_94~F{4l{F@gHrqCd%e#;}zYOud{NNj_PWShS}xr&t%QF>;B5>ni+7 zaKnl{GOtUX;@LQx@1Pc?oC;VzRbCf!Z}G9Jr>PlRIGDT}h~{w|i59V(vGw$v7!;m1 zTBQ8Vw3GUbZ8s~6c|1S8_?w~1>WOERk{pHLsS97^*pfc9uIIDnn0}d3oo<2VX&uM+ zEF+b-y1Ma1t#+^=h2R@Oa;O_&k}ntyINB8_DKkLuG&Y3iIx&iUIW@WBUQxjSZVfwG zmqs2H!{)kmqxJ-6=fAiHrDIdNL@*6me~i&1-K*x2w9iuapQEt+8dqFL_pESF@7Ql5 zqnM9D33M9?8I@<#s_LBd(KBpzDC7MAZnP6cC#aI-`{*zb3Zsu7-ITqZT=h)Ls(S|B zb88!+k^KC-sm$`bt-#hDHwHAo4C;_rcM+9Qr4x@|btSp1)N&RpbARDKR1AU`&qAJC zheDxK^Fqma<2?B`!z{JSHip(bJITQM`msc<@((9@0|`=GAhDBw_t7Rgi_v6zj!+@w zw5Z~}6(F#gEs{5N^wQ%o(tHzbjN+A5?0AndQMgl%;H6cr#G+|7u)`=*^{SJuwa7^{ zkhK9n)lr%t<8i{$~y^IH;KJJd9Lo5KGe2lsg7YBc3z3Bi9hTS{iPK zWaBrI;QcJk>TMVPs{MT2poES3Df8&Zr3OS^Cx(S?C6<+LC61F~DTR|_w}1`ppokgj ztc33J?=t4SNc1v4mHoMXb9hB2X9UgO5$dSm)Th6^isTM0)2nP7WvRlA@+@{n@g_6O zchvf{ffx$$LGUL;DPv{&_p+8m53d>p*TItOf!9q0@nqyoJSOP)WtHEUx!_^1UL%d^I}*151`GHK!1=up4$Y6 z(_-32HjP=QxpQFbKm#M@V41={ZY!&zeCHBD8<{hRo0yYClbK@56F}&e#V1Q^7$h(@ zAejX-FP--C48qJuOaWV>=5&$(T5L!0rw5NHFMYqWVnCO2axF1`X)EE&0Dm7vdtbsk z+$$Oduh>wyo^vI2J_tnb2$ERNqhWCAKA{vdyF_ioH_O}%f0MoM_m!*J5v190*iLaN zAjIV+Nl*7CS{nGW1d*Xa632lJrLKfSnM^v!Y%sF{{^)GYb=qH>;=4aTB=+~E&c8PpiTe%>bm49F?`rYWyPwC&1bBM+heeRFI|VWr!g6VyMl}uI9db+osJR! zPhOHEyGNN5v9`E#Xdv$#LW|ooc{;@20cMMHHq}AcNNhyV7t4h0qk9;_*TpM-AmXL8 z?3v>7vnp1MB14olSd>v~9<4xl)a{maWZb0gO#OvrG{uALa@+++*y2b95fJ12&mxxD zUAqkR*`Nvbz_RzuRN(xqf+FlkA#Jh)kB&?^3yH#krsQ}M1u?+LJCI9!M;iQaEqS%c zN9xlco+h|PF`sk3!c!qQy5-I?%(_X7n*=%#zhFg8xS2;ptM(VfpPOOLgluxcHclDs$|X!PgVR;zN!AE;WD~+ zm=hyXlpZJ7zL@w~ET5Ko+%a%ta#?a|6eapmnAYemzj&HGbUs@y-KRnQDE}JMMO6Ul z@7Ycpx;?vg>`&lSS#ZG@%^@WbtzkJH+_(@;F0KVhNvY#MNFXzW$KgpmOeB^s^7-VK z3irQp)dRcfy2A}MRbcur$nH+#O1p)BQEn9kQ>+$-mRl?hsX5u1RTxE&jdS6q2dQx@ zPDx1{ERt>_!2q?WP_~xN|Jn~jG&B?cv(T@sGAU4>h13WW74 z-3I?&u|4Md39&43s&i;PvOs8`0wL8k$o3P46>htC^k^Dtb2dWvch+$w8o zG`+t{sm@LjPY~hSab}4-L>QP%9$9$&JoZ)NdRXPzc&MYT)YRfosfib$zLXzVvA;OE z*sq~HBY;&FY0j$A+vo_T0LVMfMr0=&dz1 z!Ok&tU@IvRrWOsBW*2`I`TBV}Sz<1+FH3#E{BA@vLeY{%wq{glO^~)>I%Q@9?>Mu@ z9wIEC9JHwJG;-5faTl02VycADVya74U~41wVTn~ZSXNZ9o?MyoOu34IMKgrjRS7Q9 zH@!NEOWQWZA-w`uuy&f&LO)TwrsnQ9g=8Q}bo_6U;JDw!cXB;(-j@CFxhZ!t!mV6z zTd5vA$_6qZh?H0ZnpYy6W#YDJHa=G+HsPYs7KO> zSZ@+lVVev6v&NZB1h#-_!tS9bSFNx0Mme06zU>>W^_^DVz@BGG~~ za1vI1zKR;)t3Sdv`Aqd0oPC%tFLZc)eh~KQK23{6@DPXC?(qJzJ3RH5ax@7pC9+?Q z(^}3yj#IlEfUH)=v3!Kv@j}?!sR9T`IowxI^Y{;gYPfDv%UHKE0E(@+O48|gDv7QH zTCP82+C0Ou)0jNDJ3|PiEW`g|zG|GEWR+jVHp^M~-y<9uq>dN$Jd7E=b(P%E`Ag(i^`{4MZ3GIqtv=v} zCdZqFEKmm}jF9s(+Vj5}SRTEM(VYi&QM@J5i=5`jpB<@CkEze{gQ`E> z9?MkmG5WSFdsMGQIg~_@%`6bEP@daevk6ekvg6B7==y?>@w&-?WVWY*GtG^vp`1yE z1d{X|f=eqVT_C??;|*(3S}1e?*r~Q(bmKTppNfR}+N7wSWEf#ROxh#+Q*Flxe{`Ub z?g^mfUL;+q1-^#9gq5osoMPn&Im{NM@>Z2E`fugy$8i+rEsXTc7TJrkU+UsZ?ug@pN(IUq>UYOLH-eBwf1HZ^^(ts~@o8QE^j)G(99BF{5?-OFG_1g{A|lm>8Xq~=(6YuMFuHSlA9iAE9z<^`U1AbDbq~NY z3dC_y@j?D>ohK8}DpoS*2Xyj5iPQ@?l}r;y7IlSS@+O?8JyIvKKi&JYeX>L!@!a{H+$TwyUYYK2fPHEVG2Y2p zNz6e;+<>X-OS_)#p>7>3K)s2RSgDGaT74uxo$N)1UvyhpSi(A6jdR0NY9jAKI_VzA zHl@FMmeEBw#i+A};vEzFr1BszZN?P%)~Bb6{n4p1RoI%y<;Xkel!OZN3g5=ZICq3# zLU-{VjvcHb5h#Z(+++!RRGZK^3$K>Y-pp|;R|Y#{$ud^q<; zQXKnMYAW;3jBMKFoC1o$!t&y;<+K84GU}EkUNyFm!!+pV_9^Ob5q5C7yhQNM4)?EF z^az)(>~!m{;&Q{TO6Co|U;I<#T~Y#(f(lZsg@IoWopLrjN0jdbB9W8DxFFei++iW0yd3&0tshIjzJkxGNp7l95T%D1b|%B z|8}EU8kj&L;JneK9yX3f#AQC{dr%^aiqEW7CgCJIQt6a4Sky~skg;ii34{tNtQz5y zNt9BdaM1ThQHf-tzE>N?)Ibape#ea}NsCA{B2Lp@*QFBpSUe_;A_21!I1m<!8H^Yq}=Jo(bi0sv2acz=(V?Sru(-E6;#*J~2@lWdj7#Bc7d(S5!ns!bYKU zbARHp9CSShjX=R;P|Ko_lnO_}BolGnFM@_eK*eWJOM-$9g+)XoWRM6R4E_iW91@g- z93343!s$u*yHTg(QVRUcM;NMN;a~yKQIQ|Qe1{|k2YL$oJ{Qg$-G1+xGp=1ZZ|zbhiyhf-{64d5-?~xT zp4ix3nHl9{qYLDxCqVprx<1}fkrC-_ts}=rhmY`dvNqOUo*v<4r7g=#iwFPbcy+X` zEG^vAQcH%11{dz}Xl10eG&Rh_LQ{IgY$PrcjgU>RO2X`;onVp)*bbV8gu)?Ve1yX^ zI2a1b(%jVer9bH6wg2~jf8NUf=j!8DX8#@q&dLXiY_#v!uh;k2xA*?{f!{NWG>cGN zdJ@bf==`GsfD#YqNgKR#u7aZ0P|tWwd~Op1B*wM8EIyAdGcu*KYN|QQHhk7a4^;z1 z1Qu%)yQfH`(_+tv-0XW1r!j7s&nZt>io^re3rYB6x%R!9X|Bdj;V29{wLKkqS-dMr zgqSmw%Z|h}N%dvKJ5^)fE_n+WOqnj(itl{-y{X-9hnqkGcl+~AJvA`jIo2z_aGtWa zlOVuYcE4DMC2-*W!6BY>`KmGzcFolMUCL#f$q`$A-O)_=5;-VhnNQuTWI^Fej03XY zkUum6qnlr!rS%PS$iHE^dAwogql+X_Ct(!yPK|B?nxW@$m$^#x0Mva;9k$z_`$C;8 z6T0iU1igcPd?(6CH^u=gr%VA}+jmeGu>+b&tT$%E6AFR8xo(sUlR_;bAUrfd1P|9A zsaX=a7cfcRfw@lOiW#6+XV@}TB(hVY5`si-!fsa|nF1P+Xr*)`v=?+k<>vu|A@B0H zK?m%(6O48>OW~&*8}o)(S25xyQM7wWGOeCj!96t{j2dH|P>=1l($Z;rWypAwP?l9! z>n+zI{5Q=e*EyXKRC@{yyqR)GnBTVNWWK%bf|rt!6x&;YpOpxg2_aVHmPyUf-&@Fr zI^hn%!!kGph_tyd)P|!N9X(>VDlINhvH}VtQt9N!rD66}1=9gS&eLne$4~B$ zR8ysgwW+r`OHE`5CHi`eq-)cKRCUNP%hXeqYKXC($EumUK#i>ZCkAMLd3!QlOCQ43 zpN%TrWOhW>M&_6fB51ll=e%-8k~>Da9nG{0CJT%4#z?p(B$W%f8qX3+i8@(Jq zFy#)h>sm9#DNqS5Zpt>qY-wNQJLK@_b0|DzxY@PkN1EORTC4IWNU+LB<)^eH_c7hUCJov5@4kr)n09@= zp*r2lYJs%MY>swps+vg!T!6X770qL5Vt9KD^GY)Db_{T0wDs=s9QT-BSQmR0?q-nz ztdY^RrlU%>*{k#J@Ooq<4|5hsDmUeyBUzF9n5L-cywYHb4B0$EKYfp}PsZtwyc5P)72TeTD8y9Yw!NJDvEb5CvF zM+OUnTk5&`;+U-(2(qHGtn!2(3NA)I8|?!BV6}R9@xEt0bzi!K^3`O0I8DV91D`_7 zY<>CfzKFm|ZQZ2$gCswWEw)kJ4ZCZ#CA{gc#BD{zqgt+k%bI(b5>6MQHyYUHpE!S| z$E4ht|7l*UpnHH$H>=4#XYBG)6F$Id?r}q4F>!(te#Imnf;K~dqJw#~VEfgQDlk)Q zK%il0#dwAH1Fz+SkIW@?-y<{}53L_e_KdOSXpzx$&@d}d5rPw(2TuZD6{h#gDOn2x zb^nvfREUBluxq+MLYKUi$;E_xXIsUmaylHyw#V_BHT9`ys$2kf7=Khd82hWy*K!(_ z^TVDbO|364Pc=&Uth3mh+-~)~*h553Qcr8gcobg>P;DB5f1SwXu^4r6hEf!>8f+*q zCd|EZ6{R#Yd1LvW)GBw7&8Ix(wGkc$?tq#F{k7ew4IHemu1zzJeC^GW!a{=^(8nVc z`D!*?X+A0vr8Oe6OZ9a9fHesGEd2wRfaUB?+lgwg3vAZ(iE1`s*PhNqKEv}V#zz&Nq>4T9-(3ZgGlGbGR#(AJ`IFH? zV_so60dv8CIN4E**#N53a8@h&H652P$;{o%%WPkddg+QS;{06q-RGeRZoUeXx+#HH z&(s_@kgr)*|H3j~qRA{c&z|ob+{h3|a8nt9go#e=Nr-fY1QST6DHR3rzU~c4o?fm~ zq;QTW#~&O^lYH;Dnu=~-W3iAw@E}`P!N~irdE_?`?Lpj9dIEdG7d6cZy8RZ#{q-!J z5#^qA>qg|lm)o?~a;{7BTY}}kr+)s2jye1x;u&K1wdR!~c&amo05^G*O3Xr^tKOVF zbXn#Os?H<&ma7bw!VG+e-E>3z>8J=*7(*X`D?nA1kzLyOhc}Cy8QY-QBC*xZu71fb zCIWMb$?i`|RlBbkoyZ-XkLH%;v-3Axjk0b5p26Qj11>uq|IDet{SpY;XfpqzeYy?Z zQxdZ)E2+&5MvFmlnP=`^tJrpD%|i~+Tz#ALYF``duL_jUbsbw`eH4=a;RF>*YbLi% zS%;m%A1k)w6(d^AF)IqK>gLs*{mmh7t-AT#IO~X0`bg>kmIB{&=5Kl}eYnx8NONhY zrx=HGnF9&-<+hXRpq5CWoy{p8e_{%3vbDDA{(3}0Y`#Jc`mc4;HWq0C?mqO)b&eX* zE~~=VobfdP(lux>`-uI2Xgq&yeKNwO4Y-HmHc@7BJjt#W=nUPp zu}_pC;})tE2NJ-2kG$njj1ZrL8G023-Rg`?d}sm-A60QPX7~0=oH8rW#k_8FcZCz# zzUZrC#?dB?yz->VHx4b8V|4!{leiy~Od1)e`=nZ~V74=g-wS53DU2ZX&KwXsQw?4QXzHaH8^H&)iwD&|^6M9wS z#BX<^k7ft7ExUAMZhlSbRaCZ=&uzi9T?H4kw!%vjo;J}y{XfMx#qqx=qr)=K4zZ3| zdGd{6%%sV4oD6lj>?YHd_!_3+$x(k<5CZy$waJ}~ZcxtqCQGYdyTbT)+2jf&PHa#ix+`^o)-oyz<-og3+*_@l@I z8nY&Na`lLglneVV0j{YUZ#Pp8Hfyr;V|+tdMY$$V_wkD80ZuJH%Ig|cSB0r&joXXX zo7irzd$a9htqww)T%5FLJrf(7z6{^zm5v=u`TrLHhd_A0)PdNxz(u%Uo#F5W0cyo= ze5VAeyQZjv)N|j*oAsJR_8bI*y?`&1X@Pt2j*Er%){;{~HVL_`e4}f zGA>{*wiU1=_J^|?czlE5{9qdx1UHZ9v(N;<+?ltP1z6TWccpBoyai_{hl3ntZA%}Q zoOyO5yk$%_w$nZ`Zvo5Q#K}yyltEj>K&);}gkcGLq*+_wwVkCIgYS~u-rSHbkDC!I zC)Y=fh?u*rV3y`KlTE`?H>hDHToFM~-yG~`Qx>jgHA}08_KO#H)&g6m6Vtu0hJbT` zeZlvj4T$VwPb^KuyHJcMzHp<$Yrv}{2yL5~t#LZSviq`VLG@AUfuZH?F}VAyW5lbR zD`#=kk-<%t5zIkhD~r#;hhr!pd<)W=&>hKp+pWdz zu=~@ZXe%-&;XI^v1#XEv&HI5I!Um1K0k#o>stR?1IQ88f$=Yz$ki%j1k(TH7!e_oq zpjM_=x*aV`afe0Ig}K#%pqs99a6#mVxxcJrA|!B2yiKUPIhLHeYe#(pn-aCJJC~Uj z-U~oburF>Fo@WacJ(w3)Inq6-gSw$PR`v)hB2fhQR^AV&%lCsh)2^s^gf^x%9CEy~ zPrNX_O4thS0(&4AS9H~T;QT25Jx*IPiHkMoG8Wna6 zG^RYy_I<3co9CwU3ZORdevqZWP655atC%x-UVtS*GVq;A*CdzU1azvG+%;?(SdqRVd|-vg<6mkTfSOTMkn$!LSj z#&AQjhvNEG+k%($4Vj(W3;1M&4FHrBqk-*mmUt0tuZjS-Px}0%cRpqw)-k1lA@glb_nK4o<$+SeoOQf|Q> zA{>%i?o!x+yz z%b8x1|@bqJ_OL}X&WM_+-iciXsm9>-|w<-x6|F$0E z(H2TmAQ!`JVeIdk;{DVGSr!48yFxJk5buf-k`JR>Y>(`ap|kKoKx#rG-Hlcip;N$Z z87o3hs!_^Kp>L2iVaw1_UtqHxdNYdx-HUEAwNv=PyvH4~vsa$cSA_&cX2Ww})ij2Z zE0b}F@7Vh0`ociWDr67Ah+*n;ifyk}sSwLyl<{i$p4joh>d=0Zs_Yf8 z-eJ+)8S0rhd&}k7z3&XTf#ooG3DZv9rrI=RI%`A%Ex``Yj2vsAZ=^e9m`TS_bK#(u zw!kjSsZ5);#eTM=&u=b8RdqF9gsERU&8JM2*Nk)oTZij&oq?DVdP!~1@XulE9vm#s zrR<~}t8>?#^fSe51;)d+CIY&n0=BJ`1(8( za0R+1;{^H!Lj)@Kf&*&TPXN{H4}9|VJ!iM?E!BM=S?_yB!oD|r?0dn@zMq%&y*UMA zl+JIA%DLhgg_Gn_3daScR1PWR6b>aM)lFffRSi8dYePD6OM_R^>w_lx+xKVa5f)ejxKli^ep7x(v z&KOY5MjP11VNVb)109~uf?NJh!WrRS1F*s?0xZMrfTzQ-OcA2_1t*c+Pe_#a1`hrD zzA)eSc*nl~!|eOHXy30<`#$WV@A-xh{q|UqmiCxY6StU=jklN)i1(OL=9`RY@ogpq z2u}lg$Ju(y^0yrBT+XJCZnxpio`(U>kB>sy9ScDWkh?%DFkC=N@C*QHunohLpZ6E? ze|>_%!M+z%?E62;zOQ@s{o6y|n@#jRmqy>`Au;;qv?DI&G^8hIG$b8ow4@McG^EY5 z+EF8#jYu%=`N4? z`TOf<0Q3EVzr?=xgY5gdX5Ys}`+gmw@5L(m9?PTe>!=ug@Y~S`zae!2yCp#hyD9Yu zw<(DTyCrpo-;f@|aYlJD-Ozk|2c#zJ^@PiOI@$BpW)HpHK&Sp#Dlxn+^HHiJS`i#NZoo$$V!zTuFup^H%yqV7x(9UcK zVd=O6EViA%lUq=R2!8lt6L0Jx#3_3L@6G({tTIaw5QR+efM(o9xoVH{?p`F3g<+ z^s9YHzuK2XrP{YdWRGtt#a`di=$G2;{ci}yv}TYL1q zU6me2?Hm0(?a|NEB7Hnb*3N zxXRAa^1W-SR{2aF)_%cF3pnA7n{V*-MLbXyZVaGAriAG+Z}u3>qCJ8#h#tVWL@xtg z(Zg(M^gUmY{$=j>0MNeCuLB?bIxx}|I&jhvIFhJtZUc=Z!uRuJa z7XVh#1L(r&W3W2v&`6vsV87T?eIVr{)Itj#E`e^fQZS)A& zCMtx32i@c3KX17@&zbI?1Fj3S$=(fe*uyKtFsGMDfsL>6!I)JtfmZfXz!SGKM#xLr zbNGhn8F*9l1iCQ#0NNUTOy7@Q=8j4q!0q=1$Uf0;V;B9lwviUL_7M{{_mPdY_Yvw^ z+{klnUL?6zA3|jF3kqxl^qIQ}@~q$TIpa7(8?GE8hdw&S40d*p6yESAADHc7Cg|$x z93X`qjVaQCc(K`<=pATZ^aZjw`T^D+y?{L>{Q$b(6JY!O0kJRi)96D#jZVY`tzPs5 z&0f^w>|Rvj?2jn)EO5y33?IaZM);FYGwiXeA@01_7IDsOjW%F6$PL)H$qagNmKEgY zG$Ewfc`5+Q8zoRx{UC$XN8%;t%A${8wb3W&Bhn+NKIsqKY3U8b{oX*@?-PLipkF2^ z^vm!4r7TxnPZDTk7SKKhh&dCfn||7bF;~t^_e9NISq3| zS8WqRADbryJ2z1XZE>Xv#^z25VA++BVai+a(z7?Czp(eChoGmVXMl&LS3vjs1a7}a zQ1<%;T|X@G=YuK!e6YnIDqxL2sb7yjF1{pvKzvR1cz#v#P<~zNEP`d`^?qQuZ z@Uc%0X);j_;WE+-*)tCm^wK~;h+7k(;LPnr0j{_f0HEe*ya?6#=xywb^cd`-^bzQO z-vI9S3)Fs}Anf-IwEiT|=c45KTa`Rlwk~&)Y-#G)(CX9~q4k*~x&?}Zxg~l7xkXxo z$W=;%?=szB_X458{S^Yk8Ek}wZ&-;5)-eq(p3x zGF3uDk*O3KU`VmZfGXu8!>H5?44P6gE=Wqvu&_8)qe9^n4hnx%IwZ)edPGoL{df?@ z62jn`RYZZ^))511T}uXdeL(>*C1xcCiR{Dpl>L_)vj2i1?7v9h`Y#f!{>ufZpJKyr zS~2sRSZL_t@@MKJ((Rb>EKR+a!*TVJNh>%$Cv{g)iC|1!h%Un*Gr zmkUz=Wrpcz_((P|e54x~K2nYgA8AL0pVVUll_Ve$k|+&{up`MxgcM0gB78_n5&<-6 zNrcQKCJ`i)nm}Modh(!93Ce>&B`FT(OH&xEnW`)ZH(^O|ebQo}4(bm`lllR=s=iFr z)sH#C`Y=6M|Aoftzi5#9FEdX6g(m3_4XN;nh*0=LL?(QqR0*HR2!tv{NFFF5V)DQT z(US)(h@?CSK{VyTC{dLMoJ3Y0R1#fr@JNKkK_XF>27^Ug82A-&S@1Uc3Cu?y!4~Nk z$W!_Q+L(TTFsCnL1@&W;q&^Ig)Q7ow`Y$w2|Aj{BzuXjk3IAtFg#W_?!vAUF-~%~n zUd;^4)PmIfq#xHKsJ>C)iy=QqIo`3xj~J_E*|zrYaaD+mPo3Fbi` zfja0P_zrynd_-SBoY4zQhf4MpOFFHbBN6Lf0BgMhr znbP3zOj+=Gsw}v&bA^GE9W4xo=yYK)L+3L%q4ODh(D@5Qb^e0Qoxh-R=PP*J`3e$u zeu6=skHF6J543xJfpO0#fc^Ob>Vkd%w4pDeuBuGk6`iU zBPhiA2mWxr0XWVtV9xmiwmM(H9nTN2=JRE|fPRcr(2qG1`Y=I6AEsyMzw`wC7oeVB z`TLj2-@hFG{>AV2FX`XEX#dNL-IsY$`vP9Teu0;(f1ro@3HqeJpdb1SdY|v0&-oB~ znm?h>@GbNdeumz`-_R%cHT{5()AygJ@1FqmeUYTT2SU~NJHq;Y##`T~sP_G7xbH>Z zBK`pw5dQ!S=RW`g`4PYj{sb`Zp8y8@7r^X&1}|{m!He04@Z$9)yjXn-FHrx&i_*`~ z6aAUq=hO5!|EAyYbNULOr*H6o`U8KckFjFZ_e7TZ{zp~c=LqY28E$=_Vz2K*fct*5 z-S?V-M1BMrAU}c(iXTB{^h=PT{1apdyHJpccd-yN=5is%x+Oyl-If`cH7ztSV_Ixr zwzA;7RAte5al*p$a)jll54s}M=U5@?XRI9c5~?Wm392;p0FprUeUGcYzaiH5FxhDixRaa!V<$QQuuXUX z*fsHyaccq;W5z@%#)}CN%+->km!~C3FGot2`ky3DeNB?6o&u7oUI7xTJ^&A`{-p+4 z-@kb4`xJY9kLvAv(skc+`t|)JV&5}TF5vQ`M}d|fJqe-!@kuabh);qOLw*u`3i(O! zC?p7hU=X1L?jS@3#6gS*T!A1BxC5dTzzql!05c#?GG0HRVyu2l!8HA-dKvO@)!+EY z>L>Kr>Jjwl>Icv$>tkBH_5F#xz7Nsu`wr>8@3i-QrD5MUO7?x>XNB*AXo8g&MG=<3 zFp?lcMv(*$F^nWs#yE;#7$fNcJ;ss)bc`kjT^LUasxYJw(7~WGzy{-L02vIe09`P+ zWT;?#!6?BHd-;K3)^C7;);D;e)(>Fu*2|dK>-*Dg-*-UweJ8!|EeZSH5wh_e9$_WHIwA|g-H5FQ za3s7Gq>%_a5JsYmKvoGf08k~^VvI__z2uaj>noJN>kp9F>tjy0eNTGsdrx`aUk>&? zWMkhaV)i}0L*K_wbUOKbPp6(NpHDkYK%sVUh(hgv5RKZw43*MJ45h-ki(1*RMYA}F zMY%L&N53xUjfzo_BQ1-tMvB&etOHwuu2Q%HRHbzRmOSLa{CKeT8$4)x2BqE}rUSX} zJ>h+i8QAxcihaLW+4qE@eQ!tY`!tNAA4x^@C8>zCOi~Rwp{yFxVOceFT4^ltAN_e~(l6)|&2%TpO`VzwVTnp)R@(k~6s2tA(lpfnLKalj7naIF?0c2s{J2Lh?qGjI`g7*F2hrWNK=zFq_zP~zo z`f)R;E^;#{De|(YAoH@Q67#Ys&-ob?Bt6Y3PhVT2*WZxTcDbVP-7YBmm)Elg$d^MW zh^28G>}EL2izj>)o*P^pwhc5T(g6r52ENcN7WNGm8GD$-$-XD#?EAnDeNPwB_v;pY z5BAabdmk$5ui2!gtka{z9)h7{AAnG^f5D6PFn%EV7x;<3 zKg;NQ?vTF6`%%9vC-uv5Q&q}wR7J{gRVm7GRib4%DoQh*)S>zA>3-H@a;N!_%Ia%T zcfAd=w!b}i?=enX{LEo(&b}~KmqXa{yC;wW929UQmE-1{(b=5$7ZAcH7oV6S*k2oY?YEL)+&Y- zYxTjZt_QhzJS}0`g~rgU-eFDsd}lH zRJ~R-_PkZ6dtfTgz3|jyl?+u_Jv;gJsh8Z=_L2DtC;DdRK-aAwPhL0hM(&%RSra2x z7%MwE&`MJpcw%qL2)#vLIKV;l4BjVt0&N$)05pvrfY?VbAZw*3fcN_YY#%D>=aHqh z;E}1E;E}6x+?A_P@61%5cW5ceJ2liHUb_h}53Us7n;)gCcA{|KACUGc$WzS{-qdXo zH?(;hEtG{NA8?T~4@99q0Tiu0ywtQu^bOc7`T}Yi{ebe0e!w?LU%>i%19iVop!T7D zojU5*sinBksi&aOt*Ly>v8PJRwWmVo-cgiu@h3$(dK13Ru0*%{J7VJr5|Q)rLEXK> z9xIBq&PK3qIX3Y=Uj$lF{B#alg!^vw_I^6k&55iXF4Fm5o(Pmb{DJ$Fci zu2Td8-!=AR^CEHd_9!{b^<`dA!*)Ul0_?-`hVzoy@IclvGjr_c8HM1>6ys>qrk6pOXNCl70Rr_HrOCP+3!rNcHw zq(HYuqxsw85F1Vsh%8sh)1|}2>9gD9V3+rap{+_9f$Rz*L6(+;fF|z>7@shWUPBm2 zKjEvSf3VHcGwA#Mg1g@*X!|b>#{Lo^(@!gG`e=qtSI`cdkenqp$v9hN3USuB^m+EE zM2QxOw1_roluxrH@>9bUg0pG-dUPHz#NCCEaHdy60axFO0Z`c) zFhblOFE`xOUV}DDPvQFe2YbJ7K==CvYX9W{*iZUpjY^-)QR$OCDrF&yRAMqVnWSUP zGO5HECer9w=27MtXAvZsClO*9sF0wUC{X>3w5N^cLC382;l|5`%AwDV1ctge6cX0z zS}Xv=qcMQ0+C%23uLw+!mB~v@-<1~()!$FJ`~3sB|I)zhzew=h+AAlOcPJar+PWTzt!dntEls5%NC$G!PhP(e_LGHgqSo<#!#{Nr%tuIRS zxkiWn7U|I6@*p&2>jRJxE>xfxTO@u&Y?<&G-8#{exs`%Pl553|VpofteAf${yH|{w zI2a8M=r9}_zGFKqSj~P&pi3LlAZ_gkf-biv1Ek`n06?1C65})XVQk|5ONO@pGU4pM zO!)dQ7_L4l(7zG^^shz${VNhb|LR1Km8%pxOR!q(@VJVhgW_sNj_Fm6oX;y9IG0#D zZXU6C)F5X0puuYa*+x^QBzye5n^ae=5e#pPG?#Wy*#P6RI6GK&XDyu%HrB z6Jm-;jp$Sn8cnKHnn|i792lvPY$&W!G6QI}B!m2_$%Qo5lL}ugDH8g)syLYY%A(*b ztV@9{vL67-?8_LP{g@uJ4^t!dUov$4ml>@8LId@Ae?8g9seVCiB55pnr!_ZKD7@VeWW2ELQ9ijP3NH%<> zBpR|TEz$5q$%zIcN>Mb-P^zNAgwhoarb<{efGTClkg22v!{pKy42(-$E*LU>3xG|Z zg1qTZKtFv5-l*STZ|W<6tNI4WuD$@QtPh|`>&FafeHfjq4|7BHVQ`>6%udqZ@Qa+x ze4{5Cz7doR-zW-(EJjl>Krym{!H7{73^0;kcxU}^L%Kpy=H ziASHpAkv>;Px=wqO8yb^kICQ zK1`3&hXEq`7yeHc3;!oeg%6~K!Ux(?Vaw02Fy-f0Q1bID0Qvb9Ui|zDC4N2y)1OZv z^ygF9{P`0ofxZMspfBMt=tmF?`VZzpzX5vaFW?q^1bIilKwHum0H*W-P%(WOC8i%U z#Pnf+mOjkS(T5Qd`jJ0COyU<11NaBTxIY1y@Gk(v_Yc6xeFQLQe*p~GX8@D+9lS_= z2ro^4!pqUG@FMgty!3nxFF2n=&*9(n7rss(;rH|leo$}V7xgb+sqdjY^?ebnzW-6y z_c-SIp2fBAMU?xVly3SN0Py?_07(7@01Uqa0QCPr=KLYZXukv*yiXy1eNTei_oec_=Opa$FAzxaF%T&I83j%YLbs36*>S6?=#3c!4hzrxp&K9T_4qB#q3RbC{%D5q*}pY;G#;-0@Erw3ofhdC`hEjlfZ9^4+8ZR zpab72LIwJy5D}=8Vl<#F3X%Y`C`$ppqBH@(g7PE-1w|?*>4_B#&=akf8z)}91t((t zf+l4>03T`niw(NIcX91|6XU-B0Pp)x!M?X_?0ZMeEk%STOi2}*P~?cv1QthzCXgN( zl0a#sC_ppfTc## zjFTHmGe2@D&5X#QG{c!=Nkb!!rVN2NoG!d^L{%W)n3|xyVHH8|29|@l8(Rz3aCjjY z!x2_s3rAT1DI94rM>yJEc5uY?6>ik^2yEc>0$yPIm=oi^KXv#0C}7`fCicB#W#2D~ z_I)6UpI@TMXNw6Zo+u}tc7mjM;vtdZiKi0<)Q%$xsT@2CDjYZps~R(kEDqchTpIXN zd|jwZAx6PXMHz%M6=@8ULF}}yfmR5;15v2g07fFZFgYT|{R9!|o&c)5 ze<{89y{BQ{Uq<#l)EQ;_^?1>I(#-xr+OA1T26=f&mg7zrudgvWbQ z0R(y0UpRaU_6$4?`<}D0?*PZnzf3K74B$C(0>p743@n7Wabe8m&BUj$4~FNN|QQNwb73 z%C-V1OS*ufOF@_$%)=f6rehDlva;_XJ$o2M4?WBviN2@5=zBDf{w4dhzCCU0+tIeH z_@QwbxubQlxTAHE-qW}$?`v9!e{EOyzc?y)AYD{h5U;5_SWU9#uS9r-+@H9^aL!ty zdBfNuyTVrEyn)msAc3Pu!x)`O$DYDuWlun|vyWly(8m~}_A!7e`W{}R@5xDe0Da&6 z#`WKBU02?2UP$0>UoPNnU#0LjFH1NamtuTQ3s7!%1)u9po$Gy1Uwd{5d+!?Q0g8j> z1kXEdgKU~ILivTR!@mbqii8G~7#C%F94Gq=kDL90#?c-?;%N^cZ=#p+d(pqJdGs=1 zE&Tw!&-KxDT|Z6dg(Xe*r6fHE79%|eW}2q^!c5zFF{<;n@arm;ynU@oyR}haxVK3w z7b8{Y=b<_~yT|Pww>b;+%kYICgkVLOc!22;62Oll=8J~K&_03UhdzKIi9SH!iXOn6 zMh}w?(hunVzQEn@`r|#XPuBa=Le>LgLe2}TLCy;UG1ddyBv-#2$$eb;ayF}e=324Q zV5zZs?8IfGllt6jqC0rp2X1c1NvjKL2>UaXfF;XpahQp|F<7Xz z{6%R!cO_ocT7lU1)m#goQg7)`oB4cSA3q0mcz zjq0tvEce=6j8%BnAm7}o-e;d`Ujd=YSv-?>TX=NsCJN<&8+qc$lQd~(ObcM_&jwZS zQUXbI5imRU6}<#M8$E+Lk$yp2O244|`vu(JE6DpV4ch+J&tr4_yf)Xzb9Yt2bAP4Y zy}wx4!M#fDnY^XYPwxvo`L$ zAa*8ofWReao z>|ICZ{JLPs-Mc`@0djH932I5s<*^Xy3abR|5T)pMi%;x4M<()~BvQApa;Wc`*<-i$ zv~lx}VjvrTc%XGIVZdq4BIf69Mt#C_(+o2ZdTOwBF+oBaCoFi2}E;7n*Zqf-}XSsyF%RCC_ zbq-~#pgnok4mNRF5E;(ABp`H&R~(2s&yXQf`+(6&8+qYyj`DIr{Cf%9|MH;vUmC>y z7YSzH@anf6VtuwmtSxMdSWea&v3#sOVx3Tnydqtjw8~toq=IC-lsatFgc5Y)Y@)+` zDzW86jmmT#gt~Vhdie5CZ|L|;WLU$lhyd1=fuKsvLqHOlNX!qJ3CxYy%FB%Rzf{2f zmk4?P<$>FOp+NQ>tA1K!)kmAG>VigDCFSh0YRFk86$>*?st;$LQ=Vs_QIu$+PmF1# zO@wNtOMh!0km7BpMd>#YLE$(PJXtywH+Xk0G`v-5NFclNP=K`^QozZp1OSm&O3Y4| z3k*&6zhL0|FB9tii-fiRav|)OVM=|nO{q`TDfP!brK*gDLLC|F05xOG6l%oSho{l8 z)22)^RHjHW5=x0>CXfctP#F24sTg{zwE!}+!PxP-(Xio@)1hI`?gs@ly&?^`_>>%& zx<(O@gvBEBbNw(kd>>{8@4r-l`!5yFK1>F$A13T7G zUnrJRe5p_}{bG^i`Q-x15)6jXW0;JhLopgdf@3&<=w&;4*k?X;#%V!pvTQ_V?%s}6 zV3T74A2Ik> z(z4a!=m{4LBO+Wij9zTzC=$W7gJ_hi$B<^%524AfAVG{?LVyUri1_@0rSibVj^v!l zl-P95n#@$wpj?1sqY|O)Ez5$fxo;qd*!)Q4BFf(8ursnErwLtn; zGLHUL4WoaBqeu(Zj-e=6KZ1&62?>&cl^Rq6%Sccu780ONtRz5|SxbH%wV3!IYB}M- zZ#~hecSXTrdrhekhgq4qj%B$}XVyhRTH0su#r6+Sy?p^nZeIqd?Z@DxeHad7ABKkO z!{AW;qEE$x=uZV1`cp%M{uGg*EmWyKQmT;r7@=D7bA#&14+^R%J|C#0_=HkT>B+33 z!ZTTAWrxw~icaBGmYhEa2Rw~^ z07YV7rswO&=xlu$ovIJx)AW5llarp$6ouzAN!j^LQ*^pCRneisbY({gla?JIOkH$d zFnt{oOkc+X)6W5I`Z$?Q|3=d3+b}x)8p5YfbBFpfZ>gVwO!X~bufBx3tluDw>m%su z`U9lAJ^-YyF9WpoV|uQBOpnxu0b2T1c7D5`-+$4XL{941LWXGqe| znUVByP9%LC5J~?=H0j@PDt((urC;N?^l1*6{)WJ&uR-qgF`S`(h3(XjfLHY!uwi`! zU|D|vRn`xXl=WqXu6_(q)sF#s`Y=FBKj$B1!P#>2kGkCaqknV7=-)Uo`Zq<4{>=@e zf1|?a+k`m!HXV+Bjpxy)xkUOjkw|}oV$z=(RQeg{OaDT;=~M7N{RnzepMe|IKOkoH z2Uu18083S0rl{)22ub~zpr#K4r1Tv>0sx910RX|D0D$}_0AT(K0BAo0nY`aX=Iuj} zIr|b~z&?c-t$!h=>Su_7`ZF*~p9aS0+rR{U9T=Y9^J4RZUNroomkB?qXYiZ)1AnTA z`B;7b#H;Uzr1d=yyuOFQ?fVkyzAwFxehCCVzXSr5e*yu;Pk{j7uP~tfEer^M3<9iw zMu6St5Wwx%1UUOR0K&cwfUVCXQ}usjpngz{(kF@``baQ7e+lO1L%n49RDFb>)hGB~ zeE=?GeLqC4?|b0&eT;42pGfz;slD$#5n+4|4ODy%4N5-^1I=Hvpz+%%!2g>B@SlUA z{C5s$e~$sZ4^%+z3lUiRL?R!YAt) zbW!UEM0x9bA9#H~gWLBh(0zYe?|V?gzPF@QqJ_tT5iL9xUTEp5078pTh0QEK6dJPt zO%TK)Bms_vC_?#)(Sz+3BnNv}mKe}kX;RQ;1uB7=6{!PXRjLYLRlyn%MTILs5*09+ zpebcAH&fJJE=YOn8Az$?0|e3QUutyweg(SkQR{s_TG;oQl6@cfc|}PP4HYFtG^tIB zXkIiiqVd4whyx}Qqz#Hol4kyvEM;&jX~Hm80%bv~M2ftlK(1#Qb-3OIT+Q$_Q*hkjPwFg%Q zJRe^c;(UlnNb_O#Ak2rFf-E0y1)hA&0U+{`2Xo`Y+E4JY?G5Ar_X1w1`xq1TzBeW8 z`_IR|x7_S|N7KF^RPk3lDRo%^g+!%Al+h6vQbsLaNF9;7m@=YtK~-ev!fMFwA}eU^ zV#`PD!fPk%LJUXGMH$W9i!=>vFWM@Q!H|Q%72{69R1CfWNHVlBJ2Jw31sUhwK#X-C z07borIVJ3SQOCa5%VKe;ssf@4>PQHys1_ltqDVtrK~aXl zdO8fT)#MkVONku<>}ZTiMx;eO1FDKxU4>8;u*XBenB^>#&EDR(jnKX4<8Y^?}Ub z>6ATyC9raA zBxBeLWj~M_r5iZvAQ+=lir7ymh3p5AUiL4XpM6ZFX&=*QqK6rT(f9EneE{$;EF=4d zWn@l9k0sBr7vbEF-&2JR4I~OalW{Z0mwA!fBlw>#V+xc~VzMz9zlM?9trh zH>j<$tW$Qmp7F|*OQ_-mOrYd>2%rb_@#RA3Wq;uFvzN*A&;y7Z(E~ta(Z~GZ=w;Yc zdIH;jXEW>a$99G#Pz`N~pc)ztLAA6Gf@^4*f^24t_%|{^yj++(Z`YM3)XNGRTC1{$ zTBvH`SE+8XOyuUc?m2^u(|}F>Rk*4oT+oEs7ywAK@@0n8vrn*D+6Sl<(Fbr;(FY)u z(Z|$(^aReoKXCnVIW#VExil?wIJG8pIJF#fxU~>;IJHf>T$*FO&TP<*H%9lXg01gY zt})>DiYo|~>KiOK-4&{h;11zGX^MU}VvU0rvLF>3JWWat7_!`i;c!XXFStbQ2S}#q z2PnSi1K@M?1maQp1LNN(;QMG>)_7@K)_7`K+IMSD+IDO_+IDOr+IDQ6Z9Fwd_a0i| zyRXa+&xW<--L5h{oaMEfsjhdmQyw53Ne*uhS|b$189O}8u(kN;AjI|mnBJQGsl8%ww%F_JHfw?5>#D3A4z*XWC(+#lh1voOb;|OL8oCT8AxtT97C3Sw zjlqEw(L;b_(Hm%_(HAJ((Ic===@YDfub}&17~uW1En=;jmT`7X$~fC5M4W9qAdb20)oB=}4cnxAkpfUtC zfTZ{#MyJt4FToK;k3jK9p8)TqUoiju0`%_}Z2wDxx{tQS`%T;8{iK9ZyRYh%x38wr!9WXXLzuBj4p0}vB~i*vbk>6MFU1z*ZCzpHzE<7+>s~EUdaIr zkW+$Hp7emyA*u|IqKclvVvW9m;gG%oV3fWA{PzsX|Aj&JzdV@xT(o4gEn2eL7A<+L zinh#VL|blSku|rKX3uMwS#?^YmTi{b!pH30IhgzF$7K!&UVX`VDeM`MNl)EaBKbaaUYG#Rh_ow zs#d#VU9TaruGwJpvDrTKxz{%P;AxQ7xtZbBKK7UW1B-9*TvW78_?#i7%Z|PARGdG9|Tyql&pzbaL2np(d(Fr8-!olw31p}PR3kCFliQxS& z4)p%ZgtnhoH|v6DKkI_$hqmSJ56#IPVA>8l!88(fc(hPELNm)dLo!CZL@~lV#xK9U z$1OOYBo^9gIR(yYI%T<@M|$8jcYw9lq?-<;}Rv6`DbrJ3I>_mw1LkZqclRTw|H0+~XMJ zTqM|G9i>;G-Q^Y?PxFes=Q%~@3w7dNJ0S6GK>*Q#N!+ZJRa^*D$7o;$=TU%3P9(rk zoJq`%`w9#V`oCZZ{}&3_|01F8!(2%FhhskuaqPn_lKnSFvMFznWJTO0$y~Tqj)8Ey z6!Ub;1fzW0^jd`TsrWuxG ztrM(<8YtKZHPNq8HPWukwNfrfHq$Ibx6>>98!A;jjzm;@?gUfWZp9H-?*&k`k4BE1 zmWKv3uaF5{Vk8Wx%1;a|nXd>CBLA2i@Q2}${V*5uKFo!;4>LpdF}=Q;rq@sN^!jI` zURlmMbTv6M<@#ZE%9X-Q#Fpt92`$ev6IhjKD6AUOR8SeJv5*2}d+{W9ld+_Zvtbmb z+W~aN1EOPBCBk9tn#4jF_sIjSZ(8a^~c=g{g|1!53>R7 z!`OKJ5LjPK#MKW&arM7kSatb=K{W-8hSU+T98x&Id_*`o0Dy-~55lf$AR?XC-juf5-(itig>;`;%D^1e(C z-H)MZ`!O|TKPG4Ef32YUUM;A87mcaE#Y3tK*AFNvTtb|ZbQO8x!G+{W1(y;h5L`=~ zLc5wYWp+7XlJtV2wD=VTi7;%6(Os+x(VOfFQ28tik5-M#4PM)yVT=0}#Ol5SthFx_4qWdyCZ$IWH?Z@DR{g@rBk0a`D>45rKL7qOA5vPxZgsDoF5~d|sO_+XQMPX`z zMTJQOR+Xg=Syq%fv#umrYGpxs+|pu%$hD41ES2ZUj3p)tUpAU^@IGjzE8XB?})*^&2{Y0(4Bn? zXlWmU7u!!T)%FJ(AVY zeG6-49|G9fPf&&S2Pl<&07GJ5=Ev*D{9yf8%hNZ4 z;`EJ(A(Y z{R(bkAA;D}PjE%{2P}#G07YM4CTQ!+1XcZ*9;#2p=tEU0`c7Mjep45s-}H;LF#RGa zOur}z(YoUh41Ir#{Zw>eqa;zJ@B- zuK@S;A&iCn1Xy8zK-1R`aMbl>hO)lQ(A1Cdnfi*q0fE8qfI$5_7_j~j27teW0oG4J zfZews;O%1wfc9qshgND(`aCj8|3`-C55@R=qZpj81T*uWUMPI3 zmj+*}Kk&i&n19yyOy2q)NVV^0g!}%4yYEf$ogV`O%Aa8Y<{?k6!|`%4SVzEc9QAB8~cQyG~0R|A;-Rsf<8Cd2c~Vrc%_ONB4j zGx&M^fGF6$*Aedf7Vf?m0oeDWi+!J&IpqJ`y!b#jr$5N1^9$8Lb{V1}>{7&`&gE!E z%_T_#n9C9d>=vgBd0V0?%xRgLz^272f|-`A2VYsb7+_@yOAv*ntUwZ$wE#d@;9_pB z+`U||($_Pv!tDX{822wQ+h-DHv~*g z>IMuo*^9wpl6|qjB-|%xa_$4rfcG#d0{b3>V&8*W_Wk8(-#3~_(jvs^NXif>BqBwi zkX96dLLwy+byP`2$_Q}?RndKj)sTBgRuFl}md{p**G^SP7*5@gvKqG`XftL*+AMet zk%M401W!TJ5bJ;;BHfr9BITC}L)d+R9P&Ou3&9>{gks;5R`$JSXx~SU=zGE#Rb668 zO;wRG6}4nWRTPdIR8prjs-iS$SV1w)z6mmmyDeipMRPf!5tX!y*u zGI=JOSXvX#E6iD^mF>){+ViwR)q!r4+(gAeXQXKyu+lh&s}Fw;nobP|1ffE{%!t73 z6QG9nG966&0GuTH0Fy3ynL`~tfjO7{fcPV#!C6^6i*wRw7RSTUEDp8tERM4g4bIVV zt!>cJZH@jYcczZC8xu?JeQhZKc|eB0uUUMRT&lZEaTA;-x+skk&Lj4DpUDy>gaFl{ zvA~drOBk9DpgjZA54`}<68(To7QKMS8vTGlB|QT7?-O)?;M*J(d>h>pLN+=dglu&q z2-)g93Ek!(3E|=h3+deKhIeah#HtxN5i8bR>~>ij#aDQcXec?!H_}?A{f7*5kY;OB zae~z6C_?-i*3%Yy8un?kRvlVYc{i*lE{ z3v!pcGjW%@1HIGPN#5<~4FB@t?tpx4ZozDtdC*JtB^-ZY4bfS8iEb%6M!HhlV_=Y( z2pdtDQpelL;oHP0YlRNqZhF1Co_1`Zr|Ca{VUnj-AXD8*Y zcNZm|hc_gSmxm&bmnRz6!=sE>>k#EuIY7GhEnfewq5Z~YVL)`4R?xjw&CjXo3;~(m z5(|gU8X0lEA}cLaReCB=%Jcw$hqFXap%X@*Ak#;`z>!JcVDR@1v;NXOm~ZM?Q^cazkAi4 zV7>{CP?0DNF;XY2F%!d8q$mTX$`Ap3GF7}-(5%r%pd8XeP=wM$2>ktn_kVE!{x1>q zew`F@*Dgw#dk4kL%{yY=>bWp)^*ofjd6?$!UE^6ymq(l7>S{Gxo9#v`yXRxx_`T~& zAGFH5BRl~ z-VY-I@7GBQi|wL>#dlG};=Cl{uwIO?Snor4oVOV^-#v=abcJJf+`SBMd!OlO=k$FH z%*K0V--lOwb4DmSzsVyuK}{aALQxG`il+{q9#IGgnv?-^Bl+WnhEd5&hCG&+3ibaY zA^l$-a6b$MyI&`zd$yC(J>Ny~p7EA+(Rnqv=)4eIHC`u|eHYn%(;a%{ae!cZn_LWt zrY;YXjnRi*+chH=-#Ze@ZjOoMcK_@#!*atIEYPPHVAHc zxgh^96VCqyLiWQ{p!;-FwCp-5+O}O3t^2M?){VCV`_3DIjpKo0=XR7?`<$Zo4-U^2 zY^!?-)7oG}vhtYm%WICzvZ^nm@ZOzBaP(21v%k{~UilLcq!42eIAZJpKuFPu3{PPc zn3});k^%l-Dv&=+gzJa7Aou5{Smkt6tn)f4R=OROKKtDdKEFE?eD}LiKES%mKKxvx zl@N~bddPOqE~de`k7VfYyD z)L^QCB)k&=LVs9fbmkvsgZjf<_H3yb%}6R>K5Uw&^f|ck(-25 zB3B8ABMuXeM4aZ^XdGu7rrhV+BVB0PVI8Si;GG9Cx*iBGHlK(r^3}xDcDrI}4;({C z&Ya^0jU6Pz7yO8XtGN>fPxC7Vj^bay5BtZ|jDL)V?2pM{_hUGueYwasEOC@>Sl}+* zu)J}$IdKDR1JX9ymZGf#o2i=zwN5t>X_Rjv%oyWFkP*(62>Yu;(G|aAfrZ|KA@#M= zSh8+;4Ap^y?5vfIa1c{Bv7pta@<7VX1%VR#4+uJcnVavAnd$p6Gjl(t=IpC!am!rO z;>NkAg)LM~%bEu@DQc%_scIt7V5pfu6QOqEOjIpJS>_sxvB)+TVTo=ryawTDY}w~< zT(#|ZNRjn`FlqaU6y;@+__%qS*p!W@Wau($iBN_93z*Koz|i;u$l3mw4SGL@1Kf|f zA^U5hXk64v&$y(Wo^d%dVOHgA1(}jF7i2rkWQ?IOt0883h9k@qO$V4|S`RNrwI5rC zY(cUB-H2Gp<5Hv0bV!=KcubD?s!)LBu2pin-7RgO)$awMZtWmslq3h)dIR9sC9)47ey5f|Cye2x4chd#`iC9UH^lr>qn5p z_XYUaeE>RcUuMVb$K-^4uOBv}>x1dw`d>k|eixD~EL}{ro^UW zvP`?OV1asRvC90~QWXi-WlAyZM-7gBr+V36TBH3VFxwaEd;2_cbH4@+?_c=Z`w*)3 zeu5>uF91mH1L#@%GCgBICI{?qeJ>wcziUX==SqV0wV+&O$)bW4CF=^7k*q9NJF>W3 zsmS_PA+&zg=+>v=-1<|MTtCXu>q8lS{ieULuk}gkAeQb#A{#P3~g|v-=UQ z?0y0!x-VeJ?F0Bh`!YXZKSt;4V|}h7SAQ#s)z^Ab^{=d0X|d8`CB=%1RT8UT^~35{ zxw!gNBd-3G2dppknf0SEvpy8$)^|$e`b>geKS>Vui_T;}XnXc~@Ya4!o7=}Q7WX4y z$^8UQaDPCN+6N$1_GN;)evFRS*HTjTvz$QgPD`cy)w{!|RA zKV_ooOMR&NQK42JO7rSFO=A6~#jL-S-}*>(uV0i7`#@c?zoUotYu?sA1~RuFLF?@& zfNJ{#j?jL9A+awrwDn_jygrtbsed&^>Q`Zr`cr?3i`1X0BK4=5NPQ_HQeUcu)Q?Ir z^`SyceW+2Y|HN7Kn<}k7Q{&ZFD#ZFnep{c2?e&4cVSlGu_G{wMK8CWjA7QKQCy-kE z1CYvofFiIDfGF$7>~#GFhkt^C!aqTQ-=~n^{VgOYe+&q?U&8^uzrjG=r!nyMZ3?7) zoC0KDhrrkGA)xht22}kZ0Z^Yvz|uzwQ1q7o{Cp@GoKF=q^R-|oe6W`W|Ex#w-+Gyk z*Y`uPeSgE;_bTRn55h3>Z?ItbIaFZ$G!+Pc4F&tRnXvvi62yNeg7)`7*!w>Z>b{VJ zw@=g{?He&5`$-GFK2w6M|Ac_*OBrbTRs)Q_R)C-X1>okF#bo$wFcJRTi-KS61z_p+ z{SI^AyO{UA2*SP>l?3s1a6J5-o6XO26Zv~=4!=*$-v^>`f1wTKUo^x0M;iLRQii+F zbV2SvSz!B76wE%A1h9_9Yde4!y2UuX!#9}@EXL_w0j2BSYLrc*0ROq^AimLRJ*DHT$ALb6klI$}?uDjH9@ z8rn|L3gVm6^bR$a%J)mq0Gl~{)vlvaltQ+0=w<$_tZ6BcrIzIcR@##k zT4z93XrYx@v4!@jg6k|&g;yD&3b3)diZQUYinA{9iZ!h*i?^yO3p%K}7I{i=0^CTjII6wEsYI=t!#V>7q$&i$Hj-LcXcP? z3I#{Po0Mjv{3C|KU6ZB4Zvs?{!UIGv9%E==VD<}Og!TeXeCP#uDA5b(bkPTh+|eHp z{CxuTuPn=ZVo8<W8d_pAA`c6oq^@T>J@kvIq?HNkG;rUI@)wwC};>wm) zHt!|&?3(Ef)7vC#&3Uq;+Ct|?W~72VULF)PRGm&TND|=*V26ZhKcR$)z5wcrzCh@W zJ^`hao4ZeQ~Uhb-7Fb=E4@C{1@(Jh+-(Jd>A_=crKjI$yp)>Z!$^YY${ ze01l=EFAdJtCo! za`X*2lk^ZCxAYGL|BHk6hk>yDc06nnKfG)bKs#-!Al-I_Al;TUux@K9c$ZZa+_MG_ zTKD96?OnR?Dt zMjt^`$cu%`CodHg|4W4Xe}RzxFcPpI-j_`h@7p4oSGh;(dR-TFy{-rP9v73|r)8{T zv3Nf&AN>x_V+WS$$au9|mk^F-J+y=6DdMr@921?^BqN2!E;VbkI7uZufr=m?MB3tI zL#d9J8O~G~4wF+}G93Pw3HARH!Tez;^!;oS_Ew98t=%4JXSqk-Sg(xt)%(Gn?P|KW zTgSIIOBfgT+2v+EcYR%F_JhBD0qtp6yr7s~p^rqa_j9@{b44AKgA-aE&`xB+%ae%;$ja6G#hrdtQc_q}>q(5{Uxj5Dtl*SzmXvg^E(EAP%Z6^AE1N;?dp1J;B6^1xN)u! zuDqLKJLi&a>s!q29oyIq>?8c<_wM0CJ2-hU4t<7X3#%*3wC~NXJ$mTW+}{VJwS0*k zF+~>_t{`9eVfXR`Gfg{B$1Ay`$0FM2~Xh8oM3)&wuL-*-eCE0p5N!G4Kg1v83 zv4Gna*TAjm6|ddA=CP1i!@fhUqFx}^(Qf{glw*r6*~sS2GOilbtMBcBs;({~sVz_i z&zRv13|ovpB2+ONVeq8*1VPfF76L-}A3$gRV`j`h1_SoTY>@lbC04-e5-T89NuMA# zNnbvTgwIebql%afQBA~pRvE37e2;vKR>{0UY^9ywHZ$(+?PNQLMa{lu4_?ef3m>yV9UHt3hg85iR02WDaEgMZ{5LT4_XX_0zs!d5kI7*DF*IgC?n53b+$mmY9I73r+z7fyIumh-b}8Hh@mQqM z_h68%tu?&H+Z|U~FpwuabLx@XdWp{&Jc*U4{_c8=!S#Z8ovlQ$y4oXAdtW9=apfpMYU(RFV(mYp_5Ks2+@FDx`WpZme*ibv zFLOipV`|zyoM@etx6!&OZl-io+C~5Vaa{F4T0ug;3-1Cc5UMEt4(C zTBO^Mv_!ZfW_URxV)VKsUh6z2Sz$gXQdTULr8+MbBDV40Av=E#T}evA#sjY20rE_Wc;H zz#oEB_ah*(egQspA3)FAkGWC%FV?P{#ZbFqrX$S?SrD`;V@c4YfJH$=0!9Um2iTRf z6<}G;LWFHG8x`Y1wmH^ijI!*9F`9iZLbSi-xAw8xZhuNF_nWMC|A=ny|EyjAjhOal z$lC6G0YGsdzz^Dwxl#K$(6E&4IJ+_?#O#U~5;H4cPt2xtT`^PArNs_l9~BsF%*6qOoLzdg5Y0!m@44D-?8rd7Jq#o zVsY}@+%*0ho5GJ%bN@Ls@6W^G{CzfjKhO;J4{6Z*MH%Y;5eB%gbV2PiSup!g6u`dJ zgspECq3UZnc=}%pkbc<#puaW%=gY}t_<1l9zV=1IFZVr=b>GXl_k9V)z9+pL{XImR z-)AWD{|F)epdi671SJ1OKk0wu6aGp(+RwC8{ZBe~KME)AQ{Axrs~WSvwE^sZWzhO$ z7N~xjgr*PI!06u{@cGsNHQx(E;h$d+Xo2@IIR5%xMPlE7ud#$> z`g#$PzE*+I9}B?g!iJf^h29ew64=YYQ0!qwSoScZqkW%wV#pRHtruICv_NQK%Ieh8 zge95f>FO{`l+_=WDXKS?Dk<|7EGX}lEvM@hFQz;zVo7sW%#7Hqs0o=_dGi6QVuzDe zg>Po63U>li6|({%DsKTbRQz5XOv(2Ha4_}&Qe5^irlNg}CyBm~q#*`{Nz_;oCsJc9 zoJfh8HklIBWI`3z$i(Vva7mUIeaRNrdWn~oSV`EGRtXtZRS8;Dof5XDHYIULXG-pb z!j@Qfww8D|P%S}ESXvTK@WTWUzzmbamjz59`vEaBdjKOwdjO;&dYQ}^y$m7~HYz-> z)SmpnQu`4kOU*=(EVWP|T4|F&xX=!Nc%9vUfRVX>jER+hoPBkFtZi9;yj|S^p_76G zqQ{gL@t@h@b(VB8o8>icIzgdT{mwSaj$EoQmiJfV${qBz^P< z0RLSS6kK*uTx`u1fzd^`0tOcy>5DG9$`@a9h%dt6{$7x|y0g>yi&(d_+Wm^{Eg6);Ee8#z%=dRz*ZL%lku3yL*dhn=_Ayd-Ia8 zQ@fU^lIfkudd;B#SFx!`GnKWF19`y^3YA7czPSs5$xv zomE~YH2#+d_YVW1{KG^V-NZy7*~COD)5I(+)x;et*Tj~UZQ#U8xGtdOoHmY$XGLf7 z>4i77b=pp_TlMpNW+&?Qg6rTnmG*(~NDTEcC#yszg(noG27*|SfYH$6Mb7|*j~5A~ zOJFiwa)H5+_+KQLnJ*XR$cv11VZp{cuiUXu ztA^C}i6y~!+)8s?A7)z(Z?hgI=Q(J#Cc3CZR$9rkb^3WBY6J)bL?=|ha7b|jCIbi} zF*BY~Vr(RT%nad&nehHF6}msj-<}yrVBY!1;2k;!paMF42AWNxsd&5AagAGNBx@i;n%0G@mF1+-ucv5LQf$4eB11}YCJ7954BAwbAfh=3kX zCIWgYvjpH_{4z9vKgI(3$7HzvGZ1$d{o~C|`-pGXI^^25h`4s`8Rx1a<=^w+d`$kU zqrdTcTbl-y^NI2IyKQ0qjW1Er%&t*#3vP1r2@R7p2+VWDjMbb+PF(9P* zgg}sF7XdwqA3%-YkFj9>F&g^*8Hiep{;?LPeXzsp9&IqY1UtMAWslXAY%@C1ohA#y z-Qc-=zFpVvedPNfURMwi&Pw=rhL?EB73cT~rAGNdC`|JNhbvGS4WLGUC{XG|f}qH9 ziUFd>PvFP!2e7ICm3|a-kUz zd`IWG@b#fJ{cg+)&nM%wewk@iJg8UQ;RmUV<(BKzv6aO+Az&~bY@0)>uJQ#~deaMdj{M@uhv7nS zAUTmcKRa62>d8E^8&j@3_e|^iEB&&|qfWUcswgUhq+#P_2}A=HC6WqKkx?KxIdWm3 zl=u$>34Q@M?=Lgs{V^N%emMx&&kTfnKLg?Zp?_=x(=)V#=@8jG`crFYrraiu6}yb! z!7t?2F0Qn?&6#;;wWr+oO{#{apMvEDo}hv=d|`yvxI-rlk_gQf3j#MN-E&c{e zh97~@-ye|E{xTf6WFc9>a**txIS7`}4CH!v-eFZdm*`_Oi&`Dam)A(KBbM@OxW(K8 zdO5A~Fle5-++j|vK2_7gO@Qs$TU5#6Z5*v70{IzRJc84;_+;iv5le+B#Q%UP{}T-P zeF8b^4}d27V>amhAy&h&5Gx{CNZ%q^2;XBl$R1@`g`OqZ!ye{%v#JS}DvT0^yi zS<*f`_d}i=j4Ed?pIGy*S%me)X;|6)`Cw`rEJ9>fxMTDF=Y6l=&Q zf)DU&0#CkGA*TkvSPPeHgk{-1vhd_Ypx*9Dj?x67{EQ7=x#7zH3|06qq3Q20D2o3A zJLM1HrY|**@>?X&@>}Fi^IOF3^P5^%`qeQP!fRqqM7IMSiS1_HiR|QE3hQDXi@kgm z#+{#)Mx1-AW33DZLdG>0;nG_tnUae?Sz5DK5hDBJlH&#VXRQ7{1*iIFP~`pvdc+?9 z&fb^dfcNh&v1*b0GLp=|-@B6|u40~;DQ!-_d)qxx8AaXVpX@U($oguLS#n&JYV(C0jvHiJjH(lBlS1%^ZfyI z@V*Sq-KP`PBAF}ACXqYMCV?|?Rr2P-n(CIrI-|`;ABi>~d>!0^R5#y-=uO6@-Yw1@ zq08GPfwSu|S!?$_F&k&6d||y;uI|WNp5D~|2+jR{z~a9qEB-A&wZDTS^FI)@{Q-FD zz6_1szb=UbVO;_z!n))QMIRHl7kowT=H=Up*7^JxLwVtS#0 z$bA`_y8l+9+QcozUXwN$dQ8}K|7)_XGUE zeVH1%|D=sYo)fkhcum%H+%-`Xa;M}h$(@n1D0e={tk|_E!%~N8mW7V=Ov@Z++Lky< zH7;+BY+cw2-G16V?vuIe{?|C~XI=Tel%4h~wS9k3+4$phk$;9N@rTgF{RoWqegL4i zFHqf)1X3=5qKGA(l<%CyLhnr(^WMC0;S znbxJvaqX`iw*50exK9?J`(Ng}pS9ilQhCr-PEVW9~^BkU1x3sdG)rn9L;^lQKs{?8=;tu`F^a$hO3tl5v3>E#vaWdDf-P z678=&ru{U;wQnZq_Q&RN-z!b`v%Gj;YOng21Q=a z`d$izKA8cXzb0VApNpCB_h1_Q?#qFX?)x9`zJD>;_aY#N6u*zq;QtBo`$0kQF9ekS zL_gwROPYW+<(GZ`%*V%zp5teXK~p2T^g$Xn1!dGCIRWgH3<56 z2Xg*3z|0TBMEL6$0#oLFZ)33UQ9Sl9rJYcgpXjOa7d;t%Bd5TB)HMG|OzW?-ME*=l z-tUBj{ZL1Hf6A!tTM@ziETOgE1yuIKdcM9{PS$6unfh}Xn*QDdqrYA7^UDKhz8mJj z_v-=p$oDZWAbXe)oIOk^hz>uAEAuODMgAqN#Ltu^_?xik-|33|qpHZCimLmsq^w^n zD(`na(S0x`w@;RY_S1~S{#y{&x7)G$eKb(tYeDIw8~A)UftmjYvw+KCFTh4*FLR=^ zmnr?w!*IHA(WU9EMVF>C(=JYCo!_ZUvI`U$Vi&2gKrU2baxPY2=`C4a;VoQT+AUyO zd|Jk^?6jO!!D(TOO3M=0M3x2b$ScbgC@Ty2CJIaX!qF9Z1D-2_0xnodd_j<8vKR2e zvj>3sp$8zkqKBFEaYTw#oDnHfaV(@v!Fh;61-BWc>g^HARa+sHEH=LVL$X%^D$%`s;f>ru@5*EUGqP~H8LbqV_WKcoQ6Gi|OoLKe+ zbcFT-RGjt#KB(vebnfU8ApRd)cuRn6;r&R>!ZT4s3s1Dj7G5P0F1th`UUh*(!sg;b z$l%aJ&f363*3_^=;LP%d)P>0r(c4-hlBFsuB3}ART?b(qsn0_Ju^NOvS~(ywMD^fIaP-3ifEyqs z`UXE>^aon_fPv6~N{j}}4|AdL!$@HN%`|ln&NQ_T(X;f6QnR#&4rggh(lqoU>6%%v z!`oP1I`>t3z}t#V`TDjXws?FY#Bp~c)M#-f=3Qzj@K9qc7=^fCU~Z7ALAt;w2MHM) z9xz@c%=i&g0d;t&tBsY&mQ6s&(3M6SBEs@3xjZ|!WfPK z00tEOF^5+4 zNl)gCnK?6O=FDjTdO9;2gp*}Kx8$E5w38k%L~A%i>+z;daS>aQ<+4nc%O%~CKYEUr z#!QVfV`k17Gh^m7V9vA#R>)uZBmc@jJ;e*$(2jEkcW0(t(5<-AfVhk;F-HEzMSLyG zF(xj&7BA~-d9AO7L$CsqaOd)_l>6WqCg2^0VFf1O1_r|l_rp>5gCp+ia&8J!U;^%4 z&Yi-_ckmA0f!7F=UJJwBkIT8~ci_G*4sI|EE7%y0ZUtDW71I*G1NU`#cUl`UY@uj9 zh9#{CuM)ZF$bGEAb#M*X+8SWh?1n|zT9v{^*!mi~<9w@?Ms; zi%P9hSp+p@b=eJjNbvy4HK1DbK*48b4Xy%I3O!K}71jJv00mG7jDsYog5w}lRDp5O z6a~;A3ZNiU!bEAfglO8KO!Pnx6rEaw{mDLAXZFcHStsknq6T`RAPO=Ibb_8aevn#n zXqn>&Q>0+yKuHut1%#V_Z2p~DGbQ2jMP^F89jOO#Wq9SkGN?b$CL$TFGb-U~z|;J&d^j<@{ANRx-t?c+)8CC00pmnYF{TpV%vjiaOf zxBw%5RA2&_;6n{2eAFK`m;g@js=X>O5U&$Rf|YQc)RWtZioi*@PLv1^qGbgrSP7O@ zK)kBjP9*A8dsCHNbqRT}m`##9aP8b9*UlZeb~eeI3GPPgtqs})w?^w0)=r1FxoNk) zF>Si_C^yIJ7G|wJ)aNGT`e>>)N$V-a!tD9^?BDm=RC=c=Z5rS`v9d)8I58 zWS1|JKfvS`L3q-c+$Paf-N}6tJsA$g9Xl4i-=|#! zszf{O+@)hXa60Chf{XBAN;3^FKA55whnXHs>BS+Y52m7rKG10KvAC$C2Qkag51tDB zP1QlqG~uBieIj&HpF|vb_%`C19*Jrv+9ui}+9s+^v_-T}!+0xnsT0i{mif ztR!0q2&WOD!SRc%Jb21gE3x#?2fSHHwh$0bBSJ$t4byo^*&k9k_w`qq2^2d=JxdpW zL`n5ir>i+jTSJCCjtnx&G=KyO13cQ zLdywHmX$RnxgO-Ko~4Bs(*Y1pFvv$WN(oQ@waI1-5_~rRsYC=*_6PL70YD}E54=T! z2nAL!1jz|c%<@3P2pfWOg}^I-JUC$iET3>WpR~$cG2{MN8$Jrm~c>)8u9o|5Z?tR9Bu*W9Wed`wm5~taTKUt z113xW@)ZIPKtI6=C$PfFlMUlzPLP-Wd3+%Yu0`2xRy7bPtkVDh000000N^){0^lde z|NsC0|NsAg(tp>ta+VZ`=DgwHLC2%+25Ezv=@M%UabeZ!XNlv}7bZl#X6 zwfdk|>wH?TU(hmfn-+@6v|{JVl06FR#YI;$*1@{*5^Dbo$crs`F)Z&jz-Ki8y1iJT zTMn>9w{5OSY{6KS&cw>}9#yB)R;8|jO7&JM)=8;c_oRCL!<30Hr%o(6mEsIjuw$4~ zaiA27^Q2lVC-q`9DH(%B(fA4q$4O2-25K7eR7GBE$#YqG31U7gfsd*n^+Kuc0o3Xb zJhdKxQ;DH8nK(!jifu5ZxCK+}CYDx=y2Rqpr50~0&E7%j#Tt}g9H}H@MkU$}m1z8z zWMjLe9J?j$m<;L15{`!aWR&C~A}H%1P5CD*KNaSu3iu}nKIwjuQow$ZV!uZ!mcWR` zX^L7*rO3rZh+v$9D8?{|XpeldXQLjMUR*AfGbhKp5bU4)~)6-l&5wx?iCfGQL8zdk{3^2!Lvg z=EufWd~j@}C&#rswaeh)F$*3ao7nkr>Q0bHcZBSzL*x)TM&_V%WJ{eSKk6(wQK!j+ zIZ)QinQ~mtl`Y_Gc>+$DqizPcXnVjvJQIuqbioVVAG%L;ad>=hdd$Jr#}I6POu#_C zvXGHXxB$thm4>rFoW$)ZEKb18?I%9&TW@+&*8Wej&2a4_3l=C;pFVFS)u z%}qxz(e@6sbJy{??{n7~x=Fq(*@Kll!AnM9CTrQrONKI#rHo@L$Joj%#`1}^%=(s1 z?{es0?mWyO9`l9)c=7?xaGDJ_V7?W2ZU#QPfxDJqsyjI8JB05)H}{^Mdr;85U)h4O zyuexp^Om99Wg>&Qb}`SG%(Dk@>;qilGq0ZJ(hE5B1LhooKdiu*uQ_ryEAGI8OYq$l z+%^VNxP!I6JAF6$F7$l}=T7u<1NaWWz04XMzzrEs(f1>so4|Jk_Fy$j z@B$m~nzhVkCbwD04qW>K(>Q`(EWslTdq1&^M=Bi7&$bMS^eSn|8#_rULU z-_^c@eZTt7l=OWG=l&yu|B=Dh$lzIE@TVBOhYNnug4e6y)hPHc3Et{~XKLV!78#4`Mu!0mY~04^i!bzNrYcQ_DJMDh~Mv6{2CghDDXuT_#6uS3I%>tf!~n8 zBPQ^82)x<>4_3fqp?RnQKFQ1zmH8kt-y`O4!u(+VFMpRm%Wq}prwIL%qh}KJNg}+F z3P0rSd+hxU3@>ByXEg`#@>jh45if7U%cJn}pI*Mw%Nu%myDqQR<-@l8R+gW_@=RCW z$jT2@`JXDEL*<|HN4a??KEGt>ktBT)sUH&IeNgz?wXb3KFa%$=@nJLn1ZW#3pQg!M zY4SptJd7sK+T=BxJY)T z()W;h9b0cB>}k{9#oVv(dlZT9TKTFXKJ~PM9^aRNh$FcM^q@GsmUx2-=*~h}Y3B6}6 z{L{xL?n-$BE32=N?7p96~LP-BGE&Hc36 zVgPEjT|m@oi-m;MHVBEUZ444v+gB1(Xo|6D?9{Rw$3NocfP%dx(Cts956g)j~rRQr%{Y%-e)bW7}=YrtE z(I5oZ4I53an=K-^ZitBBx|tzj!p(_Iuba`$ukDA;ubI^NC!M9|5O3SAN@spxI`TCSH{?Ug9v7CD; zmcs$2SWW{Q#B#o5hUFZ|6w9%ZNdX5oa}38clk7%BX4#Df4rbd%4rp6L4r=>xSlf<6 z+i)D*M$3GgLMGf2GUcY)Ft-xsg@-Wh&e5Rvm482J_>qqfx#1x>|IzdzVIT6wD-OAl zW;z{6TF`l5(t?f{N((wkCN1dTm^9N_L3x&wN|}b!NvU?Tfs*Y8lG1I9SP8cctc*La za_+fGy4fh}E~B*DixO|F$-JE;_b!t3+b1&c9}T~8@iS0ri z*|`|vcs@Xct)AiFbcTet85(ZE5W`=EiDMNq?nKx)5n<#zgOx9s`GJ~GDEft~Z@A(Q zZ~S19-=nf~0rhw;U|tUefq6az1mgKb3C!#95t!E#BXEz$L?GV|X`r8uh`_%b%)me# z$iTsy#6ZIBV4&faFYs_%7l`;M)kLULJ^;FQ;=VL%$pU;%OJ|7@`emye!{&-OI1@4&k3F>h65$0s} z8RAIx9o{VVA>I`BC2sxt7PohO8IJ37T&)lCu|CPU`YNaDyF7_6^B_JDzsLD{qVJdb zfG9pN#tZuRz9nC`W#{G%^4vVZ9t-jWeGbSI^r@03*b^jguxCadArEYx;SOrvp^j%B zVorxVMI6e!#hVOy8Ey{q8n=LXk6XRG$m#MXZ_BH^Oz(29ybWLFb$BT+#6x(TZ|_3? z&h+Ur%>NITa>@bJ3%N)(l!(nzH)~XY+Q=R8T z7edduQ2G~GAMeELwRpZ8zgOh#r2JbMImVqk$H;T%7I`w5TkKII$Jmoajyi&AhwBZn%hCQ%`Kl>=XGu)c5?&W zf*Xmw+)A%%OFgNrbs)AA-=^Z@Ts&QkuLJTInmpW=KRZC@s%hBSK<#peP1@y-mo&^B zC21RUYSB38yg=)ag97bC4k}s*oKG~(8xUxlH<)OhH+e5U_Enpgn^U_F{ zOFKO+O?9!f*0s<|+=Q0mAv4)OgYj%M9&X3W9eFq@zZT}n7Rb34KIn8{;e(C?Wg&FN zlxgZTArq0)gKRTL23aRgY=sdxs}(+PNK^>D*{nd~rb7kNt!0JM?IYRgR*@`qLbBES z$Xd4}d)^0F~fSM5F%f_zw!FI)0vRK5(%iy`n4DlomdT&Fh^ zaHZZ5AYt{UiiFl1A`x7-IU&ac6AGOOJXZ_+qt#k4_hq0+-kAp*6bO! zUVOL}U9sY^-4l$B|Px zUX-exP>RQVQa_%P5;9tpk-eanOyv~iqNObNJb8IrHRiL>Jl2}8!r-HBi`ngcaxn%@ zFkZlkb}vjcX2N9SA51s4v6SQ4rP@;{?U+@m$E8X>{!{|8h@>EMNE)(b5|JO1jGUN+ zc-lnL> zV~BtZ1wxRAKp1k4p(4i^GV+QcB%2sYvg**1M~9d^LdeOQLQm!(6y-{xDK`pL`7j8} zenDHl5cK6VpfZ2`q`4|+%}GNOJXCb|LAv88-AB6TEZrgBAR=b~WMl?>l02qI$x(Kg zOk}6YwL4Fqp%dj6I#W)eQ)O2jE2HXQnS@T3J$1GmLZ{0ca=tv7BWA{&G85(u@E#5U z!{H!sSx%cJ>{zhW&mC##K16i)A-eAn-GPwq&6DpI%J(Q^u$0+MWdN@7l&^f`EcbZJ zHtzC^zr5lwpLooym)UePi$1`eqxr)Ge7ONfp1_GOFyL&q8-mro;IQvlH}@uP-OC#MWd{Z`n!|i$GAlWNfh@o|Mlc(LICadzv{o%@It%2W-Gw zMqno=@Q~HK<2BcqfnnTc*&8^;5Bxd=ryjwiPw?jythohm7=tC>V8uNc@cV`DY2UxT zTYYExjpRr_&ypRN2U}upM=f#VDm88JSzjQ$-qCZdB6oeZhh3Km+eUz$qGU1n){gJsB0{A=_-v;K#=m1tgslP-M zz;_AYodEDW%6yA5e_G}}kogQ`{%_37jd`;$@Ac)eynIxbSK{(TTwZ9)^KALG{8x6K zO3+6sdM8c4#OjfBeGv;UMDBa^eU8MRVfimMe}(A-Q(n!K2Q%fDP$j)&_cs?uA zGguLyw~G8cRRn0IB0&=s5&DM+gttPF{>RhzxOyEE{s!64XnPoTFB|xZ`_H971=1L+A-|1WkaBprO}ArnkfgF zwon>_S*6#!P#OyirGvm&8U_reTVXiu3FGNY7*I39kXjJN)OatbhI>&p+6${Gytta{ z1=dzBvTkyrb&-n*`^<7Z3kn~L_9x)pRqs;?zqIj5V18)lgPwkswHM@ZXMY}-^Roe!Ju5I%|+nx)3 zdo2`hve3A@K$hX)7?J+mRscv?fC?SO_*%kiMehBK+@hO?*%cSEQtx9t-_ zx3v>d_nQd2&qUxYrkVGa=H5{u`Zfab_s@sH?||VmGd!k-zZCJ8qk7rE6 z_H!-Qey)bKp9(J5ehR=;`-y_N_TvL{?dJt%o6jqz8xM&Ma62G2$mv*apv!5@tiw6X zyt@I+%-iN=?rrBX`(DfRn=BLX37LXdZ6I6;({Q27#J3t6zf$rkIXvZvr%Zjy*srwl zk3b&L31l45jf@MjAQCR<07WoS3BZA)UOyBVy8S=aLdn=aEt` zCy|m5N3hcGX0H-(d!bTr>sCp)S*77%l^D*VOx$UbaUw~_bs{Gp67wHJJS2&aZ2ia< zAF1ORiG1M{JD|&07j!e!1znJ#PAGxK3qn>JF9-!`tP7%Tv;#`fi1%aKnAgMUxW|*} z*ry}u_?NTz7>HB&IC$gtSa@^yc({#wL_F;9bvp9k4HrEfmrr}E~1_g28nt@Y7+B?t|aCS4N24!qLH{46r0!wl$z-0GeQxt zr-P#44y7WYP6kE8oWn&7IDm_ZH+hST+quOI+cieV$rvM_usAu@qUA}78xB(R@QIs` zLG%|_pE33`%=pD0Z@A?BxCGt8KcNf?{e~7W^n;Ma&~K=S;op#rLO`JvhJiqR2C9Xf6=V1V4ZVVK+k2Ft%NV19*x!%G-GEOfZ} zhM-?)`iQTeDEo>x9#O~-K6yPbK{t?4=n4)BX&^W_#DL%+ge`)D5RM2A4&4YW6p{@z z1Zow;@FOai*b_ofk>^s7Q3q16F-LLm5r=Rv@}_Ta^2Uy^atlXzIT~T+Vuc>oA`Gz; zA?HMcpW_UYe&FgGqWHrXk68PLMIKPf-<1Ihx_%ghHt@qR#K!LsLKi;_LrDBE4CUxc z6l&3@2=wa5_%rI)*dxNvk!OP+ zNQqtsp<2DhAQyR$KqmAce@gWvdph+fc{=D>?o{es>NxIg$U)rWfFro);f9Y7#I~+a z#MZ5^a|?Z+clC)*)mJ(aU+O-4uD=KSf3q)W;|GKMACuS9@^S?L-8;yj4(=d@9I%5N z8eXBMdp+nb#ox7fJ{kX4`+r2956atlc{c@qE}bQi2XmG| z5X@;1GBbA>^h6Fb$cNkpAX>T1pIEsLKC5ycdPLV9nO9@L=jXn4 z0FvO%0}vXw4?t4fK>P%8)BLH?Cc=kB8)uJenQLx76()9P9QD&h@uA6I+ObeL~!drQlp#^fu$1w;WH`t(^uO<_5Re8s2%iFOp zPbR>RDezwwe1sdTBIk0gz}bLH7N-FTvo~ZSz_@`DA;yi62(&jiBFwmXu~p+HcI(F0 zbqmMVbW6t;bgOo|xy57KVXMa)wtoD$739RNAqQ?5`ED!8ZCXvHkR@d-EG#2cg&C;o z%sLSW_z4O;*90Hoe%6&idvmX5+<0JB;|2n&8(TD0wc9FHJGMrsdTe)4{n*Z+0~D zfrF|rmDbDBxTj4DU&8uc|?+xHIcBq0ZGe~NL^k$`tsn>nDLGTxb3Kb$B@*lrS!m0P8nLZXItMJOlfaxhYVM%Jz?C}=oMA_T33n_wuSbIjL-(kndy>%|g3+BQ>3+m? zamIXa$n3!YOu=SW^8kYx%~B@dBO9=f5g5k_EMqm#zQD1s*~DyKz0IXN@aGV$`2=tH zf+OEx#6Q^YJKgqvmUH*&xmy9%j=`O8u!lX^@_UBwgWvDIvwau)o`rLV+POCg z-J6Q;P)C=|_cq@f7=sY7jof!0KN{!uc6^# za6XIEPl2KXCOvP!q~{2l^h`h#o})D3c}J6;Sv2|C)CtfZoCFQoL}M_&B-^kfXZz#0<9tHE_#jR~XG z=$ZlzueoS^T}318r5R!m&7g1&t%Zl-_ATsQMc|`Q{MX874e_d^pL*g;U;Jl|3%jM` zLF`t}1S+<0(y-Xdc_JGjXNRl=99dZ|997w_n@(A;n+>vH+ecZk?V;@0_D`1VJK3_~ zWG##)d$yP?+7Vc!J;h>eq!nxjt$2G_xxG?O>n3u&dxv%JRR#Z4@kv;IsOE!?{%45?h4CVE{DvZ- zWkFZ84Cn$9Wk3mxmH|~EEdv5VS_Tw@)a=JZ=~>TcX&O(6QnnoprfxZnrf@iirgArc zr*t=Xr*&Jo)4Q$OY2Fo1_vSL?o66L0q*B0vNC}?=;)6QAXXbNyJ}2sP#{MRa@8IJn zmuRkC5o$viY^n{Z;Zz&C(y2Biq*HBZMkkw4iq1A5Q_r`bP0zR<%n@`ukR$4J5OlLpZlHi$Gf=_7KnchCG`wj9;zOQ_-*7s<2+9uu;zNi&2-gqM;yvJa z4MN@#N@g6<t50>A7!*k$#4yWG% z#dnbLm^)tb$SYbYx|MZC*RsxN1IxN2HX1G*anW$$=!k|oqZ=CRidJ0S5vgA04Sizn z1$n^i`>EUj*weT{a0f#sp-y0?A&y=q;td@K#Vs3$4BIjek9Tp1yegA&qz#t?VRHCO z&DRk6m#mMu;y2Xz${+8z`<{^2J%tJyVsfUz9a?eOslCP*$ z(hq{@B%n};NkO1X%0eGV%0r&SO2i$)O2wQ$%M3VnmX0@XmXTXC4U~gv&@h(ehLoSC5(gRnk{rn3mlUSaFG)$`Uy_bS zz@yp5K%>{jL84K|!XXikhd~=05q~5f6?+yRGwc{bI_msELgL^>PTs^tT5i)MF{dEO z;VsDzJ4K2ZLga{VAV=R(^(A3{GR9Btct<0TIOPG$7~Mp|qnk*G)IlO)(gTTuN@NlT zm6#+FChbTpM0$~EXkI-q6bToiVzGh)35D5y=BX z3`r0eVo(ae5QCBvLkvkh3=@%J7%Cc}Fk~z`J#2*FdH5*g;V=@&62qjABZf*IL_`fb zgNPe)_!2qb)G250R$P9pa^D2@*&uWBty{A2v!iXD0NU0!sj5R5Qsrbpi9C_ zpGd+DJd1@Kb_xqWG5=ja2wq60QDSLJ+u zNBr@JNPck2_lbGBHAO~u&+@2(Jxe45_AHU8=xIO_qIY>zLvKS73q1}*u6iDZNcTVp zb?#{bb>Q3dvE=LQdF&gpqu2+kL+D2$r|+-yrVbEw>jtWpu0#OVVxMB!^FW&yu zc)}fDIOGYLJm8hjGxKo`5aDqsx_2CmEV$!vguq>gqbqhEjEdNOFv4LMf@s!G1OyXy zoI|H}oBMM&m6iE8COA;6Fe z9ZE4rLP&Lv6o|x}>CYw|s!s>HR36IYvTy+%dO5_mQoxtI}7H@g^K zjIZ!&oMczKk6n<@JMw%@e$UF|iFr8%K5c`c3l|FEzJ)^2;7t?)18*cCcyS|zu*9u& zsE6CB5UpEk&w_XKwnsH>1rLcd*`3a`*_{t+E_5!{VB|=y$-r4$n{iXf zW@F3OmSa2D#$&WLAa}J9If_lm5Ync+!-nN8T$hIn;M)>-vgR)#$l`Vj2Il>p1nYzwQ^tI+63J|7t`J9 z>D~l&H+s4sN!^#G?)C}|Aav#%lx7Zq2snbD13thffw}mo`H9b(i}0yqI+1;J?rQmm2_uX zx;H)DpP=rJny-$UZ_Wa9aIfH5s?r%T$3PE=XL-#47dsNaris??(bRD=q z@CHM$1WRxNPcWD%n93HcWDFLv2Je`IZS27^zR!M-{chsB>i5y_o!>irpZuQq-S4{` z&fUV!eS*&&OXyxzbgxRfUn$)m_ifyly`OqN#NP)W=fRV0@LU-@)dkN~!5dNVLlXQ? z1fK)J13#Nz&Gr%K`2vRi4APg0dN5nhW$dxI{S>}ug7`!#pNHm^3SLPC&x3-WrQlU3 z_z)8OqXZ8a!OubPXAb;Vo6lnNQ4GA&nlD=5h15Kc0)GO(0XmNc=*9r`{2d^k#^?fSo{RzyMS<_3z_&=?Q4{zN2)rW#A9%oDFyPe+crgM# zLYk*avz-YI&P8W(7CN)D&Hz4d%LsiKqwmu6S*qR&*GsAJPuiYI-XAf1Adz3|;c0}v z3)c(CY_K zLN|dCx(LkBKwpU7*@E;I%hOZ2dMFdV3E3-g`y+Tir0{(_ehv;#`}wo0AFJX@A$o%; zL_bi4=rL9xjKm7jG^`YDqKeU=RgT_J1?db_k`|<*^xc%D-=;8aHl=AWDNbuid0Hw8 z)JRdH7K$?UjuWh3()CElzDU~(x%(f3U)%ULFkeRJw?KW^6#ok24tcuifXLH!f+A2` z0**j!pCEzS)*^}8vN$reMLj~b6>-FB%Oh7W9>H4hh}L;VwvICrVKgHWrci`y2}LGM zMWkybB3=&>0UJk1h5xbkJnCKt;M-VyTFZYS;#rV>3KRd@;!|(j$kMquvT`iQk%f~$ ztyT_|9a=d*c5LOm*bxE8bq5y?=#B|D9Cmo!MD76FBJL2|0`8zN-i`{#)smf7Yhent zW=qg<_LL5^k#wjXl!NVW%smahkAdN5BY#DQhXs8Ut?#PhQ)hgrj}~4(ch<`X0~cOB z3&8m5!4l)EM@WpX9@-dTJSSp^?Tp44(+LrSEXOj21)K~r&Tb54q}}+*SlimkXxqZc zcpFVd++i`~)`~&*6O0QJ!LWPhh2FOid=(iU#pRdSd{@(NMe(RFo`jAUK?K4rQDG>9 z6BUFQKv6+xONt6ZMN*In)kskm1Pf~hbP9{+!wIX_vjLWE2Qv0eCowiIM=*8{XHUh2 zn+q!Jwrwi#E>oemmx{ivV*gf(4Lk^z@KGRs3CL&l@Ts7W%KE1)ezeAS`gjgYu4&rQ z2B&F8ZBWyUyr`lX9Z^j)x}mC8v_h2)LaM6U(1=yIpbo2ZKa;E%dKg*lb_`qdbpBfR zaPD0DZsJ@4w`s0|Q*bSO%N4^D(@Z%R2r;gV!U<&IcI>54!})Is<_0#C?tR1e5;l+Oo|^p8id zG*D-+ln{rm)bOUw6meT-x_Fmq<5Z=PACXS(GpgZ5Jv`{=gRVa4iXTDaHGBMqBoB#Y z>4LbxbVFQV>LA1gCI~d?n#hQtYhogTj;U5e-O}u)os#P&9uf)8JtIxdJ_wkde?pL+ zfv$HiYPd zNc|A8uSxqKc06X0kCgI=W14Q|pVJ4He@_xv`azws^n-$8=?CS*^6x2zhQKBi8U~k4 zFAI}AFAt46U?vh(Zde?0+|USwnEB`<$U%~4k3*%-g$|cEZ5}eWWF9`uaq(lxv{fH!dqK!0k zf|bO;lq#91^f|eB^no%%kp{}gA;`+eAVo{cA3{q@o<0qjI(C;GaNaOM+?Z|H+15^c#7@`bl#1KWL5kpjv zMnotYjfzex8X1{Z9UGNG9UqZGJVq8_K2Ac2gtQc5jJ*6Yl+5h;lk~8IHyI))j*{~R zt@3jlW=Uc!%hQ!ASqG{-@suoH!;IIk<0*^$4kw*t4=b5o4>c^2V!%Y|#E@y!0nr1IB_ar+$3#t_ zL`6;?!o?9heT<;bMxJkpHVlc!OTOr2y2!oAx4tsp! zkXKalh*O^M%l94dbQvsNz7R~8FGSM@e40*e_B5Tq?1^wh}6?>pb zt$U(JqHzPP$$>l2rZRV;Oi%1cFafa}U8-SM znj~v?id5n*)#&tYwFnfi6@m!7*C7shCW0#UQv5jX#r7cbvDgXho6W)VgK-1*H)C4{ zh+_$WJZ}0Ea*&^raR8sZV3h~-@_cB%u7Pj6N%umj(xo$1@_^1%DFQlFr89G?N>k=m zlZMQ(BH@^GJ#sNu!YIYuiK5lH6c9$~Vuw8GRtSN(APa<9H|ry5_o9dKPR33l9*rEn zyc;)kdbL}$Js*4R5jjdv$q?|QyrYNZ8@?{T7r@^o@Nycw+C92(u1NRo73sc}K%(Hy z#E}7RD2}eU!5STLQ(?5jZAFm`H(8<;ZnF?dxV;cUeTxw!`X&R26K=+j2e=wN8scv7 zJj&_NNu1k}6G->t=I(-!ZCl-u#p;-Bbz#azb6fVI6SGd8nXfb8=OB2u`xfo4D7tDa zimsY$kp*fkh}5LLfWRd!281PPv_n78W(e6xt0Ckf?M4u6S`MJrG#x*tYCC*D)q3=Z zSo6WdVGYO*CEJjk3~fbl4Be32@VznF%DqLo?9IwsZe3Pl3*ezP1@?hsU>&###&Nsf zq}_QeT-u>)VaCuct09y@nT;SeWjTPxknI41Le>Ll2$_!`9b`a!WRMB*ktG|_bAqf0 z&uWp6emDpV;`2Zl5gs&QM|iA&CE-B=wq(Z#7?T|vU`}>k zfI-nw0TxBa1lW`uRWK?!qhMBWJj1ZyfC$raa~a0v=27g+_E3z>d=v&4jzR&GjV*8l zG6CiC`tCNdzo7 zLLy|rsg1A&XGVlBII|JF;KW7%b5kOMn48oHWNt`A0AR}*L4a)s323&I5(?NZav`u4 zbe_AxF2!dmn7UUITuO{&V?F-bD_xG z+@}C;EO4Qy33J+L*=1i^N- zNrEkD(*#@3rV6$moG{o@I%%+7eDYu$7BG@i5l3**uh=T17qYAb*j4as3FuGtXYm~tj z)<}b`tI-Bq)T0izp+_HVHIMEV^XP6Jk?yuI>2C9u?ru)^3Q%_mQ+KMVJ5<#j%IZF4 zbs5-RA~xF#q0ROVHLwLk56ot0f(IC~U@Su!++=8ji41YD?J?BB7RS&B+ZjW5>tg6` zR}9_liJ`k)9lBf6p}XxIy4wyzcPlw`w}?b{+gEg#JGxs)x_2$zv6}8xQunH<`_4VJ@b#hK3w$iNjgJPW@#)|#J|7Il=YGWJ4#ekP>vON+bGI>k?zY9x-NOFd zt?SR-p8nh|=+E7P__@;C z;}n7ho`E(Wrp+&D^EcRh3N|0wW;^T&`oQE zHdj9wGq=n4WHJ;4mpR%ViJ3Lt4B z0F(wI)AY_4s<&Gr{FShua`sTpGAwCn}uoHK^CTM1zVU- zfD6-9x-e~fbZL5)%hRe{pyuQfwT3NIFK(e)P)pT#TC9f4a`jmjti!Nmy@f^VC@fna zU5PM|6|Qrpg#8n=Un2KN_?`%c{~`Ic9==BCzhM0pEB*uxovXwu2ZJS7IBS|*;b?Jk zg)_tn77ncwEgTb0wr)r?k#O^&3D=Q(#hI2}{V9 zT4LcNC1(F5N&6#kKSb|=7(S23$HC!ejCdERzXJA8#Q0SnJh*^vj|o2-_?Y0+z>lt; zFG0F|js)@Qu@U5}Cl&}8&uI{`oy{O)I+#Joavp;^DM+7>U!+BPmo+i5}G zrT_%)Eg*73{g|8R$Axu#;QbE4&ynG4T)vFWpEZ3IC;o(sXT|X(h7?^H6o@#Wr2ydp z8efN)W6#OEM#Smc&H%EU8u&4XK5!+EJ@4TTy6>HwYkF#0go>(g8Wn*7;zV#p4Nd zp{Jwgq7P@z#ox`^3&CxL7lliBf#E0@i4)B-p3?&IS!j6J&PRdzB~m;qj6c=!pG4k6 zl)KvY z!taL=MPN@~ioqSb6oxr(u!uKiu#DR;SjVkkBTq^}`40-rH}T0yjyP(qky{F`m2bIX?$p}hA=VJD>iMXrj|%&yF`k5u z?;P?Sp1kB2s@sWIRe>j7l>sB}tfq{(s{%3tkBatFZz{zn9~3I6pVJ7?Kqe2Ngh`g7 zhenPh5FsFrE(#5fHvaIDLh@WFrOatFy}T(q<*=P_3SueK&524!j6+KLqpd$G`y^`o zh#nu}$X`-<#{jDC2S2L|!tcrg5Peq&gy_Sn5~A-)NC-bG8X^9wVl@M$R7DU%fr>C} z0bji7h6xrEWwCMRbR5)T1$nVp#=MEH82QIV&&RMAD_Di($D$&r7LP_TVRsGKs z4+`T){b=3T9o&6wD%$If!K=v(Rb z4v0~r9TcBLc*wxC;lrcSBnZi)3?V3q4mC9h0gA}{$=md>Bgd(klh*0E74sAw%d>Q) zPSt@pOZ?`F@0{_QJN`qF$9(dUT^<7hR<{!I>Q-XF!oU(k)&iD?T3IX)wTxI|$hu*f zcqMDOSmmlAgVgAyBNQiQWD^HWN~OvTmPn47m_>*jI1CAP00Fe;K?DyS2+y3g5TZ9@ znx=1wSiMQJ#6lrrAH<8-yzwA@JO?CiIprP2eBm0a8=2wiMlxb)5Xq>uCUQ}WOXQ*! zl1Rp^9FdJzvyzWhB_t!KOeZIyJSi=mJWyUPX{gLZdbIRVq^Kdp5aHz}P~V0TJ$0F) zI&K|GZ^}HVZn-{`7|H|eLmgT?2io^g`y7Nk=8~Vh@{9qzU>mFJ2zhlKA+a`Sgw$fw z_^9Qj@lh*DBV<->qa@agMoOy_j+RrXj+arNkC{#&AUiB!MuI?!jMOw@oa}^PNckD` zS4qMLPlM?W+@&gx*@o9`S7wT*GG!;)5aTW8jo;woF^#>W0;{`Nd36^n zu{c<)%xXZ9QVWb?rPdNf%Pby>8m?j}VpgSC%$!nP^bi%w2*OE31`$k|jhso8jU$d4 zA47={AxDJ-BTaquCRKOjC|_~VEK_X3G_bfz!;6h9X`EBJ<249*OC>Kk&f>z_1~U%wcAhlS2+zJ`Ouv(H?%NQh_1F6f1_!s8PgDC(gtXOdb+L zl{6@h9ziaa5-Dat6(Ve8{o#8o!E*sJMb4Tei!Fu87h_S*7-=%cJd!|u(#T6X`Nu22 zDCP+jc)eS73qe@jLJ(IT5Q4yhfDi#bR)T)GHwIQaJeE{fbB>|x4RhqvMS5y8#t%m%CR^9j`F*W-~Ds=+T z6e<-E)k!pPm1$G3RjI?^>yahliK53s*r7zj6+nU*TJ-28!pMnJq;X?`F~?jPdhEnV zWFbW+=P*k7h7rprg84!M-j9O6aHaZ4OkKWFDhl{Ws}9&Jt-7*zN@ZjZbxOycsuYYp zR;buLSEp9K5>2FiD3(U_!6t?9Q*pH6k44cW;Or2iAPgYGLE4_aLK`}ChdOT3F!w%C=BQet-^s)rql zrx|vzPAu+JG?}<#v6PC}LP<2w1rlc-3?mPEv_u;AXbe5}-2h7T)6o+M$V2BYFvv~Y zpph*F!X;Y`O4&$o%RYi)))_SOHqe0Yo8ax;T?g)7K=E|xQaoKcSf~u>VxJh8Tk!;D zPS(lE+^kcOxfe}0=3p?rn3JIdOE)8_m97So>4h6cpcir!iKwfEkZH$5i1O~oPlh}p zJ%xHjboTg?+{E=c*_QcHdCPChNql4$;zMAcJ~i|3Nw7}dU5D=O<93-YoQtOW_M+*& z!C>OxEe4YVZ!?z6xY<~m;&wx+h+B@NS~nd?v~D|&WZilgop}3EwE6~wh!jr9(1r6|Es_LkJ&p=U17d_FO^A__G$KVm(uxq>NIOCVBP|I86>3U`N~kdr zQdM&jG`a=_sB=w<&j&UtJ(g`(b{gHZ%GM+CfGohz3~| zAQxp)5`=+Rl^-@@SbPEq~7I2+$~tyJ*(^vMRr%Ba9}09Mr1|a6+##z|q9o0OvBxgPTUJdqWuE-r#ZFZClsfRsg$80J~Ft-JiVfP+s?` z371EKohzkA=Sr=~(Ljm;oCl&Bz%d{y0-P>U6yQXOvH%AO)CD*|pfJGE0i^*B4X6!p zU_fzz!vd-U928L>;IKwr9MY(Z(;0PdI7Ho>$fE3`Y-5U@~_okBR-Yh!Z8$hVLrIWf_G^)D=v%1?Y zEG(_N-NL#!_o#xKdt|}Q21Xd%NMN+VjRHm<+>9~0H&%@94HKh#Q^e@r_%OORIgIWN z4WoPW!sypt(KCSb3P8O9S&s(n%b-1&y4N!JQXD@ChYtek<$jm@O77R*Z*f2MzUKYN`-S)O zF3xXvXS3UL*atsL@F3_b4}^~DVCba|h&JNDXrT>|-pN4e*EGEWQ;%lr0}%T#Y2W3- zV-fr)jh|%02O04>RvoaQXEPW09OVL^dsy)EtOY=;RuHrZ3WTPdU}y{zK)6o=qUjw%=T9$0$Hfu@%nc$%mIsDIi>eUrxO&2YV#vG=m}S?az@;6KUmNH8Br z=jTX$+Sm;cINCy>z|j`X0!Z5@YLd1))F^Fb6N^9Z zb~9jV3k6X9RY28Q1XvA~=4zufSOX1^^$r=Yw_nI!3)@?N<{n-pWPZb-#sZ9|CJ+ER*%uw4wpwP2VD=YjD$ z4$RkRU%(do684rCv7x+BxTwq6K3vMqxzhGh?A}S>D_MLZmhVHv=OBGr6W_wc!{PwC zP$K+XC=q-(h$5jUf+<`*0#M=VX+njoM+cP&JhD{0c2uc=?RZiV%gLlNmJ>OJ3@33) z*^S^7v#s6~w5{9}wbiDuO+iZATTwGU9ho5x^gE_&y}R*2CKX zeHp9YLdD0@{%emXP2zNIlr?>D!vYfpIV>z0pu=oQO0C(_kJ>Y(7`13gCTi7=NNd}Q zMC^EjfVo3XC~^lKkRlH|A3`2@JbXO%bn1Bc;kfk(+?Mqqd_s@Ir+6S<#AETC9gdG< z!_(+|7^mL?#mBz*5;-1~$a_YiYN0lDXV;`EaIqGJ0E{)MDlpchg1}&lqJ`lGg#reg zlW7b&rp_1_k~qk?JDQ}`Bg#O_Cv-UD4+wCEU=JRRLLE687;w^PEN;bWIG$A_@+BIT z1I@sE+7Ity^jW+xRLO(LQX&rPnux=);6&V&0x9CIuB3>&5|TphiU#al z6)fz9sZ`jzs1dPxP99YBEm^JrOmbWyXoSe35W=7=A`qYKV$X#ZjXG_!kvC+smRkuf zHVifE;h`3qFZ=l_Qa=Ukmx%GKwNFaqJE8mrFsF7~7u7BT2B~&i3#isb$g)}&fyk=e zR*hA>tQD(zSF5aiRiUkaQXyOgpDtYsnIc~?BrTRuG%7UFIOKN%66g*EB@bMR%N#S< z4qGo+&8uKLH%bw@4hqvZ@%pc`&uZgOe>`ZC2Vvza2as?>`M5F=-J7t6#NLE0B=#m4 zA;HIWgG8SM+0j3(R#HJM5~PJyB1$nxWtu8JO`c|8%7FTqbeJkx6sT$`l*hWkN3PX_ zPMWKT8?o0BTdY?SE4iNT)7tv0u&*lPPivpVkmq#r9JV~90bF-eaKhaT?9zZU(2EUc zpjVepKrbYnfn7N|2Pe#S7D{;aJk%l`k$A=V$w<`!)G?|uG}6gYlyWJN#O2W-X(j}| z(hoovOgDMhPCs!fq>gSQq^91K>Ut1riZ`8oQyV|(<3}L*5K{gU%x6$D;eI&mx*ra` zHuzxp)xZZsFfbqt!IXSB{Hg_kq3Z>RM6MGd7`IL}8?#1;fVBFsIVq)q)A9+j6O(Dt zvjbA$2oOYmpqoK}ViZWA#Q)7otB}5OPMuH$lhx9a0f96E6>ZEa=xB>HAv6bhGjXG(Z!&&>B zLSBTD_b}x(czH(yzVL$ySF*C}N>X}xAV~=pfh1+v0FjhoP$DV8fJ9n;&5*q8QX#oP zLhED)tx?JkSDclaR30ojqbxOyaC*E9T~gdYdPJCs3W2W!E6{}xCU(|B#NJR4F+0{K z?MWhV%%l0^t)`Lx;N&x}Jf)atRNw{ggzJdebsaIiK5)eRn!phgEKMUO*p$W%U_u%* zy=pXem>}y2Lc;5#rdH`AC)Nna5mg`}L8UM!NjynXmNGF`rXm??U?uv)Gy!Cn3Cq*g zp#@Hb4>Gn}Aa1+_g2z4{hImF4slmoNy60yDlQ9 zmjxI&zYsX&{K9hR>1E__M3;_3P_7pkL?fiYK&rKhXo^K5F}12waaGF0qAQdJMiWht zjVY28A5|0)Mz#>zn~c#TS6L(HOoNVX7v_(nE{!~-;mJB0s*F>)oF~CA) zBC2SjRFqZ9u;{|*FoO&sKgSwA7a(=ys7?OZf>9Qks*=e>lU3%K+_H`&m~R}8X`GojT25CztWKv*l)z!g*^ z1ZAZ-2WOu=6vANAU?@Wg5kP@G=0=adRwkLjiKhSCCUSa^$j)6=hF_mXN&_SvdA&uVC!W zTE+6cpmObtA!X`E3kCXD!$}0-4W`UM9Z8geJB%_A{unAW9OAQw;fPM0Vw0P)#VKzw zYB_0<%tDF@{G-UgJ{T;xXYk&`cMqz&E3hsei!2ZBT3|(R=K?Fu9Sp20b~3Jx*v+uo zVMn8C#+{8R**hFkC-8JYt>*1;BGv2B11WbQ}QjX5AtFzAFlsh}g$RN{gWrql~alt|PmN#e9)V$|X82~lO96rl`z zReT8hvgqXfdAVr=1Yj#BSio6>Ylb4E;H1HO5#W7@?=E!VUIce{NZmIcQunQg)P4IA zb-`PZr!{Xxp2D~taf;%W#HonelBOMQOqgiAIa!*0gQ5frCnd=hZVJ+ATot2Gxhq5> z=CllD(scofya1*L(XNb+AKn?9IlTk6W_%26z5FEj>W_n;{O(A6_oBNyk=z}L?H+l$ zYDk{0S`w$Lrld&&wI)jrs6kOWqeeyPN}81qf%}fvow=+K)-U#Svj6+~&P;P;oJGusL*y<+Oj^S;vm5_I* z%DXe!-H+z(M{ZM?t_3Pf*FqJgYvD=~2Ntp%Nnn9X5ds##5OpadLj0Kb&Z5d^n`B;gM8xUd+U`qfUy!aCn4>Ee#^{QXDe6Eh00zgPqK;s{0_)xF~fzKMP34F|GUEp&?D+8Yy%M`ytgyS)>bemC-6{!wu9XBn*J=VD53DHYsi5kD9tNs3=s8o>L64TI+XJQQ z_8h6YJwU2%Pi@uZsZn)#VpLro*{ZvPqU!FfR$UzuR#!)a)y=uQx;YuLE{@>V#j%rc zZ`LNbhGl3D@UFUC?u*FyOgT80>ryr2&rxQ62EKiMl)lL|vXP zP?tvv)a6kEb$NzBU7j9LcLxX5-Jy-TJF`(&CpPNpxJF%_(x{u$8Fh0qrEbn6)x{~S zx;S`O_oglD-c-=K+b^!W<(hD}TwHhCwRLyguW_Hn{nh)O_bu-y-uJstcYp2TOm=tv zx;;m@Kwn*C-e4S@?MMKQ-ORY9i9m2?`VA*vKlb(gd+i)CmaJBfWooT z02EG;Hc>b-+Dze?U_*5y%Es!(0|Qq#k_=>R6W45Q1J^{@<}JXr+yWC;Q{(lRny^H}{FE8YbSpAQ1i>e0ZA77qji zZ0)2$6JZAmng}~Q&PdpCafVB$b$~7%&;h=3JPZ(qGbw=BjiLZ!H-l!#wt8mDwsU68 zP7B~{F#yumDx^!<6nhVSKh&s<{7|E^@qK2G*MKUVb=uwG2U zhkY7~6-d5&oCscXjwyAliZoL?=)I!d1wI{y|3cEE&s3HHZY=4uF{Q=EmX<()X)6j%Ls4)#iNe!FVo(Q(O|27F^Jw)I|J?MpOSv%I91>YAFVi)y1TtB1O-?wO$V6C^y>gy#x-t_nZl>?g2& zSKN2qeHY+QNPMo!-{SmEa2kUNO)t>JG?p%=gLE+sqpRuGE~h&uJZ(7vYQ+gr|8YrO z$2B#Z1ggsa7S@S8ZYKw3Ri|*49K@5eC|tFpiejYlXd5h2OI95!7D7+jG%9 zm)=VldZ->s=Lh#>Wv2vKK=5cQA9)g`X52EEF9gO%2eRuLYol5k!X*Kt%6 zPNV92Y|3k|sjs!9z^0N4+eu37BdM{6q{#M>vhY%9KehH%ZGQ##SQkDk@3R0u3-Pfe z|LVh2nSQ4vya82&+fZ%Yg=%XfR1u~zCE*iOT#HUkn1fW;k5OJ5Mt#i(1vVX2*lbT? zmpzRg=2T$`PGny>l^x||VWp1z(2zHZ>j>n~4UA9?b+#?#j+oWLG+3fn^yg(I8B#=ulzza_HmmMr|HWHy@8 z*<(s*dnKj4m9#ch65CEmZ677QJ(LtTkJ8*T(Y;gMN8P>D-ctcS)!|b){uJg%dH$2@ zSE<4sC{_3YrLn~*jh#fP!Z(yE9Gg^jX_AFQAel{xbhhGA+JQ%E=NYXnXVmtZk=quE z-VQ5*o2y7}tfIN4is)V{s+)-DZlI#PagOp{Y44f({t57*3O`Emqa+{7!#jyS6N+z9 z+8!989YGP=Ws1;VQj~U%qO?~Ot&K{=_9ju=8i?M`00C~oA-Va6=DsjQ_uJ5g(S+=- zAawVa5Z)1l^0pGv`$>pzBq6_tgaGdu3jCtLKN@@`!)HqTCdY5m@Ju3J>GY9gzd~?x zAO!aVLU5B2f_sS&+&O~cUJ)cWY7pI=Ko`CQs=M*$Zo?nE{e1Gi^VwU@hi|r?zRi04 z9_#triwE#69>J-225;gi+-T=;A)mx`gc2Wu;ukUg(c>dkK9Yw=`g|kTCzA2yxmyFC zyB*-Ud(6(=OLXwgp_4ZY9lcd^_U_E#djpQ&kT`!M-T)qWBlzBS@C#eQ;VxWl!!3Nc z*oaR!@vIe>n(?I@C;D-qoXBx}BtMAqhcrA$IFIXY{|Kv{A$Xfwmj*}A-rLq zIegBC=h6Hh&j%uXpc3yB<9UXBPyC*E1uJgAic48>A1@AL#!H-G5<4E^$2AQ3`H%Wpg-e4@>yNR)^SW&5hn%=+1Wr{YQV)zbWxIvroQLey3c6 zDPJ??Sgw4?mCx96*e^dF^UgEpFo!AkaE3pO;SdY1dEX+&dvm%wf7^4lM@%;8V2i#r ziK!-iY14^D9q83>F7d1QVf?jUzJvG<;tvin1czA6Ax^U9ITo?(oVVV&=p)wrbIe1R zTy(}q$1v%Go9_4Nd82+|)!}Ac?bgXIvDYTf`ovME80pxDmYwGpKDhh}+)2k^(hs=w zD3|Wz(_@S}id8?I^%1xJ;ny!OapWh?Fp4n@JK?eatzx}dTz8A%o;__Ck4^i8XKZzi zo4#?;wFB+rn~nTX5C~k9#w+wrcW4E!2SFbG&!$b@TY`AEP~Fv5g#IBu}m6q?{k8vF1 z9H+Q;)p;zsk2eRhhlPAOktZ**<40Bmyvch%`NC3m+sYQ!^4DI*I?PT3aM5T6T7lnz zKZ73Mg>W#D)m-E&8+ph`wy~01%;XX~8FeU!F6GUs>|rZkj^)X-?D&=y@3P@w27JtV zn|bauuZ_SJW?-({JoN)7J;6g`u+ASiq4K@T4_xIiUs=gn-tm@Y?B&?O?E08Z2Vl?z zc=I!RIL(+BFy#mQcmglJz=pH=ZwJOZg5$2>vo#p(4#xU}o4ylWd*)%BdDa0Q!~m}- zz}uO5vH<=HfJZ9xLSSB}%ay-M6D2%;94>wpKVQP=QK3F`>qSub4s0)h@;sn?izhz{ zSG!JRXlfG&ZW|6}8IX#BK)j337D;&1WG{wsdi z-^9=Qqi%oH=SL8|=F)Sh`pO9pN%o9xkBD~~6>nw5|5)*|vQL7=SIYjN?8~+I3N3!C z#YeIDBo$8t`#~ta=fvlf_+S6lpY>P$QUB6k^dE8an?L`7=r<@m<x ze(>)#rVr-yk(@p)>aU`{htyw8eZACYQ~fa2&q{qL)K4<~BGMNc{U6ca3H=+;|MU0! zIe*RHgU)XNdP}00RQg7#SJZk#uopD@Ke+eHd%wUT(0l?kABu?AA>vbPz9;8TZvG(W zU*P;R&iCT{tIdZJ@k*Ls6yk?KJdlUy>F_um{>&e9`<6T}8T5}tf>QD2DlfnD!M z_IqrfSND2*uU9w)m9NI~uUP&M4KJ(VNiToHNsG?3rWI(tQ*m-Kc{e_`t9upW-<-Ne4l!n3VCo7}VEJzL^m0=VclB-&p6%<^D!dxntGT_}g-_%Aw8BS&{Id^VIiW9*6Iy|} zps~yW9b_(O7;{3i&I!FaH#Ftk&=}^3-kT#jZmwuFIitnoj+P*Iw3QsvPjX2o#Vt(~ z$21U}(>ZTa-)8k|Tdy|Z(Zv4D!k?}ES=^u9{n_7}A-)C5C)@n88LdH^(QVp_?&3yt z5x1gg-HLv7GuqSbXbo*hE4Cr+*Ov5!Hl^3tmaf3ov=^JxS!_>FtwGJS7PZlu)I(}i z_oQ7tn%19jz1i27RroTrFLV1cx-ZlFGQlTf{0f&(VZ`^0=?&19ZU9Z`FKI~|NmH6e z+R`S{mIg&*8WXK)Ml`1b(Vn(DgWByZY71skhnYSi zhHS!M^W{fb%Lftb}0P^fwg1*(-$sQLzls$nTu zJt}s!2QjQ6#j-XG(|Rv#>p8FpuYpOJ?2Bu!FRr(|CQRk!wUgJ^M_yqMby3)-tHO6# z_^!0?YJ0D^|Ehbiy$2h7u*LtXd@m6{LG|mkwFh5XNARU}0AE=z`N|r{m)0u0wl4LG zFbFRROLlRMp=-i^6S}@I!Rt1zuPtza9VP*62@%4+ia=qg2xBWnAe$(Hg@GcReG@_L zBeZ>lxSw$0yYk-4@4XD)tMR)opUd+jw0ZTU1$imCEeSsk1Mq z&~B7cyHILvz9_aYM78Y(<@OmA++$F2dp*gm^)xrt(}kIy>MnA^JLpO88z{b)@_Q=4 zzao4r#Lsg4EDet(;;&A>O7;t-_JvB@f~d3&fJ$2mRN6hD)OPW-cIy+{Bb?gKYZ!V?0uax$NQsNsa$?u`0z;~1czlrdn41Wso zt1`URXn<#a~){r^bhh z{HV*93h_~+4|U?3YJ7|AzD9OqP-M3PMRs2)x;qFF-n2w{pCHnElxXh|5c;+p`a8oA z;D$qj`wb0_Cq($1P~mMtho1>ycuWZKE}_J;f)n#){?g?$ zefXvk&y@O0EIx_HSIF-x`7d(NAW$L#qD@99LLl6+K%HEb{-eoiJVJE@+%$6pK>&8l!Lht4~KJtI{c7_FADKS zo_|#OM<(7##uE|w?qK+X9Sm2XgW&{nFl+?}aUnQ}&$v+>_Gay5t zyzt5Yrd;pJFKjv78de+gvNs$yhb8RcszJ=O=0$G~wDo+;^Z7ucFJ$^duRqk{g=#-fX7 zgZT9jk1pcPInTWF$w%Dy=ZJ$&xafbASntsl zawk6y<;A6JIF$jnvff#qyUS~fdF%j|@BmLuz(^;s&~47Sg8zg67U^Imvw6u}ZZeXe zTziybJmuJ{?D~~W&oby+-n`2n_A=&Rrd-UAmw9nB8;<6`3m9(%hI`FtJMh;KjP(RJ zeZfR?aL@Ole6O-)3GT80d-=&=2C|rKT;|!$jN&u1p61dE81w_)9DzTqz#Cp*%GdmO zn-zCp!XvnE3a%T2+2-J}KRCkosG#pSH{W?2@U#cK2?M^jf6es@@A-|6rJP!lHuUPsLR-gLyqG!)Z_mV5`d}xrT4Dx|N9xlj>1^KHW-}K{+c)ZY#_tEh=IQ|=dji1I(`^)%Y{Imax z5<9;t^rc7-8ugo7??LP@)qe8AKLTFBi`Vkvjlg(X7|#mhKg4*&7@rs8)nfctjK6~M zPA>ka#Y2$&pcUVv;&Eb8{3rerseftDiw?af(rZFJ2dvld^^s=Z825{NpJ;d+)<4tw zP+Gs%^Eq2f3$i=u1BOW_kes4IQW1W zt|0TKgqM|65asvq2Xz_0Hk`@OZ# zoBO=H#}jV28thn;vb;+Qx?C+;>#|+DdT%!d`HF?WPCfu?Q4uUS76L}0;A4X zj66dz_MF7%GZEv@L5xA~Boh5$(ib|tAl3h6ecy!FqwsfVZ};|fbzj%__;eobU~p#& z2z6FNsB;#=oP`wT45LtI*20}P3wfR_?D??p=etO7>M^;hyW*_Qio04W4r``3t&8Bc4ua!a=gsTg z!k(?dubI7?+pAr8HN968d^N>SqkJ?Gf0p`fqxu84sVQ)qI?bEZPu`@ieVdxa+tjCS zREKb@deY5m3~pEVwPAgsEo(M5t;^W924jnGgf$6Qt#R$N)-}?a*F$Px_oPwyG_prK z`?Iw_ll!x~Kl6LE!ar;Lv&%R0{Ib?>TGlr$>k8DcE6qH%49#`W9TgwM{prf}x<1hcQP%)pK^6I;mwg^4Uw7|4Q!Z_3cVZ0*V5 zzU;!6>Ai)(w}9a-Kzs1RA?zGUg z1`A#@mVGT)26i4<*l}cGr&SQULInzYjg74}R$;2KvYW=tHX1uyh=STW6xyBw+>6mY z1-!4|_Z0}AO!3Jkzru$f8+|d@?-LC@F1zQ-d#=8p0Qg^p z50?00kRP_;!8-p-^}AeryUe~_W@pf4wwf*rU+J=NkS?=jOgOv51hhrD((b^ecBF*0 z5hbt<7@=*vEw|&g-d5X+yG%=NFs->IXkFM!t8OQ)yN|N+7RuT?N6YWE0zX3GPeA;x z4DU7hULD>G^e4DJ7wvb8?Yp(M1`*a?6JhNvTWb&5Vtd9G+b6Wz9@TOiL>AnXSaB<2 z$t`#_x8Bu->8$E@v$Ffl>h3YCyt}OQ&Z^p5s;Y0Ls=pyr3C>eR_*aIXh4@>HF9Gto zE}tvJbB(?P6u)KTYZdpsihF~rxEZ*Ldy7@vMXcneVI_BpD!NOlF04sax8;=G8K&?q zl*-#rO7FWUzT=|$UW@YELKNU(P=QN;65I-ka3?6kjh-+J1f}>=j9=CGSCOAp`C1%) ztMj!`KWoKf?f6FBeWUE=AjeJqwPJClF`Tf}RHzE_@ zflPwyF%5o!sqnWY!_$@yudsx;mr~+dN{dS=WjIO6@u8&1bC@LmiSng1d{yRKY5tYy zTe-f~i>C_mUGn=b`R&1y-wrJK9Y)FTBuanhNCKQiQsApdgO5NWyu*>~^5fm9-hUjo6L>R6mLVOlN;;uuBiw-dkVyJP;A;%qsAV(CE zTu^9oJ|W8Qge;c}(lA@l*27nwN^W*pupT~FjK<>gv@)A9hkMdM5g2!^qoy#M4Fkjrs z+)#(ZfjS=E)A?|n4v6D&G*`|xVC+%I z7BQMPk22>!?tI20j{S4hLl2$AnvcHW(kVaPanut}z3|lmXI<~s>wf)h6Qhk{3d1g8 z6;my{(k>o4#ys2jcl<+s3#Eb4@1$EW=?PrAl}#ry>Ni$h_SIi+z4X^TFY$*@j5&%Y zhu!hm6IXHJECyWmy=TAhi`|Z~*)tBC##-MPY8@NR$~|jY^J5_6dkh&*u#C?W?yvYpr0GnOFUmx(* zYG%645SCz`H)!(R$@eGUpZvj4e&8sNdCE<$@{g@tV=T|UWfXUr^)HtmX3)pXd6_?K z<_!;UbeW zeINS1^IhkMny<0I$1d zD6jM6Z=&Q+a^x@a2dVvU;5=>6yD)kcPhUdoQ^lTy+I!Z0h`_%X`6xyn$dPw(Z=SPJ; z)aW~#UbE^uNcc+%PpS5gbf3ZR84;fm@(M_w4e5(1{UoJd!}Lp>e&^{=o<8B}+nv7J z>5H9y1kr6qrYtW%Bhdk`o^zcEPD)WUwGjO{eIB!2OWPfa}6$kg3DLn@~>ci zkPZK0!>eX~h|Gt~e8SAX%lx*?C(FFKD-&m|%$%b#bymvcxhS*epiH25E+Ks7(MvY{ zJ71eP{Id-y&u{0 zslA@u=ixmb;Nu}aUgY0>cstT7h;#*mM;kzBbQD6PcMu*eTZr^&A<`fUla4H0+K`ax zJ;J8t2%SzNe0ppU>aM}4u?D4<8k|~5kZK}9tAPTsz5$HzeP6#f_IYQI$M$v?zHaa9 z2EUH+FIfJC&8JiS@Z=>+_mrsCJM4?m}8{hn6!gL>01>PkPU6aA+C>!Z4^ zuj;lwt1a|h9ma=s1iq}R__TK7+xlov!b5u#=F$6lJPL1T;p^1?!rRl`{oLQr9sbn^(qXW4ZvW%spG9oR&5V*gyI@N^b_F74&o9&Ya8_8v~~;S?WE^5H!E8;N&= zy>k)HfNtvr=(NUiL2Drww3cyU>sPu6f6_^K10C0nbQ3O|>w0g_>$$nF*W|!1lM5S6 zPV5MA6t0RJyD5%rBRC5S!JVD+ruJ@b-zN8LcHj2*ZHDib;oB^~P4n4Ke~ra`^I8MA zt{H&qS_|BSiNJMD3+J^7IIl(FzV7e_Hso8_hi+o`xly>zZEUx;3Y)c&E!I{x7n_Bz z*v^JxLpy0r?V&ZcdDh-uZSK?T9&PW{1b+?j*B*Zj^VK?EZS~V$+%*bsz$P{uHnF*| ziCu(EY#MA7M!`1r=-SvH)+#KiMmC~a*??&lzDqM3F750zG_)(ArR{a5w$>TjQ)g{2 zoxM$TCijmS-J9LLg}pBW{4>KxTl}=iPwVh#pMU21X0+ck3*WP{IY2AB0kpEcppkt9 zt?U|T7KTN$aOs)ZAaJK*w@NOaj?;R2Ho{gY)>(<_&T6}xZ z@_RDt@5L;@39$t4!y5b!i|{xs!`H43FS|k<>?-lCtHr0RGOT3f_)t~kI#!gQW%&^{ zJlBWs@_aAT??UliGkyY+@72Zfdjc%K(Omic;3TRB4?$J9C6(cg zQy9iDg?ONp;(AhxUyx$_O{#G;DaXg6An%Hj{3?oaCn(E{pfLA=;_y@+{wl<0b^aEK zkFfd?SbT&Wzvbjg6<&c<;RQq)E(PlFA5a*U@r3w^r^HL07T;`Qd_z;?kxh;(GC^L* z6uBSMR+9BEEs?F z-jaNx(Tu74RGms#UJ&HW_Xz~$9lzT9;ywd3MMkCA-i!vW9;&5M) zhwq9$ELRlbwIa>Sh&J~k>Ku#cb0-L*6AhKlQ*`=CsNb~uPbWUA#Y^FMDI_2Dq0#!&JjxSMX`T0`${!_3CJ%^`6M(RzCRzXzz4(# z^nmyZPl$=^i1^0N=CV4SyXuUXH0N{9oX|Vqh;E59`XdhMi#Mhl-kctIlltC`>UCSz z-?pxwU1G6Myfy4mulVTMgKlDc&x{ws@k2nKsL2z3d7(9T&@DK01r9ySq91wm9GC85 z(@%^#>Z*UfdWc)!@avS9`0*1fj$*@M4?On0SA1dF-JadTFaA2lSkt)a8xvi7&p>`7 zpX20twmi=Pe?!5Y^$cE}fm!!*>q>r|$0n9BierbJ_1H&Wap$swxW$;enDQ4t9^=Jj zZ1{`=r?K8PrW?m>*G@K%C;Vfog`BjJg=TWjRSfte_&vC<*n?RN!Lkc*i=ph|AiucA zF>W#KCZ=)fHWvNHoagw%I^OV(Dd+LyK3*Kih6fq&A?Iymxs#mslErqi)=`Gq%0+Ye zX8fH!-C2IS%N7Q+*JQ4GfR#4jp&3|b3VsjnMdn~5xB19lPBN2~{9`89*vYd)IYsGK z1|7?sXBos>&b-T&e;M*HFHYvd&D=MdFMPmjtC_+KymbRZEx|=+@Q;-$->odcS4K0I zr>tcnb6Lk;jxm^7EM^mvd37_FKER-(ne#M*xPUW1;K~#D@dZwt&4N3y-Vq#k1)Gh* zU2kyJADoo)J!i~!0KNl&ftJy%WCHfF0oV9|U7WzNtGUE$R-J)KZ!_o(%=rU@ID$7U z!IV>Q;}?8*2LH{$b>G*tzB>VZ2MW@_<7nVpw0YDv-+|31GVph6-fV&2Qu9s-d{F@( zgywg~c=@?#@%bS|-^1x~WIb)z%dR~NyJr=AN|-+q=5vU76=I%*n70h`gkgRT%#(e2 ztu7Cx<&UuZkd^RELC3A107`_jG#MH~PoUxmpV zVe&MXe2FH%+2kLY{2!BlWAbB5UQ5YGA^9XDPgLZEhPuljMc)v;bARb?%~S>5k9b@rgTLZ^yIMzF6&R z>3As|zjWh`Z2VA-2cq#iGk!bcr~PC6vpLV==V6K7gwnH0{pr?|5PQ$G*QEOne!oHS zFDsWo_7{--FS4Jc#n))@C1$^K_9thbaQ5qFKW+BITD(?^pOSqh7JszjA*lEu70(m< z!~Q3B{x#@Xi5^wyNu?fC>pQ_dgxPa|dkuO|S@@EVKZ*GTtN+026TtdXSf5AhvvB=V z*XQ8+jIQtJ`f{#+=K5f*uhsfftk1;yN2@=y`ai2T=cM+$snCxWedyAAO1+4z|KRnR zWpCN`l6LGF+IQfkq*5LCe`1}PxUrXo@3H=zN-%8>)N&F;?q9wmCSk|@qaqB;kO`n&@w^qxk)x%42Uz5~`{PI$|)msESlxo6yaMZ;HY{KU&o z?7V{yH(&7K<~AR;lL4_^Nr=A@VtWcroI^Ep-k_=Tq9)IOnmu38^jS^w=P^y7xio{m z(iHkhb7&;Zp&@8O*aw8scUbxjQLh2(DY-sU>>ba3@$C`s{xI+l5#KQK4LiIciEpLQ z9#{%Zfo0GIXaG9OCD1%BgO=W5ST+lVnVnOljuCmqTw)&KD#_R>=J1Smq=G# zChc^o^pOUphcq=D=AknRcjevt478DG%y1x0*d>TN_C1IMEm zcr^M7q|rbikA4A(^eSZ1oRCUaLN1*M$+RD2(-$C}W`lgX>=Ej($Edd+rKWnEn#r;1 zB1fx(8nMP<7;tunnKm27^~OKxnlWLaU7s zT1|uS>Jx-mixOttfl%v5!mSMnBg{7_VY)%C*#;+UVX$j3L9ZhSeqALPc2l6(MuB4o z0g`?5clLd2&lmT4b)V<=d553p_;{C($N6`ue>eN(@OZs_@==b%XAJ}(&VZ-%_jn+ruF}|@U@R5zhS9a8%g_HKq9@0yD zC$H`C;QsEy+vR<8ugU0rtwir@9C~1@ z$P3#vPi)OR3RmWhy_i>FK|Hea@XD6Mv+&xT*=2WUf8C+IWv4ck-P%lcZ4=eK{c}O? zFVsC<-pl#DoZ-)9c)H4`1Mzeue(uD}+4$0teJ@nlg9{aopsvDZ>c(bLS79A>6=qRK zc4}SOoEI#t;ey#2E}TtBhc+Kw+85~5Zkt=%Y>sWQxwg6F-nNp1+euDtqd2;Q;_SYG z(|b6-cN=`S#E*mgILwa|@o}af7yEEJZrs`SgSfM;1afEF0p!fKP2|qDwQ^^-kVAVl zE^W^^wKv4A?Fh&AAza&jZ*JSYy$$yUH=0}AV{USHxzU~FcHyWtyOY}T9%|cLhwbm# z27e91t7Uk%$$#VUZ=U~_`fjrC)}xJWd)?Ny6S%Ey(YUQ`pSZ1UdAPAX>(+K_o7p7`EDi4nX?atn?ah?tH&GhkJ!piFhT+c` zA1(6NG&~!KX9In<)@OV1YeKd+?W)0TDNuvk60iohjiLs(?V%R;3pKf2)8-CMquT@8 zg(uPKRy?y?@a*nAGrTXDT!NT8H76Ny&Fu0Kg!-2{Wzl`zA9^XRc zqh-FDh*ukZH50E!`)Nk5*@dkIW*4>wm|fUPF}tw+VOBQ`GrLPzxSNCpyg`)ZU0J3# zBiY`CWPSHl@cXX9-*FWHSF0d6+1TJ;V})yt9WFJ7I1&ZLg(zhBh63b|QGNx?xA5W5 zJpb(U&tU(|#-Azqj@=4*Ur-@$i$I0EEtLv-0~ym>#@OB`#`q?s;I}4)zcq*f?kE;G zhOof_!wA;{E1V9@a5pdvpMfDR2BtU{7~@r6jWc~cPUJQD&X?tbZFn&cKeqW~qF=W9 zWh}l-$Cp95V1Hu(_O}9He_H_qTnHH8Grj_c{Tlq#i*Qb_!Z&mo9@&Lq#R-TLPEfpW z!s2&ajK^^`zQ*M^85iVUT#{!=p!`XKTY6n2#5u za=8l6fUEEYxD3z2bvO|Thu4sRVHFV)SKU&4#Ma^+v@%So)p!Q2#}Bh056p^OFKhA( zS(U$KS&oKvIR&f@OTgkV)s=^nu0kwi#raqxK0@eomA)72gT+1=jR!08U0E*H;u~Nw z&HxtUT&^;Fxq6;1cJ3OP=2?eIBQRtFIB+gJoItHTB1&L1QBSO87NcA@&*3XDu z2ZLbZtf3S)4Xqf0kc)GOUVM^_Z?f@FK0b=cOM&?%HAJ0X2%UQX!E*?PMjU_;=ud<~ z{~;tgZP4f{f=XuvI-LX%>YG5SclcS|@~3skAJ-RuN__ZZ;y@o1@A<%f!AHeyJuEKk znZ1h###B1AC*j;!Xs7mrdOVSkH(K&ZSsp3PLx8~a+jIH`Jg7UcgL)aA)Suw24&;V) z8aJ-9*p~Qpdt%d3{5k9)R&nMlu3YxVTfF$}hQAo_80$^@+%#VM#$)R^!aar>$VC_V z=OwQb<$tui(3uBf^Ew)QKk*&KcNB9liXk|~XNFzMu@8C0HhhbGVXlF zAg1w#ZCv?{AJ6gPIyQXAfcH3WAjdsqw2KTjlCfqo(@};nmUkBO7jVHn#vdGG3YPH! z(|+X{AGyZ0-&n>uUUBWO^BDCWi~eKIfed0HZ+OU+7a8&+FP>z>m;ASr?~bzDR4)6< z6W;RFU`ASigGS)(5BwhR_}=3T?(v#^>}4P`S;#*oa*d5VJCakJU zma>kiEMqIPSe?tIdpYzka~@_8lR5JMu3Ug0M|0u@EO-Lvy})p{*}@P!;R&7^gOTpw zpzk{0alZF_?^>>4E+cT4v;5^GhuOzuu5kdnSb$?cbBWWedI6Vyz@Q^A=L!ts1>P_N zQ{Lvr9r*AF{=0(f&fv8BVz{8D}=|B@fc&KD7SAV|+6>TyJP+OcoZ_ON*GTKK7u3ozxiO!*~KzIMv1nDQj1 zyap+MDCO;a%(BC-v8BqU1 z>sf?7inMQ~`_sQCvG|`FW&p?+0OYd(c_Tr729S3R@|!_EGRO-Cc{(6}2IRedJeH4# z>hVfEzKF*Q?f9SC&#C=&{5Ni%hR@3w`WHwq`}D9_U*hXm&At@wL-oGX@HHbpgoh`H z@oi-M85ti%#`B+z_ z%l^Cy2%iG!O+0<;)uVR(DA|9u{ifZ62>cF=pLzM2BF5mvPjKSZocJv$o=A$9k>Xup zpA+^eW8X0L^I+c%_Q_zs3-+;6d{l~8O7TS~ZVp1R^UjH&eq(_DN(yJHc`VnI< zLhV1$eP-Wl!0?umU&-MsKi|OgEinBCOkaT0Z*uxMP(L>HM^m?RNWD2jYR-YFI@hJ{ zyq4PYSgOwxQh&Zm1^Ov9Xr$Djhf;;^IVF11q!)$y&#UL;dQGwSaP}ME-ZJke2R|~y zLsmYd=0lo(If3?|6X*&&e@=86L(_L3oOb*0wAlx! z!#+eE!GqLQ9;SBkK($eas)agSos${iFTuW&>?PIyG436 zlTdd+3H1Y&Pmi&5I*H}eHZ(w;LPOLfG)Ub+!_<%(s5aDKHJ|0xbe34JS!PXUsr6Uo z)>@Sjo~o4a5+&C}lwJQ!f_-F#cSL)}w^yut#J)c~d_%`K#C$`}FHHTy*_#1t>p|ny z5;$Hhz@ybn9vh_#BxtW6)a4)M4(;14SPpyUg7ctOw?e0{+g+j}@+dofJdet>~% zI{_lsHcg_0t&>EqZEYggRyJ|Msfk^4CVs6U5$p_!VG|~by_YyP9b$#q5Xmk>EE^2b z!dn>3rowRc(qY;}hims3x;>%X588bo-vbu@U*rF2_&?46BmFTTj9YZy+Swk+AGTLbLq{&$b&x`)x37v_Z8g46Z#v zknJl$x1R#vHVTY82$1gW-`)H1JzwDOAwFN^^JP9?=kujLAB*SfvAum3wzscrd;Q9` z7W|cM5BMY7PWdC-`uHo`zWB4Sb^SBjg8rS|;ScS|KeZA6)&}%r8_%z8IX}18`ran% zgZrybZY{pKrT8wK#Ao-=Ufw!-eV-Tjc!qz+_<5C|_u=zEKd<%kUc8==?afo$-n_K! z)l0j%KrihkfjzXFDtl-*K=#tMG4#~7CG^&|qIzuG4|;A->b}+ZjHN{nT>rtt0!Hw1gC30PW$&db_c=KI1Fp74XkVxd&jeip3!HsW@ z?w2k8z=$_TUn*~;Xm#_Po;(XU*!2-Yz3MTK)cJvp|K{s7$@}gC>VSZC@&Ky0l>tGP zkbvNI&F&uhy$jjwg?oOVrynGGcQ$WqVlI0Kh|c*X`a3tl>CwV;M}&T*UBg{Z(|(HG z`q0Yq4SkOKTX_7r@kWxKanvd7`>BseWP6V}rt7nEteGG2;KFa(sa-wZr?eZ_SK3b} zZ{K!(+Z++Eu>6knd5QlP*z|=`!n$D7$1AqDm4a_1_#H@WsD6dq)?xB|c5O$>^Hp>3 zttZ1q>gASNc;U(Dbc9v<)3CO;T_3%|#A#cOz6tcbeKr{9{c{AozLApK1ZjuEqU<|LnX+gkZFiwJE+T1WRP3XO?DEA?)js)11#GB5~PlaD}k^Q9H`5ygYEnB$nOd0d5ezLK897>X>9T_*JSi|VPgkQKP1*~81csr*Tif8 z4fnAgCGUcdP=2ACJZFs?fpJO~sR8{J@Us5sqaJh`E+hmemxlP7nci;UI?y%Y-M1K^ z_7&~kJROuT9yoke*=wGt>iNq}Qr3Iibkl#3vWp>>*9Qi6{F;S3+2V|mYu|&};CH`$ z;^(=Ie=*NLF8Pt|^Hp8={Nx3Jbsl4|J;O>)w+&SSf8g_#M*JZc>--q)6=~1t10Aap zGIi*Sgp;d}Msf{9rpOmU)VBLOlV#E?(5A7oH%#3*k(&Nb091R|iLn!n#JIVW$dWB` zh=6Kp%NE3v?r+PIJ)tFI{31qcS?+$E1@qs9Z93h_0Ley3vwNHkEP2Svyh7K$G$L^L z?z3^?cZFY8Tz^l76ShL|RAf&lH05KSw~A>2P4+=nL1q&<2{C3bQ1KKCY83f_vnLsfML2I+-E99#_4!^Ji5$FJEaqY9$2;aDHhm)0zTd-DkWy)$P8i zhkgp$>5d4`dzE*-*GNwCD|PK_G{+2aGJ)U|ZDZkB@ng%pD@<`MA;t+cWVOa_yZzL+O`Ax3eTu+)~mVG!>pv3V*9@K-) zOrc8Jh=N3)CXk@(Wb_X|C$ZxZR|;FxOC5~&O=0cWVP#^%e}q0~h8HjH7BoBfuTmzQ3nYtuE+OEmyNX z4sHD#qHu?9rYX$LNNUxAjq~FBZSP}mOh9>f!iCHPBhMag(FM?udeTHkDECE! zGi|`_u#JfzN}FeYY1}FlkigehSWG?SzTpcrjOa_{KHnlZ?ZUK`h+b&2YY|-WATqi8 zacO;czxM?7({5x_pxJ!NmhbY_wDc};*)QLnf0L3{;1vAu9;3c#f1U~&h`nDp&FHf^ z36t>0wfPW@GrIXf^5%@V>0D;uPxViwfOFsz0Sw|63`6s2RhxkHRj(cF_*TsJn<#y{ z{w7V|V=}E!td~ChXnjfK*|JA>dV|9l@$+8x&*N#>mp@Dt;puo0Dp7ap6`An987?8 zXJhoL0~sqKtM&T|I5uVQL)0;3Ga}P9FDYijK|q_jDelDQ%mVyZdtvXTiSk}*=KjOW zK6`R|%c}IU;waJXk+`K2yXVF{#2Ay}4$tLy zh@b6on?1)hD)N{RSHCUiAO=og%G}bkYyV}H%PQo@xKc>kP+j<9@tE3F-Fe>b%h>aW zmFT7Mg}&m{$XLDLg?&mm8B6odSiQsrJWYB(Q&;*TOm$;=>=pGbuJT6Gm>l741xy#d z8xt#?o*_5(Gv?qK_ytjU*W6Wy@xSO3bE6w_ub&=&YNK`Yx0J=h50RYjevy2=0r`BO zq|*hBPLBO*k}MJ))!h#nq)X4+S@#c*qmu)U+HQq#&=m*nB>IHU5*7w|-8`g}rrTff zQQ3+XU5tj*@ zd&gM%hZ)QmyA!r@&LH(&@wlt=fXE`OsV~P>v{~Bm`JTBNZjpWVVfWI|=R&r~CJ~58 z8E)yV76Gp-#(+B;vdS*nt%+3GGDl#oHg-;xHH|V9g(QmY%&qRK zdO7 zz6^Y!MvXFE@7eOOMC>nJ1Bgs5Iq93;%1BvxyFRh|xP~~HA+od$&hU*VvLXvcwb&#y z9Y}?IT;B2&BC>nw2ewScjH?tH)tP8GpN($|5}4bVKV#<)-`#jZ84*A7xnzr4v^evM z+FnjEjazbSKU%%15hL4boh!#`oiT^(tpHcg+ol|w_0JQ{-Y(87895G)HFWpQZ3DqM zPM?f(Q%&6M@v>cXOQhF5guJWOJCot7e-p#Q#=4(Qg^heNReBg2)17)y`mAg8I7FGg z5|cSFJ%PC$>&cPZ+p?Y2=rs2e z$vT;XcVY!B>&~g(FGb%sE`hs@u)8y%2b2yC_bP%v$sQR7HyMR`OZRA_Llo!4!ygBs zx=_#oL;jW(0i~~qd`DzEb}NCZrUNxmrV_q{FzK|99=zyV23y&;ST&<{3$`Y2Vfb5X z)M2nK^Vgt6zfY_asqqrxSVQ;$ux0DGEuX5v4~ay7_5~mA6$TiU!VzPPU(aI-b6>7Mw43xHMx!zdW6*~TQo#ou=P~;()DSVt@Z0R0iP2yjxHIV ziu>mCfsojsNytW{3R0Zfe#0gv%#rZo5q{H^X}2jC6-SJ`d71iC^)`}$!FLF!Dq=H< zc=1=bx^L6>$C$yb&7Zv0z`iaz{>Z12s(|nQK7fI(8bHdRI#yxN5vZbP6=2pC2kKa7 z!`R>jqq;VVHv(UVbv^#}zCL({V%)Thh}OJ?q%A&sr)Rr;Df+i)_rN})c>g}J*Y@Oo z`mb&JXGeeF!H>DUz!ufK9RQ;>5mfelAv`=#QsRbhw`HY%(EM9_4|Qw8vjuzpr`+9L zUDjWB)=7W-p6>ed>>I=8)6l5$BSIZSvoGJr!TKu_+X>5Xj^151Z=!XgMR2KWDN2Tll-n zb%TvZZ{e?4K7>C(f7)puX3~5mpA3FE70`0YdzbfZ+bozmxGt=)#>c-1pXM({LN(d1 zXC>)*ch6gz#dm%qAHI?ohLsW@*lbSz+QhIW0#V%rPFQVD+WVLCS6?gG&YZ@{(V0rg z)9JtOD!QIa~_pn#Ae!u=1CEJK_? zQ=}r^vk(>xqCrA7t8GeB9lv_;sKl0K{QU;qr+3$UU*yj`V`pDfV|@+-PVW#N;>HGZ`=kW$zlj0I9LVS zshsQE+U%C`ya}SCXS-`VVuKdD37%P)P5Mn;E|TB!DdQK0TdNR2ftcP=W z4~e_OBhIeAS;!Gx?>RkuO-~5BaJTdESz{)`a!;1Tsu0{(Lrg|uf*K(Sl+zHL>(gIs zzj-e9FTMQlw^IB)pSyIwkY4cJrrwWref{T+-^!1vL0?xUl_v{UQz@6c!kRSP1rV3KtUn$ zwZ)|GYm&)I%Thz1eNU-ELgVgAf^bUQjjz^aU+eVm5vgigTETVbp_5~oE|ld>(!i0L zL&HC0&W+$o7t_6)P=xnrJLj!1k8V^XckuI=DCmAFhCQuMORVz-IdI1}26GDSB&;Ny zotPrMkMfY-2VeG=2Tc!hZCN57?yP=sUk&ijJ$_L6bjc+%a}p@Rzcl#4V)U3xa3PkQ z*snE&pNe1Q-brFZYpei)ThS?3=Jr=08a}T^4nr!`J%D%pSXFI^_&B#caBi{(`vH4c zG|@NLcO1s;6>=sC$?I(0hMLkc`yE69P(F*f3g5XTwk0e);VMC^ z48b?{JCjGL3}$N2$e%M0;E=JRWJ<`Ne-&f}GZS%30X)5;8hc;>w>PPx*}=jBeTI{M z{k`EJ8B<%^jJuU3a%jDj+uNF9&Ic**;lxjm7Q4DM=?G$ok6<2Wfq zvsx!1?D1i7NBY+8S_Hs+v@kbYBX{RcTJ-2(`$AX%nxsAO^iL1ZJowpG%=H@ zdNj5ku^S|cwC@#D@b#JO9Moe1IWwh{H0$dj-1Qt8YU;+NL+Zy~WUS@wx@o>M8r1WM z+=(=GN^>KonS2OEU~6QB4SL*bURDCFYxRM{JI4aQ-WdnSPV{AAW{>337E63sy!i@@ z$9Bu$=TnFa)EAUenoL|^HnlNN*R$u%geP&GlCszGTSI2S-ojt_=aN+WEwbf7myL59ywcA``F-f5UUY0_Cp(~MI+AmZ+PBiC}c36CC* zKgi@3QlV?z#voUxpe$3X8OmbIjNT5!%_gnaDty-~WrntI3Lk zv%=e&*@_X%+-62Lj-wd!M1qE@K_RUt+c+$!(BmgLL!o@#=_cGj8HYWKOH}HWQKld> zE(KMwdevAGS7w<58l1dJtFTbbd!Rz8o{Y7Lt%S)wF4bU&Ja{7GGr5s(xLQ9}E~yD% zc8G#N{`%wWCSTeJR(<3SpZk){o9Z?0%2BpVPHC}MTI2n-PI#$0tOiagW~6(=2U#4v z3VHJ8i0H&uEAwpHdL>fk4i3N7v%Z*es9NZ%aYiROOpwfx8F{%Coo8(e4FigkxS)|& z1%CZwMP)ZD(#&zA`=p!_5_w9cbKUTy6o_WYhZ`r1N@b2$>^y2Bex#afvFb^N?3y|? zvg!r{6cZ3rGJ~XwB9u_l2@nM;=q!TpBsD2GzS0@@=_%FZD^BZ*LKOn}BEbp)+GRMz1U&vLvuv1bm6+){ zsSqs%HiH&>KLJc_1!-dGKg)cH@0trdV8=yBra*i69vA>hV4uuhUKUDfr~`5|6=C<) zSa`(i2Q6yF8|edS^yRR8rO)U9ZAL?{)eK17*_4HGYaTj8HnYftfJ98>U)&t9r8cR} zRiikzSFx*KK$;S}DozF_RkhPuiUjoE%9ysbxMT)q);arM2jV*?&-3QAm0R(cBmL+4 zFghkHlOQ$%j*_38s%AlhTd_ETj5dPfZgmFvM?BU_m$$3W zK?K2px2We1yMud^gt`>`(k5?wS2FJ^6wf%xi94(GWDl8AF_=f)vPmSAD&01~Z`#}) zK@>VPZ;VxDlUgunFhRL)|W2Iag$_zD%-4=YQ$Z^V9sGz#6 z#l-Hp7WH675b}U@8OD-ZLwakJr2#j><$}z?tCiK@g1kaW6^m*|ka?DB6*W3_R_6Rz zQMH4tWd{2Ecl(hk7hIU|GE8xuF~;klCFf6`6!=PscWRuPiABM76lYQz2#3nz3BerAW39Tkg;v!M4 zrUFPchn$DCs;Fd(fNZ|6lqxgKC2m)Nm$h4(B*TioOc6*G{@+iIsc6<+kqZj(f7 zOlDBXzcfF?ilK~!DusZy)WK{POz-Fre+XTsE_c*(MI{MVmeRy`jGW}@)3(^CJnh(2 z$X8U!owuMfmJK@%X@jX{u*9pPu2726aS3$kJUsKkfb&|D$$dS7F_}p&|I)%Vt0HC= zswo1Cc|ifUjNHUZdaOQU6;5=SUOk{dr8BFd)J`nafQGv-V=Z#4@=Aq}w@ND5h`JvW z`AQR}hJ-Sdl7h|?C>X;fS~@ZY^<6*(L}Ce!az9onGiiT=;zv%8pwt3-KvYf?S0664 z8naC1O@>>2u%cSOe8;Sc+H;Kr-9jIXFEx<$xAFYA0E~ z{8*T+TZtQmBK`>;AcdmY)x}kpB(iKc5yQl?Mn2|vdl*n7-~sT}u*Bc*z$W3Xkx0^l zOqUKNx}>qG@jX?7U8u$2ktjn=tZs&xc5oBQr|r$tli3hssYzV^VQEM!eo-4ujd$2-YSb+q7<2_=$!@jxf3wj>A1(aNjBQYPSg;x>PUEtsArk8 z;AN^?a3P?!W`00$Op5E*RIvnS89ti43|dy@-D^5Xw~a?j8CbYn6s@hATQS}V^hX0$ z$@v#c^z_RC%M_B>YB}E!`-{Wb1~eLlA@gooLxU+CN?eXwxZ8&VN%;L#PSQI*9g-b= zJ7s=Bt(i3V@$q$dLP93aSRws)(E#%=)Yyd_Q9A_ONoxQD$1o(q+0SX>h=5f`*1Z4@ zgt6d32nDJ@QH43}bNjvu|Bb35In|KT&aLn*6AR|=xYtxQ9qtx2+ww8doy%G$+{8k4 z91)QSV@T{|El(zQHGi5KBB!)My@r5YUBpp|Gy2v+gJhi2#07Uc#HxdjN!-I@>7(yF!=*2V5Vw&&nTK0-xP-r?w` z>J0!mc8CF1=NX(03hziR>+p8*>+4DQ>e!J^>ZzgzSjJ+wSvpkuNr9+0q(fQCIh1kj z&QeDn|HJ$cX~kKG##A5M^H&TU$)hO~8=6fzW+ICjN*8P^mmBKK#Q-{D7^_j4F2gKL zG|9Wm^0&b`ixe6qB{M?y2FC-qS&(-9r0|S4q`Usg6y}4gLWNWwB6diVu(=NrR-9b0 zFRK56V=l($(UeFHOaaay*eY%$0&Qi8-k~hKBmeEdv0T$7W-|-xR3melD5WHihAj5l zHZ4@)j4T3Ujaeg;gr^F$T2hwOu*Cx?3=*Lc(5nCFW-HfZl#_8%PQL5YFIe_AITa%F zVfKc68w>j|gRiYj@m&p+wTxBS9m2z=mulwctgKMksg%W0&zUQsZ#?IO>ygZ^rev-J zV|EzMW}y7YI7{=DlIrV8IU3kdBIa>HWlA|%*(~MU6(mPS2rM-mGj48y z4zS&&j|yhIrzD?+Svt2f#qJ%?-XUEU8ak7wgC)i8`LE}@8p*RPCT2+HTGA&;u^M;_ za6_F6uEdLp_q&NFo@8td9EBX_#=t$bT0BlAl|F;pIpB2?o|3t6K}ZJ?v~BO5n}bZ# zB1Oi@AZWKOemv4aR4bmW)`3PFmETr_Sa zCf$Yh7FYo)wK;?x5=z*%==|#{43V{@ zpP4E&RRGl17Tbk(?B$(Bx*C4SjA$v$cD&=$vfM@T=k_N0oM1f#P&N$}b^OXr z$UU$GZ;=?q_e940eXNQ-7BikR1sIp3PF}6F0)sOy8gJ+d*Mnq{y&7Tk28C_!?Z7n@ zgv$vbQ&WL*3hpXqk;=jgP%S_SR;=V!K320jmnb00y+M7n4%L$>5Clbqll&17=CdEctb<4=9yZw<>Q8g=IF{45?LXX zszj+%Yc&Fu*j#$lY{~LY7`|i|Wq_s6l)|zy#$%`8&bA4~MGdZWeeN1*jrzh1iAIMB z%+4ftVq>mK8H?)dI}TXfqG}RHg`Al@5Nm!w9Sq~k)gq1ZgP*IVGT3QH7XFRB6?$!i zg&|SSO>Bm1?|!+Us0=WnFSRfULVBn#3Fc!NtGZJztS#_Vb=I0`c$zn7{0Qk(n7~w~ zFks+93l(1&>y>H>J5%(mP!yd*xf%m06$dN7x!G#i(1JA)Ta2SH`~Yl0w~}QtsTE8Bh5W3p3arEC$alokmX@(BBHj%w$m5 z&gK}0*yHAz;VOb^8XTgd3dyqZTT;r*NI;;P8M&3;EbC)AL()>gy2 zB^35izig5tjoeBlpk-F54ih_fevQUh{`oF)9;lWMPgG91F>~~fODIU^X|Y9sYNj;& zqpY&Y2wT#M>`35O6&U2gO5@omMD)}#fq`J9h}B@mvINj&F+PdET-+)UHjJpRG*Ttv zE|CtA9|sBTs%=3y5ZBRWze*E!H1Jv(F^zoayX}0E#<9)Sbwt249%Tq>ijE3ENd|&O zzMfzKV4$4=djokApRJNCP$B9N&iT>aG1)zh-bD5}$S;~&DKS$SW)M%lYyxCf+tEnE zlf|yt74q+=TgoD8%g*;=5*c#B1Xzv96WS>gECMz9Ediyo zhP!RbIA|NB4o~M$qI{|cbWroeQl?G^ai##Jd-dy65YjkUk_(= z6XpH&KhU8_QAi&?1^aA=8_*MwMA?enGBH6}vc{~EZ$u@6EgzSO8ELr2fR-PiRez6i zFvUErdXevjTUM+hA%`-$R68&lIyiybu_l~jtH@U}sBjTwvOr24o0J`*$2NjX4(yZ9 z+@A7kz{4~dn_)hmz=%hCQzBrqH^YMR$I0~0I`%h!%b|9aS8xUwc}@drM?4ZG^k7St zifTcM+dyjSrdB{-h_PT_1MVt@S>M2a##1V$mFcEh`Gi6OwUIf4laA}laLG6re3G*f z!c{GjC2?CZ2Or0=;7}InLt(%n@i}DfovvJiu~zbVLAxH3oVRv0YU4OgP7Q4UChYoC z0H1{|d0!bQ6;lV*msv#-mj*Ple+GzrUzUI}G-tBWu_hSD&{&77gQ@kB?0V6Vzv3v! zJfvog;<36Tbxy{X{0;mQlCr`DR8)?=&@L7~$hTBOGm?ipO@!Sdh+y{aJxk;gnao(l zz+XVjduDLn0%e&{J{$s3{13jiM(_xfX$;1<YKet7wO%3xqwqi}XwQqk zp-J&=O+H67E5$XK!iZA6tYn7I-QaFOo%Hi6hqAn zvCSGB2&N*hRC)_T0q|5-qXQd+3)2*2u(dBUIh9i6zy_Bl{2vNmYcioCPA% z^??&4etQdXIJ|HpwNUC8^l(Fq<9IgX*gP7tSGuGfBy{N3kQTbsY#P#bWi5EOUTspK z?8M^?J+@j!rMwbSSm|!|m>cN=bA_5lDvE|$CQC|51eZHQo2{`ErDx#^xA3zTJg8Li z$OUjIKzZepw??Nj@njuqNPdU34jd~QP1F&S8W_Dy(NKTXr@{K2;GVs}Sfj5KRihw? zHE>rUqK3;}sbC;-OPA$<3ubOnfu|2GUn9(6PogH1^-2KI(7!3I+rL>6 zBTe-81TC;~aNH>t8LH4}dI>sBFO{lX#Hr6VU~Jaccv%vufTtAqzoZ??TIrdv}4EFqSQxDJWRhVxVFl`Yk%Y zJaE!I%@Cb}8gynKn@uZNoFv6{yd9#Lr{`?QxUfUSb&TS3mdH8D-vVN>UyZ;dk+ZCj zXhn5ADxDI3;3+T>VmFYU2SA3mWc*?B3$6BlF;kU-ZtA)$S)7VcTXd=Pzx#< z6RK0hLKCbxcZIZDZRB+eq!KfOPLn(l04#e8$0Mp|wUSEcXxNJBbB^L;;scT2+Tk*F zs<@*TXsYeBJS(C(JNxhA7Dwvy=4jd9r)(qlgpw44@oUqd<{F;fX288t77aEMMsSq2q{qVtzzZO_P?Tm>Z*JhJ!Um3Nt9>C}rtCHXY8G7l62QVDq5# zX?Z@RY=q3kk&hCfXTu*2IERVHmcguoovsDU!Wq)=MrH68X&A2Ez^tEd;$!6Eq=YvR zv~cZ+0F0<>(Veo~cnviyqX~{u$Q{*O8%Pv29K{IS38`%~o_<|%$d5_L(Pcmsx`Eqv z!8^p9h?{V;TGsp0;dWd+TDeLtNI1kiar3Y`3D_v0hou+S>ai~}QvZ4$%7MX=Q(8vA z>5VT`A#8`_tVCd@)DqHLp>zA1i}69VSA=qfOo=IrJ{F9U-zZ_Lp5E&KNCa zT%>eBef`Tjhu>4j$;-ku{8|Kdcq}H7^UyY6_DBd2P0kiVRl+`zf{m?cHcewPl0~!W zw!JDDJX^91jQdsJ8Ha|Lw@6=mLdFONf-^8BT)v1G&Hs;daF`?=4A8wn_p%G za8ZhjE-&FEs?gusRsuHj>XEmsDJbVJu9}O~@HrJCULXdi+zIHMb=;ptqK}|Cp*MM$ zja2;Ler?ao)k!t!^kWH*YuaKO#gf@6$i!ai=<@)cGoPRGPs z4!cfe0t_Qp_Y$<8Py`r3JDqi$ZvX*OEXdpi8s(adHMQ-E|41}fybKJR(vMTM^rrk= z$La+rE-@nshF4<1VVq^)TLEZDjx+J#;$f>P>hVGCD1T$qP}X+3>eT!Y*jbyv1T24Ha5B<;0Kdw| zKvEe|D7-D4pzQWP#z-VzglZIq&x@(IMX6-NkYaU|D@2Ed29J0^4+OGPazZV`{Jv{(d5pEBCZ;MRH$ip1zteN9>?JJQki z7us)2|f2wl&4f$B4?vZm4Ui)z>c6#_hkBm6 z%|Xrm13=LtW?AznmDWNl=(I&BdNiG#CYnSN!wrruhry<2bmMl^1{QJAqRxN{X~|g) z5oFvA7)KgH*`~5bA_+^3u#<%{dxa{HN+ zI&m|bq20@laD5Ps;RuEvN=RGQYNSLzNPm0kD484sHDG36B{JgmpC6pfS@}k=DtqKV zB#*Ab{Tuz^SED5CIfxAC(UbYuQ4qU{9>5#791Ldz;LF=%xLvF&uuUKH;GtJvqe96S za|!@MsK69zqy($aNF`OUO4vVAu=BsvD~-umI*i0mffKHtDFfFHQ45J42F>OQCLNF6@b41jlDp= zG%Pr;7pR~3)44Nq#^PpvfE#+SOUyZ7sA&RH6l_T(Fz@{L0j89^1ZFvLh{)qp0yJfL z5JcW#Bv3CxJJFfXIUvAeXEfK4Ua=M z-?*wK0Q{jX#81AS#~WDB>rtWq=A$3m?>Hji8o}TU$v1PDQ?-Ol7CHeWr30%hxUa@w zy77mwQiJu&moQgWMjU_5!pGMDpJ=ji8iWhL}O@T z{)`}jtx}rAh4HvT6W=vRk{!48y-KGP_Ry)RiR$A{rp4}< z*j8(os^8J7OSTZM5eUhUtb7EbjfnsJbfnhNX{i%>df654QjAs+u*6(4MbjApQg3^J zH*zwcoskzKd@~A%KClmnL+e(8z^1l~?8l-q(?ej9>k0IONR>!{AQI{GNK8VJ*(yR> zY^Yhg3sBSpy)T4*U!o6yiN_QfDdIL;NNooIPN8rCkwVU6DG)L?0u;Z^dtduDC3RA` zN)ic#bT@&R;Yr};JAKD^0`wt{7Cm5CS?q~N-BHb9^??hLm`=>fL`OwdOo)q_fszyt z!KhS7S%aKIw@~DN(W<79!-SO7NSNfpuQprDO;m~K#Jv_v1Y=B82%!8{ zQ*SezTNRXfyzt1`wX#Ekyq&F$wUuQ=d0FXMVKjJmk^}9p0t{naz$A>)ftwlKz)OP| zXV$$6C-`o;qa*f+^01N;K|*|7Y)o`ifm{>HgyJ`?-#aO`YR?KLc1-*pN%70;Fr%>^^IGZ;hm0(2$s3y2RF($Ut^P*+n`R#H@m%FWJ5OG#oPzy&_uT%8{7ZhAfYcmeBp zV9sm4zi@D{Ntz6c+g8jmzHD_H%N&H`6|mii^K59u-Go$yEtl!eftU4?6S+k zbXy7M@7N7UdXV#(8p+oqB-Fos@)!B3b_htHx8zQ@}T9!-}O@tlnIMi|_R@x1(fA?Xz|I8}UdP9YVZo8WqP zuEfZ|aT&zNLi(es%xn^lxW@obVlg)zADpT#N6bTOSAbn0{s+KH~Q> zp51GFqI$TO=xYYtSVj_4+^9y$fo|kpd}{wA;rfz#CjB-XH5*1DlQ*v#xfa^gbWnGm z-<5~)M|3;F_=n__4EFo3o^v;<-ufT)#}VT{l22`rKVI$@;r#IX8ZmiY@aXh!g|X_= zR#pyi$U^dwa^6)m>M=KtULcIe)i(K>ZQjQ4Ot|53W38+8BXr%fRfc?>rBw#9j$iwc zc_}@h#SuGJcCfq>f)r(y1M51JFeztt}w1HSjM^SJK3?=yt7dK=H{zAh34k_(2nMR zccDDZ2^V+pX7-EFQe5Jp-!hO0)sui0-wlhi@TJeXxGxrG6)9xM=j8-t78g|2KQ1om zD&N$$6ieN6bThsS6)=;7pIE!Ss9kpSk=b6}@s?R%He9)DY%E?2Wos0lzk@Xr58N@< z|EwJ_cTI9FtSNq461^cs_|cmybUpCut@_a$)L-1=(i&O3lXAB3#TCUybXz(iEg4iT zzrt^UI)={UCuWajOPA)41xv4tWlWbWjXxSLvFOV{mhiP@^vGvq^`tK)9vc?O183%E zB{h`}IH*(GW3RL{zb{^C>;E+Rt!wdb_qVR|&)sXS=Uskx{JOTFV@LUq9*Z%^yOElI z$)Vjf;xVE8HKWmYHPxn}Yd%+jml=jL2yN+qJdaAk{bZtOd=+@5Z7|O* z_&C2Hto>qPNnHJ=s^zIvSk*hz?Skp~F!N9rE#1kh2JPn6t8{Jtl`E=z*;My1=)$z6 zri3o<-q(tX%gp&1B@G#T*2jk!@{Z4Qvob0qKLoi#55^7)St`2JG>R9mH1wK{eruY4 z64}b*craMwo|BpXPC)mOszN;aN;qpn^hom6TTQlaR;buZYl9gtHK}yiuc@HL_NC0v zx~j75)Q?t9psNi1S$SoJf8+>sbzrw5=JKGFv=Kp#JC-Wd&`@}-Zd+(&qv=H`Yn!e^ z7`#WEG+cXb+9t_Y)*x1xu)I(cdUWdE5l{*FoS-6eLUH?Gaur|>nYB?uxI(aamEj^} znX@V+@eDLHQf|mYMzHcd3H7vMV26(WytuOF)7>i-{g+0+)y&i3TbTn4;{%M{A0Zo5 z6umCU1Aavr7V6_wY*4Yj{5duGz~?-YNRwESj_(<>fr<*%goN~Lpe?Y1k|8d|8M%>& zLB^aCLIod-o)8|}@mX(PT2bRb-tI1b>?pCM*X=mDX~XR#{rz9$0AuM#{jXGKGN7T= z2QdA<>LFZS;X8m-N_lp+6rt#djR+A$y2+K49Ym(6+zVQq9=PGksaf7K5$?UZ%1WPC zk<}!-72v&ExFXJ2kXFz+0V7@rlm8~EDH=OY?C3?FB=_|qPm>pmkUwBIkF(n(n^14I z(L+`#mG3KpQdsG8rP8X77fc_=UR`}pn-6@VfiBZ6Q#E)6dQ;UNSXeagx2Aq@1gEb_ z4y<5U%alU8dN4B9J&UXv!EFmIt!G64QE-X-PZBw#GgryFWk5ZQqz_D^+$eWU7n%vJ z6V%blYqnNzseqHf_{N2(_3E>?dR^Y-eCryIzZW4@YsYovZ;ge$8&%dEv^$1F!R($T zbd6!b9W%dHLrcJnL%$Vl_~F-z&TSKSr~3>n&5s#0r?+c0i`NE? z!EyqQv7kF|Mt%1;3RC|Z^8g( zjtt2fPWKDFZQ&B7u!VI`;Kvjp3`Pt9{cuI*)WfeHzt?!G9k!X%n!j%XdHGT)r|;TJUkJomREryyK5a*pJRt zm-W-PqY>+#?^x-?m;H5Rx)uW2wV%xe;=kA`<}VER_Hs1uMl(0!+5L}eP*%w z(swa>ACght?ytztkK*5+eWCCA=K0-QY{T*3v2%lTgVwXR`g;B7Q|8}-!%s1P3taoU z@c+gp)9ZwPx{vsS{r$y$PhyIA%B73-&zV2HAAS45t`mMdTwwEXI1KwBF`IHwXn*nLJjVCYIh+_?I34@swJ$9dx-6c0iajw4U-xzr zKVL}vAD<6CqdW11XJempKPW8c>3+1Y68E}_PuTGN{5rq#CGN;5FZ6;}^@roQrF8dk zPC%3W6#vWcL8L2RNW$%P%QvRnCqq7-Mc3%&k9=xut~V&dB*VKKj?PQDo`e6!iV>ITEt-YrbGyA`eZZux1nsh@`-AUXNe zVa+kcVNJ7H@IZXuH;uFTqaOqN566Khf_E8z*xJ6S{P>@gF?7)P7lCho%0MF=)#G7&)#h*72=VcLRj97k$({>;}amul(amDR@2$?~Y@yn@9d z`%yy=!IMwp#)~U(Qf%K26|b3(U#2RhM;&1{R&DKAM?ECK($#hkQo^BG#TpmN8h=N} z7>DBU$m`)WK@5YlIBbs_i|gtuILWTC`?5CvqwRaUaEp{Yrkyu##mMUyHV=f!WDP8` z546n00kV3Bf6_LHj`!L&2v1qzy(5Rsxe+)EqrP_mwQ^N=X7!(j?nsROja~(yD=?7I z@%&RCVf>|g@H@J9=5VL5clvmNO`2xaOzcDYRKnT$8-|TvgO^b^og_agwrfS?)GuM- zAxuP{Vs0%7<@We*6&E?`-U@iqhJkHig|nzN{tUe&Xp`M>+8 zT`ez!-c)a1+AI81z9LJr!B|j5-tpF_m#`vESxY{Gx?vpmf|R|;)lULp3+>+{uaR!W z3D;h3y1=up*$BL|yx9%#S;Ca;*ucLG8Qr+grM{P9UI(8ddNmKbk+RWqo}4e17aX-; zEE~stak%H{fr0OZ>|ZLKI_rfPh_Y|1;nt85ZK4h56hsA0^=L4r5t?x@){51W}j7|$8PWGEJN zWn^*|P0e5AEL#SBq=@U%xUcK3zF3cH;4lBJ(a}4yoVRi})0mfX9t+vfNlK%xZg=)y-H{IY(9L zyi~Qb5mnDaR6hev17Sk+O6sAiJ}T>_xLzvkrOaOH?V<4A zsqhA(K7goaBK0|>9(L+Or?z8G-K;o;bDvbsaZ)>-A&r?u8H$ee?1Qj$9 zl+Zy>L-RZlozrCW4O7xvrloIk`lhINqWY(+huV6mu7?_XD71%)d#Al;LcD>doCRpY z*@+X*I-GJ=@uc(U)6N{8c+TMDGoaJY7o31rYYKX-DQGSxp|6;RhFTh0X$itbOGFDP z744&RbdFNeI7&-zm70DD>Y1jV$?BW3-ihm-!2XHsoz~uo?wR~vY4Hb=d`6?xGZdws zc_{fTyY%zv63`%)f`(KQnow!zyriMuk|11$M6?%D(OO4EPaPS}bab?l5z<3ONdp-z zy<^mL&XLnMNKl_N^-5K*g!M}io~i4b#J(tSfq%MCG&Hsmw~p{KcopuQ4{I!Z`trl6^hf~bZNWOb0x)jdO3 z=MZ7NCDQt$2yevoM_!L4_DL2#Y3-Bd9%=854xfnf2!NC}AV_H{K}!D!TKYv0)2l&D zZw5I{8T2%UAgK9(qLu@aI_+obu^+0te5%&+v6{-~>Zcy8m3p!+^4WUGC&EBL66O)& z8fS>>A#^>_*B6Ps5!oN1{gK-r)xFW*7b*VGaMusq^_ROYa@VwX{kqp9{`zvT84p`;B*clv%u@Oct{XYz4C{HNd9=HRu}ytb6ru6=D4vt4@IAa;B5w-b*WaJliGTW-49 zzOdPKk8StYcXOS0)_PODx6^wk%{S3}3*C3me{To;2Y+;Yec`!Wae` zah(-^MoQ0r^>^|8@B7~x?Dv`dKJwo)2HeDfi!OMF375R^#19u7!+R%Qx8iXx&Nky` zXSl+RgY7uikY_FV)RHqTxzUpkO?l6i9DYsJ9f~us;!##y$BV0&@zWa@@#B|6?zrR_ zo;>i%_qN<_4aRxkmkJ)uC1$>D7mB z-RIXI#n0lO@#HV_J;Zkr9_%>+HwHa+(NQ10^U^aveR9+@toq@s3*LI)uj5_f3!fNn z6t5k-g;yMQizN(Wt7F`>jE$CY&^68(#}PFD0^dn|FKs=7UB7aPe{A9xqqxMdkFH`7 z%f8{+C4X^-VT@rJ4?g3+X{@)6>&9{1I$nFn7WOgNLf+cQQzKbvB}16WK12DhAn-Tv z`u)WB72jX1!7( z%4=7->@9m8W~|HnbO0ND=AaE2=LP-z0TCD1n-J%*Ycg z>N~FfUhAj1{sh;Tdi^8UA5wjv)5rNo{GFPTzvHiX^M*ekNc4M4e@FFm5gv}hzo{MO z!y5tdFhabk=3k0`DC2G)%j1If3*2SA^zvX?{N5=4gckD`B(mvf8_7@GyaMH z;V+o;em}2A^ma-w7xivgzxMTN7JhAQ1Rg&M{d+op2lN++J}&9opkD3j&nCRt*q@pGS=(#~ zUrXWpFnkq<|B2xxF?=B6%OQSQ;&UbbRN^lsei4QrGJGJy?-6{Rz`p_f{eHe*@4vhG z3p_6u^lwDZhV*GpZ&vkWTVDa}$trwV+Ly)sfZm7Edm?-9g6~QD{?hLc{r=AH)&4&0 z@3a0M>hF{MzKHLK_8utjd+=W8?r-XTx?c|G;d;I;=+%h+?C8syzQWW~$a=8}FBbL` z%ANw-Q>Z%vxi>TSNamiF?o}_mhYQbG_k4B#R`+Ce&sFzTb>CF?NEe=n?uFzYDDHdU zKIiRc(|I+YM+cOo3E5d(yJs5=tJ9{v=|I#~xwLi1=OxWHA+rPHG zh_;7x`#`s+b9*$m2Xp%@x0iDJrM5R>dkAYUr1n2)+jr1(zOmr*WI{hi^kPR3w)9?4 z-$nHkvVH>BcYS?Vh3``PF1nxKcLZgh#=<+X@HosKh1ridd&;vnJo~$|XFL0`v*$8< zE3wJvW4|}{aARLa;k_t)7KN9h@Jkf78;RjOB(`%< zn9n?4LgREn_z6WXA?df8UMuRgs$L80wIckM*Kdve7TRycy_Vi%9Uei~7tr-lx}JyE zv+#OSujlafj9>rf^>bd^(Y$UB^TIhom(EpPJ3DppjMUXLQPC z&W{pw7L2g7T?C%lBJ^A#g3lASe!kiQI%+Fur7fY0wuTncf^bh((KuK~f2H(QPG2SU zRaIYw^;Qx7s_U;P{FT{Xx&4*hR|Wo5;}Tr)jKI~-Rey zh@P7&8cnKbF)5=ZL>+Avh4d3t(n(NC6G1I41l2Up)6?6hsDGk*sH>04`X~u6<@Hly zKUMZrYd{E>Y1>=0ZM%!RI8U+*5qD)C=E-9U1iRnP4rtOrRZcBQ) zED7o_q^P%$q^>%e+Ubbuq$8_|j;;n&1*SK@l6u5SW+Ckp>m_D^aL1@}*P z?-ck=jIWgW21iUkaJ2N8qotJ~Eqw!tX;`GDNgO%N;RtHSqo@asq{cI%8m_47vm&d- zimv7&!WxSxYbheFnTR56L?q!Mq6z;Hb)8e>^+#Wy6!uCKeu?av(7vhdo94br@0kc+ z>G6>~tN~Hf4-iEi01?zoh@#FxBy}v&)TBgIdmySBlIUtf2w3k8Wi26his&tvWa}m4*EfRM+oih5ZV)|y%F0V!95b)Bjr6(;2$l1k%lK4 zF$O#nZo@O-Fgz1Z!ZYC;JYB=`csiHR`z)ty3OE+_ z!ZAAn&e>KtXh+?o&2*!-5f0l!I&A~-ysZQD_Cjz!B=sscMpX3L3%&r_d|p~wD?1nA0*;`QopBN!D$z8+E7-zcC}foHtK7O zm~G45Ui|IB;oduLy61MAZnQ5k2x#+&NApVm9+y@}>K=)QUW`<%e%5quxR z|55y($p7K+JfWX+;``hiEccq_wzAwmp4-K9vrf0?7v8Ylj^BMa-hbsd!LuHmYQmc)9O=R#Y&g&u&N=a4{B7goOg>KL<8;n`=li~Q1n>Rjy@|Xx z?R=ZKZ_)ksFyNC1uK3`C6V7+xb{j4?hS^3O?ZnAeJnY4}&T!U^PwhC(2(~m z`S!{m^Y`#~{-;0ekK_B{_ro*TZ~``*$%f^IWIog$zZF$(2bB#IHnMa+u(i|qb!$E&I=McZ=FA96)yX5!C?~$_^aw|vPW651S zdFzyeZn=jqryTPPXMQ+`1NZRWAGRCBb8GIlh|}i0?9Rg;G1s799s1LvBQ5&SqxWq3 ztNtf!Xt{s5f;~Lu4;vZ8G}b)DA~v0K&OP7o=aPfYxafzIIB?SWE*)>v=|=tS)YV?S z?AF76J;Eoh8pTbgxain}UUAN|KW&l(zKeSB=xQb%%BAnv^cJJuy6U2{9%9!qFLC51 zW*o(br&#dV{Z?__EWU7y;dZgwFfN;Rux-3Gj-|%2(mIAPk0W3x`0o0>#CO>5DVAW^ zxg7hDS3G0cVV@oK7mv7{#**7O!#74e$As7Z_m21Oaos~{z~9D$?=8N&sIdmyIL$X^a*lPpvQE za^X(~Jj#1hx$Y~&z2&sQJhqu7oMx&CSm^_X@B;Jfz<G5Bc> zF1mw(a%}lnF8}G}A;0_{m^TaaSz$g&%m;aS94%7*Cr5_-&>M8u?5lUzp_Mlzf)O6 zt|u(piHxt6@hN6}Cyj5c@p?5L&BjZx@lQ6sh{gxW_?;IkQn>!Cf9h}gllVXX&HuxX z$@7y#-Qa>2s{V4q2+FV$lNbAFJebd*!Eb)XUUhc$?o%pO1-*n=OUSEjydsH9i z^e_EKf6*WG|NK4w%|FE7;m7s{2zbDn@8;V!l4g5 zdcLQ}yZQ?gUXJYJ*j`TVHI<*m@_%Uf6_*cs`IDG0i1~AvU$)`7HayjaU&4GN4L?-* zK$G7i`8klkef%B&48P)!;V;a2!JqdV`n{vSb9%a}kCX6jV&8`LZEf#%;Rh0bjN%(v z{1}Xn%J`WXJ~6}RWq7v67i)a2#*b=zCB`pW{Gr4TLi`>KuXFg@!k_RD{QY#E59slR zzV7JfnjUWI+q7QI>(j_SE$!9dUJdUE03QwDGYR|}g6}%`nuG5+_%8+W|ctgpWIVH>O{c`ZTOR<9Z8XZ)V}m*4~Wn z&HA3;?hU|uD0r_U?^ERc2)(D&`@+4y+xs=Wr?B@C_MU3*oAe$j?}_kU=zerALc;3sz4S;VXQ7g|R0)d$PADyZZ`)PZ0MA#QhYx=OOni=AOjd zW72&h-S5#o9Nm}Ez1Q4h&3)9|E6u%;+z-WlP+ZPEZ$0C*1^pU?PcwQmr7wH>3RF)) z>%}I#SlCk_dkSh#LGCH+ec0g>tbGD&Z)NR&u>CCCpSHb+w$IS^fNf9L_GxV|#`Y1| zzKZRg*dB=uXCgM8h1PoRNede148mJ5da|S+b9%9<55sye2@mG=U=$wA?8Dj~tnR=5 z-fQs*EW8@C&tmpM%pQi>ub#c;*+ZT^;o0Av-OOf&a|N@UyUcdRGUGYQtY;-NpNq_X z7BUOkrwqb46^x!j(u*}cSk!x2eV5jEP53Ub_p0z-XzvC0UU=U%_*{=uKq2P^#&G5` zc5{)jn`y^zb{)&Pvx3f*6?Voj*7M$&&v0Wun~ebtMj_}e3PMv+5W0!N&_@&?JVb%$ zA2FkIz>@xp>AjwQ0@ZV2J(q;%@_H_@pJ3r9to?+!=h}M-hrcyB246oj@a6NDubz>7 z`CJ3c=M!K*gMtB#2^RF?m(YJ-LeqT>t@Z_B3SLBWc@cf(RWwwW(MnxM7j+>mG(l;e z2~5xR^b({#g4J(r{nmt!0QM0oe1x-?;Pw&fK7!wCB|eshIgl{)16)D_z$G*jE}?U9 z4IRrh^e7jEIdBo($W^qUgre;v7|kZ(XtD@Mdqqe(YfI^=t)-c^m^RvKdPwW(AFZfy zvZg-E>anyQOTuS)eU{j3mA#hQZ^6CR-D?FtR))9A+=DEmCCDlojTX^Qw2H3PDtd*M z(WP2Pd(cAqF)L}pET!?VmVU!x+U%-nuq&q{SUp{3_4Jcf)JauR6ID?iRAu!}71md2 zeU*f_>UyiNzpC(9XOG49S#yu2_g92(_4rgC{-8?f396DNz$$4dR!Q%$QksR;(x+BT zgQ#jcvdU@0s;Bj+ppKi08f{8y3Q|*ZNl}dzRdrNU)k{%V8%1S36s2`fRD_S3@KIhr z74}qPUuEH~(%$Ort?J&Y@2d`ea+-(K(=DW+MvaPEGfL{o zsHrhPQQh}cHQkfdYfe{_IbrS9l(klq)>KSeJ26QZiHU0>rmlgQzRqb1`z8txRpFy7 zycF6`u|1XCQ{6ok;7=vKROLT$ew(PSpo!`Onx>x8G_{W=s%M+3R&BC+v+3$eCae>g zwEkP#x=ty=ZAuchki>OZ(uBE^y1qj4ItmHwC8V&8kSHvKRAHVZvRAV3OlI$d_E2me z1@}@HUP|w!20x1NpD@4a^P8mg1xZ;akd$?mq^y4=Y5gK;YgVKPcOpqx0}|JZM-vV_ z@*26|O2G`-#ZHNknE75uyE4ly;7h+9R?3Qrk1Zy%XI( z;r)}}KN-H$<2QA9rO|Iu*B2B`SWVG{uM|xcyd9wj?yDZTqk866>bY=HkKI8(ckc+{{Se+4>HSgPBL#la z;U_gd66PCuK9TDk^q@@v584Rupk0LrZ6G{p!{AZdb!Y9*9kw%d+J4k=`!EM?znr-5 zaO94|nR^ZA!WM4orf_3-*UjBqTfD2bc|UFUPTKNKgyT2R4&XUIfG-sILxWFr_(hCg zMEOJ?9>VAg!G4e1g5y?m+*gh}$Z^Y9?h?-(I^7<=u;g|pez)Lw>#g^N_kNr2wEHgG zZ?XTLFyLAXPW9kT6Ml5zLm%$5MSQ!H_&O0o)5J8J}$oJy}^06Id3lK zUF5uJtT&4HCY|pP_g(ql83w#?!TlZ_Z^Gv;+-<|tKD=xUhn@I@70-I{sTpTF!$mtT z^y55B{+9A{H2hA7=iz*w($`7xTO8j9zYi|Kg9q^7N*)}?gs-@8)D7qS@C_pldE$vL zJ~-oiXZUW%=YHI6$kUFz?8(EXoNLRmwmfPLD}6c98wQ%gID_~!G30xO?-|bE3@31g zqnu$LH|}D`U57mM$Ui)J<&-yWIfgYH_~m?W`0mW-=CIoxPW!`UgE(x>B`jjAH-EZw zq&*+{^PWY2)1UQE;p3n39rHWpAB?#JW4>j~hrD6hGeP)X*bn8Kv80Qo;p!{a;o?EczXZC!_BhE4C zvWLz(i9;tn#HC+;dgQ4yuKMAt3*NfluIF9i3!4~j6srw8*(&~8cCA}1wTqL6@z65v z*~Y(viUZ$SJ$UssvtH%af$X}BON?R@pN`@Y!~S6vYna86%g*@h7?=pqN5c>4zb4f5T{cP0(|#t$vHz>iqY8bf(u<(_%n4sb zxbdep{u0Lz=J>cAFP7u4a{LmGC#vy4w7(PMfAP=$vj2)d_D}Jn_)Yw+f9hZQhyI;8 z4i5GhP zp4QJ<{ZoI`-}Ep2M}N_O#J~B2_&MmjhM<=;`o^Y5w0gn`4@mZXZNGQ70-=v3^nr*z z3lc9n`jw+UNcwZ6Uq<>8NMDNdl}LP1=nsAVkLT}j{>|pE`G@#H{2l%czvVy4^N&Km zX!IDEevs<_B>WzQ$4mRWxxdT1fy^Jn;g4{58695L!*}@biynUO!?%5S3LjqU!$*1e zr4DbD`9m8%=<<6se2(Q``A`0lzvIm__B^7{8ydYJ)B8=mp4Q)i{RIm@=k|0Lo{sMb z9zV_FKY@H4kZ&ULIVC?*@&zTIPV&zr-%IkZBp)jBl_0<9@rN8gsPTI;d=BHU_+!v{ z!=E1;#VQQ6NXPZ{G!7jGJK%I=OO$X!JP*L`o5vpGy1!wzYz6uR}UxQ-NK&D z?Ah3!ZSL9jp3U$I{C)wy&*Jw${(koFPXWIJ;Aa5*Kfs>@{4>A@1AHyOp8|X)z(4wX zBEJvvySb;2XPiBt#~XS(qo-5)IH-TKdbX`s`}(vBpN95nZl6~7X@8HlxCM95z}Q&D7p3?#=Sv0^yfEZb96$k$W(5k3{Zi$bE^q_niC6xyLZ~ zdviC#&Eae&r!$z`&RcRkQ_1z*BoFQIKURVdSDS) z8p6e%=O%5P2TvSsmZCi@G?otWGF)9!wO3*a5*+N7o-q~y+(ck;z;+6S12?s9FjDE= zsPk@#Hr(w|#0)H37cr=OWmH3XBEyx*6pTbDV-;^y zvcNEF>4NYCrVK#T%o=;3A9U1dYWjG?>>OeS8LH$9LunK%ztt>Bb0x(1n8BvW0YkCC zP1frK+bGn#T@t<9BN2C7q~UIn7Vj47?+$t}=}PFqq^n^ElO+y0m^^`~fnu3N@z-kELyr_Q$OTRgM%*}nSYkHG8Rbh66w6g$ zDHx@?)iXkCtqJf{XmtRGD;0xn73tkNjkw#S4|i)sdACc6_a>_ErV#u95|+?|$JoLS z9$6T2;OOFTgABA7AYinCVa#HVLnn*1hCdj44A#g11h9o8kv5HNGL{A0d z6g$^OD{`ceTG%)#zgWn;B=Z&N0gV<0%><(QI@u)Afl45x7ODXch0lw#wRvx-KHQro z4R^cLcyFEv?~N4S?YoJzem8N(-%Y&ncM)p=@8J)?TX^IT1HvYLY6zwHaT%=Aw}kM@ z-j6~pc{U8Y*tx&}h7LvIm^al$GisrZY05xI9uQV(xj+QP4GmBQdYFLePE!LSHxbu| zO!MY&dAK-MnD^#M^4>ft-Ww^vdn2`X4|V8W!yS9K@CV-|B*J$Gm+aj@Dta3LZoz8+ z5X@Zwe`W5Z{0UH31n>-9tsnt%w1Wn~ttd>O?e$PiS}7s}Wt@x-gl%w?K!RhW1y+TY z960T5xJGguF3=f=tK)Tfak41yja1{kkvhCLRekqnO79L1xx0cz?rva`y9@Z_?)qJ^ zyL@BnIM_oo_W+*(b7=U~$XVH2BbS7aO`MOu2HtG=J<#UjF9Nj`ffJmm4q8A4YKTGD z2Sg6ICKvA&qT_w~le|b5DsPZl<=qjJyg6Hr7pE%m-c$+R8?3y0LzQ=%&|SYLbhobx z-R0Xdck=>}yLSn|b#TY#ErL4--VCslz-^121#V65FlZZcm%*A2ybaQ5^EfzTvFAbA zYu_Cs{kvbAf>*+&;l0X$c%cLtZ_-`k9cpvDJY$SEhb!^oWEEbVEWdlRRd;Ww?k<+M ztGDIt;)Qv;cW2ygof@_~CxO}ra~7;kAeX_K0&*Rwg^{~zQ|4~kk-2;3gYKHyxNyr} z)ZMWZcQ;Hm?|Oam-3T52t`(%NqZRX5FFy|DKoq}UW7MC0^r4opm%Y&lyK`&MZkz;W*Nua-+jh?^XuD@@+OF9EZPyG-+qJ;N?UoIByA^8R z?ielH4Pzm9zh39=gweXIrHOZ|D)%l_qTfyO1H40Xg16@k@al;9-5jsIi_=wiak}E} zjaA*9Gr;W1DPVTt+E}=66Odgt4lddy%K+_?J%Dz}3_!aTyrA7NDQTC&5w$DkL+ygy zSi4^@*zSZBZ8ric+|BBoyHuEU_bCJ3O$yYzLv?$%XHD)!gX_?!eygi?5=GfcF`_{TXrd2vP*#jvP(e%vP(ft*%c#GcEhZcT`(pV?w1g= z>!suDPLP3iC9FuhTC8jL3e@dRb%480kaHKQu}ItnyH_SGTq;tu3#F-cnJU}vQKH)w@_V~Ib8mNN?d|56x!s&HwTtuBc5krc zZtdOb%-*d|iF>0XdU13-+?|}r>yvYMfpYw=P>$V&fCG1naMbP*PT5t$5xY${T^H(R z>r&lVU92tCjj&a^U0b3ncF?tO&RzQ}_ZRQm1bzaK4?u^M(vIPE+VQ(kI(K(UNA6nbu-&X2vl}6Y!tKK8x?wk2x5ACo zJ=-o_wk^?>8|dD>y?$>&;V1C;0(AH-q3@)`3xR>O!Cg91;4&FqxlKhut`iaMeS&b} zMh%g;R6-x_RS@OP`dPdYehBXbpT7ImV|T@P;BJK-wQHtBcGYsc?i-HQrMro`c-y4w z_s}hT=Po`2l7Gg-cM1I{uV2K)*Y3F2YRZFaCFQ`qlG<{yqK4coDB8Q_gyKqQBE4Np zniot-!=-@ac*%?~Tr(oTt2UH(+k)h7+>hF=z&ebKnak`4zq6@jt2A7`( z#DfuiDz1-Y_Iv1f8%Q(&ySIyr&GnLS<$gu@al^J?TnbqwZiOon*Fsf@n>MB4vPC)G zH>blp#{_uolJKrxQQQR-O8cO&N9+T|oGlL?Z=yV~+!}S@VV*UcKW6l=ygri_9|Xte zDDp3^m|X?V%5DP|DqIT4$TYB^jLd)x$|^L$z-%nX1!!U{E>HtowV=)G(*igy4;9cw zpsc_Si?IT|DnbhWqWCBvWV%2>qEQJKpWgGV6E+{S!07>4I7&lHf?M{7;saIu_g}9#2Pv?(gp0u zL@baecDX>G*W&_$5-==ac=flyV$|INkxFlCFo(?6gxK*~^LaD1rt~2;tM7n8F;11) zS90Tj1o<0IzJ-@pRr7b1TC#mgp%WQL>j< zLrEV7SS5jks7nk1J((!#d1&%@q4J5PTO*VXW`|NZzy7L_xU##ls>G%R_D77%iyNvg zHxA;`W1T2N&M~6ooE}rgnbGBA(VRni7s|Yk6sSOiq@cxN>l|mqvSGOKLhP^h#+BUEy- zPM*4gHjXf1is1t+E1-rIDefR$%(^AYpfMI`gGUx#t&yd+A5eP2hG^MB6)Ce)4+K7< zA;Co~?nSJ3qR+cE!o1rh#!DH2rwGdjMu@m{;8Sx$8_*CO*ocDgu)ydC5L8Hj`0#^- zW(OIgo|v$uqLiwrrjMbktcjYgCj#=GRgN`Vrx+HT1baV9wY*!V#Y-8%<_OCNHb-1Kz&T<=1D&26*m!z+STn*C#1+pG zAB1>%284ff&V>K4Y>4G7_&DDwzwoa~ht2BDIM8#L?T~~+2@X0qq_}+HVFe|M5H225H)$0^N8BLPB*9}3 z^K^(@B&(9M#1m5{{ITEybpb{IL~XPO{FuRLvlG_h0vxRrcN?X8w@jdSi`3!XG(FyJ zQ{t7BV8be91RGc@A=uEW!GR60m*wE$@mV+4$D>9pCkloNjg9{C`m@+0+aH!$f z`ay>xsR$p0ot{MaIEX5lqdbjrw%D4bOE86)C_GsLlH^(}Fr+3MfzMd%200Q++?y%W zd(-53Z@HX+`~2{e?Dlt9A?$_O-|sD!{WR>K38EG`|Oe1TbjBrT>C$r>7j zNaCopL0KEw!qP{h3Ctmhs2z?Bf`(|a2clEToCeV>XpN^~yb4{i^14eBr zEC4Dqad)~{FHY6yy?NTaH%uDt4bug?MkAFx*B9B4-^dfQ0c#0i+Db z2_9=iPS98zIRRsk$OaaZK!#Cl^5{eniX)K4DU3EUSV^39)KYjN(hFc}rI4Fb%4 z_)x$a<)#7^6hGTc7#y9TO3ze-COOnglxBK;#7Nv6EzXN0m3eQXBJWL=;k|J>yo(q( z&d6{P1Et3Sw`M4M#~#vF!N63 zdALe&9`29@4wt8^^5$qY-W;vNi=*{-Z=eWo;S#|Q2%!jiFla@vgTO9<9Q+ONw?W?m zei-OY;KwHK0zWf)8}wDt^MEhO-qriTckyiaT{{i}KRaX^S0DoJSTS7j>z4utIfM}w0bvg1>bFJ{kv(RgSQOC;SHNi zyb%yHT&lmv%XA^~5~)4j9tw+B$II~Mbp2hNt-gB`6?px;fNysebhI3oI|Sse9hfH?Gawg-i2x-?qTrHz;wp?MU2Z`yqGLaLnDb7J%yDqOfY67ufNl;5pm;O){y;CAOOaJz6GxLvpV z<^kJvtDx<=G0=A1&bZw+EpC?$%G+H#0(aGZz+JQ)ao0?S+$~!{cgI}oF4*YZouH|A zvnu(nl%n5V`un>O>illcn%>}fc6WEsE?k{5xr@VvcWf@3|b_fXx?U`c+aqk{RCv zh}XSXT@c)?t}s_?0+PEW;l`B^fyJGmrFy@zMqIJ04wtNo@|sa)xN1^|x9th=#x>>L zI;FXrceHl@Ae7z0AFr#ZgLNTsobKh;=!Tewf)O9U>1RptOlTj7kH-O}gR!%F#m?^a zI)mVTnSHrn7K&W4G8%WRLhL1@fO^lwI~BD zV{zyK0ZKugW-W%TL}-^)yTOT%@x|WN_zYcC2vE^!x|9SftNYJ4#vp& zaeyX{j{`Mudl*E(#y7wOZF@*0aMQC9K^$Ie1ax+(5ZKXqLZAo7;eo%l$p;3}79T() zBYdC{tnY!!EjtK2NWDSenREt0DNq*#tvFE-z#f#q>^og*j02^|H${-V-C^ZlnEBMp z1ds6oD0hk&T(LvM;L8QW3((LJGJpUb;gkd}5bi+0PO!xRcz~@3@bQHj0Eh?TfWixw z2PnQ}IN%Y@5CO|EzXU!=xKm)n6r7qBFrM61~HIzk7O>}5CH%|7AOFyguFAUQE+Kc6j><1#f}66Y;2Vb=<$yjBJWU9 zvJR3d>-6yQO^h`6wB9WQ%{#*|vyF|yOg6QGnQeXmZfJ9)@6v%NdzV+H@M-8F#m~}( zl)y<>DTa=sQxp|LP=O@8w4$L863d4RiCRXe33fRR(*tv&N^Z=ok%a(m=tvL%$VUSt z*~iP1d7xC8he(%qa+uj?1qTP2-mOuGyGEAr!veF5AQp&$7(xw=!w_m}2}3L(1Q5iP zB7q=!5E1m?f{0JIp zkYGn;N7EV>BR^nUqHLeKa2W^-nSqoDaM6wpCUS8nYQ5W{&bvLrydo4}z1&QoRYNBN zB`Q4+Kr#7gfC@;^0%jXSv%p{!1p=cH6b6jQP7^UKLzK?wu%WZY=46K)qKlS0R0SP> zq_9II@BjwN#oZdA-tAF{yFI$R zn+cqD;6wnmgXaONnV$x*ZU`mdRRf7MC@4p{#7dG1fu$rt8+-YgLPdv7RN2iApDscz zLC8pLysTk@5Hg2so?;IgY=c62faxJYA_doEicke9Uv9Ldpt-3!vcL@$4-2+Zw0EnN z;%<*h?~Rg&yG7D)ITIMw>_mW6bA$nSijDCa1eQW2#9 zoNAgPG2JY^Vd^Q$0+ocP>ndu9(brc@P86|_9ag-+Q0d|ZU*n7)aECdPQ1J6XrHYQo z7b3JIXn-mRS)d1OhXgxaH9o+>YH@F#TJKHL=e=pVyxSzp%b9>@XeR4FOZYHo< z%89^cspkQlrJn_GnuZd8?17UP4U z3N0=V*NKZWMS5|ZLR=iF$h%ddydo6v;JTTB2Uku6Je2S}z=MfT13I7xC4fV$XqwCx zB(iLHAzEaEj87byH8^KI=m23t)5iwXP9l<@qDmA&oJs-I;F^Wb5mti{lQou z3oix)P;xLbNMdtwhsadio(Zluhf4I~KzW$FYB-?KR3H<@W&)Y8oCsv9@H}9H><9xc zUwjszfd*)pN*$j{DtUPBu>2uH1}2dYu^fjnBcy*gK&H;3x-;#4`_?Na1r z!19HMHI*3Yz#3UQU)5`}0Jv&UWml@&$y2ue6tSOSXV}hnvf*$IfnnS%x zX{A>v?DO($b+|cMkr&4*!@Xg8yq69<)?zmB$OE$hM<5#-Xe_d!O@<|y4J=Z*Y#{NA zC7VM7ECncJpmK^~1C&J$ABe0)jKERGA_a>o8ZE%U8kvEhXe9@GA2!?zlprn}W9KC+ z1icgZNZcwq5_d@r^bS$jygXc(S7)p7;#e`>8z#pqq!NM*L@FU*L{bUCqLWGp601-` zfcQnyfkOf$*$g%ySwNr#ky!*YfK)QLLF11?4j5}3dVn~acneK1-aymGyT?g+FI-;U zHb)P)>`=t*^6R`-b)J`rjq^ev`*3@_D6bBe;>EdAyf;maS4rdqicBIWNQ@FW0pb-% z2@bzJMo^f>@c}^w9M=F+;CSXwgGMWW9WW*d^kDJF;0K5|U=#13D&x%qeY|uYCU2Xh zhFg}%;d%vvxL0@~ZqwU`OJug;_JBcN9k0WSbCq~+nlfA|jTT^_(rAI9mJAdWhGCq5 z5Y3_lf(tA@@HfHY0(}}Rmg(z2v5MaZh)Do11|WggFvs8>j6rw-R~%kGSHz1q%6R8Q zI9@gllD8~_<@F-maIf|5Qgs_Qt7*fN&FmrcRbte_kKh|>?B5FM+C8B7FCT1 zV%A;_jjgIGN{x;uc80xbwY6(it+r{=C-x{UilTT3TGdvK?q_^{zQ6z8f4#5QdEf8* zb)W0L&N-J(VE&()O}q!dIl*-c-@(@K!Go1M=yj7r82K?^(2lU!%z^K5?=~PA^_0TIFtIEUr=qSw|XX}PgGEBrZ@e< z*Zqy+e$BGq#Ep##*IFUm}+83)pi=h@Y9bV4)SzH8J|JigV71!go^y5>V}(vH?nxg*+bUO$BeP z;dPS`G0gRU-MbS0O+WB|E`KxOVdh^W*AEjuQh2|}ecXsw4orM)2)hD)G4l4&c;Z8l z>_hQ@EtOP%z5)pm-mbSK$r6Z5R-aZ46wYk zb{4q5e*w|=Q4Rd%yZS`$4Xdy13VHpk@U$;01G~KlkAK!o-f-U9*eA{!i+ov9;oix& zJPd&8tgH5qI7lQUUZ44AUo_)SR-I?!m1^h}{u@GHshqLQJl=lDZ$nOR^%vl2(ch{~ zy*GvD*DMUL-OgjJtrf*4d@J5q>r+ZXJj_~u)Z3Y$JG{|*RqOZudB&f;UNVW(CIY${ zBAE%P%9)ou>4}x!apYRl!MAmf6B4jL{pGX=uk&UkDvbx!*Suk2LXu0;&A+(0WW5Cu zlkVNpUv0WKbb3cTvGvCH{gS-3={-j8`7=?sS?@n3A7-z0$Ui)9Y5TEa$8pW5szElv zJ?`^k!ka6-SH=EEcn&VVH+tOL@n6w$lj14CmeG>riheg|)qRhm4-bO&`n(^}H46D1|zB`WJN$hFA^K$<4(1}mWDeDBPY@GU=r>OsE zFx{|k&EwBu@&P{WUWme0qm;2VZf$74cn{&Bcj-Iu&|qJXSHTm)0$00Iqc7a`kA%YsCWS9N<(h^e+wk!}u`S@LMTQxFrqTt>g7`gpT3#V)}3;mF^{H3xInKYJ5>6Q8__ z+!wX!^e3tc{g{P(iUh1YL&etazkj@Y_qht^TYpa?*5nqc;NeK&0Ke2HG+Xl147ux> z`t47I{PQ-pTNTp}*x}UHQ=hfJc0I~qU%7NUK}Y0hrCeqGOxJ)Ab~Hhx_mfVY)QLXp z)+<*!uA`PNkPxS~*B+f^hNn79BmVLkICO%e|CcuT0*QjAfw>?}I>!kI6qyH!{6smq zOgahuLw)mK2>J1Ii8q(Od>!41Ul&S{i(b!|c#9ZIGelAjg z!@1lkz;}iDNSdScc$~7|VU+f4&_(zw$dY{UK0Jx1CSVK9h|fZp4}t@#l`eltg-Ym< z0nbLR@o0E}g`A<6eP;;a6F`~2Av0U@S~iQmz#Tc%h4oPirFy<{;+?=>nZ{n(=&R-I(kCC~)k>Rq)dnzU$1?YNJhWX;iIC=@wc|Tt(jr|^-WuvPGv;>_J zHvwYNDXM=|M7Dgr=>wQj;OB+Re5}UgD4a}i{cX&-%hO|c#vMDsu+bahGt5V#Uz35V zQqV!?8QRR_GaxT@vJ1+X<>Zvz&}n3Ls|$&Y4Iai9-F)uxl;8gLb-Hi|^z)kN({=6i z83&pOan|rrR_#TaL?)a-4Z!3m+#aR+6?%TGZEF8a)aU+O;qfUMTTWXn1etjl9z!X0orcX^t$+hu-I0cinc<16m zf!L;Ga+Su)^==RDx>nNqW!LwfBGp9IOXcEa4?-xq7slF}w3J3O>KQl#xpTPA*~F7N zf$=v!vL!9eCMMap7Gtla9_USOx{)+PwhJUPSdL+ao+kdC^Bt#{;pwuw{Qk5RkfHL1(%3;L}}eiJ>ZiSoyL14N{_oHNZXrJO4y z^uk9?8yk4FXC@|fL@R626It0<(^Gq|!r1F%k07 z|KfReb&89s^)wU-7f5|}RVu?x(G`$x4fF`& z4YV`BG4aGt3B)gX+W7|Uj&-N+pplnhyoa$-vG^zhDi<;3<+ayQu=|sDTp<(fum~t8 zm%E(G5H4CR<}IBm?y+n^4FhXCR)j#tTSpPiY*Uv8#Cw6^HJOu1$e}h?{$u&6rBHXl zWOVk2aHejPa!_x5a(hSDqV7s zGVt|1VDyK%lK0z=l521ymrFzxMgL>xfNO@VH=U6Ew!4J;j#R)lIj6Y4NILE+6;O$n zSGM;fWMe-ta2S&N4QZ8}&-(Vy2tpsQEL2@%XHR{rD&?G0TT53L3KmJogg8l>>743A z4q|=l7=NL~w4&3jMsIvmGDQ*ApvGQTEU=08uMaH?!7_-!CDuprf#HQ3!G05$6Fls& zzqSGJQOOj2-dpcpv+CfDmfD|3&4M3yiaf!$VGlH!iKX-hhvkFAg09O8i*C|&b*-kx zrcQu?PaOn1zDcCVfBG8-M`62L!pn+K@bmp-^J!_-4M0`fgAkMR7sg_ql|qMpB?E=;GbAn;yALPNdcayS=~066=z!v~&-FEtK##HA zix=~!UoBwDP%gxa^Qd0%43!TMi;R`4mMTk|E>p-AVDCo7P`(*&88;8GZ6v+OD1o+w z_bta0}t)cqi9i_`LbtO}wd0zUZ z);@na5_~=N?Nmx^RixacZ<8&8Pt#{ZB=tCat}eW2t74t}h>1PG#uh{H6EyfFbth9b z4?5ZKJ)c$eQ%kKL?~*f~lgM`=@;bR5VIKgpL1lt8(=88xqij={PJBHd<%6;jz+Sm2 z3{z7jc(6MX$5fVfQ_2U@SIQi>mbvg0sEV0ZM~z2!4(2@_B6TX?4~2a?$)ga=_}h!l zE&VmH-rg(mWN&)dU*EmoDzN&t?O-NL`O!($tQi>|VYq~}u2bUwAnl;g!0Q22m!}+& zK9rBE)rTuQ$rjD$5n*VuA_+MmJ_dv_a1i*ZaMwZMOxczY?lvl9E(?0;-x{^g3~4ECs4pVfKI>v01|+E8=Ss z8s@|+pi&GN)4B%=fdC8vBVddBSp5y(^v>z92f@5bAwHgs(4v@!@r( z13AGGuXts;;}r4(9o2XkkeFMRQ513eAYL?G%M4I!%nOnaGqLZevBrQ))HHNy#Qu2- z6_h0gIi1eFQG7Qc*Y#_oWZ`;;#av~4%5Z$7ER`{7PQKed!LkZHQKE5Qg5fYh6JuBn zeoF!-7&2bl45%xELNHR*3_jjUs2|Q8FercqDP&@7IDunakZeRqH9n1HE2J8zY!DOj zU>jdByu+VfA*XXM6MFk)=W$Em&%U5Ps!x549*$2Ir!uCD$qEn0SyrAWXgXK;u!@T~ zuU{QuuBia_~gzi4`fPBP#=Fz&=MK;Sz z|1CClnWO3|tjbG|FmBTG=-vbj@*8I>RRLbx2ZC=q3Tw{bwr5i973c`ZbEu-9=+8|iER*ebU2*?4`JIYe(FxW`O>E=|mTv$* z{Aol4fSMgDD;%yRjdR2g0nY1xq1gjl@BtAShHz~D0DmAq%T(|T zsfwDY$L7Qc1&d#){>a_Yyxe!~P98$X{ixQ`Z5>B^Q3>`*y7LLGTkw{<_A98*)I0x2 zCgaG=2JeGc2Bk&H$wQ(z1tQg;+{piQV$6^4rzj)vqB8eE*YX4HDE!$(&>5e9^nO~_ ziVm_;Y&MNvog)D2X5D1LWKCDy&~G=+8Q z{odiap;Lf(!sLf<&PFBc8jY_mJ*rRYEHz%$Qkm>6*-lhJsRXpw4ZaNpCH~3$9*rzm zYXDg87m9THccZ#1zbjvRn~Ygy%eS1rKNR;XhN-ieGZVV{oh*-Cs|G|bUu_ki9=HoAD{w;v&x_$Q9Bjs@shhyr{Oa!^J zd)Z+?Yec@gBY9$yL>3CE`VloXuGiYe`Fy*4E~&IO`{bQ{P@!CF1r*HA?fiti15kEWqI=lDyfl}(y#+QRG-T}*TF;O1LFWmKwjGm9Zw__5#kA?R$Q}3+J zw?4d1Xx@AI9l0j^_AVf`FF0eu%&zA~xAOGa&L2a-kniSA0+feVK*&Zsgy!VWkB$0d z_7COiSfEX1pNNLcQg6u3>Q{vz5UmJ}L8h;|@MH=XAaH1nXn-AQmnZDrJ$|&#QWEOf z_r;W!vJ6o-a(q01_aZY*?le&nx~w);lvE#*G&T+p8e#L_VY>sYUwi!G)vjjU3+Lha znp9zle^n#^Oul;>y7BQw0qw6w0Wjd?@Ww5r6iq9IHS)4b3Sf8(RkxUO5-BB&q6n@{ zh?i*Vdg=<54XXi4d-_YwOWFClV8WYnif=xPS{`GRUxw%P8o5ysvHjM<-Y?|TJ;0m^ zN+iG&cn0^~N_@~;&kyAL6a-!R%}e8WB6y&+U3@f?-_o=#x+pLLpl6DQh_E6E zvI0UB`A?@Q)@%Mi!G3_t|cr!sMl+ zl-F|(yL@A~HfcByBI~ijgU^u2)Z%;OP~WYl?%{vyTV6T>Vo1c>e@9$Y^)oUr|d-8V}()f=+gpuDR zSA`A@t6IzXrMzC^=BPG2jhcdH2gs?-^{Qy}^yD|z*lDMkwUlpgrTiP zgS6X~a6xpnz_03-sImAKmn|8_^g612fwgr~p-8MpjF!01A-$dLF1A+Z&Q%whMOra6 z1ms-3tmDluimVlEf9rlcs>zV;RC5ciZ!xLiUR@lCq)VGv=v~T)$`|H_6zENq_6=fz z9sgRpZcI5dc!OhzxLv$`O)~s*p@g%uk zbWyi{q|_iGX)EFGA%D) z3=k?^z6BU2%18aj7w<@Ub2~Wg@oBjyspOm&JE~i5&C>x5`r-+@tl^O)!Tz`C*^fY0 zI1}Mx=04mpC+%>);p&L6Mpc(lW7M+_CF}HTSy#dao4P$DCUc4aO;UQ>jQL)YUcBh$ zsa<;)J5Jj`ou`F{WP|m!UeahC`Fwmye;DeJ6>=`6EZ8CgN%WE-!^p4gYJ=2{=?cWZ2SW?M+5i**5LN;|@ zzJ9+Zl@Z{ptwipbfparhT1r8E9AFS%wx(kANuYtY!kTjF9Cqq5XtZQfM~Ci{85kIB z1;B<_RR!MsrbR1`^GJE=v|uccb0FcQ1u8AMXx87}OWe7Cr`LP>q!6Egtn1+0T>YAc z043ZE>)4PRorC`(sG}I+8+C}Y@?!DJYWKv==v8J|!lwzaXy6!tKUWi9#+bQ(67FkFnihLzkc+uIPeG zNQ&G>df~W%$}PuQYJZ>|O+(m`*U>YMgzq8JjjHb75lQ?B%HhoMegI2rxt~Vfi0e;e zs6@Z5W;g_X`4&0b5TIr}qbaeNM6EUX@_qdHIdc|QpN?3N!nheEmZYhM`=M@m!E3k zi^%dZfLE`fOo!meEWlGakH7jioyd_G3jx;kltdEFliLp+3zluV7lwG{?2Xi_KYbcT z@n*deg{a$J3E27&f121I{6}NL3+Jz9{R}oCuo7WwKfH#f2B`fX>94L}^rUJ4Ft^=# z6eg+)CtNt{rNWRZvx|<^J@jB;y#-1VRiGTpGb3Ge$=u(kYdO0=Ze!?D7)aLTj0;Yn zrJQGr-&u^$h_>Em3x6X*{ptn4P@f!FJQOF>H6W9c?)^Y$rN5TzTjdE4a(Tzm3JiD$23=5 zw5;Fd7m+fux`CrlBpsv9K0F_^`v3(=esW~;P<%B@6C=fa=a|AiMMCcfOSuB&9;L%) zkgA4GnLGrrk2(G;i(BEM!McPm$F={$6sDxWEM%7B_7bDIeR;choREcLi4;p|OI1M! zK`mcBU7FlZM+*9WmKf+%M1Z;7%a4zDlQe~|t!tl|{s?-%Dac4+o4PCRI~BhBrawrt z!QBPp1aau;S{~&oj1Df<0bL1mmsn2J@r`Bp5J~O+Er_1C)Z_s(9d9tJm z#mSA;ZM3-sIIpd{qbT%@=_+1SVPC%C-R0oblk^q6FZI`nj`ijQ?B)qrh%t;JOnD8k z2AaIav}_6ktYn?F=^|0~c2W3rxHZ2a18V{0@^A5?A`S2x12%Oaxeya)nyHX)N@&|Q zxbR`IOo+*&Pk;It;N_ZGqL_Kez|&YqnAL}ks}z`w6ro#mNVtm8JUnw+POO}VY=87w-44SX#v z$q4vATGR%lvy4lV7O*}6#+~CI`ARnjZ>j{CJ{)v7wfSY;db~H;FYd>ymd|i|UpB*u zf|w(H4C1r%hnbNQ;@)2M{ofSxRnS%?#`+*^wWk+xYT9|kvs;t;X3yh_?AmU8r7U%G z+wdWooOPN%AxW3*wBBnxR{5kc@zV5t6xucL`781@_t_VXLm~bikFlbWl=Zi})wYT(_AYyS@V# zg6Tp)X#+i!N89`cbV!+>na)r!1i}L*&-_`Lw7DPs_|ck6S7)V6;K$mS*k`)5bnHy$ z4lH>4j=K9xQ#i-mP^ScRWKx2u9{XU$(?2aM_=%BN(|FFnE9V0J4H$5?N*}ePR4fws zF09gON>mi%7fGX0;juuHt+kMrq>#a`%kFy~HN`c_J663d;en^Xh{cTAp14cbX;6IZ zx!W2)%%!`}aH;=eP{GiRhjA~Y&$_FG|L1tK>X*R-s%l4^RL%yLX_$`j5-MhQ$)m!hKKfxZ#5}!pm54Ek=k2JgOTYb8sSAbx zoB4A*3J#Xl<-+}18(qhGDWWLoo0x$A&fo_-U#2b$J{Af3GHG0W=6I!gtke^1ms_|< zp5W0C$G>td5>9TsM!thZ{y;7N4SWH!M);JWUWV1gVy|bgl!P$;xbb%zspEszh!IJ( zKHOmKtt4|Ejj%2wV!6Y-1CM!rB8FDf?ZPwb_}sWPKMvl64XLC(Zf^rBhF*}sc0LQ* zn9PS)=tE0e@PL%aaml$ZYJ7dz7lOvV=(vpAE%U{EH;h$|x|38msk0DkH7;o*ZO|S< zhh5(}9Em*Phpsv@6r+K-m<4>;Yr`SwSy(EhEq3O79k(ENTG;Xl->vaGmK{W`sOgd> zE#SD8E7f->t|2|G9%8QJiuk-xP}nat5FmdpLif_0`I(L(hUlcc^DW|{nJKp^&8}GZ z)HPDd@GQYTM8sR8M4v>SC|=k`bJH8jTs&-eJZy#G)##!x02%2|5&$mZNuyomij0I?Q-Uvt0g(|%qB4m3il&wnFBoBY zRkPaBdj$#V!+J3AKp(hR```_7Q-=)VV)n>FIkW$*lEjZO_>wcqKFBl!6Q~pfDO`pU zbH@){hR1V6taCv%TB%#(^-u|0TvE0w%+jjcPMsy<=F;XIR4}{zbBA9g2;j`eFR-D1 z-_H%v*4D=0qey11KImN0w6?ikH0rT>M}4VR-i(@pRfox77y>g&B$pdf zdhpYOB$!kciO!yJB-GnvVN4@W$=SKeqDdF?T;!^($l<3k3V~42sr-D}NJC}M5N=`nc<0{I|&mjYKQAH(o zyJVhT0I#!URzaQgK~ixHsP8M)(-gW(Et(Gt^N?wIq#V~oJLG}nz;BaM)9r0iVkgQ* zSW0|2&_BNdeY?H+ty6_!>`Wrgi^opPTw4_gH`4K3VO4tYeBXDhVV{SnMCW5#nyvDi z8sVm0Ouu2s$L0d`gZn3eRp`KiQs)_BORlQgLGC$ikMRS_9yZy?3bVwjap78GajZb8 zNR9_^JjO?bhj>;^1hS&ii8>cOo^kbJ&FYskFm{cosC7G|Y|FW;foYtv$oAdXA>XzT5iGX@R?7rv%qc`IqISr$vtppOd z$WS7YoFq&M$y^f5O~O7id(fDNkO~%;Y0%5AcbT5${Wc6;d9|#jPFPwZFh2y-UJ4W7 zyD`Z66g!Th6n6jHPryv{SU)ant8rkKO}T>GUyx<7JT`~dYV@g3i0ueE9F9%JC>0P$ zaCnDRK7AB!ihedoFK}7L5G^if!MJu-jNDfW)*-g6gBbstgwukpD%Xy!%NHDE_~>R_ zAH0Fy7_ST@-Y0)R*Oosu$)w%iKd0B&_WBT)Ox7}yRH0!7JYg8ST|p%~ysxk&a3yuT z;2gp_J3F6RPr5IHAsXLA{ZdMu$VVzSe!{y}gz~j~L`dE102CjfDjSR8Ho!a55pP2j z=BhVb^GEqIZW`(+D0~iWWl=|V{m5->2}E-$WAX3ciEts5y+wE$M*5HdfxkjpuR=wz+o2YD1ksCsZh+Fb^r)$;Qy>zDyM1jbP8)J*l67nDJ@a+FxTC$=(+V#?P%UJAnA4z^<-SWexSj;?z_-jL% zUqZ|(7XKjz!!IG~6hk~w{L=(fZ_5&l<2q~7(r!x|THIi465OZS1fBIY9B)+U)tE6= zHO^^-zc67Fk!i_US@ zRTW40=-gv-YHOG*tn^ecsQdQC=NqP@nzp= zCaKOZ^%&r`029ncii94g6d9)4i{3|Iy;^QbeCz98 zBaQyf0F8&7Jf~oulqhF z>z`pCLJ;DCn4zklKvRx@R(`lMKE*s3ZHJK&x5CU8ALx~jJ}NDwEy|XH+vXy1fNx+` zU_)2AKtAnr2Z!zrjWTpKQe1*g+F8~&-shv2RX@>n`#Px3To+8OMzEq!Vk%je*PQD; zBhw$L7VRtZ@ewU7d3uJZyHft%$>4Qgq?GyUBmJqjHeN|N(`mKd=FD94?PQrj3guVX$MI0&MS@Lff^#NZQ_CI`S)^P+o#c4s=2 zLEivZI*!xK2cQYM9YZ_3!+p1Il^{CAjzstL0<8LmDibXaj7JaG>>fX>ytw%}I@Ob1 zv2(T!LN`pY5XZBfUGzkYHsJGG#)yi7@&%cs|zt1J1s_rRn!E$g z#FkF^p3_$FN#Ct458_3>dPCB$Y0Bg+PUT8-e%IurER73Fj6=7NIAfIyAV-X$dPl>D z6HUe0)!xi!ZiRZiDP~pMHK!FMu$`yuh`RnYMt^xIvuIL5r^4quJ-#+&thGqwQL~HL zHZliZJE|t>ERAlby^Nh9$o&0trzgI{@gvq>W(j&xXq;Y;PZ7hn3zRkaYgdCcH2{Ww zo~{}lEAZHRoJg`_t89XU;nqIK+zmtDDNwT2V9leSV)S|K!eG7_IeHbI8#vxl2*ul% z#~nf9;mkC5ruyYZNzY{)orfxbvEjnOngSkfRF0Ufpg3;xqaTU|eqaF1zUZF>a6IX6 z{5G;hV9c^Dyp8rtNu#0g5F5IxW%P{*cnE)%s%1Jwe?YH(2#zG;wNg}So9iDz-WR-9 zt|kym%8oYT1|xZ|+6y4E{XEJJ#Fc};YUhZFMMRZpdHJAT$ot3`L{rE}m*d!4OE69; zfdH06FbA_grde-AGlIyfmSLyw08%=CZI1~%VJ%{4bncKXKh}swqoIXD#O(!8%4OA} z(q~R?jwQ!c#l=l@8rotyo2+dZ&-Km_HU%S6pElaO*z}b#xE^*viY;5@#+eI?s;kiX&Rm&jDNrC38r zB?vwKu2Av>WIUpi-N4_f2yin0Ai6;ixE%%cC=ydhGHgFn9l7#GJId%PN{TX{Z5lkW$YQ1X8``un93 z-&Li%TAmRht&v5j2hHbhu^a6_99Bd&EQ0n*W!0UBW?nYNUd$*UFtNEZL?T2fD2=yB zLtX1(F?GZxgXaFH8sx@1^}r1){{o2Wds+z1GIwl^HAH>O4I}G4T^90`;uU~-c&k{s z_?Lt1;^Tps1tX@I=NpaF9v2>M_-MnzGA}W8)F`2j>KTW1^la%jlMjfW+zOW77dNw`5mU!n!#tPx~^Q5wSwIitoP<5k%5K=Qk zgZtsjqIVu&_@w;|G=vls|N z^4u-@r1K_h56BXASa)I%!vexqXN;Z20WHO@Ckr3-26YlWsWF4rs3B5Oo= zZ9wak?wVJ8;Ot~W^Ml^H{+MgJI$*P9q0sN#|04R?Z_&%w>*>2EKtx|G48D?T2X#T5 zyLDqUe=Zm#qWS3VBcIqrnVLC*A)`pV#i_98E?vJ~qEs;NrKE3IOvGVm#x@J!BN-|I zus2EL5v3WZMtrLKym3xP$836=4Wep(V zCjSj=0P??#(uf7Hn`L%`Sf$<&;Xt z&()7L7O5JU-O)HnCYuRez;D=>v zOres+lXZV|vhI>^NU)dkw#=yCctVIf1dc{$W0cUTc5qaMOX2QPdSSXvxC91gQ72I# zc?XRt?Xk}jzwZ;vZi%h2z6>4$el-HTfvV2s&(MNW#PT{|c#Y9$R0Q(+4#=I{d0G;o z;q?8#sELyr=}$&ozlKOK7$H{zM%_MnaL9)Wz`zW#X)Gx=%0`b+=Pn34;kb~CJbpV_ zg0q^ZUGgKJ9wx7-WbQ?lN!C5!VaGm)^UoqP^Yv(6NN z2Zo(@n5Z|gmZumMFVI}37mXBjE+{8vYjvie>KK~g5!}qpfqB(mn^4R`>^x>Ih_14E611;*%fg8E0XX)>5$?H#S)Z)xH zaSh+?-j%GV$o9-jP0YK=Vy?18733aokI$wEiOMRnm#V?D8GORix;@=!4##Iez0sNp z3k|wp2~PA>DR@^rFOdSf+Z1II=W>TLTUe6Dv0ao6|Cu~+;6iRPToUR5pWvuQ-(&ne z`XqVtyYrRR_1%)!y!}^`?%WnVcf7cElYQ-4`Hyzzq3OD!zU#3$PsDT%fsbJUgNA>y ze6A%J&B);DQ6wrAOLMEB}khbECr=< zACy*90f*WrmvNx6ykMk)wk9d6xQ|Bdgz?%A4~t-g1kP|k|Ma51?zP{STamY}jPdTR ziqiDzg z#{tsvPv71{OHP$NIwHwl0vCQym$Z{^_f`#V3aUw`GDnWU7fT%wHGalD$j%I2*0hd- z2kih3v<^eQd}FU)-NFnb+Wwj=j(x7qVVb_fw*{5*^&~jSg9Mag@l3c&xxTZ5gBiq4 zCiaOHAv#;N)WXMbc0<`(+)+`bB5HSbFP&G+Q%&JonFUWxumm$KcLrN!Y~v_x8Qg6j zkuBy!I`0v!m*E6TTdCm3YcdRglrxSL<+K99G{txaD83v|a$8L3w=fZeZPX}zsEVdK&_=>M*XgE=VO3E>r@oYh%Omrsabj`drvP#eB(90pE` zD54sN50rwKpDyLSQg5{&HdOzI%Loz-`DS+!D(*j0zNm0;A6|GpPeabBNGD~zG z0>o}oN;svK5A?x5t;-0O@;(KXUS8Kb`y#(U7jmx`d0fAL(A4Q0nkDL_c`7l&FJ&P3 zc#5) zVqZ8`jJ9h+k(nwVP!B(ig}V1ryPwCd*0ii#udCm3-aHISFY)txrD+x&6V~b4+Z^h} z|6WSM)ci@uJIHiw%hp%H54msD>n*(SJm8}sMwXI(wu!=1*BAUM5TR}zcM=J@Aq?r% zx~wr)M$V529N%$h?T?BwiuOY03Jutg_503I?;rRm`87PY#!AWK@Q*gC50^9ea$y&| zR*{Dd3pNpm*RoB1r1+{1!sL*3@he4xvJlY$%XHX8o?N191;-Cy@UB=(2&k-O5?GYv zm7XdbX<>G7b%?sW60xeKCx;j;$-O>Qyk-BPptff+*T+rE%qKf~K+j>&K|~re$Z1lt zZyXkO)oV2L=*?cIj}ZJ$TTRjx&5EN?Kn#gnsA9a|6jgA!((Rqm13MY`B^#-O89oef z?1`V~f7~dt@sn1pg?+W#W+3nzQ&i%IPC$Ec@j4{{Vj z%vU9u0~qL#Ia^6Z=IQn&$m(V&SV7F736Y4ZPO=>1EkHy?Kz(elFRxesL%LZqXZ7vT zr04?NC$6n@BQ|(}SSI}d3raj}A&9gwWkOy2f6m0(pC?W><)}EATrpQKObWJ#j>~w( zQaT2#7sYI)J^SSN>FnN3{IhL z;T=~t82vP`1t=`yz6_vLjUrJad%3t4Y-)&Qi4l6oa1^du({nC6y}ATVOv~0V(;r{b zKTS%T+Pf>lYy2(J5LIqlnL<&>e!=%+oBv{zgkrr$(*ZJ+YCYD}CMAa<->9)9*pwqN zvDplxe`gyWmCz+UeK4*r=e2wZzqu2zmk77am>Abk+if9j9o#r-==Z5Qz)Oo2KGh!~ zAt?3_Zr8{J+gCD@4DofQ!A_1gWM?c;k|A-zHJp9i4P9#| zfs-(}R=w~t*)nvDPs%sI{4w%%^3PP7^8n&9mgfQmw|naPo9&=gEqxh8YH7~(A&)IH zo+D{qX5K4l_r&ZD*U+r{Cobk@*Fz>YeL)V{=ajvC=;-poK@Wjqc&lSYPen{^h>;Yt zz)jLEGjY^ydDGIm6j7)(%GP*{;&jJ67HQbf;!FEOd%uF5tu5QC+UJ@?Z)r0oZ@QRav0ovgg z;M0QKS{Y9ToDHhy|%0E0KWAE~@7RCj{<~1_B!R9?2t7n5t(wCha zo?y=YA49r_HSH7xPkg*ojBZnF6jncDX`S_+p{-+9je2b_?LbqjV;UFJHE>Yj5bB%V zfK@Gn73te{P-jJFT9jzgQSkA2ks`Xxe7199+b+jh)j23TVlG5#UO-$5Q>Dc)x>*Ra z)+kn1@oSKv8tJKwHw$Q*;GJU1!PHd8)=UX-oY~;x8jtJ|luQ1IfA0o2tbBq^gh!C& zR99XQ$KU0vS`y_h0*N4THFKgNH8riI39Fs0({xSET&19Sj3UM*<6#f*6HL73WI7LJvb2 zxzlR)(J>aTggS91Tw1vrlf)wk{(k^kK&8J)3(+V@Sd2zF5?d;zND9;`KuoAw(b3Af zh4!=+jEYZJGpQh657^XDq5;Xr6Bz&!s<>))vo|X(#wF^ay*NxUZe$4x@*r0$(0e?s zV9$}%0=`C33-~g8UeL$biGkmu2n_xdKV%S?n5lsv21^bK4I?`^9L(^6;@{zI)C;_n z7xr$Z8{HLQ;p~2TO}M33P4`s^=*sqN2dG^K%MuW-YXCo}G7#wDlmWpGq-+K|h_WH% z@Y$v?v$Fxi4I&&kyaeF@1`Qk_NUXe!U{P{%g2ujIzq_d?cR%II zZs{4;b)9f@Wu0~w$icG%M#(!x=ogh0TtvqJ)qo0tknhM-)4(t{(L%MFVSc&IFlVDnM0f=|S@4NCpMu4`P>Wg#~7 z0gfgf0g~wWXyLNmi2^3p^-CkH>KDfpr(hmal!k#sF&ZZFM5q`^6Q5!zOLUH@d=)`P zLo0|fn5!HD$V|Ny;6v%;84;q41bB7=CII?@j)7823lX#;YHhIMF6r8!5VYn<(vKJ| zLp@an*zP<507nTamN#0ZT;5Qff@uSF%7z-IR5ELjQt3d0L=_J+K&^m~G2)753{EVY zE;O}xw(!6ORD}jFBu+4j8C~rQ;8b*tfL0J)Yg9N!Sx_UdD1#zjKm%y88oKg8(a@6& zZg!fSL2=}Rk=G+3inJopEV7DZlSnEOP9mpCI){`h86kvJ2`CT}OGJN!HX-FP!pW%) z5l~cZU`|nuk>LvK3cfC{trp6}j<7m_Rx)XVA$JfMxIzKK7X`YWlzd=hq{IUppd=MQ zfd$eTbPM1rrWKTp(#Q5(7vclN+;JTabVj5fFxQ zDodNh2}nH18}o6$w1ixwry)0|NXVs8yP0KH2d5TT9iCfC%b}0i_d2j3yg|%s`@O1V@m$B|utKZMhOU#YIgAE+!H*k^$); zuZ+k2k^tl;MG3h%MnSF?95+0<(B|;$B8$V*3yK?`URrO2dP%)8+Qk$H>K4)*u31QR zq_{G=qXiYx9<)(7f7EEAK}e1xhDUz%SaZ^31=W@SQYAP!Ktu&14nU^u*dWisj{7y? z$4!z7a&wM=TrD_agm$q}1N2J_8=zrS)BqJzf<~wqRT`vWOlzcmA-z$8%P5W&T1a)k zutLh?hLp`8I+#=r`9VZ5i4q-WQMSmakR;6OSdlcHX+GLWy5-0K@2vKIb@{kSQ$TJG z(vPbJhb(Cq8!tw|(4ZY1iZ7!!X1S2&pn-*SN39eQ zKYl2YB!UFU-;ydk?1*Hs0q4WZ2{j*QM!40onUu4U0Unx;3+C11CQ<#kIY~cmmK?CB zU2Lp41w*67=@=O?P6LpYaVo$o3{*3xF*W2 zfB*mh1Ok8n000O8hJoRplL2!XfB}F20~rRw*sKE+K*Pi0Na83IN--P*APfjW2njJn z5ds(h3^-uSDgglUjMg5rj#<+X2CO?lBk8J;INcGh=|G zuMWQSx-8Z`g2ix@Yxusn#^2XV!$q(dj=(?sgZ=RSxqN=;dG)a6`-UA@;WjDokKG%g z2-}pcl&!SO&;`CIyH(!Z_rCvY6KjE3g>5*m>-h<*_|G|D2zR z&9V%;%y~9}C9x&8q&lIVR86Rx%$?v}t8Bo=u9{csdbn1t4%Z4Bte#L!swXR{o~%x` z2mRUi%=^Sp0=w8bhKA*{JnOMmk+xa2Qd=q8Xu1}GtEYMcJ(!#7fRu%iX&tR)|+jFzZ!<`t%u=2qu~*1$b; zksRb0i6ZZ0t)o6UMpBIXiFoAV-^g{?#)XGCIYu6>eHjiW>ChgUI3sb>uD`J+W8_lP zZ<+@{){vuME#nc;0(+RmIM#M7;y6+J8%-b!^At6`%hqCupzjN zbnaIYtc-MKlYw&}J*%#0$DE$@WP}F+9@F$mHfY~++@z@vCg2aR6QF|>Y#{ApVBoyu z=HS4-_ny0R;kavVnfrMYUEX;Se<0@yb64Edb9Zi>yW&EU4u0p*;!bV7#I)s6)pwK{NKJ z|9ai!_5Bb4L2!cbgPcbzLA{*xC zmAquNI#Y|mX@hqMcLwhg)gihg+9A3_RF~)u(Js;Nfav?jF;>S!(}=!P>v|R1cs)3n zPIR#D=0C8+5f*6v1BJF_W_hAC5gWj7+h&(Zw5UUoVD2tDCJZvlMLZn#~J;3V_7NUl* z1a1g>C$P#PI_57XHyCgL^2P1S{P;fv2ch9Q029tiz1zi`tJ?!x#-*H4W22MlA*j`U zELH&FCp3#a?lXr$!`Od*(n+icZ8@gb5r-uFD|QOf9^)=6X)z>sB#+ zGV~hy=}Yywf@-A47U!CFK(;_G$Pf@oyj{)UKnBYJN%Z<6fgXmQF5=r?`db4*bzwkc zfz*WyH4QCj75O_nJrqzchT`Hj<)uW4eDL(I-SVo&g^&bbgI3;x#aGA!hItUaeL?aN) z?jZ*ML`c^2`y>t_Jm)-WN8{195*+h81>!s0vW?kvi&P|z1hI`RtfTq7%nlF z)07O-sa}bK7r-9y-{)iQSCWrUaV?hG+ack#0+^T%3iS2 zv3uz$n9x-nt@ox{1%4PLKNp5lE9Qoa0p#8HKQZhsXyJUI+|Gl=C25Usz7V4t{Ql+fot3Y5q4EXwx{>$rNFBYBGlLvPB44nXq39xDN{&wX>p9-4rWue%pFjyI3f=s4wD zatzq7Z?2~c`Kj}URpJnP(#7;n1eGo|{YCyezx~T!{pbJT55Mb+KDv*+;AJs+w6Zv; z)on1reg5_x@y6pde9d#UJ-?{)+^6o>iTt%*a89@EY*Ms;`Gddz?|=5Q?{>r2@ATU` z_2|W;xI5yt!a@GRF`HXRn#F&vB{m^dCCX#0H!(z<$^~tw2B0tySYs`4c z8ha(>onT!S{2)eI+3hav(Z1hDdXkRBIq&CwyqV7H+#?R*^?dU4T>U)1-ZNuS_xJ&~ z>Y>_yu^whz)sI{^EGOI=tJ}+d_uX#pC75U(7+oUPH z2bp74DSR=^=dB;skeRQMA7j_gU3b=w`p!aD1_!>4ENcw=-~Ybf|1j^{L+RiGZ}=XL zDD5v>wXI^C8Qj9rbp_XP3yz8oaj?K3=}jGmgE8oB%@91-eDD=XOMKCw^i z!Mr4StXUQ6SFB#4dPaqMx6DnlHp!^}`!HC*OO`_?obbMb@E+%VfOj+RTi&O#rMB=xp%a8u=g#V9gAqsI@-4=YykUPVC?-@wu%Q1t>>csFgIM(@KMJ{HS=G8)2u1p8q|_a9IAJ`)~;oev}7D}?Y*A3RbA zU&O)tZ187*!EeDY!S4XjD{%A$G(DWFZ)4%puzi`l_fq&)DBnqkH$r0Rd>syc0y^J? z&JUsUD>(Sj4c>8s_tW6v=6o3jzZK`FD)=P|{z!rsir}pf{0{sI^n3wC9{|$3fqFGq ze@5)bv^^NS-?HJUNPZL0H1c*X|akAat4;Lj3x zF9hDofNv_`j|g}n0<0fE(7Q4EHA{bH>d9z57_#qz;kDrX6~d1a`Aa;X2#D{&;&0$s zV}05Dws{TOd>S{;q|Mi8V_ig>>zZwNUTo8|9~+gT+otkj~1)H()Coz zJ_?3+a`#FC-w5Lm!F(X4zvIN$knu2hyow}Dq-WtN!crD`y+Kf z`T$@*CE%9c zoPb-haw1M?$cYR|GfvP^Fmb}p$ODQzB2E;1LX)a~K#Qt?K7=U*dHPapxYngGw6>*4 zoGiuSSScD`Qhhw=gyfsx@TnbMMCylN@gi_Mh$27w6j&L?Mu2Dxwcy>LO3x zh{kK&2uLg1h{?NBD~}R!xlsts2l4qIJiO_NH-+&Ze0*t=|D^I9!u%ouObhDQw4i-X z8`}4@p?pvubl{6BKy)ukizpw|5mCP<9HSbXRE!EPfgmMJ3MDl>!YoZJiuAxJl<2_` zXwWp$hwlR=&)f$NR<;kBhkf9@%Jg$251~(5`l29ybj6R>c+w#s+T=B{yrh{oY@lg1 z1)Ww?@M$*#p>{Jc3It9-sRTL!r?_+mN+szWjMCA$;c3;=uqo9uacT6E5or_D(TEZ> zl87;sGN`Zvho8R=nLT%+oL4u|PivWIhp|LSSL$H84`_(@H1VG=9#qGN9(hhE&wOD+$kcw8nzxxi#}B9ZCHRGJx? z)Tv2H!E)2G=#djs=#X<0D6rGBC(kqV8s|xBMe{VB%M-*+ov90Ln)u8Wze(dee|)Es z$E5O;U|#XfYlzddl8jC($@sLAi%~ndC^f>QBGm(vid0}G7pa;|Hct7Ne2juY8Tn+Q zlCtSTrKJ+6B_`4(hfSl%%g!OiOVFS}Oi>@c%u=2VnWxq@O%Z=(k~j(z#YG%eyd{jk z%<-8+KC{VNUin8de@MXh&1qW4NT+p-d|F3HsC9&tBEb=oDgsAHDlv|cR8Jfwr+_?C zM#*^O@T3Zf*@POoxzr)D6G>AN^k{NYl!%d%G-v~*>5pF}2%b92R4bdMiNh>YOjWu1 z(IkwQwDFWap2Ly1jPjCOp7G2RD)4z6Fs-6x(<)j%tzsqADppFBpjb&Yfnp^U8AVH~ zD2kU;LKHKjYAkL(saW)E3c(1vG}I?V8frSNq2|*XYDU>H)SR-w zP;=@`A?MVT!cHk7hM!S43_Con9*#DRI4oWAfcOFFGZFRZQc*-vq#_BS#6=T7f{ZD8 z^cq=Q;~HOB&l+c(#du>SMIQ4c3VB8({|MzBvHYT#FMRWU6#QHWODh=4w1c2cI|%x; zgP>4%20^7R5Db-4BPc3mMQ}9gh+rv{4nb3=7lWuwCI~|)of3{f>ad~2k!IluBFe%P zLX3tiel{Gs=;=d@ah*e~VO2xSG1p;_n+k;tv;oNvOKn=c5l$QEm2e6m?}QVVJQPh$@=`Db$y=dhLvJ-{RnG;5 z5_>R?KJ3jX3gxRYq{(+9XtGbo&!QiXo*0;YSkNGjViZ9AXdRM;oad{14 z9x%=S3GjLoeB1}$wiHqzZJmmwty7`2bt{xQpj%Z+fbNA-7`hlrQRrkO6``X+K?j|U zBN%izj8yA%5Q(VcF;r^jBgoSZ2oQ%mAwG|HMR*MHkX-%nm{{lVpp14$Wvn_aC(VV~ zhwjYhsd+s(pVz?C$$1R>YiqP{GLB|YShyM#KzO@x1i_n*BQtM2j;g%*FiP_FqsYfw z5TY7yLxxu0iU^s2I}(HnhvY{TPKnQET$3KkxF;a#gH%a#_9*=jARpGB>rc zS%^)5d*TTAIR;*?f_D?)*Y<1L_8{_6v}-_!NLUjxRKc2&p*3nqgtVwB5o)5wBxs0Q z6CfRGPkv&kMfp*oCdFrj8kHVXH7h)zYgn$BYg?|6YhA2{YhmV7D>Gc0n#IoEJVlnk z6v`+#C@s#{3v!-KgkRe~+k?t>EJ@IkDG6FKCO~$`p8Uuelj0M=m=zx|Vpn{mh-K+P zBBrJ1hZq;08)9B~UWk3+Q56ew6(Kg}S}In?>KTT{Y8j^H#V|JqS_t5}F9c5edSEWF zI75YXFjAQb6P@ioXSGf&}wbu;4eW z3?|b8;jAtbegfN(!givt{pV{Z0^5px6=qgApE%2p=Vc#{t5N z_~0==_(KmKuY+gf;KMX{tT_)Q=aVpaBMklrgTI37MzPXN`w;d(Y^j|T3^ z@ckFW$71? z=cEUICUPKXp9PBEfTR!L>D^qtny){D_G9864Bu}-{3@02q{AN>@jzaDjvZD3h+YDS zKEZ&YEl5D1OcL57*MpS0YzIDVDw=DM(+`DG#mj(n+-r=t^tsi5&-EW0g@hq zvvg0Js85siWh{Icwf7?TTKfLV;Y*o(CZKO5#Q#|R964SF5uO#}bX_t|)-Cg7?cmJQ zY|=daBn{L$&_s;_jntxOrlveY_2HSS`^;FKq0H54Wh!ie4Axg zCL2Bpg75RoQi zKr)(`{g7ya<|Eo>}{8&6{s4A%`!-mHC1;-+hoVKydnhcTi1YKh%SOYRO*qW4Dv ze+c6Px%?iYuY<(faPcy7{3?+*0p&e-Sy~-wO1mRXX?vt0jo?F#LIXk6khla{qi`h1 zg`*ll+Kf;HaU=2&Bu;2ENCqHDA$2~9Lhg73h3H-Ngz#DAg!ru{M8jV~0-p*p_)!qT z|AF{CCLf3A*FgOlDjr6SU;XhYmOSc}_cZgu?P+1SJ#7rPClY43IXzJ0&B;rRH>V^u z;+k;O$grfs23^vrjk}}I8+t{SH~54aar^-l;t2Hl!$HV%m!r_?mILv!9E)q=Xq*Yh z<3TngzsBXqAbl4lzQv1YvEx;Zya*^i+T}0ZoT!sp6m?UJf^KS6>89$apqq+-1>F=F z7IjlgSlCJFV1Wn4g2kRwh}FKO4lOhwVYU!FigdAoDDlN%kl+hNpFJ3jJQ-j>TGwJs z4hDn6QZO(#da?N}Kz{}5r%>@JYCH-cKVr$7R(a1cFKGbPg5ay#5PMcDV$Z6CRK2SS zM(9-#Gh*+m2?;(c9whvzT#x{i8c|~KiPS_P)8+}pB+3(uM~o;Oi8iQ42K}X=^s!5E zdBvbY(@Lho^9m|LPf{KIisHkY81W@ue2E=D!pNUK`3_nhGtDcn>cAgO2||OU~tEmzqn+t}2(2TtzM?wQgWqVzJ1)v`Wp? zoFdiigzDS`ZL;JPReJ0!Jv#I}B?1Hu0fZM?g6FM+s+Ft*i?2Gs7zvcdd+K=5ArH#r zJ*j-Am!Dkoi4Xjr2UR<1X|)aPZX)8(bMCv1AFE0?9FwMUkQ8iy#e@ zGJNbRZ(P|dcl=ceHt=hxVt352O zu!p5t9vFJiI>69W%S+*hEGLGjSU?O#T-7)nQMKaG#MB5x6i^-#RXBkvt}bCNxSVKtg5r+>RWRI&`WRSxmjV$%ZWTHkX|9IsgyZmFBUj*O_ANW2xU$;^_7-DJ% zLs0Eth^ie7akYaXDu6I30;>VR5m;gbOH_y=c;bqPpsE#)K^0Uk23Ms{5W09m-2kIW zv~Y&frlAd_%0nDQjEFphHYEP=;Zq!Pjax)wEn94|m7K0 zCmVV`np)@qSvs*NBnbpx5elPxM~pc8k_=J$F$r4ygW@BoVagQ`5Q~)!u*+T=#>~{W zW}&_S?$I#712XV_6?|Tu&#?czZPU`>aLVztbUU8z(DitdL-)f8&YTdZE^|bhpv)Oz zDl&&;X$M^rB^h)}kXF<+p+KVUiO~nUC`6%lRD>|^to%UYY3VV{>vHAO6Jv$bJF^&G zo1^Rs*r;xTf$$>unrFe|h46IyaQjf(Dh*lMHzG^>R)lHaj4bUN5+x4amLw(M_9SVH z8x*7}Zc->Haieks#LbEkjyEhtFy68drFh#iBm%BWP$?W3AkR24K9zB0coyZ-Tm$9W zOeZJ6XK@Og1vkM?88nyzT?rG>?LTw-6>bM=+mF_k!m2$%S~Vz0s}_aAs!5@+YE_Ir zP{TqLff^U0G-+Olw4{Lr%91UWj%K z%+QXB5gK8v3{af0G(T^|+WdeCbK^rL?2XToumF07gbmQMBdma)8DR+UtcEGTQzEQ^ z>xZxhuApKPTrI^aur7*WUoYeXCm9JH9^b8 z257n5{4CcSpE_Ft@W|Oh;A((Y16MLy4_vWmMR0YJCBfB576sQISruGwWW~9{$im>7 zB5Tf7M3x3u(ku?Fn^_)MBeg`Z7H*Yb)$7{bz;-gRJ!@=_B1;r6>tog-zzH32C(*E3NXT(?ARaE%g0$C@On1FI0I53D<&K(Nw)62aO6 zY6PnZs5;hDs5@5Bs1vM~QEOG4TC0YteXhc;rnP@{?Nwd7)7K95wO4g520XhY=ekRC zuB()RwNBCoRt=Imur`nsf>ljY9jh0l60B5^?pT{3nP4@7wALR;YsG=I))+`@Re`kD zRHU_%BCS;uX|0b+Yb9J-E1uKd7PX5}?O9fPRn|_mwO4KJSy_8E0>QdPAy}6vgi9D2 zVGf4u48hReW@ryHv{o!bYlSkj)+9q~9Wu059z$!bF|<|}Lu)-TwARz1wSEq*m6B+! zh(>GeJ6bDT(r&i2dqM45Ry$UOJqv5^x>}z**6q3T^4xi^cV6qA@4R>LPQiNs*1e2% z-(uaTSobE@-H3IqK(1@eab2s7>snP@*P41=>*#f@m^^D`^jRxl&|2q+cD18jY-#t3 z+PSFquBshuYB%BigZDV^2fU|w|MGt29m;!>_aW~+UTcll+TyjAc&(pbi8CIo#4Lw~{1SJ3nkRDAWh-ub#a&vxgs+eIYh9;CVT?ZbApwDd?c*EgtwB!~>vLbP)6?9ib;V7<$77 zMCWZ_w3`M;n`Llx*Bqp)<|NG|N9iLBnD%*~=@+2-0=6E`*tcQ(G<9DF@WC*C7R;AY z`bti`kQu+@$8{G-vTg%O)^$K>5Fk+c%>+tIkznZ^5ipGcf~Gq_;Pm7JPa8S_^6;BCnuC5fksc`iIr^4098?390H(J(I0Jf~30CX)0fY*m^zV35l z;khF`KWUxZt*JGE?+|?cmV^76UIW&7%>)tmV^ld!VzZdM>R~DkB2a4J(^+Cu8?8Yu7zOO ztbSlHOb6!e3t!+4`y#iMmkcL)ncJs}-D3g#DvA%q@|lGGkq{qb#qYTBHiZ0(Cr^Tl z(>5tp`@~G?geYf77)(J!GD8Io$qAJ-3OlB(9kHOoR)k8$ZRnE#I3_pxi z=z0jL++F#oX=41?7m_laC{CPA-nfF{K`%OAcwK8^;B`%jp|>R@2A`IU7=9H_$N;P|oiQlo zNyG5Tl1Ac^V+}?Mhc+OK0B=kN;o;!W^M*t7YL?^ktQ;Ly!a@4g#_7{I@iJum3m(tn z$e)Pvs9YXY%~M7I*tH}yc&&-QuQl-pwkQ6;HbCKLp^d7ag(|8Vz=o&>deulVxTUJ1 z5DRq$qE#ph#wpCz$EU~^lS_&&EGZ1W$Ry%}(fN}hMh6}>8KqV)8K^(S5OEL;6YpZh zuj2R>LLLQ^7g6O)!~7OAQ zk&5`WV#MNB2ojE1qa`G*HceDYQJ%nbVno5IRG8w^=ugFHP~8gCo;DUCtYNDqo?@YT z(FzuyLdL85_!CEd1e8D3@}Xv)!vU|Tf!B^Me(mTY*pez**wRF?Elnirzyo4g03Hy_ zvUo@&JK|wcEQhJ1h1Cy{UL#H^wMtPhD|CX;q~Z|56Y4QFl<9B;DFr|hr$cxqMEJa& zFmWYAq2enPE~a2Y#*@hLC5HS6CXY(xNxgjMnxAyw4Lk6R{Mye*umyz-ThK?b4Sgi@ zAk@)}fKW%XEuoKPNkSvZc66W&dm+l`eaFyU)8Ex~rPGHf|3 z$Ci__3w zc8Z{~^dtf02#R9KFm%PypXiIAI#L=vY^6D@Umkw^R7zx^Q6)bb{VE7HdpRGpDDpe!YEFga50pipQDWT=k=5uXg0Nv>y^Pp*(z zE0GEoTy zMnxw>gN#ys@*J;R+aA08^+;x>Mm7sE3b0RO0b3axcti_+aG!4yGAv*tLkSKtjNl%_3%(MP@PB(x-flFv zJFHfZ$JLJts|N&Y^Mq6YA+JaU2=b6zoxxLbbp?-!)sj3XR6+8fOySUz5(Pt#%2SFx zD@`K!uq>7KZ9(ev^I}x_7e;6iu#692pqeY4;G53`5x7EN1UCuJnZjV5i3Il`4ZF|Y zE>yQ8#jTO4r8`2kbV{n0j!D(hIiVVX4vLfobX26;%w37ZoUi^uxan_4 z^4p8{b|JgnD8sIVVWmLr+Z3pEvl6v#SEAMp3)H%4d9vfyrD@Jv7^VW;$}Ej>JF{fP zEsYWrH#JE^+}I%PaC1X}#@idC)wckOO282?^Z^cmpwGAkek$c2=uyl-0ngtY2G$I9 z99YrrM7S$&r^?%z@^%V#yOG?E^tLx$Sf*72)3j<~nl_D0)2fkSS~W9EtA<9&1hq93 z=%@i;^njWGMrzUuDB_ZaKv9%51&ENOF)#!~&4HmDY7hv;P?I1ig&GAxBGxSU8C}D` zNAp4kJ&$a3cnI4-xZ=5$V8sBp)-ky~Yi^Iquq(msNp3q6+a86sV*ntn*c+r33jk@w z1fZ~D1RU*{0YxW>Eilxvt+nv^SsI=XR zY>jp+f}-7$plG)yC|a%wh?a|jq200|=mf3{emvlk^FzVb20w4MJopi#1;P&&tq^{m zXpQiLM2iHUAX+8(zxH|;wpKQ>wThYT zS!O$x**;~qSCMTXv{W1jZ50PWTg3s;QqlQYst$aX%7Y#ds6yz8GgYT&fT=q@W~R1h z%hdKvncAKuQ`PFlGTGq~f$0Pu449Tjfu-e9v$Q;4mX^oL((*i6 zS{@`zdkwO*JUW(^N5<0ftXSHsh^4)XF75S_X|Ib;d({xN*14&*rdh2uOlz%PUTfw0 zT5DI=S}kZHZ4b{<*rUCYaJ1J9j`sS%(O%sg?N!XtUbP(URm#y`mmKZ2$kASX9PQP{ z(Oz8~?bXE5UOgP`b@OPik4SqJOj@fQ(^}i0)@o+8*08L#f^Dr8Olz%US3FB}-J!MC z5!x#ULVKM+Xs-bXt<{XsTDu7CwTjSQoe1p}iO^ny2<=se&|YH*?NwE1uc$(M{S?}( zB+*_Ajn%l?sl8T+UEmirs_?e;J1hwW?a zPwhADC+#2Y5AFYK&u`E#(eKfdfb<(Qy&S8z0PG`J`vvTN0K!)z`Cvfb%7_y7FW5)3 z-(=s$zKH$IeZ+mf{kZ+KeGB_p%QMyf?6g8BZP7=2bkHLGDt(=%mlO3CWW5Ao-vHYm zVE1qcKTYI=@q8;Go{5Xyr?bCi-^u=swLW66b8gR#`!nDQZMQ^YSfkDM=&wZ@Yn7H- zrkD2VqmBB<4%N%KdI`GTfwD&c!wZ1-ZV3MjJ!oNfsu7VFtV-)Mpg~# z99hrQIkGOPL%~{u1SIQ-5t6K)A}9?h!qS8xG`$DH({dn0t@gpxVIEYS)xp(K9blbA zM{A)Cxc&hPuK?N)0QUgs{hAG52J*ddzLnE&qT-9(cppT3b^@rLr67uDD~P(@u|Vo7 zfgGu87YwGZMKGwk#<;-hDk4q=t7jZ6tE2^58(Oe+Uj-*{rQaI}gxfGwgGfJK0)tbPTkJx9QW*9KtwYk;<= z1i_`Y2G@}@3)QU!+VH*-fgCXb=&D)P78o8bgXy$nO(AZh$(BQpx zX78^vd`Fq_+sMq{JYovJ3*=wfd?}^Rq{JJ6@jrNcjw3Il%9{{VVW*^BEtNE@t)fP? zSJtFJ$ifz7z!tVBELz&2h-ks^L_-T)(+MqdOCGk+A#rZGGn(9@H>9|w4@i)U1|K~x zf;@3tHdxQN9KOXxai%Sd|7dyqDwz+3^p}kIA}#(0j@L2dXFPcoT>i98k+-fr^47IT z-ndo?oK^@-;<6BM5~robiCmVECK)Q&Xi{gT>I84I3=UO~G&G?OZG0{P-WWB)!|?&gO^1jp*^U!i?O5@V4i|r;#?t`uF_Qf1 zlsCcUPtm;R0w2i%+J;a}+tft0C2_d6B@Wr@lrU_Iz=Rj6@2KRm82sw3N?Hnx=jg3)aP5R7kPKrp&J0pW<@ zM~KJ^GDJ|Osit0*p^nfD>rleeOVdP$EJ_rmRg9{rP#aW?Na|yOf@rSAs*lX{Cqu@`4C{fRLy%L@RM`?aMRW^Q*A#p*A^6$Z9_3J+zGvW zivxNIcLo#_ZVG6o8<9}Xwi_Kj*H%SE(MF7xl6fdSweaa71lQ#U5)~j(Ri_d|uxJWQ zjd6rm!UmAr2_Dxl6hoFmkz^qgPo9L87s2IE$-JnW*L2_^LHG@ewwj)5yXm>MoStmU z*@@u<&dxUsHap?eYM$gJ{}b zimolD=!L};ZQD!Hw>g)haBVn@zFpx^>W1X_fcfg7%!{^p&I7*Ef>+#u z0{3huKcFq;2eqaAaA7G=+m`Ya!v>S5Z*L}1-Ly=Gz_4U;1e*`a5^FOmQJ|@?Tv3*K z*&@sXCJe7nO&VF3oHwcn|!D!4ers~K8OvK}lF49OLS)G$bsw^v$Ks9n`l0=9pg-CA`OAm(5 zFIF&5GdFd*d1$kMe>4%8=kkG{++geC?lZU_@$J|mwjkR>7iN11<7^LQpsnGIG!YDG zs6|kCBh5@5A`u%RsKi2!K_*`?2%m7J*bpU4 zgdvry(!(oLorzkWDi^&ZDK?HF0)#~4LnjG<^=#6Bu_hzfNpj9ek##nD+=&+VpbdMG z-=1W*V5`B0h1CGW!u}Bn+dm^>>vu%#j-QgSIetwn1keY?0+>E2U{d_3cp>q#(&gh1 z%a#odTC!Sb=yG);@Cy~HpqQu*gJqg9H>5#&G;Bk(0Z{;-yhR1BY>X0EEi}sc$`OQ} z8uui{{m5`90^E=GcBZ-AL(;W+OT0GkiPz>u@!Gs8UYloyi%wpaEe7zqY?;CPqJ;%7 zOjeV;F<3SDmM8OAs{0J-HiQ6#8 zdS>{6wd>G?s}A?3!W~I)KkD0)?)Im+&DuIDT1$6DYv;IV?VJ~_odc7#b7LxiFn7j^ z0=hL-ZRXrmd6|1dHDxYS)*42))~va;PRXrx z3T~}cZEMv+TQUfocB}%Y6~mxu#pE!p*c_!D(|`gAV;oS(VC(~=2E;@_QX^J}f)_D6 z6sm~bq0mIkgd-zjYve<0jc$l-k&Ce%Ix)6FAIKJ{vuuAP&(^0fZF%(8_WIVg*D?%i zV%;?S3A^!)eco) zwL=3~?a&#k9qMAWLR73)sEO4I8L`@+A65%=!)k$CSnZDqtM&0GBPwSK7X?swgw#PGSdnBipXG7KUuTF1cFO2#F$O&hdJ8?;Odv`jy{v_K*(Ezq2$1$tm{cJUHN|cf-K~@Rh5LKk zv)lrGVTUGLqrLWMtWA1qmv&mGlQ!z2rMhP;{A&FMULVKoC3yP=_P&6_Z&UeWL_bT3 zcOqjK=nBFDT?^QttCbbH0@=V!G2~a#AOpv-}nm~1Rykli0w1Z{kv!i95E1}EUR_AN8I$?W}K(^Hg zW;2bDc995cPeI!^K=%gxJ)Fc}Q~6>-KTGOEaq&lZJdh+rE9Yux=Tr#VIaolO0Tz%3 zbh3cbbdUw~gpL-_4i30}RB+JcW4ggt&*laecpx3~SYxUG18) zs%3*VWyg&g)CF%YK#_rS0gA~RwkI8LFf^@r^R|TgMh@xo&D@dY8#^M#H+eyXZ}@!n z-e};F13=J<2Ec~3062Wf4dOv;80Rs=;lG4_77#B*#V@JxLxOydCQn1lqfisJSK2IW zmNu~E!uGXY+PY5o;--Z`7&xsmV&Jr#go(@I5hg>07GdhDM1;YU5*4%elqtrK2~tcT z65<%bBET_+L3d;pdoYlBw2qOXyowBmkH%om^9A&=fOsh>e#wj{;^TcJ`5RWA#h3?C zz!5sMO)`hJPUOxON`|r%qRfq9K!uLX3>CVuCsgXhd{Du&@Pf*o*N7^9TclM0D{NXJ zq?){P2(@@+afuMi;}KsL%AgEcFniLpbXvKzbeKs?={;FaUkZw6661^T_#Z|-N0f)r z>OdPEon@C0(I^n>i;mM|v9h6X@wkRI1VNyUmC56O9QBXXS7jHzz0~zu= znmi3Gzk}8G&$XLj&CA_=8&=e{gbm{KcW^@fR0`2jCl!9)cKT^w@CW zgbzft4?h^mK!ZSnT?(n(iX4Jzl}Mx$O3;X?k{%M%A-E)|J!VQ!Sh1F@PC*HbM{?tX z1bH4x-UgL-;pI)J`O*gdvQ7%bxK(i|x2uL376#$m#vr7dBZII`%?v`iDKa+937K&y zM`MPfn`#Y5HV`%>(K_7VK&F{P^J`Mar`95mQK}6-J~Z_K8DTUhMD<5)hzlzh5gIQM z$?-ydJP#y~Bg)Ir@+!!@h&LZf!B6f8N4X{8IJYJk=~h+IZdD}SX`mtzkIjliyecae z?})5uq_e>SQf|d6rJD#BmlsH1aGr6-02Q;GLE7b5p|lR z=3~cCK7!oCA~MeiiAg&gB{}^%AYF{eLWH3z6*!}X5?+lN zLvA;8@QCFIvQ!Qv3*m6Gl}43+!R1wu`4Miuw1LkY;U31#u%(!EYkFz7ryTAU)l!cR zYULgr)XKdqryTT96fOL~@3n}NG64Fn+9;F@XVt6Rcp!(tT4Fsx)3!))PoNcD6idB+WCbDQN-sA1`}(A7*jraq2-=cF!Q7^vz0`f|8T&6TJV$m(BaChXNL^y z*@139JJc;`hr108)dK-ElrI6$P(Cl8qI*q1N%MY$n%d3i03w5m(GhQ{q$k))J*YtQ z{Lo^I0|*&ikEu7V3`z8O`a?0~h)#tPA2k+HtXeIwjKl)WKPk%GV?xc>5OBVw# zA@`!fm0L{@8CKK7+-`cPTTTym%W0Y?0;lO-1DmFMU^Y+no@|1^6T+ElXQR_ZhE-2h zI1->N*gkZ4kp?6Hk6Fs9LdhrRcJfs+ogKtl1|O zfUlO_1_LPyxa~V$1c* z#F&{%tXXJ800V&_V4hM2zJ6vf4~HFyai<`-a7$StZYfL1Ev1RMr94r$l&W?mOse8l zFsT9$jpPcvDv~YikW9Xy<3TAyPKKq8Hx(Rmu%%-1Nb{fxgv_&($QOrCBv_6ennZ0V zoszWoK}*mZh%nbM5ovxB5x_*K3LIqGz(1mK{^2ZPAj*9xaZdu=w}n&zw~#907LtYB zLaxp&Uy(B6p+1UFmP# zmJ!C=GRlBk#u{?Fh$F)Zia9c5ptxf$fJ7e|yd(mVd!i@=j)-CrHyw;f*kUv)QA_dI z#4HsOidg9+6|c@pELfG7T%;B?#V7@Kq7kYK1%L-l6alN&s02rK$l#^V5~i@Z8(Ho} zjr-Ator!PR77<6=BI3ZXh&$Z&(8t>v3V~b0A#ijGjl3zaA<5gA4NKgp6gp{3QV8Y3 z4?`(xHV~_vwMfK5c4Bdh1=L0_T^%09RKSchBlSqRMv3qe&Jdnv0zGV;5nRJMQSBf=}2$2$XU`g1{;l(gb2z z(-Npf%}F2^G$Mju%ytZhA)7%sCTvBa8LttBX|y~bwz=wDgcCLCSb!2@Bm+c!loR~K zS<2x#YRteeyE=gMrPtx{}O4|BmN!z|HXe9V~ zF}uKTj2WChGi6}<(2!B-Q!}Q-k4;#RKQ~}GG{EtK0Vu$hYM=oXLJTTE)nS7JP?(1j zEHx&^F$!GNAgIpqgB~`>60TyAYt@2W>r~^;bhy?f!L>T^t+i@ytB@_9m9pL2Qnqf92F9ZAhc(A#|I8^dCn5|>Xo?GtHZTE4X*XcZ>>^zYpnpc zVJxiO7zT!QJ}SEE>2dLxeKzU%*nx0LYF}W zA#@sC>8RtNN=97=Qz`5|hzfxhf>Q@Q5|lXb-8owHTOkd9`$HGFJr@S|8aBArtiZKS z`K@(IZ>?5zYu&=Ia&Edd4o=s?A;7h65pb;=1zPK70hS4F7hHAlHs@*rH#%1UxP5RH z#w`S^D{dlKIdLPQ%E((CDj#olq-?<5iE06N!c&R3W%7_=lQ!wr2-0qa2=VqOPj7qN z_?D-vZ?9Z?YpsyC)(UlN&5B#A*V?u@fVFH9VC{uC6*g^yt4*U*wQ3zqov`-7)CX!K zOj(dt!qfn1CQO-0+o>&SDF!g+X^AZ z?N48Z^^v1np0v8Xg3;}jYi_Muack{rTPs-FqRFY+GYzP=>;tMDt0T2zb)c541gIr5 z0V;&Cg_1M2Pz;PM)S9t{`ZBgpR>s!J$=Es_8QZ2GWXp7eY?WM;Em8@yJt{q0qRg}n zYFyi&K(_U%!>~LT-1bTaw^s{qd;P+&Rxq`-f}O1x2vHj*0@Q-td0MY^+OKUQ!E2i| z@Y<#byte6$*EW&yS|%-C%Ou5XmzH>~(vYuB+VQnWGQRdm6>N)6E$mRI*aAh4?GItu z`rx50kDJ=^pk3JO7~5XM*7iDfwpI&f>vhLzyH04@tz}YLtzFuzT`D1Km*`~eQUzJN zL_pRmp~+e$FIk&pC2NzMWG&K>tflZHYmaPbZ4nEtB@(fntA4S5yMDud1GL9L?kDj31{fbs=9e*jEh;kDdED&REBlqmTCXwodJVBX z*UkO8I;84tfj^`Y31G-B*l@7#|K76fcFwBN-AU zv@0c4XQhNB*r}i{OBHlvtAc9m6_AOo@FTieJ{*?Ir*Xe}Hf$JA-;VLrt=ZLW)vjsF zX0>p_+gK;a{bWMjM|bk>*?{jK7<>dA|4rtfDg7=dp300@66A$AS=tV`lD31cr2U{P z2?PJ;UClbkvv$$xdy0;Rk?0KG<3QmX0Qqk=|BQ$CV&bdBcqTrc$dcc&rNT-uX0;TM zQ7r{!Q72Sz7A5Bbw5T%}phZ2oAnmC~oHeHwaoCtd#Boy!wE&I+2MaP7HCj+Nw4nk# zAifIrd@@w<$FoL(pcRaQ!l@`o{D^|Zfee`Z`iAq-c=#_S{tAqD(&LRJc^_9khMBQl zb0KU4Py<^5E_f{gY+4=|;FeW@0&ZDY5_rQRf&g3v8VJZ)oj8DQ%EJM7P@D(oHBBDi zm!yb5pphT~8-(%@KJ0Lvw5_eb(4)TD#hS&ctSPAT=MpiaGIGUc8hXuY|}CdGb5B zybCoS0s5VF`|IAF`2Tb3<&7>sPuW3#cPPc6k34Q8lZ2FF6V z7=B@PQPhI;@@R$lg%T<#CAl7i_+S0zVqUK9%dYCM5qBMI_*=cnsXtMnMu>FcLcOVv*3x3r4~vFCGJV zz>xfV5yNuN#0<^54>UeAgxVOzkeTBHn&ghuFvT7yvIc>)h}s)cgF>7U9zJDAh*+(X zEScztl&?av%)UlKZzJE;Vc_hrlIaDYz#n3G1jz7Dk{dX$VGj zvapG2C83WLmw_@=Z2F;K0h$M*1u32Ai_^DF8L4ZOG*FB+-dN?@pku~VUk@Eg8Gr}@ zLK~vQibX`qM?;=;h0oR!zZbm8-k4R6r@q2!=DNhrF$Y!pxTkD7!lXW0tK6;i`BBn7&5#JY2>K- zy8&d0ZpV=zH6Kr|TR^Ui^yAAvI?C)rL(M#^aK1zX??HrnGWR3K{aeyd4On)&c~DCI+F;gHkO!5gTi8dgJu;3JVr;?A^$Y6j2~t8JJmVw^>? zxbcPf!p9Y$j3P{THkt^r<*0J);?ZTL9c2!>vF4u`Z|+fHz}D0V?h)OAIP6G@`?sN` z8n#1e;EIwC?kMR76G%x7FBl~~Ofy<~NM+Qtum~yYV2;vMKo_Pa`b?arz&i!eB14B% z85lH6+(4T|(Zj;xiXjyQT_|zNiy?&wLk%rFU^v9gbYsmzG~gW6f`ENcFt85@Is<|3 zLzH_G;`*&8s)qeU1>8?m!39MnTu=~&b3Q>FviS^Qc;z#-5XonWK^~#1fH69-=u;(q zffvHVj5*Q|INUlx^hm25AtbEv1QIMl6;Gb_T3ji5i}8ggjK-OtXuz2W1_BGMVBnw< z7c69I!b6^Wk>pyH3b$`JNBr&P2o1YQB5*lN3@)dM4I6BpD14)dqEL%wiVaINSqT2% zbfJMprwqOopEmGDbkH$pI_ksi)5DOoOH?Fbk0P988Jeg9)t7>cQQRt-9g3ya) zj6o+HW&{ekwDE@nQ^%eQO&@nBID^cgeim`-&_RisCg~F~$q!Yu5JPN<(ojOo(VVK9 z9y1mKSSzq_;3riKRw5z7NT>TzhusKsM`~QFli==c3}C5}q;fIK$Avk?h}uEpe(I1`mu+E6dIq=jmJ z5yR{xv-KFNMruPT9Hczd1b{Gzkbq|_#2o7uY6w;;)7`1VuC%!$Nv?GXajjB*i?@wB z^0twO-ZJ{w+eIOJtB8cJgv2F#7bH5_bAuQ~j|<|IyeElP?usa4q1(Z@MQ%o;m$wy< zVc1e7%bb-~rU~P`bfY!7DS#B>=K@N7q7o3vm15wNR_ehOjMR=5tHWBOI@ij?VXaDz zYjxVN)~da&+e9IDn^@#-5s}>OVH3P9q=IL{hAVd(Y|vt7zy>dMV>W=HtFmEC1|@}M z;({2WdBcIIrtL*y8#NS+aLP~}39t|vsesmHxj zIM-T*xz?x2wKgfPHA--;S9zOZ3zy981}Ae%D1~kZvCwT`n7IukBj-UNnz#r8*}$m* z1mKNJARIR+fdjN15iH<>kHG}eYHVP@Y}N3AF%pOpe3dR*ptb2JrwTH1gQpFYARz5= zC_yL=bA8Y-m*n;Uj=AF{^FSF(0~D1jKDczs)8GMf!NT27N{Wz zRh5Y(KqWGokfh;piRdEt=R)N2h(+#It8uSXh-;-9T@Sr$AxWl!=vAUmS>ftU}y5W;NmjqtVF zSBHzl-yJMcz!R!82N%k*aFqrRwf!q|gr%Z8qx(@e>MYz`re|y!!+w0cc=CMiJ zy8zPGZUMBVqu^{a%qiPB4aP>8qcb*V4ur7d+nmzE0^5T zEkL#zA|Tti3&!@1PT0DM0Jd)&e@89eLZW zA8?yxBW^3SklU&eb1OAkw@#OMo3yC6MH=`Ph^}vaF7$2B7~k@M>FxE)Zm$;N_R1Bv z78cF|uyxA-Y}-Eg+6yIg?b-=kyS80+)V8Ywwe7M%ZMg_gTdp%|yVXT)wWzFZ7L&Eb zB649X>|k4~7;HO*0>eU?zHQT{+#*5FEfHbe0^!-M4+gvKLAzU?F}uBrW!UQ&hP`&d zZP&8V;o7wjxHip>mf2=)*=n6owpt;St(Kg!&0?Tzv)Gg^R+zG_fW>UDpqQ-{60@!P zakf)7&^C%i+CGI?+oeyor7($Mi45NsXb*0E;^emHLWbozliO?A+g{Dq_S*HfX(MFq z*&VDc+p87ZtFW!G7Pi6&7Pf*07PdkJ7WN8G*j|+hTPrSMTQwzYsg{WCln}9v$}zT2 zFUGcsMA<5dp6!uk+72bIEs!7E`p~&;4~DnpQF~jSv$nmSsqNLvZOiUl?bupv*jDY= zR;|}s)$v*@4!pLC0zizKV-NzAp z2q1re&PNmCx44*KfqdXBkdKxP@=3BnJ~wu#2gMTgjIL1+ho!)?xJf*NyTrq{PCRuR zb$wf^>)Kvj%Qnjjwp~`OwP3AUGHcVKjkIhFE!;2NhoJWs3_b*qPeA9V0r6f|U&@Z5 z-6bIsc1dW0T@oU*OhQh!DTv291+iEtpbskr#JN>Jkz2tBakG2^m#b%Qzk294jK^)u zc+wW_Dz+D{T4j(L0Z_xLHpV{XkD9uTGt00 zsC5a@!CKc9Iv2KxaL}ejV*@uV6dS}@nQAac)v1A96r~1vPKz4!Ee&R%fx=z}ghUxK zK>S(Dz>&wooJp(I9L$sEc>bfn=+`$Q-pq>UV&kO<`6Nz0NSB}C2E(>_Yg;&O7d8P5 zYU==ovl1SFE2}^PxiT>l$dxgPAj8-W2JS1% zCJhiY9@Rl08N>zwCQn!dp4O@eMmLJUbf5r6%#&vIzu5RHL7oYdCz9oJpm`My+^v>6 zyY)g>w`At#mW`a832x@xRBiIx8`=3@l# zsTeTn`&%b`eoIBKZ?)j@?U%beIb!tk%!tv;dm?5J&xaVlx))*sGmsdw!L4)*;)PB! zjb@c)BrPN) zl=H+g*~UQ&Cfef`PYZ`#NU;FFgn+sWgTe^I7#BSj$k-UFOpkrW4B3ih$v>I0^$3^u zapq+R@Fgs`=W`8Du#tFp!VSbj6LuIPngC=2;vFM`h;on!BA!VyhzJDLz6AphVjt36#5*n^6YsouRJ0?baj^s&i;N}gKx~}jw$PQ6>qY#<=7}*ajypD)dFE(UgIT4p zBcuj293$1xaEzQn0}@h5jmgFuHaH}zxUsn*gbq**qdZFWIDLeevkYRQ&5;NTGQ%S? zwEmXxc&c+kglH{Flk3(LD>b9TJ<1C6v%? ztaBpskp$IC$PrdpN^Y=9N!ezVlCsLGr6iG6%*PupIu}{M=x{hfh6h0pG(z-!-Y{Wz zse^?ZWDgl0HL}SPF`BPtyb9Z?CULkY?!q9`PTjIMCBQR+f*)pSN-3Dg^aA4K%n11%wB z4pS8gTjc5!vBMZwD*V~_vNX2?4ba(-0IU^$G;mao3uclL!bvK2BoX^jhqWeEt`+KV z6YqSQP_*-Dg0Y0p6OMI0Q9zFHsVXVLXNpM)JXcVX>0F(Z;^`u?$fu0P%k6mSu->`~ zuie#l?yjET&T24tS0}-%unoMdOW*^NDBM>dyT z*;aLDJJq30fllopx(lBH!^^>aT;0dBF)3Hz03Z*BjUM%5i-s z!`2ndZ7pVw>m_qt6_V@Z)iKdslCB%?LM~)*SWRr=7!-k3~q~IczX*2+*ECH zGqq*dh>dQZ3wOV!_iBEBLGalQpUttuvyJ!*GyZ~-S8dnKKH);y7qFFWhOKNTY-jIa zLwg0A+NKvSym^6b$O{-Yyx{iV1-S3l;*MLB`@&j=$<*fdQmgwa&2FbOyeXjR{d30m zXMb-d_-Tc&rub`-zn1xHq`!9J)oy&6lh^EQ?L|`?12nbKpsfuB?ZQ3K+FnJ&Fe=*H zooI1Op2>}PHh16|-FjzrXE3w7%?$4|v%D>o>77-^cNAIQNn?KtjRn5g;ENf4S>m5L zK3e6Ybw1jMM^o`%#bc3+r3Wg5P2mG|UACI2D-SOJ9T+c@@4F;(slEnB$L0J{jhhg??FxFKh8+ zKz1zU+qag?Fk~-l1N5bMWH3(aZ0`uD}1f0LSYJT#id{ zHZH==wl@5=Rrrx2;=ZBTPScwml@nJ>YLYEgS@6Ez{ zweaRZD{nVhdRNKXdq@`FGP3+Gfu-S5EWk6d1c$^L9I?WN11l81SK)BFE5zNd5>K;Q zJj{x5tt!W*s31?8nmlN#@~t>L7UpkpepiS0LjA84?{(w7mV6hPcS^%j{oMhThT%*B zj%6zFB2$Ccm?9j7RN)?^4(F6Yd@@S$#i+#zqZ;>vavTrpak;0+*`6dXbE4eK$#Scv z%bA!oFIwvSC(obyd@Iq%`tVsKUdzR6`S`3TpS9*?+NQ!kXfnJ2ro*=|A&z88aU4sG zt5|9rRq63hrN}ubN$#jb`C(G!fk_Ok%Q4ytV35uQ=%z6CLA!Xgm>)Kl<@VN*+neBfWVe3`7TE(Hofb zFPGkA(|!Em7^8k-5Pw+2ppO`H5=(Ak#!qax>Vl`3?-kp!{| zG0;K&BhS<1fv|iKniqoLuON&_z0M=1U=o+v#8O7_kX5&_idVd15wA|Vi#vbu{xG)A(G zcYNa)=lH}sMxDo^`o{AD6L*~mdgvW=6x`jSiB#2+j+_sOyx9N*~(Z(vX*Z~^1Zz;8#e*%ZumoTZ*K(sT}bge&h{%DkVM&S)NBHFufK zPj>T=-<)FsjxhnN7=cNwz^60t=nw391YEWD5rJ1>acCFV5f;Z!n2D`1B4Q{ewLRVa!EX@;WPS=fO`{ z?>+AQEa+!n^A5oK0q+xV@Cnm7gFE=mAKYaShH?lSd4zjh=Ngx=i|?Fb6DIL~>iK^8 z4*8DwuHn7z$cO?H9*qeP#e~OU!k^xGNISn*!lTvsD-u2lgdd{wYY=Av1KgPDLiAUb zKFZZA5&I!-&tvav94>+dAE$yhQ^6;(;9FSmpA|eJ1z$J8lS%Ma5xi0ZUzEW62*4)I zf5wZIKZ?*ZNqQqw55((pq`eHfUk!ZK$9K(#u;v?B^I6tBk2RmV<~gi+!8ISJ=Ec&y z6`E%%^G9PoD9m5K{9S%7zm`ACkL9o8q=?W1LHZp~UqkC(lzj@gFTwXA5dW0JJLPxE zYe41CRQV)U{)Lqn?W^(_sytYgx0>=zQ634(6FK>xCjXLO$)Ds8^82v9j-juC^e~>D z1=gPsdl78^;qFNXzXRlldM=O`A;=>Tqg-5mUrD`9V;XD0)Gx0S!{ATFB_&YGZ zf*9XL#t)J4t2177#zW3{z!{%m#-q)6FBxwYaTFTRCCC{UcI+6C-Tzbr?x4inv zu7?!+MzvRjd&Ijp1boB8H-vn{%{M%KLl=@p?_|++D2axka_BCVLmROyT85?3AuNyH zw4|`3CDMYHN#9v2?Pj_3nI+R;l}%$+IvrK{bW$bMMU+zWNM8LT*E5E_BHACe{RX-x z*9_^J`>C&sLi7_}B!?E1v8H$3|d7k&`#1MwbE!)qvf4UVq? z^EHsZ2G!TV<0YheH%z^SQ?H@a7#v1z<}m6ehg0V`q#DJc)uV@3TOMMqc$hWd;nsME zT*Dc5oo48@nBmu3hG0__T9~O&Y@-ch_Yj`F;MxPe{omaC@qM4*_a%OxR24X!&u)=8sv4$d;H4nk9RS0tJLC|YV!LJ(yElemF_MV{FbAe>H z1(t0VX!cj&*%biMh5}3*NyFMge{Apf_I(+C@9y>fKCkfe7(dVQ^FTi@_48zW9+FoN z?A`l%`M%yhuQl-VdX1mgQvAO5;b-AhKe12!#@_TR8`95g!#=eC`qH-RQ~RxNZ8g3N zi}AI+#fM?4J-3(k;4adW+nPuBcz18N_jZDBhxm4oZ-@DIqi<*8?QXnX6Fai^j_k!7 zn}fWu+vJT+C68<%d1kxFI~zn^+Mao9Pv%`XF|TbvJPhaIxh;qHHrpNCWp{FW-DUX7 zZf+;Lx{vDa_MyxBxxUW;_!$a+j`8OvpN{kCOn)xL&+Yg*DX*N`n<4F;Lz@mA+5ps{ zU8PQKA$4oRpkq6gu5C~{w=3!3UZjhAkWOyCxw+})=ysc{8!dy~7BbwOl_BpaIlYtO z_zr^m`?tV%+wgCRANTli9A578dXE3xGS&QTiXlAwg+)+2N2ixl{gF&iE~>< z9NZ-0-;tk-)7?5XnY%zZwvFH)xBtSZ(7}}RyPQ0bsL~ow-zw7cJ zZ^<+bC#C@|h$c86+Te9)gu9(>IPJ{vFtfw6%o2wxTO5h3ai1~BC!72-4v)6^Y8`$J z_19AT8jfFM@@i$?vAuUp??0P8QZUmxafvz#>ojqFm^U!-HXd*yfLczFFy?jd-*c zkJjVSqP$s}_Y3g-((r<5c)>J00vO;9fCc^qOzFpICkL3|w!`jt2bFU1|b z7(euCJh030y)MYRGoEb7lU?~R zH(7$3USC;!-arn*( z^Ej)_*Qz!jtLj{f>T@Wm4pWFKooCAQuReTMi03l#Uo9Ri$Acw#3tpb9fT_lps&PA1 zj#Du8cp0h4p-4#%M2hklQI(sB;;@G(%sZhnuY}s%@rm=pC(i?)KHqZ+ozAJlZcU?~ zF_r$s^x-Nc5Gy669;8&_pG^Fe>R+usR*lyR@>@}U>&#m{u(V8;o3O;;I7^tLQObOZ z(&j^wIIof9d5R>^QIkRsMG_qoiF8S%(if3T-$3ER8jeuUI|8xXk%%uCjd;w+#9BqF zKM}3?Xz0Z}M=*XV#yi<~DIZUzT|^LwM}tDl8I*b@(1;&_N{skp;=vyj`}wGD*P~)M9u}uvT?}T8vDGwQ8plOE zJbviM9|`#+CC^mlo6LNZ1Ao*(59-Tb6&BVSI=^bmHgr!$5_TQR=vg{wlV2A_B_Xz?^tpkGwx%VF-9@oIiwraoo6rag4w^{xXlB>|-GV`NlzR@sLYgDZT=kfhPV>-keg{G*Lc&O!H~8!9 zQQoP%V|mZ=p5+tPvIcJ%&Ryp6mz^ADA&)u7Wrp#YQ=H}zule*giw?k@!})Rnmb`!$ zPhi3uxNixbJI-s%*}@uZbq6m!!bF>Jec!v7Ve{T*5C-4}elwW^n92m4WCQ-O0^68@ zU+lmshTsuP@aYshx&?b4=gZ|Bc?K)a!GeEq-Xr|Boy|7kt#_#RBkOz5o+f|^wlf7| za0RggdV@hwTjc2_L@yz0+BEuOfOM zdMDZkct_wJf_Dr+xPwDjfkk-CBV1(?R&ogo+0HsPVH%%si+9!c5$~t(qVJsVm4xql zW$#%@6e!qdvu|bJ$K`(D36H^q=eqMwb>1k1_vzr@00Rjg2;g^Prsu~T{gp$tpTYDls$NCcpD_E>w?EDM(hV;v z`J$gcifZHc#&2-r)8Kd}IlhLD5252Rb^M`@*VFMBbUay(-^%e+IKJt|BiZ;O8~;<| zpV9L$h`t5Vt7!TYRX>93QOVvz+l$uyC*XH3J}2gLihgH_6u&C|0~J5Oil@Tjd#t|d z>W8jAN~eyGwd(Xz7oSr*8QX3HyVE9<2Pb{Bk4P$ct;)YCFatbsWbKR%Sg&skI&@*K(9yU!e4wZ1QWaNwBLXE!-p-_L1b+K9Oecc;Ol0 zKGE(E``)nd3md#I09R>s{1(8MFSNF>3)Fv$o=>wGhXxUpRIR z!tv`)N3bIu!#->j`>%0qyGF9#8p}>&JUfgL?JUN$r54prT3mZbvF#Jz-jMD$=sh9e z4<^1~mh+tzDEXjU47X_pvWTXc}^ z%fYrA2i@K<_;%kQ+;)O;zX@hIO>pk8K)SO6>y84v8%aanK!1Gi_xF8*zi0S-kl%;- zeI33}#P_{;zah_;h3|rO`_`=Mw`Lu|Z_V1k-khktZy_^Uh5&u%$Cyf66WP1dJ3SKr=Le0?kN{XMh?_?O#_t_@JTBI~d$TT{n|1TttR(2USs&PQvwqoovnttxv*Os3v!>XavzqSJ&ADf{ zq#oXhdU*@#?Y*bRH=SPJ81no+%lrEa5AZ9zz?<#{H?k}II>e`A{0xy_clmdpfA`_> zR3Gog<1KkQFxH)$vo2i*>(*tk*0IZAjleF0HH_Vym5E)QRfnCOb%hXsQcsnRBN5;BvcyV{umAkXrk-M`_km1fMCIgs5E=dk&7ffp z83K2d8{ANia1C-B&XX%#F79x)IK;`|6z6(deCn<7Bsa)`Fid_Mhi}vTH_(ss@Ny(R z!;8=G-m*p?8v2L1Lpmo;*ttPAmTH~wFbBu+F(GfG!2rVI;58dA>e;7>FMm@n@=^7UR`?{F;+r zBlBViykB6AuZ6}lu<$q=FvzWdMP3AKavNZktN5}U_3QG`ugo#MG>`P!T+yp@!Y7;Wt^BXUWPO3D)L0usC1E@;tQy=%W=v-@Fo?@@n+PtI`dvJRE5C;k~NU=c+)= zMkV60DH3N%o!ALV#YImop322ni1AxD{_DqEVDe#I9t_S`h$GecPI=ye)aL|5b(l+( z=u4nR?*Uahj3?7gJbl>I3H48>5Mw%tII?NPi%lfnz+~b-CKTT-r5J8$#b`<`_DX{B z6cUY*j&j_Sq~o7>JXMgdkmR+h{MMPrTHv2J@bD^`4norD3m}E~3zCSPAdMIZ1rpOZ zGI5F{6o)ucee_7hoJK3QG+O=9sKtp!FCHw4abMAl>4<9FHgsb$AsuG{`Pj*4$VNa% z-YCf{HF+m0FBRsg1o$Tl9!Ufu6YtQ8=?JNgKv0Um{H*wi4~vEPwAe<^i&^xQ{K|`GdBa=w+sk!}x$QEGoo1}t-1Gn!?SKcr3IG0epS(MH zck-Q~EaxZ#u#~w>WhYyC$XU*@mSfCi6?=KaU_L#}qK~=rGGBgX$a0fsB!G?>l-z7ZvzV`lw z6!bo{2k>4%1#v!M4BlWl8}OXPtie>?U?tNz$R4cY51w%dw^)Q#AK?+#`NJhF`kgmF zVaj*L_rdqP3KZUtl9u2-jtKh*gOKJD*7BX7Y{ExA;UDig-etV6zN2_gef{1uC`G_x z-*ew^Kf}J*KGmI1BH@QZ_^SsK81SzF`ZGvBhU&jq_$_Ws-!S^Fh+Uu5uqM7|Et7lF5vz-N%at4ZLSAn-E~cn=7Cqyhg|z`GUjVFUaXoNof) zDYSW_HSd$=NAsWg&HQD4G5?pJ%k{GmJ(Z+~BK1tT-pJVpiTfRXUqkU@V7`kFZIbc|0i32Ia$^ ze3p}sYVu1={%FY$DR~|xKa#)5&kHg7A4ThVh46$!<;Z@MR3Bb$;VfrCdd;I!% z5q!J?Ki&$E_W|Tn1o_Y)4;kbIgM3|(Ujy=CKtAioOa1sIAAjWIgM3(@htT6V`Wj0w zL+V{*{fe+Zq4py(JP5uwHGI;^9|e8V5bwdo!uS$md<8NdjEp}r<73QtR2rX2;}vPV zAB~rz@o6(2Y=-p;Gp<9;=s9V|&qOnZ=7B-sTQL2KsYj9ZBrJRgwEqD2qh zGxI%1|I@^W+9)CPnh1JN0G$DfpVg@P{6zKV94UZ4kwWOu6hljB1=3HdNh7HyErf#UOH6%g)}w;G>DiBBcu>3V{CiEr&z$_s&d)r3O%}iT zV?8Lnrb+LIq(eYOx_}TAwh{tqAR&@o4WYDY2&OMVG`$GIX+RK9+kt=@1Bj^2P^JDt zo%#xu>L*mIkx;J=`jGXdU@vO+pKQ;W;WPK11L0RXekF&m4E@T~x0LZ5j##&I%DSFY z@8{GDD)k7VQZpbLwH8sSiHJ@eL$vA;qE>f`Rya}=>%tCM_jTBMt^?O>9l9>-;B{B% z*H)omH-%dG2vqDIPqW{=@R)JW;qECld?eyWNk^w^ci05max-j2O|b(t$IjCvJ5ICgwal}}GSS}3OnVAb zZ6(aLhc4UR67DDMK9cVr2fwlL87-gj^BGaUarGO2yrLBAK0=0BH`0RD&ZGtF1e6x6 zU?{_^N+`vwHz>)hC@9OUrIcr5Ql{-lsdgddh5abmuA^+*Z4$$1lWv1ezFk2w?k7pP zk0R&pfwX(ay=Me`#llaFe8kL06#c{zpBUp6i+te~>n5UER}t;Hif7kdJiFduJiAuV zcy=A*cy?vtn0D3SsAg5+c)|L?vCYcq=(eQe+lY>F2Rh1~*O+0sM!GLF)=kD}Hy7jG zQ;T^gE$S_#*!PHle@OU+iC)Z6WFZ?r?c z!4CbtGW?sV5b&Z6gHKragNYCL_=1=(82W=CesIMT?)VK)-tWtG0R!W%D+qTzVG!<$ z0U;dE8NxUoD1>r6J_s{hT@cb;MG)FqLkaP$90>KSj)Z&{685cu@NYaq4aX4zzBU-R z*x=wde0e-; z`1E+F@a^#k;p^j(!RL1^!KdL`$`@$ea2OMS(|nHN`8mcH$jcEAbMojd+R|;xVoP&vCrF$K~!IU$c`u%#L!b zI?J2rE-#wf{0yHjH~MuS{*J`st$v=4*IV*#*#2wrzikcWf0L7q2rggjT~ zHt-agE4y9-_Yl}5Y>xi{WE9n}hm2*w=$~Dd#*E%;``y5aW^gOi=%cYIJmR5Qgn(0?& zs4tbJE<|SH%~U^)#k0wHw;d0c+ec7e6-R(Q^EnkY{`HZDAe_&VOC-wP{^2PU{F@omL3IKCNQFLak7~I#`2zjZWjM z^b}vFgYf#Ws2A#+ULdCI5^-Z!>V{n;2IMkv-WH1Ewo;s?wc;-<7f)4)F_M*ytynp} zi^p3C@?=W>?8={^`LG2Zivz6DH;eSmD!opYhbh4Nu$c;|PpJ@MA{9i8<7&jKS0x^C zWnvFjDE_ok{jy5MkyR^Jq*|Sj%Ef+DFs_r5aa&Z4$DnYG<;3G9rXUA36?rNnpS9$> zsJw+R5BBD@D0rz5piBfjkITlRXv0jpo<&c1!MuEs(M@Wt`VzLp@lux4aPguSR%wx6r3JtuJ z2ESzIMPjk~BDMGq(u?IB#kh^pjI$Wkc!`mXe-z=EMv;zJ6zzBfQIAcDd>l#yWX+)< zQw|NeQK-miF{lK}W82A=7HM;hUg7RMWu z;|0=jou3_F(9`2KJU-T91u~O`9AqNjxX3O(@`;liVkMK#X1U-{90a_`@CC;Sb(CgefQC#qBKk3F9rj8@(GL@i_1f z!JIvVJ$Qp5n1j*W!CLmE0ivrHGxlEM9?RcDVf-YTg8;&R0Kz{2!h-?fi-7PfK={uOKGB1}>)_Go{FVj} zCFhaiJP`%&G#54@EJzJ~)( z!hz3p;0qi0x&|I?f#*WtrwI5Z0saWi3jy#vZr1#2el%fz-pkQ%ae6Ejo{HE%X?rDj zPlWJ)NdAt{$6?|{^NZ$BK=T=(c{FHV37VII=1tK&C7K^R^K)k&&CGj|`KmGRB<7LA zd=Z%c`SO4Hz5H4zd=#f|QsI+W_#$f$WbSupcpHo_L-SvpK8)3^@*`Av2CV!QEAPX~ zt6F(bEAMFKHCXu!RvxX&e^L1>D*rU)lcs!;l>d?PL+SY@M6U$tk39X5tN-!!JJ9|{ z+{@tm7KX2a@>M&$YKc!FqsXt3zhmSvF!E%Me3BzC1IdFx@|Z|I5y|%<`8XtxcI3Z~ z{MC_nGV(}9tPdIa`AVedgCxBVsMm4zH@<$x*}HiA6?l(A@JTqnh|L#q`l%_NgpM0N zFOHr!$8|b7uD{V?UCNHoYA0oql;QP|T7k&KD%?~a84kupJ#)BSN5mDAnjk4~Ds7)XNwFM@i z7IOk>B`2h|aboHcC#ViRVfEyRs}E09*v|ykbtbZIGof`^iLJd#aBWqhYo-!j8<7C} zno9N{F1%^nlj41-;Cn7Ur{!~gJ}2sTvUpD)kJ;q9qgvM;&AKjWmUT5kY z?w(G&-{gBt!M9ZW%F3tgd`i=&RQ<{uU#a97u~ee*tR))HayaAJ%QGGgFwc0{WS;R% z$xPz`lBveyBJ;vkgl5|nRMX9BsR?Ig)Qp=@GsAtFbJuCsjUm(Sv&_4_F!8R!)LZG2 z?;#F;Um5t5h9BwpkeCnI;UPmjj*QPJ`HY>v~K$-tm+==J9Mg?p-Ax`>ctNf7ZcA4f8z)PWL!C21mlp95>w6 zX!sN(;z)~%_b57kqU0ZPc*M|8MEyk7SFG`iM*h&s|DEC4!n>X|wDZ|RyB`k_?S2GE zwEM||c=uxj5%0$aqMnaxB43Ybq90FZqK0c_V&IiganQO!af9_w(eOLP!{HJWPfJuB z43TjwM8}m5ArC5?{0)~s$oU&ezcBR;S^Qy+M_lrRT7GX0&kFMO?BJfy67KzcK)44~ zz=l1bEgSfNkZkb#!Li}bN5uxf9@GtjJf9l~c{mgnUL6Y$t%rq)R=~o=^$Ho!Q0VxD z4Umg%kQ|GU@}@z{i3Bk}kn=Tkc)`;jH1UKlenXEheDZ)@{tf`1{R8Y-4H|qFP{U6M zfEs`j0Mr1)1rQJ@383Il4nRU57J(alLIfK2bO=P`!321Sa}&BhqnMRhtjaQk)P-Hb@)Bg{}b_mDL!z<3kLbYDeo8N=?LK2y$w9ON652$ zggwhg=(Bx@_<%eugu3O zkjS%lJ&T9vGeTbC&jWdhKw$C|f12Pi{`A0W^m&o@*i#}evZpj}lBYAT@@kpK;p&)& zX+6x-vi&KlIH^*CJ!i`=Jk?}^BPI# zX;q{HwGPsCxQ|ZsIy%zZ=1eEcaC(+ph?(L>JOp>*;a2?Ii?_4!ctBq7$>(|b3~qj{ zg6kV4&%#yqlmJ)hQvsZ%j~Td2A1QB`Jwo0zduqIK@}zk4gWTk(jIP#87N1Hd<@(7h=4djhEx`bVlBe%F~T`w*lTv z&YI?RFKqJMxxm>I=R#*snhT#jRW5+?6uHoW2glkd&+1y~b;O$KwRG)+)$;yEMzR~Tif))QqUtfyoptR2WsST)I1rzA^p zBiV`z$5#IvbMc)Rj4_1Gn9PEWvA%S?)b-<^t|2dG{a@Q4+pQ@5vH1*`OqWqVYC;RedYko|D@0#;gC8(ybZUq(A zt+2w{L4_70a0O#9RxyTRHRB;xG{#Y7V;5C8HbIqRRH_||Qt_B`%Ey*dKX#M~@?q4F zHAETN4hqR_PD~DKf^yYTm64FR+~f4+tH%7+n&&FuwJP{35MFAXX1q@{-lrO`lZ`=e z!m$HPJSMZ$V=7BOKC%>K9!f%%p+saANk$%#bY#<{B!?y`SrduLlt@lyM1t~$BPsJ8 zRawpm%VDW3qEatNR*m;PA!^9ReAK3bOa;j&_nmj!vkjK@=Ew`-cm ztZu%VfR%1wAs!5VNX{F1@D!M~$H~j{>K zFqc@&sE>K{GJAez4u`+_?*XPef!B86uP0b)3`UxShkABv(EiZ=(e9J9UEaMM!e6f7 zFtd5gTrM+|&urv0`*_VZZu5%)IK^-tu>hY=z@i^8=L>AP12-PQ8?NBJF&OR)E}PC- zi?GxC(RJ&U; z3;ys0cUaDwXYk}4thfgQF2Z)#`NDVpQhG0XFS71NLOYVtMgYM(1fQ@3(|Lj&xPs9f zXDwgwljVHm3O_)W9chdLH#dpB>x_7nruJ@^TrzYzT z1+*pXcDAz#XYdIp@NVW^3yjD2w)e32taqySD5{>_fPDi}_c`nb?!WDWC7JLHLE_|3RInpw6GE^G-;38YKLO5nfV+ z*AT+Hf$(7+ywwKZl))ci@In_nj|CF^5d07P4EzQF0Q74pJenu7?~>uKXm}`%uVnLu zoW2$OR`4WL@D^0?VJdhd75pj%-+_X6oZ$N;c(({%?19H(;Gq=wqyyflfDanrtpfZ3 z{BZs@znbft8Tv6v4<_onY`qqifX?Z3sf0X5g zvREIB&{sKnDNO%_>X~polCmd);ep)!4#B_S_%k*?2I|LX@vAqO{5JV@@*p_*0-k)8 zC-39Qr+D(BPhRrL6FzyoC)P1MxlZ<^=Pf5bPdWKn$qCR!PloPcLgAA%y%DM(vh_d6 zo(J3Gp!*qr|03~MEnkJ`t1$5^X8egCK8)TUMlTSfPY6=91w)7qGh}EaM2en4#OM%2 zjNT-2bmI^T2M$5nZb;H?Ly{&FqBNI~rKf~2tt5o$q99INe^mVruE#OpPb zwXCbFTh`Ont~Xb| zhFk@^aMi+nt76lwj@`zB>@pT*bFDCYYQ@<}E6^5FvGyq5o`l_t`1@1D_aOPColk1| zq$s}B#*Z3#O)02gJPQJhXG4JTe2@ypBLE37o+~6Q@CYFx#sh=I*fo_znH7}8nYEHc znst%H+JYo5yhoz#3?wjoVT9XbBi`0Zg&Qh0?jsQy<_S^vr+x2X@I@Ox^zuPFJZOjq zUH#A=-?`)|v7q^EhcllQJ@eVoGoKJj&wLIrJ@e7x0nMj~hcq7=rfWSVOxbuwO)*?C zP2aAMrg2t7(+pM(rgYz#)(vNBcbX~QU>@?m@}T!pDc?h+e=jQdpos4&`J9};>ESm^ zJg1EB9P%8Xe4`gl3_rVx;b%G9fVQ&@NCeL|pankLg1CIP1s(Zx1G4c6_oE6b=VJ;w z*TV@~$1@3fcU26{vkr#tS@}ZyrVI7E4OGKqpnz|E3J_>pJfR?fuNY%CDM-;*29TAwxJEA3%c|$uU^@LU@`GP`F z{{4hf0{UE53id!&67nEc7G4D_53PHZiPpKw#n~zwAFF&Ei!ySi$;pQ#Egw?z9YG%w z^&wGwq>PsY@{vzIvC9Jj0JPW_w2Xa1%g8rWLL*<%1de<~VjTO5nmGCq^>EZMIeB~wS$b@4RUfb$jY}KISl29xl!ZhFM_^e z=`*H&w_E0cr4+Dc1F;Hj`1BvV;0v1t_2xvqmG0^BqqF~XF zL_#7MiG@NX6c2++6%&6r6&Zah7ae;V7a@5J7bUM67%8oIjF#3o#>>+dGyhuTd`i)C z3W%Xw4@9f3X#BYij+PWC(=R`MVoa<~c}Vp{hOHLY`poL@NnJj^h3 ztHRNfHh8#)5cLH~e4&arl<|l@Ua`p+W_b;0-tGaQ1yl^$Kt-VqOe8WPFtO-@Kt&@o zfr>^`0vU^h1UeGY2!tF`6_gA@6{G|T6}0@p9K7t29L(fV9NfGT5_Ves2tTcLgdN5} zI6B#&hp_}tUkVIykOmU}m*NFoJmHNm6!L~qez42)vH1)W0Bzr6(DpqFt>5F&34V`6 za{L~P*7!jdRq>M~D&jXebi=PwXoVk#pb&nUK%#$}KAV4?J(LV=`LlVIK$}+? zw0V_6t5->sL7pX1n>-FgSMn~2j^t$y<;dFJk_P}R%~SV;_GOokM5Zc}IjI?f?Dbe%(5=sty%(18Z=pz9C>gKiY)1YPORX`QK0Xx(W~W*rEg z$vP2NM!ON#4R$50es(9WvrBPX9gD;0TwIkw#z=8Cw#M;zIv}5SrW%&xm<;}vpTAEiA z;Lj#_Fb}?KyzHD4jHxE8kb3e6 zDJYvpNjWr%%9&7AmVCmp;**vGow|I-6lS(1Gk+zq8R}?&jgB1Hr|E&Gn&7SGyj2NL zwSTqNWjcc7ZBp_HOibQj>B$8oO*u>wm8T?G83_`WeIRAo2GW*YQ0OuW3SJg*1m@Bs zF@GM7`O?VDk49)VELw9P5dg~#4X~Mz0%r+Luv5_nA0d_S&yt;Y+JD-I+I^cDwRFoxqC#?!%9KzC*;+KnjeKS=vXyHC?j1hr*eE1B0?=H1M8MzaQ| zIf2()<~Cm$fSU|wA`5Vj3E0L6{9*-8F$0g-flr6v4ok4+6)gD$Cq8ElbMV|CoVE#f zy-V@zNE1P3l8B6wqOi? z^PI!1!ByViCeyjd9{l4E#xV%XScF#};S<+6#3fAnojX5a%DH#6sCTA!rl7s)XpchL z4?KAP;5viwoI^N)Mfl7ltmQgK+0IJ7^N>%t$NP;H?6|8{v3Pd{r`vmN-*}t+M zWIx5>KHYxVKGx9*A7tmPb^a0l5dIJT4t@^)4E_uL3W6R1q!&Qy-E_SgwJ&q`UI^cc zi&x0M|pfDoIeEhd9YXm-UI_40|S1H0gt4Bhf%?6Iu96}p!a_)QxBh~@_|{T(U328|{2B9wUr z%Dj~_@1xA8DD$CZ9buKaA~iqA`~{9NM-(5Y8~ z=DZ?wWQD>&RFqyy)IY&`CKeuv+82@gAba1V z@N-1Ijn1DD;$^t_7C2s&ND(7T)(2uYM4fQiyom@NHc3DYZ+Z0+lF-A-+Q1JhtCk;ERw6$tSY!OivWobjwdW76C4Y2n_`_>IA79h? z0K3hHg~@t^z12f(DIQ}Z@gN&$2ioIsdm45RL&L8oz6#1m@%bl0{0SD1LdT0R@}p8( zc=@agFQ0wk)l)(YFP;cyeD!#l@x_B=h6SD)Gs1XG%ow|l(kQcD(m1m=(ny<-hT42H zE<87bZMGS0kIBHWmJGL{V!VA6Bkt=9x?k=46dT^eyRIK@!s$lEkRN=-WsRHhrs6x&f zxMI$#w}Ngt6?U(wxI0V*-W5{l&7`{b5DLFHA^0N_A4KJ!g8m28|B&K8+;~$X{~6^g zznHcS?FTlZJ!K=hgUUu^K?pV?G9uWBnux$Kv_piO5DO7-K_EnA_yJ9&^RY~|>uF5Q zyB4PIUHwM%S?xyr{We0wXQ_dAr4BA7g7BjdgK6LAS!m>2p_4y>T3+Od`JJ7=Df*kKzsce`aeU{I zzm)QiU%oJaNWqL7h9nvs-3laZD{i;|Z<8zeP({3JQAc9Wl043ixunSkDN7XFfaJ#@8THk2FRl=gru;AkV#wkxa2|MBhvtd zkW5<&BbSsEPAcsvtW0uY$bkY0LrfzNhnqy6hn+$k5I=z+5j*@OCXV(9CVJr6Q#`e1 zPzT21OfaX|#csM=MxKVFfFZ zR;N$2Nt)HgR z`e`2RpC*O<(@YBCZ&Qf@KTahuew|8A{63R__=QrK@h5te;*Yc_#GePEQ4Jr2I5mI( zsst4A;|MsS=P=L&4`3jQD_)=qt6N};$pT?qK?9AG{BUgT^YMT~o^Z);sPcnf{;$p7 zLGTzp$O|P}z0ji73pHB3(4*B0r7(hC2P6pcM3Khiks?LOE2S_b&-BQK-l-7_y%0nr z^iqdB?2!oi+%o}$$%n!xk}pM%A|DH$!M+yPy*?OLxxN{r@!eQ!Z^uh{K>ooi@_b5O zgOmr<@*2qeUI0I5!Cw%8&UA=q>r9Kb?t@XCy3->H=s+Bup%ZaLg)Y^|2px$c9dss$ zUeKW!I#HJ*2(@knkZ9fN&!(LVpGrFzJdSoUu7-CttbcbntaW!gUbFkLSKW}G=8ilh z=j7|8ydIY41M_-o-cEsc%iz;CXyHf*kr-OI5=3_3Ob}fFhk|I$n+hT$AMi7^^8bD6e zZu|&Q%khIlZHJGGH6A@D)_nAUSOdZ{x)$Wxxi;i#xmLvLxQ4`9xTfU3wI<7{MfnV^ z%2;MwP8t*QPZ^rOu;wuY@E98SH91eVFSb5QX5(keZv1Q+j-Msd;llx0j~)cZfb@hJ z3(_NHY)Frhu_8P?#*FaH7(;SpF{b27Vr5N9~0rf_Q95CyM**?*ASlVBEqv>MtDBxN^*UG zi^=s2my_$2EhyI|TT-k+wx(EdY*n$s(6VApp@qeos-qFlFXid6)vD%J|7 zvdl(`%UqZ4P&7@9j?sRfj z9{9pi1%EYVFcp*tCsFN1SGy0^eucdUYcI-LE?um<Zn;H<+)fMpmFFbg9CHer;&s74DcYUIG0MG`DoRKbTu7~E&H!E#3*Y=%_A zTS|MB)1Cyi6GiPnQah2=j&!v|MQmQ8Ht!GslMn>wI6ngn$H#!f_#kkVo&{#o)4)V} z9=Hcj1l!<|;1@g;oPx)KM_l7P5$#Jy`_j_ht z$0Us7I?K3(S$t;`pAh8``)|T``EkI`xVe$Wwcu%?N~{BLa&YFwPAeW z)7j3vZN@PyxZ8TeEqB~(%YF9TVbk4p-B{mEHQr9^opjzu=RLIEJ?r)T8WcZkyvxbG zXZV0K4CT3TOgD+^2HkGS?>;>5zxBSG@3{MB+wZgg4jb^U1IK#ssR?(w@T50v^oD^h z+`8e9_$yk17~(eu|Gi}m0~zoY2kv3PF(hp`@AY7aAQ`q3U1+H~uuC1yzZzdXK2a{j=bSNU@vhrZ$te|U7x zOP}0y#ZM<3#D0qy?-9>kVz*Dcw(4c8`0LfNesR<=RyxK;$M|O%e~lo&lV8gl&G$|a z)49ZBHZhV_ud#|v%wp18tT~J&m$BkB-mr}|jAOlZTsM#5_VI;-Ty~MaPI85v47HP! zZZgqOwjQO+8VTM?HfOa5fWqipz;1Gh5YSiXD9 zaeJBVFqds+ui0$105=`LM+Y#_2>cBE3smsaF%je4$NQ5nxXNJ0a+0@v<1edt%pf*% zhtsV2nkRR2<8VHFfCWcjzZ)2D36|S}*`71mbmlsQrygOYNw{ba_BjI#{tSK(TD-^j zA!ZR4U;swoDK~JCBUr{2jA9HvJ%c;E!J2n)Sg3;7{W`qnyu>!Fz4+OLYFHgMZF%!4JWkFj9Ude-Ruh{t*AD^*4)t(&-tq zW+Ud6jQJF2p2M2=bMt6$zG{F!D&TtpFvI1?a-_tMs`*b2J)_bWT795}D=_j^lzfdP zFWTe}qrBXd|FZH=Sl$TB^Gpe{B6SpVG3YlSy#}e@cl6Zcp?(t69V+_TqNWJ`n#jg(DZLskKyat&>lnEJNXz?{uK^i1M^8WpKe4@qwNqp&;4=NcN2QHqfb-%v#2Ml@MK_LM)qfGk0$r- zJqdijrtgdVy{d-S1boB5w+no=!1oe-D#2e8{3F08`g@?h=h^%7{=1*<&+x;*wb66dJ9`02KHlRPuBKhb+6o)kl_WGdnR=6qV7ZOp7HMY?jFP4i{U*M z-b3Mi65b!#{Se&)W%!=Bx4!*t|Ak-8`e#31HuPdgZz1Wwn%-;byRhDi>%qd_0@_=6 zdoaCs?eARp1=v1H+t0x89^5|i?E&9j?(NUszU%F)-oEMWk=))0!w<1N(AxK;{q^iG z<9RWlw?On?N8gq7TTHJN^;%ZHmGxX+-!=ALYu}aOx&B_Vw^Q~Gm_3!V$AR{!Xm6SJ zglTV=_G@WxA?>l!J}T{((w@TE7n!{f*#n7vkAXh-#U6Nq{mwN3Qu2w>angq zOY611eyi-a+FtAKvj*R~6nb>oQeU;cdiG5Po zA6a;!uNU(2*@r9Wt5(rt9eo9)w_18Dr>~NFtE#`M@D;coE9|k*9t-X(=)INUtMzrX z{(-H>!u374-bB}PcKu@4_jP?-*Q0g)SJzkQ`Y5hn;(Da5>qc5W4`m7cgH?3T)zQ}~ zq^DB)sivQTda9|fvihp5xAOX{vA!H3LYV4uZ9xCpi_TI_xmn>hU z>mVpuZ(!*1R_Wpf8z(PGln5kE!T6CZpMwjwV||nrkU(tEHr&l$2Id zV){s_=^-Vjf0Ul?QHuJyB=t*G&vf-oS>M$4P8QzD?48)&Y3`f+o+RklF%`bhz5aF^d^$gjz~ufA|ZW;lr#n;rOl9-{z7URi-OZq6rN_H5H-?~ z)I}7g4x&J{4-(ZpNmid!;gzy}iR+iXo@wlv(4NWdne2WE@RJ%Jsq-x|I)oylAt*BX zOOeq?jEb8y+=J~sm(0)lBKTk)Tvgg zIjvSZTCX0oVx4ErI?k%K+FOs!wbxx^?KPG4>nAJNN>&R~xQ1QSBX-cw*gQgGPek@b zW^a`CMs0r-_eXet1o%daUxfKWsP9?tSbs3qZN~b^SmQYB5^MeW){%EzxYvAtonf%q zwy@b@TUhL^$-cU5sm*Ts?4-{=8ttLe{<*5X&)NrFcp(fw6!%1RPt^BB8-57#gFL^7 z#Jh!eu@J@@WVBnHw&`kPzBc1* z3*NTg7|yWUZNH6n++xe^b%wK^yXv~1wp(etk9@}a^?ttp?;rdVJ!!ndy}!M`{lRae z`E4b`J>$4VEVt(wj(l#z>;BvByYG%0Z?^S5dvCG%?z(TR|E5~QP6M7a;6?`?bl}$q z4}Zpg^5^`4?0DZfZv@U;%6jW~Zxr)Adfy!On{f>f4mjU|*F8AggkN~WXBRH^;aw+= z_2N@A-ZbM#Gfs5lK0E%Am!>&>CxP#R?}TG8;auMEkPUzF;ix0tx#E&9j(FpNJH9vM zbw>{OC}xLu@8%%29y8G z-)7Hz_k8d01B-rT(u3?_7k@a!AP!x`93JuGBtG24f}^X>u$3yGbx{qJTujC8_?;YMn-%C-5tL+Dsn%$y`&p>MAo`<)g9uvz8x>0vwEZM`2?-{}_RTY-J(~ z`N%R>vWc7Q;U{}8We!sr@+&Kz_2{I&+C?ZFlv z;R@T?X%jxWgoPI2|KR_?&c8d7ykB`=vj`V3oEO;25KLqWwsD+SEa%iUxO6&y?!lOY z@Z=)gc%2Wov*0Jp_g?qz_HI`7&h&ot2?u>=>vzsb@J1+jRSVwog6EU-Wf{Db24A!R z1q%EGw9)b_@!0{1pGW1*th^PLN6PX-RhR^s!D7Vd@A+Ty ze5BDUHvNVQ4=DD2ZI=+^37GLqYW(Vr=gjehJ07mbgZX%=ACL6og><~t#tIW6{?kxQ zN1jJ4dcvjutNOjK$4mRW4Dad30r6Kv{EgHvCGn0WKJUby=MnLv z{-&ayl8Jv|&j%KLpVH?^ecje)AbU9sKbPUf{0=f-%;p2>{1KjS`T2pM9~b lk<` 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

afZf##isg@vMHYC6p=umu=V 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 MAJJ0DQab1RKOdB*>i_@% literal 0 HcmV?d00001 diff --git a/docs/assets/Logo.afdesign b/docs/assets/Logo.afdesign new file mode 100644 index 0000000000000000000000000000000000000000..5c7c246a39cb554a2f05356d990081c4f1e16739 GIT binary patch literal 12342 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 TO#J>Ekjs7eC{-h29QeNg!(k~` literal 0 HcmV?d00001 diff --git a/docs/diff.png b/docs/assets/diff.png similarity index 100% rename from docs/diff.png rename to docs/assets/diff.png diff --git a/docs/website/README.md b/docs/website/README.md new file mode 100644 index 00000000..b51abaab --- /dev/null +++ b/docs/website/README.md @@ -0,0 +1,54 @@ +# Starlight Starter Kit: Basics + +[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) + +``` +npm create astro@latest -- --template starlight +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics) +[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs) + +> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! + +## 🚀 Project Structure + +Inside of your Astro + Starlight project, you'll see the following folders and files: + +``` +. +├── public/ +├── src/ +│ ├── assets/ +│ ├── content/ +│ │ ├── docs/ +│ │ └── config.ts +│ └── env.d.ts +├── astro.config.mjs +├── package.json +└── tsconfig.json +``` + +Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name. + +Images can be added to `src/assets/` and embedded in Markdown with a relative link. + +Static assets, like favicons, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat). diff --git a/docs/website/astro.config.mjs b/docs/website/astro.config.mjs new file mode 100644 index 00000000..b4e89961 --- /dev/null +++ b/docs/website/astro.config.mjs @@ -0,0 +1,86 @@ +import {defineConfig} from 'astro/config'; +import starlight from '@astrojs/starlight'; + +// https://astro.build/config +export default defineConfig({ + site: 'https://alexliesenfeld.github.io/httpmock', + integrations: [ + starlight({ + title: 'httpmock Tutorial', + logo: { + light: './src/assets/logo-light.svg', + dark: './src/assets/logo-dark.svg', + replacesTitle: true, + }, + social: { + github: 'https://github.com/withastro/starlight', + discord: 'https://discord.gg/QrjhRh7A' + }, + sidebar: [ + { + label: 'Getting Started', + items: [ + // Each item here is one entry in the navigation menu. + {label: 'Quick Introduction', link: '/getting_started/quick_introduction/'}, + {label: 'Fundamentals', link: '/getting_started/fundamentals/'}, + {label: 'Resources', link: '/getting_started/resources/'}, + ], + }, + { + label: 'Mocking', + items: [ + // Each item here is one entry in the navigation menu. + { + label: 'Matching Requests', + items: [ + // Each item here is one entry in the navigation menu. + {label: 'Path', link: '/matching_requests/path/'}, + {label: 'Method', link: '/matching_requests/method/'}, + {label: 'Query Parameters', link: '/matching_requests/query/'}, + {label: 'Headers', link: '/matching_requests/headers/'}, + {label: 'Body', link: '/matching_requests/body/'}, + {label: 'Cookie', link: '/matching_requests/cookies/'}, + {label: 'Host', link: '/matching_requests/host/'}, + {label: 'Port', link: '/matching_requests/port/'}, + {label: 'Scheme', link: '/matching_requests/scheme/'}, + {label: 'Custom Matchers', link: '/matching_requests/custom/'}, + ], + + }, + { + label: 'Mocking Responses', items: [ + // Each item here is one entry in the navigation menu. + {label: 'Response Values', link: '/mocking_responses/all/'}, + {label: 'Network Delay', link: '/mocking_responses/delay/'}, + ], + }, + ], + }, + { + label: 'Record and Playback', + badge: 'New', + items: [ + {label: 'Recording', link: '/record-and-playback/recording/'}, + {label: 'Playback', link: '/record-and-playback/playback/'}, + ], + }, + { + label: 'Server', + items: [ + {label: 'Standalone Server', link: '/server/standalone/'}, + {label: 'HTTPS', link: '/server/https/', badge: 'New'}, + {label: 'Debugging', link: '/server/debugging/'}, + ], + }, + { + label: 'Miscellaneous', + items: [ + {label: 'FAQ', link: '/miscellaneous/faq/'}, + {label: 'License', link: 'https://github.com/alexliesenfeld/httpmock/blob/master/LICENSE'}, + ], + }, + ], + customCss: ['./src/assets/landing.css'], + }), + ], +}); diff --git a/docs/website/generated/code_examples.json b/docs/website/generated/code_examples.json new file mode 100644 index 00000000..86534c4a --- /dev/null +++ b/docs/website/generated/code_examples.json @@ -0,0 +1,104 @@ +{ + "then": { + "and": "```rust\n use std::time::Duration;\n use http::{StatusCode, header::HeaderValue};\n use httpmock::{Then, MockServer};\n\n // Function that configures a response with JSON content and a delay\n fn ok_json_with_delay(then: Then) -> Then {\n then.status(StatusCode::OK.as_u16())\n .header(\"content-type\", \"application/json\")\n .delay(Duration::from_secs_f32(0.5))\n }\n\n // Usage within a method chain\n let server = MockServer::start();\n let then = server.mock(|when, then| {\n when.path(\"/example\");\n then.header(\"general-vibe\", \"much better\")\n .and(ok_json_with_delay);\n });\n\n // The `and` method keeps the setup intuitively readable as a continuous chain\n```\n", + "body": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Initialize the mock server\n let server = MockServer::start();\n\n // Configure the mock\n let m = server.mock(|when, then| {\n when.path(\"/hello\");\n then.status(200)\n .body(\"ohi!\");\n });\n\n // Send a request and verify the response\n let response = Client::new()\n .get(server.url(\"/hello\"))\n .send()\n .unwrap();\n\n // Check that the mock was called as expected and the response body is as configured\n m.assert();\n assert_eq!(response.status(), 200);\n assert_eq!(response.text().unwrap(), \"ohi!\");\n```\n", + "body_from_file": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Initialize the mock server\n let server = MockServer::start();\n\n // Configure the mock\n let m = server.mock(|when, then| {\n when.path(\"/hello\");\n then.status(200)\n .body_from_file(\"tests/resources/simple_body.txt\");\n });\n\n // Send a request and verify the response\n let response = Client::new()\n .get(server.url(\"/hello\"))\n .send()\n .unwrap();\n\n // Check that the mock was called as expected and the response body matches the file contents\n m.assert();\n assert_eq!(response.status(), 200);\n assert_eq!(response.text().unwrap(), \"ohi!\");\n```\n", + "delay": "```rust\n use std::time::{SystemTime, Duration};\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let _ = env_logger::try_init();\n let start_time = SystemTime::now();\n let three_seconds = Duration::from_secs(3);\n let server = MockServer::start();\n\n // Configure the mock\n let mock = server.mock(|when, then| {\n when.path(\"/delay\");\n then.status(200)\n .delay(three_seconds);\n });\n\n // Act\n let response = Client::new()\n .get(server.url(\"/delay\"))\n .send()\n .unwrap();\n\n // Assert\n mock.assert();\n assert!(start_time.elapsed().unwrap() >= three_seconds);\n```\n", + "header": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let _ = env_logger::try_init();\n let server = MockServer::start();\n\n // Configure the mock\n let m = server.mock(|when, then| {\n when.path(\"/\");\n then.status(200)\n .header(\"Expires\", \"Wed, 21 Oct 2050 07:28:00 GMT\");\n });\n\n // Act\n let response = Client::new()\n .get(server.url(\"/\"))\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 200);\n assert_eq!(\n response.headers().get(\"Expires\").unwrap().to_str().unwrap(),\n \"Wed, 21 Oct 2050 07:28:00 GMT\"\n );\n```\n", + "json_body": "```rust\n use httpmock::prelude::*;\n use serde_json::{Value, json};\n use reqwest::blocking::Client;\n\n // Arrange\n let _ = env_logger::try_init();\n let server = MockServer::start();\n\n // Configure the mock\n let m = server.mock(|when, then| {\n when.path(\"/user\");\n then.status(200)\n .header(\"content-type\", \"application/json\")\n .json_body(json!({ \"name\": \"Hans\" }));\n });\n\n // Act\n let response = Client::new()\n .get(server.url(\"/user\"))\n .send()\n .unwrap();\n\n // Get the status code first\n let status = response.status();\n\n // Extract the text from the response\n let response_text = response.text().unwrap();\n\n // Deserialize the JSON response\n let user: Value =\n serde_json::from_str(&response_text).expect(\"cannot deserialize JSON\");\n\n // Assert\n m.assert();\n assert_eq!(status, 200);\n assert_eq!(user[\"name\"], \"Hans\");\n```\n", + "json_body_obj": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n use serde::{Serialize, Deserialize};\n\n #[derive(Serialize, Deserialize)]\n struct TestUser {\n name: String,\n }\n\n // Arrange\n let _ = env_logger::try_init();\n let server = MockServer::start();\n\n // Configure the mock\n let m = server.mock(|when, then| {\n when.path(\"/user\");\n then.status(200)\n .header(\"content-type\", \"application/json\")\n .json_body_obj(&TestUser {\n name: String::from(\"Hans\"),\n });\n });\n\n // Act\n let response = Client::new()\n .get(server.url(\"/user\"))\n .send()\n .unwrap();\n\n // Get the status code first\n let status = response.status();\n\n // Extract the text from the response\n let response_text = response.text().unwrap();\n\n // Deserialize the JSON response into a TestUser object\n let user: TestUser =\n serde_json::from_str(&response_text).unwrap();\n\n // Assert\n m.assert();\n assert_eq!(status, 200);\n assert_eq!(user.name, \"Hans\");\n```\n", + "status": "```rust\n use httpmock::prelude::*;\n\n // Initialize the mock server\n let server = MockServer::start();\n\n // Configure the mock\n let m = server.mock(|when, then| {\n when.path(\"/hello\");\n then.status(200);\n });\n\n // Send a request and verify the response\n let response = reqwest::blocking::get(server.url(\"/hello\")).unwrap();\n\n // Check that the mock was called as expected and the response status is as configured\n m.assert();\n assert_eq!(response.status(), 200);\n```\n" + }, + "when": { + "and": "```rust\n use httpmock::{prelude::*, When};\n use httpmock::Method::POST;\n\n // Function to apply a standard authorization and content type setup for JSON POST requests\n fn is_authorized_json_post_request(when: When) -> When {\n when.method(POST)\n .header(\"Authorization\", \"SOME API KEY\")\n .header(\"Content-Type\", \"application/json\")\n }\n\n // Usage example demonstrating how to maintain fluent interface style with complex setups.\n // This approach keeps the chain of conditions clear and readable, enhancing test legibility\n let server = MockServer::start();\n let m = server.mock(|when, then| {\n when.query_param(\"user_id\", \"12345\")\n .and(is_authorized_json_post_request) // apply the function to include common setup\n .json_body_includes(r#\"{\"key\": \"value\"}\"#); // additional specific condition\n then.status(200);\n });\n```\n", + "any_request": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Configure the mock server to respond to any request\n let mock = server.mock(|when, then| {\n when.any_request(); // Explicitly specify that any request should match\n then.status(200); // Respond with status code 200 for all matched requests\n });\n\n // Make a request to the server's URL and ensure the mock is triggered\n let response = reqwest::blocking::get(server.url(\"/anyPath\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Assert that the mock was called at least once\n mock.assert();\n```\n", + "body": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to be \"The Great Gatsby\"\n let mock = server.mock(|when, then| {\n when.body(\"The Great Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with the required body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_excludes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to not contain the substring \"Gatsby\"\n let mock = server.mock(|when, then| {\n when.body_excludes(\"Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with a different body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"A Tale of Two Cities is a novel.\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_includes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to contain the substring \"Gatsby\"\n let mock = server.mock(|when, then| {\n when.body_includes(\"Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with the required substring in the body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel.\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_matches": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to match the regex pattern \"^The Great Gatsby.*\"\n let mock = server.mock(|when, then| {\n when.body_matches(\"^The Great Gatsby.*\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with a body that matches the regex pattern\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel by F. Scott Fitzgerald.\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to not be \"The Great Gatsby\"\n let mock = server.mock(|when, then| {\n when.body_not(\"The Great Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with a different body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"A Tale of Two Cities\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_prefix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to begin with the substring \"The Great\"\n let mock = server.mock(|when, then| {\n when.body_prefix(\"The Great\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with the required prefix in the body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel.\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_prefix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to not begin with the substring \"Error:\"\n let mock = server.mock(|when, then| {\n when.body_prefix_not(\"Error:\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with a different body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"Success: Operation completed.\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_suffix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to end with the substring \"a novel.\"\n let mock = server.mock(|when, then| {\n when.body_suffix(\"a novel.\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with the required suffix in the body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel.\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "body_suffix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to not end with the substring \"a novel.\"\n let mock = server.mock(|when, then| {\n when.body_suffix_not(\"a novel.\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with a different body content\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a story.\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" with the value \"1234567890\"\n let mock = server.mock(|when, then| {\n when.cookie(\"SESSIONID\", \"1234567890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234567890; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_count": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie with a name matching the regex \"^SESSION\"\n // and a value matching the regex \"^[0-9]{10}$\" to appear exactly twice\n let mock = server.mock(|when, then| {\n when.cookie_count(r\"^SESSION\", r\"^[0-9]{10}$\", 2);\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookies\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"SESSIONID=1234567890; TRACK=12345; SESSIONTOKEN=0987654321; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_excludes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" with a value not containing \"1234\"\n let mock = server.mock(|when, then| {\n when.cookie_excludes(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcdef; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_exists": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\"\n let mock = server.mock(|when, then| {\n when.cookie_exists(\"SESSIONID\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234567890; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_includes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" with a value containing \"1234\"\n let mock = server.mock(|when, then| {\n when.cookie_includes(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abc1234def; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_matches": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie with a name matching the regex \"^SESSION\"\n // and a value matching the regex \"^[0-9]{10}$\"\n let mock = server.mock(|when, then| {\n when.cookie_matches(r\"^SESSION\", r\"^[0-9]{10}$\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234567890; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_missing": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" not to exist\n let mock = server.mock(|when, then| {\n when.cookie_missing(\"SESSIONID\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that does not include the excluded cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" to not have the value \"1234567890\"\n let mock = server.mock(|when, then| {\n when.cookie_not(\"SESSIONID\", \"1234567890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=0987654321; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_prefix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" with a value starting with \"1234\"\n let mock = server.mock(|when, then| {\n when.cookie_prefix(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234abcdef; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_prefix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" with a value not starting with \"1234\"\n let mock = server.mock(|when, then| {\n when.cookie_prefix_not(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcd1234; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_suffix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" with a value ending with \"7890\"\n let mock = server.mock(|when, then| {\n when.cookie_suffix(\"SESSIONID\", \"7890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcdef7890; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "cookie_suffix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects a cookie named \"SESSIONID\" with a value not ending with \"7890\"\n let mock = server.mock(|when, then| {\n when.cookie_suffix_not(\"SESSIONID\", \"7890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcdef1234; CONSENT=1\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "form_urlencoded_tuple": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple(\"name\", \"Peter Griffin\")\n .form_urlencoded_tuple(\"town\", \"Quahog\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_count": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n use regex::Regex;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_count(\n Regex::new(r\"^name$\").unwrap(),\n Regex::new(r\".*Griffin$\").unwrap(),\n 2\n );\n then.status(202);\n });\n\n // Act\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&name=Lois%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_excludes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_excludes(\"name\", \"Griffin\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Lois%20Smith&city=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_exists": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_exists(\"name\")\n .form_urlencoded_tuple_exists(\"town\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_includes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_includes(\"name\", \"Griffin\")\n .form_urlencoded_tuple_includes(\"town\", \"Quahog\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_matches": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n use regex::Regex;\n\n // Arrange\n let server = MockServer::start();\n\n let key_regex = Regex::new(r\"^name$\").unwrap();\n let value_regex = Regex::new(r\"^Peter\\sGriffin$\").unwrap();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_matches(key_regex, value_regex);\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_missing": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_missing(\"name\")\n .form_urlencoded_tuple_missing(\"town\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"city=Quahog&occupation=Cartoonist\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_not(\"name\", \"Peter Griffin\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Lois%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_prefix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_prefix(\"name\", \"Pete\")\n .form_urlencoded_tuple_prefix(\"town\", \"Qua\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_prefix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_prefix_not(\"name\", \"Lois\")\n .form_urlencoded_tuple_prefix_not(\"town\", \"Hog\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_suffix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_suffix(\"name\", \"Griffin\")\n .form_urlencoded_tuple_suffix(\"town\", \"hog\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "form_urlencoded_tuple_suffix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_suffix_not(\"name\", \"Smith\")\n .form_urlencoded_tuple_suffix_not(\"town\", \"ville\");\n then.status(202);\n });\n\n let response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 202);\n```\n", + "header": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header with a specific value\n let mock = server.mock(|when, then| {\n when.header(\"Authorization\", \"token 1234567890\");\n then.status(200); // Respond with a 200 status code if the header and value are present\n });\n\n // Make a request that includes the \"Authorization\" header with the specified value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_count": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects at least 2 headers whose keys match the regex \"^X-Custom-Header.*\"\n // and values match the regex \"value.*\"\n let mock = server.mock(|when, then| {\n when.header_count(\"^X-Custom-Header.*\", \"value.*\", 2);\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request that includes the required headers\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"x-custom-header-1\", \"value1\")\n .header(\"X-Custom-Header-2\", \"value2\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_excludes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header's value to not contain \"Bearer\"\n let mock = server.mock(|when, then| {\n when.header_excludes(\"Authorization\", \"Bearer\");\n then.status(200); // Respond with a 200 status code if the header value does not contain the substring\n });\n\n // Make a request that includes the \"Authorization\" header without the forbidden substring in its value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_exists": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header to be present in the request\n let mock = server.mock(|when, then| {\n when.header_exists(\"Authorization\");\n then.status(200); // Respond with a 200 status code if the header is present\n });\n\n // Make a request that includes the \"Authorization\" header\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_includes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header's value to contain \"token\"\n let mock = server.mock(|when, then| {\n when.header_includes(\"Authorization\", \"token\");\n then.status(200); // Respond with a 200 status code if the header value contains the substring\n });\n\n // Make a request that includes the \"Authorization\" header with the specified substring in its value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_matches": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header's key to match the regex \"^Auth.*\"\n // and its value to match the regex \"token .*\"\n let mock = server.mock(|when, then| {\n when.header_matches(\"^Auth.*\", \"token .*\");\n then.status(200); // Respond with a 200 status code if the header key and value match the patterns\n });\n\n // Make a request that includes the \"Authorization\" header with a value matching the regex\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_missing": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header to be absent in the request\n let mock = server.mock(|when, then| {\n when.header_missing(\"Authorization\");\n then.status(200); // Respond with a 200 status code if the header is absent\n });\n\n // Make a request that does not include the \"Authorization\" header\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header with a specific value to be absent\n let mock = server.mock(|when, then| {\n when.header_not(\"Authorization\", \"token 1234567890\");\n then.status(200); // Respond with a 200 status code if the header and value are absent\n });\n\n // Make a request that includes the \"Authorization\" header with a different value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token abcdefg\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_prefix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header's value to start with \"token\"\n let mock = server.mock(|when, then| {\n when.header_prefix(\"Authorization\", \"token\");\n then.status(200); // Respond with a 200 status code if the header value starts with the prefix\n });\n\n // Make a request that includes the \"Authorization\" header with the specified prefix in its value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_prefix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header's value to not start with \"Bearer\"\n let mock = server.mock(|when, then| {\n when.header_prefix_not(\"Authorization\", \"Bearer\");\n then.status(200); // Respond with a 200 status code if the header value does not start with the prefix\n });\n\n // Make a request that includes the \"Authorization\" header without the \"Bearer\" prefix in its value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_suffix": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header's value to end with \"7890\"\n let mock = server.mock(|when, then| {\n when.header_suffix(\"Authorization\", \"7890\");\n then.status(200); // Respond with a 200 status code if the header value ends with the suffix\n });\n\n // Make a request that includes the \"Authorization\" header with the specified suffix in its value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "header_suffix_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the \"Authorization\" header's value to not end with \"abc\"\n let mock = server.mock(|when, then| {\n when.header_suffix_not(\"Authorization\", \"abc\");\n then.status(200); // Respond with a 200 status code if the header value does not end with the suffix\n });\n\n // Make a request that includes the \"Authorization\" header without the \"abc\" suffix in its value\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "host": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n let server = MockServer::start();\n\n server.mock(|when, then| {\n when.host(\"github.com\");\n then.body(\"This is a mock response\");\n });\n\n let client = Client::builder()\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\n let response = client.get(\"http://github.com\").send().unwrap();\n\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n", + "host_excludes": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that excludes any request where the host name contains \"www.google.com\"\n let mock = server.mock(|when, then| {\n when.host_excludes(\"www.google.com\"); // Exclude hosts containing \"www.google.com\"\n then.status(200); // Respond with status code 200 for other matched requests\n });\n\n // Make a request to a URL whose host name will be \"localhost\" and trigger the mock\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "host_includes": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any request where the host name contains \"localhost\"\n let mock = server.mock(|when, then| {\n when.host_includes(\"0.0\"); // Only match hosts containing \"0.0\" (e.g., 127.0.0.1)\n then.status(200); // Respond with status code 200 for all matched requests\n });\n\n // Make a request to a URL whose host name is \"localhost\" to trigger the mock\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "host_matches": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches requests where the host name is exactly \"localhost\"\n let mock = server.mock(|when, then| {\n when.host_matches(r\"^127.0.0.1$\");\n then.status(200);\n });\n\n // Make a request with \"127.0.0.1\" as the host name to trigger the mock response.\n let response = reqwest::blocking::get(server.url(\"/\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "host_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n let server = MockServer::start();\n\n server.mock(|when, then| {\n when.host(\"github.com\");\n then.body(\"This is a mock response\");\n });\n\n let client = Client::builder()\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\n let response = client.get(\"http://github.com\").send().unwrap();\n\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n", + "host_prefix": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any request where the host name starts with \"local\"\n let mock = server.mock(|when, then| {\n when.host_prefix(\"127.0\"); // Only match hosts starting with \"127.0\"\n then.status(200); // Respond with status code 200 for all matched requests\n });\n\n // Make a request to the mock server with a host name of \"127.0.0.1\" to trigger the mock response.\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "host_prefix_not": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any request where the host name does not start with \"www.\"\n let mock = server.mock(|when, then| {\n when.host_prefix_not(\"www.\"); // Exclude hosts starting with \"www\"\n then.status(200); // Respond with status code 200 for all other requests\n });\n\n // Make a request with host name \"localhost\" that does not start with \"www\" and therefore\n // triggers the mock response.\n let response = reqwest::blocking::get(server.url(\"/example\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "host_suffix": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any request where the host name ends with \"host\" (e.g., \"localhost\").\n let mock = server.mock(|when, then| {\n when.host_suffix(\"0.1\"); // Only match hosts ending with \"0.1\"\n then.status(200); // Respond with status code 200 for all matched requests\n });\n\n // Make a request to the mock server with a host name of \"127.0.0.1\" to trigger the mock response.\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "host_suffix_not": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any request where the host name does not end with \"host\".\n let mock = server.mock(|when, then| {\n when.host_suffix_not(\"host\"); // Exclude hosts ending with \"host\"\n then.status(200); // Respond with status code 200 for all other requests\n });\n\n // Make a request with a host name that does not end with \"host\" to trigger the mock response.\n let response = reqwest::blocking::get(server.url(\"/example\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "is_false": "```rust\n use httpmock::prelude::*;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.is_false(|req: &HttpMockRequest| {\n req.uri().path().contains(\"es\")\n });\n then.status(404);\n });\n\n // Act: Send the HTTP request\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 404);\n```\n", + "is_true": "```rust\n use httpmock::prelude::*;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.is_true(|req: &HttpMockRequest| {\n req.uri().path().contains(\"es\")\n });\n then.status(200);\n });\n\n // Act: Send the HTTP request\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 200);\n```\n", + "json_body": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n use serde_json::json;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the request body to match a specific JSON structure\n let mock = server.mock(|when, then| {\n when.json_body(json!({\n \"title\": \"The Great Gatsby\",\n \"author\": \"F. Scott Fitzgerald\"\n }));\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Make a request with a JSON body that matches the expected structure\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Content-Type\", \"application/json\") // It's important to set the Content-Type header manually\n .body(r#\"{\"title\":\"The Great Gatsby\",\"author\":\"F. Scott Fitzgerald\"}\"#)\n .send()\n .unwrap();\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "json_body_excludes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n let server = MockServer::start();\n\n let mock = server.mock(|when, then| {\n when.json_body_excludes(r#\"\n {\n \"child\": {\n \"target_attribute\": \"Example\"\n }\n }\n \"#);\n then.status(200);\n });\n\n // Send a POST request with a JSON body\n let response = Client::new()\n .post(&format!(\"http://{}/some/path\", server.address()))\n .header(\"content-type\", \"application/json\")\n .body(r#\"\n {\n \"parent_attribute\": \"Some parent data goes here\",\n \"child\": {\n \"other_attribute\": \"Another value\"\n }\n }\n \"#)\n .send()\n .unwrap();\n\n // Assert the mock was called and the response status is as expected\n mock.assert();\n assert_eq!(response.status(), 200);\n```\n", + "json_body_includes": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n let server = MockServer::start();\n\n let mock = server.mock(|when, then| {\n when.json_body_includes(r#\"\n {\n \"child\": {\n \"target_attribute\": \"Example\"\n }\n }\n \"#);\n then.status(200);\n });\n\n // Send a POST request with a JSON body\n let response = Client::new()\n .post(&format!(\"http://{}/some/path\", server.address()))\n .header(\"content-type\", \"application/json\")\n .body(r#\"\n {\n \"parent_attribute\": \"Some parent data goes here\",\n \"child\": {\n \"target_attribute\": \"Example\",\n \"other_attribute\": \"Another value\"\n }\n }\n \"#)\n .send()\n .unwrap();\n\n // Assert the mock was called and the response status is as expected\n mock.assert();\n assert_eq!(response.status(), 200);\n```\n", + "json_body_obj": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n use serde_json::json;\n use serde::{Serialize, Deserialize};\n\n #[derive(Serialize, Deserialize)]\n struct TestUser {\n name: String,\n }\n\n // Initialize logging (optional, for debugging purposes)\n let _ = env_logger::try_init();\n\n // Start the mock server\n let server = MockServer::start();\n\n // Set up a mock endpoint\n let m = server.mock(|when, then| {\n when.path(\"/user\")\n .header(\"content-type\", \"application/json\")\n .json_body_obj(&TestUser { name: String::from(\"Fred\") });\n then.status(200);\n });\n\n // Send a POST request with a JSON body\n let response = Client::new()\n .post(&format!(\"http://{}/user\", server.address()))\n .header(\"content-type\", \"application/json\")\n .body(json!(&TestUser { name: \"Fred\".to_string() }).to_string())\n .send()\n .unwrap();\n\n // Assert the mock was called and the response status is as expected\n m.assert();\n assert_eq!(response.status(), 200);\n```\n", + "matches": "```rust\n use httpmock::prelude::*;\n\n // Arrange\n let server = MockServer::start();\n\n let m = server.mock(|when, then| {\n when.matches(|req: &HttpMockRequest| {\n req.uri().path().contains(\"es\")\n });\n then.status(200);\n });\n\n // Act: Send the HTTP request\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Assert\n m.assert();\n assert_eq!(response.status(), 200);\n```\n", + "method": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches only `GET` requests\n let mock = server.mock(|when, then| {\n when.method(GET); // Match only `GET` HTTP method\n then.status(200); // Respond with status code 200 for all matched requests\n });\n\n // Make a GET request to the server's URL to trigger the mock\n let response = reqwest::blocking::get(server.url(\"/\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "method_not": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any request except those using the `POST` method\n let mock = server.mock(|when, then| {\n when.method_not(POST); // Exclude the `POST` HTTP method from matching\n then.status(200); // Respond with status code 200 for all other matched requests\n });\n\n // Make a GET request to the server's URL, which will trigger the mock\n let response = reqwest::blocking::get(server.url(\"/\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "path": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches requests to `/test`\n let mock = server.mock(|when, then| {\n when.path(\"/test\");\n then.status(200); // Respond with a 200 status code\n });\n\n // Make a request to the mock server using the specified path\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "path_excludes": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any path not containing the substring \"xyz\"\n let mock = server.mock(|when, then| {\n when.path_excludes(\"xyz\");\n then.status(200); // Respond with status 200 for paths excluding \"xyz\"\n });\n\n // Make a request to a path that does not contain \"xyz\"\n let response = reqwest::blocking::get(server.url(\"/testpath\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure the mock server returned the expected response\n mock.assert();\n```\n", + "path_includes": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any path containing the substring \"es\"\n let mock = server.mock(|when, then| {\n when.path_includes(\"es\");\n then.status(200); // Respond with a 200 status code for matched requests\n });\n\n // Make a request to a path containing \"es\" to trigger the mock response\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n", + "path_matches": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches paths ending with the suffix \"le\"\n let mock = server.mock(|when, then| {\n when.path_matches(r\"le$\");\n then.status(200); // Respond with a 200 status code for paths matching the pattern\n });\n\n // Make a request to a path ending with \"le\"\n let response = reqwest::blocking::get(server.url(\"/example\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock server returned the expected response\n mock.assert();\n```\n", + "path_not": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that will not match requests to `/exclude`\n let mock = server.mock(|when, then| {\n when.path_not(\"/exclude\");\n then.status(200); // Respond with status 200 for all other paths\n });\n\n // Make a request to a path that does not match the exclusion\n let response = reqwest::blocking::get(server.url(\"/include\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "path_prefix": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any path starting with the prefix \"/api\"\n let mock = server.mock(|when, then| {\n when.path_prefix(\"/api\");\n then.status(200); // Respond with a 200 status code for matched requests\n });\n\n // Make a request to a path starting with \"/api\"\n let response = reqwest::blocking::get(server.url(\"/api/v1/resource\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "path_prefix_not": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any path not starting with the prefix \"/admin\"\n let mock = server.mock(|when, then| {\n when.path_prefix_not(\"/admin\");\n then.status(200); // Respond with status 200 for paths excluding \"/admin\"\n });\n\n // Make a request to a path that does not start with \"/admin\"\n let response = reqwest::blocking::get(server.url(\"/public/home\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock server returned the expected response\n mock.assert();\n```\n", + "path_suffix": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any path ending with the suffix \".html\"\n let mock = server.mock(|when, then| {\n when.path_suffix(\".html\");\n then.status(200); // Respond with a 200 status code for matched requests\n });\n\n // Make a request to a path ending with \".html\"\n let response = reqwest::blocking::get(server.url(\"/about/index.html\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "path_suffix_not": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that matches any path not ending with the suffix \".json\"\n let mock = server.mock(|when, then| {\n when.path_suffix_not(\".json\");\n then.status(200); // Respond with a 200 status code for paths excluding \".json\"\n });\n\n // Make a request to a path that does not end with \".json\"\n let response = reqwest::blocking::get(server.url(\"/about/index.html\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "port": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Configure a mock to respond to requests made to `github.com`\n // with a specific port\n server.mock(|when, then| {\n when.port(80); // Specify the expected port\n then.body(\"This is a mock response\");\n });\n\n // Set up an HTTP client to use the mock server as a proxy\n let client = Client::builder()\n // Proxy all requests to the mock server\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\n // Send a GET request to `github.com` on port 80.\n // The request will be sent to our mock server due to the HTTP client proxy settings.\n let response = client.get(\"http://github.com:80\").send().unwrap();\n\n // Validate that the mock server returned the expected response\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n", + "port_not": "```rust\n use httpmock::prelude::*;\n use reqwest::blocking::Client;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Configure a mock to respond to requests not using port 81\n server.mock(|when, then| {\n when.port_not(81); // Exclude requests on port 81\n then.body(\"This is a mock response\");\n });\n\n // Set up an HTTP client to use the mock server as a proxy\n let client = Client::builder()\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\n // Make a request to `github.com` on port 80, which will trigger\n // the mock response\n let response = client.get(\"http://github.com:80\").send().unwrap();\n\n // Validate that the mock server returned the expected response\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n", + "query_param": "```rust\n // Arrange\n use reqwest::blocking::get;\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query` to have the value \"This is cool\"\n let m = server.mock(|when, then| {\n when.query_param(\"query\", \"This is cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that includes the specified query parameter and value\n get(&server.url(\"/search?query=This+is+cool\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_count": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects exactly two query parameters with keys matching the regex \"user.*\"\n // and values matching the regex \"admin.*\"\n let m = server.mock(|when, then| {\n when.query_param_count(r\"user.*\", r\"admin.*\", 2);\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that matches the conditions\n reqwest::blocking::get(&server.url(\"/search?user1=admin1&user2=admin2\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_excludes": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query`\n // to have a value that does not contain \"uncool\"\n let m = server.mock(|when, then| {\n when.query_param_excludes(\"query\", \"uncool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that includes a value not containing the substring \"uncool\"\n reqwest::blocking::get(&server.url(\"/search?query=Something+cool\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_exists": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query` to exist, regardless of its value\n let m = server.mock(|when, then| {\n when.query_param_exists(\"query\");\n then.status(200); // Respond with a 200 status code if the parameter exists\n });\n\n // Act: Make a request with the specified query parameter\n reqwest::blocking::get(&server.url(\"/search?query=restaurants+near+me\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_includes": "```rust\n // Arrange\n use reqwest::blocking::get;\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query`\n // to have a value containing \"cool\"\n let m = server.mock(|when, then| {\n when.query_param_includes(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that includes a value containing the substring \"cool\"\n get(server.url(\"/search?query=Something+cool\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_matches": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter key to match the regex \"user.*\"\n // and the value to match the regex \"admin.*\"\n let m = server.mock(|when, then| {\n when.query_param_matches(r\"user.*\", r\"admin.*\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that matches the regex patterns for both key and value\n reqwest::blocking::get(&server.url(\"/search?user=admin_user\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_missing": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query` to be missing\n let m = server.mock(|when, then| {\n when.query_param_missing(\"query\");\n then.status(200); // Respond with a 200 status code if the parameter is absent\n });\n\n // Act: Make a request without the specified query parameter\n reqwest::blocking::get(&server.url(\"/search\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_not": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query` to NOT have the value \"This is cool\"\n let m = server.mock(|when, then| {\n when.query_param_not(\"query\", \"This is cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that does not include the specified query parameter and value\n let response = reqwest::blocking::get(&server.url(\"/search?query=awesome\")).unwrap();\n\n // Assert: Verify that the mock was called\n assert_eq!(response.status(), 200);\n m.assert();\n```\n", + "query_param_prefix": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query`\n // to have a value starting with \"cool\"\n let m = server.mock(|when, then| {\n when.query_param_prefix(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that includes a value starting with the prefix \"cool\"\n reqwest::blocking::get(&server.url(\"/search?query=cool+stuff\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_prefix_not": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query`\n // to have a value not starting with \"cool\"\n let m = server.mock(|when, then| {\n when.query_param_prefix_not(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that does not start with the prefix \"cool\"\n reqwest::blocking::get(&server.url(\"/search?query=warm_stuff\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_suffix": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query`\n // to have a value ending with \"cool\"\n let m = server.mock(|when, then| {\n when.query_param_suffix(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that includes a value ending with the suffix \"cool\"\n reqwest::blocking::get(&server.url(\"/search?query=really_cool\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "query_param_suffix_not": "```rust\n // Arrange\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that expects the query parameter `query`\n // to have a value not ending with \"cool\"\n let m = server.mock(|when, then| {\n when.query_param_suffix_not(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n });\n\n // Act: Make a request that doesn't end with the suffix \"cool\"\n reqwest::blocking::get(&server.url(\"/search?query=uncool_stuff\")).unwrap();\n\n // Assert: Verify that the mock was called at least once\n m.assert();\n```\n", + "scheme": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that only matches requests with the \"http\" scheme\n let mock = server.mock(|when, then| {\n when.scheme(\"http\"); // Restrict to the \"http\" scheme\n then.status(200); // Respond with status code 200 for all matched requests\n });\n\n // Make an \"http\" request to the server's URL to trigger the mock\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Verify that the mock was called at least once\n mock.assert();\n```\n", + "scheme_not": "```rust\n use httpmock::prelude::*;\n\n // Start a new mock server\n let server = MockServer::start();\n\n // Create a mock that will only match requests that do not use the \"https\" scheme\n let mock = server.mock(|when, then| {\n when.scheme_not(\"https\"); // Exclude the \"https\" scheme from matching\n then.status(200); // Respond with status code 200 for all matched requests\n });\n\n // Make a request to the server's URL with the \"http\" scheme to trigger the mock\n let response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n // Ensure the request was successful\n assert_eq!(response.status(), 200);\n\n // Ensure that the mock was called at least once\n mock.assert();\n```\n" + } +} \ No newline at end of file diff --git a/docs/website/generated/docs.json b/docs/website/generated/docs.json new file mode 100644 index 00000000..4b390cde --- /dev/null +++ b/docs/website/generated/docs.json @@ -0,0 +1,104 @@ +{ + "then": { + "and": "Applies a custom function to modify a `Then` instance, enhancing flexibility and readability\nin setting up mock server responses.\n\nThis method allows you to encapsulate complex configurations into reusable functions,\nand apply them without breaking the chain of method calls on a `Then` object.\n\n# Parameters\n- `func`: A function that takes a `Then` instance and returns it after applying some modifications.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Then` object.\n\n# Example\nDemonstrates how to use the `and` method to maintain readability while applying multiple\nmodifications from an external function.\n\n```rust\nuse std::time::Duration;\nuse http::{StatusCode, header::HeaderValue};\nuse httpmock::{Then, MockServer};\n\n// Function that configures a response with JSON content and a delay\nfn ok_json_with_delay(then: Then) -> Then {\n then.status(StatusCode::OK.as_u16())\n .header(\"content-type\", \"application/json\")\n .delay(Duration::from_secs_f32(0.5))\n}\n\n// Usage within a method chain\nlet server = MockServer::start();\nlet then = server.mock(|when, then| {\n when.path(\"/example\");\n then.header(\"general-vibe\", \"much better\")\n .and(ok_json_with_delay);\n});\n\n// The `and` method keeps the setup intuitively readable as a continuous chain\n```\n", + "body": "Configures the HTTP response body that the mock server will return.\n\n# Parameters\n- `body`: The content of the response body, provided as a type that can be referenced as a byte slice.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Mock` object.\n\n# Example\nDemonstrates setting a response body for a request to the path `/hello` with a 200 OK status.\n\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Initialize the mock server\nlet server = MockServer::start();\n\n// Configure the mock\nlet m = server.mock(|when, then| {\n when.path(\"/hello\");\n then.status(200)\n .body(\"ohi!\");\n});\n\n// Send a request and verify the response\nlet response = Client::new()\n .get(server.url(\"/hello\"))\n .send()\n .unwrap();\n\n// Check that the mock was called as expected and the response body is as configured\nm.assert();\nassert_eq!(response.status(), 200);\nassert_eq!(response.text().unwrap(), \"ohi!\");\n```\n", + "body_from_file": "Configures the HTTP response body with content loaded from a specified file on the mock server.\n\n# Parameters\n- `resource_file_path`: A string representing the path to the file whose contents will be used as the response body. The path can be absolute or relative to the server's running directory.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Mock` object.\n\n# Panics\nPanics if the specified file cannot be read, or if the path provided cannot be resolved to an absolute path.\n\n# Example\nDemonstrates setting the response body from a file for a request to the path `/hello` with a 200 OK status.\n\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Initialize the mock server\nlet server = MockServer::start();\n\n// Configure the mock\nlet m = server.mock(|when, then| {\n when.path(\"/hello\");\n then.status(200)\n .body_from_file(\"tests/resources/simple_body.txt\");\n});\n\n// Send a request and verify the response\nlet response = Client::new()\n .get(server.url(\"/hello\"))\n .send()\n .unwrap();\n\n// Check that the mock was called as expected and the response body matches the file contents\nm.assert();\nassert_eq!(response.status(), 200);\nassert_eq!(response.text().unwrap(), \"ohi!\");\n```\n", + "delay": "Sets a delay for the mock server response.\n\nThis method configures the server to wait for a specified duration before sending a response,\nwhich can be useful for testing timeout scenarios or asynchronous operations.\n\n# Parameters\n- `duration`: The length of the delay as a `std::time::Duration`.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Mock` object.\n\n# Panics\nPanics if the specified duration results in a delay that cannot be represented as a 64-bit\nunsigned integer of milliseconds (more than approximately 584 million years).\n\n# Example\nDemonstrates setting a 3-second delay for a request to the path `/delay`.\n\n```rust\nuse std::time::{SystemTime, Duration};\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet _ = env_logger::try_init();\nlet start_time = SystemTime::now();\nlet three_seconds = Duration::from_secs(3);\nlet server = MockServer::start();\n\n// Configure the mock\nlet mock = server.mock(|when, then| {\n when.path(\"/delay\");\n then.status(200)\n .delay(three_seconds);\n});\n\n// Act\nlet response = Client::new()\n .get(server.url(\"/delay\"))\n .send()\n .unwrap();\n\n// Assert\nmock.assert();\nassert!(start_time.elapsed().unwrap() >= three_seconds);\n```\n", + "header": "Sets an HTTP header that the mock server will return in the response.\n\nThis method configures a response header to be included when the mock server handles a request.\n\n# Parameters\n- `name`: The name of the header to set.\n- `value`: The value of the header.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Mock` object.\n\n# Example\nDemonstrates setting the \"Expires\" header for a response to a request to the root path.\n\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet _ = env_logger::try_init();\nlet server = MockServer::start();\n\n// Configure the mock\nlet m = server.mock(|when, then| {\n when.path(\"/\");\n then.status(200)\n .header(\"Expires\", \"Wed, 21 Oct 2050 07:28:00 GMT\");\n});\n\n// Act\nlet response = Client::new()\n .get(server.url(\"/\"))\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 200);\nassert_eq!(\n response.headers().get(\"Expires\").unwrap().to_str().unwrap(),\n \"Wed, 21 Oct 2050 07:28:00 GMT\"\n);\n```\n", + "json_body": "Sets the JSON body for the HTTP response that will be returned by the mock server.\n\nThis function accepts a JSON object that must be serializable and deserializable by serde.\nNote that this method does not automatically set the \"Content-Type\" header to \"application/json\".\nYou will need to set this header manually if required.\n\n# Parameters\n- `body`: The HTTP response body in the form of a `serde_json::Value` object.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Mock` object.\n\n# Example\nDemonstrates how to set a JSON body and a matching \"Content-Type\" header for a mock response.\n\n```rust\nuse httpmock::prelude::*;\nuse serde_json::{Value, json};\nuse reqwest::blocking::Client;\n\n// Arrange\nlet _ = env_logger::try_init();\nlet server = MockServer::start();\n\n// Configure the mock\nlet m = server.mock(|when, then| {\n when.path(\"/user\");\n then.status(200)\n .header(\"content-type\", \"application/json\")\n .json_body(json!({ \"name\": \"Hans\" }));\n});\n\n// Act\nlet response = Client::new()\n .get(server.url(\"/user\"))\n .send()\n .unwrap();\n\n// Get the status code first\nlet status = response.status();\n\n// Extract the text from the response\nlet response_text = response.text().unwrap();\n\n// Deserialize the JSON response\nlet user: Value =\n serde_json::from_str(&response_text).expect(\"cannot deserialize JSON\");\n\n// Assert\nm.assert();\nassert_eq!(status, 200);\nassert_eq!(user[\"name\"], \"Hans\");\n```\n", + "json_body_obj": "Sets the JSON body that will be returned by the mock server using a serializable serde object.\n\nThis method converts the provided object into a JSON string. It does not automatically set\nthe \"Content-Type\" header to \"application/json\", so you must set this header manually if it's\nneeded.\n\n# Parameters\n- `body`: A reference to an object that implements the `serde::Serialize` trait.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Mock` object.\n\n# Panics\nPanics if the object cannot be serialized into a JSON string.\n\n# Example\nDemonstrates setting a JSON body and the corresponding \"Content-Type\" header for a user object.\n\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\nuse serde::{Serialize, Deserialize};\n\n#[derive(Serialize, Deserialize)]\nstruct TestUser {\n name: String,\n}\n\n// Arrange\nlet _ = env_logger::try_init();\nlet server = MockServer::start();\n\n// Configure the mock\nlet m = server.mock(|when, then| {\n when.path(\"/user\");\n then.status(200)\n .header(\"content-type\", \"application/json\")\n .json_body_obj(&TestUser {\n name: String::from(\"Hans\"),\n });\n});\n\n// Act\nlet response = Client::new()\n .get(server.url(\"/user\"))\n .send()\n .unwrap();\n\n// Get the status code first\nlet status = response.status();\n\n// Extract the text from the response\nlet response_text = response.text().unwrap();\n\n// Deserialize the JSON response into a TestUser object\nlet user: TestUser =\n serde_json::from_str(&response_text).unwrap();\n\n// Assert\nm.assert();\nassert_eq!(status, 200);\nassert_eq!(user.name, \"Hans\");\n```\n", + "status": "Configures the HTTP response status code that the mock server will return.\n\n# Parameters\n- `status`: A `u16` HTTP status code that the mock server should return for the configured request.\n\n# Returns\nReturns `self` to allow chaining of method calls on the `Mock` object.\n\n# Example\nDemonstrates setting a 200 OK status for a request to the path `/hello`.\n\n```rust\nuse httpmock::prelude::*;\n\n// Initialize the mock server\nlet server = MockServer::start();\n\n// Configure the mock\nlet m = server.mock(|when, then| {\n when.path(\"/hello\");\n then.status(200);\n});\n\n// Send a request and verify the response\nlet response = reqwest::blocking::get(server.url(\"/hello\")).unwrap();\n\n// Check that the mock was called as expected and the response status is as configured\nm.assert();\nassert_eq!(response.status(), 200);\n```\n" + }, + "when": { + "and": "Applies a specified function to enhance or modify the `When` instance. This method allows for the\nencapsulation of multiple matching conditions into a single function, maintaining a clear and fluent\ninterface for setting up HTTP request expectations.\n\nThis method is particularly useful for reusing common setup patterns across multiple test scenarios,\npromoting cleaner and more maintainable test code.\n\n# Parameters\n- `func`: A function that takes a `When` instance and returns it after applying some conditions.\n\n## Example\n```rust\nuse httpmock::{prelude::*, When};\nuse httpmock::Method::POST;\n\n// Function to apply a standard authorization and content type setup for JSON POST requests\nfn is_authorized_json_post_request(when: When) -> When {\n when.method(POST)\n .header(\"Authorization\", \"SOME API KEY\")\n .header(\"Content-Type\", \"application/json\")\n}\n\n// Usage example demonstrating how to maintain fluent interface style with complex setups.\n// This approach keeps the chain of conditions clear and readable, enhancing test legibility\nlet server = MockServer::start();\nlet m = server.mock(|when, then| {\n when.query_param(\"user_id\", \"12345\")\n .and(is_authorized_json_post_request) // apply the function to include common setup\n .json_body_includes(r#\"{\"key\": \"value\"}\"#); // additional specific condition\n then.status(200);\n});\n```\n\n# Returns\n`When`: The modified `When` instance with additional conditions applied, suitable for further chaining.\n", + "any_request": "Configures the mock server to respond to any incoming request, regardless of the URL path,\nquery parameters, headers, or method.\n\nThis method doesn't directly alter the behavior of the mock server, as it already responds to any\nrequest by default. However, it serves as an explicit indication in your code that the\nserver will respond to any request.\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Configure the mock server to respond to any request\nlet mock = server.mock(|when, then| {\n when.any_request(); // Explicitly specify that any request should match\n then.status(200); // Respond with status code 200 for all matched requests\n});\n\n// Make a request to the server's URL and ensure the mock is triggered\nlet response = reqwest::blocking::get(server.url(\"/anyPath\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Assert that the mock was called at least once\nmock.assert();\n```\n\n# Note\nThis is the default behavior as of now, but it may change in future versions.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "body": "Sets the required HTTP request body content.\nThis method specifies that the HTTP request body must match the provided content exactly.\n\n**Note**: The body content is case-sensitive and must be an exact match.\n\n# Parameters\n- `body`: The required HTTP request body content. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to be \"The Great Gatsby\"\nlet mock = server.mock(|when, then| {\n when.body(\"The Great Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with the required body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "body_excludes": "Sets the condition that the HTTP request body content must not contain the specified substring.\nThis method ensures that the request body does not include the provided content as a substring.\n\n**Note**: The body content is case-sensitive.\n\n# Parameters\n- `substring`: The substring that the HTTP request body must not contain. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to not contain the substring \"Gatsby\"\nlet mock = server.mock(|when, then| {\n when.body_excludes(\"Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with a different body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"A Tale of Two Cities is a novel.\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "body_includes": "Sets the condition that the HTTP request body content must contain the specified substring.\nThis method ensures that the request body includes the provided content as a substring.\n\n**Note**: The body content is case-sensitive.\n\n# Parameters\n- `substring`: The substring that the HTTP request body must contain. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to contain the substring \"Gatsby\"\nlet mock = server.mock(|when, then| {\n when.body_includes(\"Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with the required substring in the body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel.\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "body_matches": "Sets the condition that the HTTP request body content must match the specified regular expression.\nThis method ensures that the request body fully conforms to the provided regex pattern.\n\n**Note**: The regex matching is case-sensitive unless the regex is explicitly defined to be case-insensitive.\n\n# Parameters\n- `pattern`: The regular expression pattern that the HTTP request body must match. This parameter accepts any type that can be converted into a `Regex`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to match the regex pattern \"^The Great Gatsby.*\"\nlet mock = server.mock(|when, then| {\n when.body_matches(\"^The Great Gatsby.*\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with a body that matches the regex pattern\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel by F. Scott Fitzgerald.\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When’ instance to allow method chaining for additional configuration.\n", + "body_not": "Sets the condition that the HTTP request body content must not match the specified value.\nThis method ensures that the request body does not contain the provided content exactly.\n\n**Note**: The body content is case-sensitive and must be an exact mismatch.\n\n# Parameters\n- `body`: The body content that the HTTP request must not contain. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to not be \"The Great Gatsby\"\nlet mock = server.mock(|when, then| {\n when.body_not(\"The Great Gatsby\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with a different body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"A Tale of Two Cities\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "body_prefix": "Sets the condition that the HTTP request body content must begin with the specified substring.\nThis method ensures that the request body starts with the provided content as a substring.\n\n**Note**: The body content is case-sensitive.\n\n# Parameters\n- `prefix`: The substring that the HTTP request body must begin with. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to begin with the substring \"The Great\"\nlet mock = server.mock(|when, then| {\n when.body_prefix(\"The Great\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with the required prefix in the body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel.\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "body_prefix_not": "Sets the condition that the HTTP request body content must not begin with the specified substring.\nThis method ensures that the request body does not start with the provided content as a substring.\n\n**Note**: The body content is case-sensitive.\n\n# Parameters\n- `prefix`: The substring that the HTTP request body must not begin with. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to not begin with the substring \"Error:\"\nlet mock = server.mock(|when, then| {\n when.body_prefix_not(\"Error:\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with a different body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"Success: Operation completed.\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When’ instance to allow method chaining for additional configuration.\n", + "body_suffix": "Sets the condition that the HTTP request body content must end with the specified substring.\nThis method ensures that the request body concludes with the provided content as a substring.\n\n**Note**: The body content is case-sensitive.\n\n# Parameters\n- `suffix`: The substring that the HTTP request body must end with. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to end with the substring \"a novel.\"\nlet mock = server.mock(|when, then| {\n when.body_suffix(\"a novel.\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with the required suffix in the body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a novel.\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When’ instance to allow method chaining for additional configuration.\n", + "body_suffix_not": "Sets the condition that the HTTP request body content must not end with the specified substring.\nThis method ensures that the request body does not conclude with the provided content as a substring.\n\n**Note**: The body content is case-sensitive.\n\n# Parameters\n- `suffix`: The substring that the HTTP request body must not end with. This parameter accepts any type that can be converted into a `String`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to not end with the substring \"a novel.\"\nlet mock = server.mock(|when, then| {\n when.body_suffix_not(\"a novel.\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with a different body content\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .body(\"The Great Gatsby is a story.\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When’ instance to allow method chaining for additional configuration.\n", + "cookie": "Sets the cookie that needs to exist in the HTTP request.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie. Must be a case-sensitive match.\n- `value`: The expected value of the cookie.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" with the value \"1234567890\"\nlet mock = server.mock(|when, then| {\n when.cookie(\"SESSIONID\", \"1234567890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234567890; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_count": "Sets the requirement that a cookie with a name and value matching the specified regexes must appear a specified number of times in the HTTP request.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `key_regex`: The regex pattern that the cookie name must match.\n- `value_regex`: The regex pattern that the cookie value must match.\n- `count`: The number of times a cookie with a matching name and value must appear.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie with a name matching the regex \"^SESSION\"\n// and a value matching the regex \"^[0-9]{10}$\" to appear exactly twice\nlet mock = server.mock(|when, then| {\n when.cookie_count(r\"^SESSION\", r\"^[0-9]{10}$\", 2);\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookies\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"SESSIONID=1234567890; TRACK=12345; SESSIONTOKEN=0987654321; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_excludes": "Sets the requirement that a cookie with the specified name must exist and its value must not contain the specified substring.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must exist.\n- `value_substring`: The substring that must not be present in the cookie value.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" with a value not containing \"1234\"\nlet mock = server.mock(|when, then| {\n when.cookie_excludes(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcdef; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_exists": "Sets the requirement that a cookie with the specified name must exist in the HTTP request.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must exist.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\"\nlet mock = server.mock(|when, then| {\n when.cookie_exists(\"SESSIONID\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234567890; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_includes": "Sets the requirement that a cookie with the specified name must exist and its value must contain the specified substring.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must exist.\n- `value_substring`: The substring that must be present in the cookie value.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" with a value containing \"1234\"\nlet mock = server.mock(|when, then| {\n when.cookie_includes(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abc1234def; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_matches": "Sets the requirement that a cookie with a name matching the specified regex must exist and its value must match the specified regex.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `key_regex`: The regex pattern that the cookie name must match.\n- `value_regex`: The regex pattern that the cookie value must match.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie with a name matching the regex \"^SESSION\"\n// and a value matching the regex \"^[0-9]{10}$\"\nlet mock = server.mock(|when, then| {\n when.cookie_matches(r\"^SESSION\", r\"^[0-9]{10}$\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234567890; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_missing": "Sets the requirement that a cookie with the specified name must not exist in the HTTP request.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must not exist.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" not to exist\nlet mock = server.mock(|when, then| {\n when.cookie_missing(\"SESSIONID\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that does not include the excluded cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_not": "Sets the cookie that should not exist or should not have a specific value in the HTTP request.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie. Must be a case-sensitive match.\n- `value`: The value that the cookie should not have.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" to not have the value \"1234567890\"\nlet mock = server.mock(|when, then| {\n when.cookie_not(\"SESSIONID\", \"1234567890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=0987654321; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_prefix": "Sets the requirement that a cookie with the specified name must exist and its value must start with the specified substring.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must exist.\n- `value_prefix`: The substring that must be at the start of the cookie value.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" with a value starting with \"1234\"\nlet mock = server.mock(|when, then| {\n when.cookie_prefix(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=1234abcdef; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_prefix_not": "Sets the requirement that a cookie with the specified name must exist and its value must not start with the specified substring.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must exist.\n- `value_prefix`: The substring that must not be at the start of the cookie value.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" with a value not starting with \"1234\"\nlet mock = server.mock(|when, then| {\n when.cookie_prefix_not(\"SESSIONID\", \"1234\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcd1234; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_suffix": "Sets the requirement that a cookie with the specified name must exist and its value must end with the specified substring.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must exist.\n- `value_suffix`: The substring that must be at the end of the cookie value.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" with a value ending with \"7890\"\nlet mock = server.mock(|when, then| {\n when.cookie_suffix(\"SESSIONID\", \"7890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcdef7890; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "cookie_suffix_not": "Sets the requirement that a cookie with the specified name must exist and its value must not end with the specified substring.\nCookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html).\n**Attention**: Cookie names are **case-sensitive**.\n\n# Parameters\n- `name`: The name of the cookie that must exist.\n- `value_suffix`: The substring that must not be at the end of the cookie value.\n\n> Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects a cookie named \"SESSIONID\" with a value not ending with \"7890\"\nlet mock = server.mock(|when, then| {\n when.cookie_suffix_not(\"SESSIONID\", \"7890\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required cookie\n Client::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Cookie\", \"TRACK=12345; SESSIONID=abcdef1234; CONSENT=1\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "form_urlencoded_tuple": "Adds a key-value pair to the requirements for an `application/x-www-form-urlencoded` request body.\n\nThis method sets an expectation for a specific key-value pair to be included in the request body\nof an `application/x-www-form-urlencoded` POST request. Each key and value are URL-encoded as specified\nby the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key of the key-value pair to set as a requirement.\n- `value`: The value of the key-value pair to set as a requirement.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple(\"name\", \"Peter Griffin\")\n .form_urlencoded_tuple(\"town\", \"Quahog\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value pair added to the `application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_count": "Sets a requirement for the number of times a key-value pair matching specific regular expressions appears in an `application/x-www-form-urlencoded` request body.\n\nThis method sets an expectation that the key-value pair must appear a specific number of times in the request body of an\n`application/x-www-form-urlencoded` POST request. The key and value regular expressions are URL-encoded as specified by the\n[URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key_regex`: The regular expression that the key must match in the `application/x-www-form-urlencoded` request body.\n- `value_regex`: The regular expression that the value must match in the `application/x-www-form-urlencoded` request body.\n- `count`: The number of times the key-value pair matching the regular expressions must appear.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\nuse regex::Regex;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_count(\n Regex::new(r\"^name$\").unwrap(),\n Regex::new(r\".*Griffin$\").unwrap(),\n 2\n );\n then.status(202);\n});\n\n// Act\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&name=Lois%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value count requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_excludes": "Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must not contain a specific substring.\n\nThis method sets an expectation that the value associated with a specific key must not contain a specified substring\nin the request body of an `application/x-www-form-urlencoded` POST request. The key and the substring are URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key in the `application/x-www-form-urlencoded` request body.\n- `substring`: The substring that must not be present in the value associated with the key.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_excludes(\"name\", \"Griffin\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Lois%20Smith&city=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value substring exclusion requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_exists": "Sets a requirement for the existence of a key in an `application/x-www-form-urlencoded` request body.\n\nThis method sets an expectation that a specific key must be present in the request body of an\n`application/x-www-form-urlencoded` POST request, regardless of its value. The key is URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key that must exist in the `application/x-www-form-urlencoded` request body.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_exists(\"name\")\n .form_urlencoded_tuple_exists(\"town\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key existence requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_includes": "Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must contain a specific substring.\n\nThis method sets an expectation that the value associated with a specific key must contain a specified substring\nin the request body of an `application/x-www-form-urlencoded` POST request. The key and the substring are URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key in the `application/x-www-form-urlencoded` request body.\n- `substring`: The substring that must be present in the value associated with the key.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_includes(\"name\", \"Griffin\")\n .form_urlencoded_tuple_includes(\"town\", \"Quahog\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value substring requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_matches": "Sets a requirement that a key-value pair in an `application/x-www-form-urlencoded` request body must match specific regular expressions.\n\nThis method sets an expectation that the key and the value in a key-value pair must match the specified regular expressions\nin the request body of an `application/x-www-form-urlencoded` POST request. The key and value regular expressions are URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key_regex`: The regular expression that the key must match in the `application/x-www-form-urlencoded` request body.\n- `value_regex`: The regular expression that the value must match in the `application/x-www-form-urlencoded` request body.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\nuse regex::Regex;\n\n// Arrange\nlet server = MockServer::start();\n\nlet key_regex = Regex::new(r\"^name$\").unwrap();\nlet value_regex = Regex::new(r\"^Peter\\sGriffin$\").unwrap();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_matches(key_regex, value_regex);\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value regex matching requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_missing": "Sets a requirement that a key must be absent in an `application/x-www-form-urlencoded` request body.\n\nThis method sets an expectation that a specific key must not be present in the request body of an\n`application/x-www-form-urlencoded` POST request. The key is URL-encoded as specified by the\n[URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key that must be absent in the `application/x-www-form-urlencoded` request body.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_missing(\"name\")\n .form_urlencoded_tuple_missing(\"town\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"city=Quahog&occupation=Cartoonist\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key absence requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_not": "Adds a key-value pair to the negative requirements for an `application/x-www-form-urlencoded` request body.\n\nThis method sets an expectation for a specific key-value pair to be excluded from the request body\nof an `application/x-www-form-urlencoded` POST request. Each key and value are URL-encoded as specified\nby the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key of the key-value pair to set as a requirement.\n- `value`: The value of the key-value pair to set as a requirement.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_not(\"name\", \"Peter Griffin\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Lois%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value pair added to the negative `application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_prefix": "Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must start with a specific prefix.\n\nThis method sets an expectation that the value associated with a specific key must start with a specified prefix\nin the request body of an `application/x-www-form-urlencoded` POST request. The key and the prefix are URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key in the `application/x-www-form-urlencoded` request body.\n- `prefix`: The prefix that must appear at the start of the value associated with the key.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_prefix(\"name\", \"Pete\")\n .form_urlencoded_tuple_prefix(\"town\", \"Qua\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value prefix requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_prefix_not": "Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must not start with a specific prefix.\n\nThis method sets an expectation that the value associated with a specific key must not start with a specified prefix\nin the request body of an `application/x-www-form-urlencoded` POST request. The key and the prefix are URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key in the `application/x-www-form-urlencoded` request body.\n- `prefix`: The prefix that must not appear at the start of the value associated with the key.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_prefix_not(\"name\", \"Lois\")\n .form_urlencoded_tuple_prefix_not(\"town\", \"Hog\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value prefix exclusion requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_suffix": "Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must end with a specific suffix.\n\nThis method sets an expectation that the value associated with a specific key must end with a specified suffix\nin the request body of an `application/x-www-form-urlencoded` POST request. The key and the suffix are URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key in the `application/x-www-form-urlencoded` request body.\n- `suffix`: The suffix that must appear at the end of the value associated with the key.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_suffix(\"name\", \"Griffin\")\n .form_urlencoded_tuple_suffix(\"town\", \"hog\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value suffix requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "form_urlencoded_tuple_suffix_not": "Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must not end with a specific suffix.\n\nThis method sets an expectation that the value associated with a specific key must not end with a specified suffix\nin the request body of an `application/x-www-form-urlencoded` POST request. The key and the suffix are URL-encoded\nas specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded).\n\n**Note**: The mock server does not automatically verify that the HTTP method is POST as per spec.\nIf you want to verify that the request method is POST, you must explicitly set it in your mock configuration.\n\n# Parameters\n- `key`: The key in the `application/x-www-form-urlencoded` request body.\n- `suffix`: The suffix that must not appear at the end of the value associated with the key.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.method(POST)\n .path(\"/example\")\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .form_urlencoded_tuple_suffix_not(\"name\", \"Smith\")\n .form_urlencoded_tuple_suffix_not(\"town\", \"ville\");\n then.status(202);\n});\n\nlet response = Client::new()\n .post(server.url(\"/example\"))\n .header(\"content-type\", \"application/x-www-form-urlencoded\")\n .body(\"name=Peter%20Griffin&town=Quahog\")\n .send()\n .unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 202);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new key-value suffix exclusion requirement added to the\n`application/x-www-form-urlencoded` expectations.\n", + "header": "Sets the expected HTTP header and its value for the request to match.\nThis function ensures that the specified header with the given value is present in the request.\nHeader names are case-insensitive, as per RFC 2616.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `value`: The expected value of the HTTP header.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header with a specific value\nlet mock = server.mock(|when, then| {\n when.header(\"Authorization\", \"token 1234567890\");\n then.status(200); // Respond with a 200 status code if the header and value are present\n});\n\n// Make a request that includes the \"Authorization\" header with the specified value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_count": "Sets the requirement that the HTTP request must contain a specific number of headers whose keys and values match specified patterns.\nThis function ensures that the specified number of headers with keys and values matching the given patterns are present in the request.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple patterns and counts.\n\n# Parameters\n- `key_pattern`: The pattern that the header keys must match.\n- `value_pattern`: The pattern that the header values must match.\n- `count`: The number of headers with keys and values matching the patterns that must be present.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects at least 2 headers whose keys match the regex \"^X-Custom-Header.*\"\n// and values match the regex \"value.*\"\nlet mock = server.mock(|when, then| {\n when.header_count(\"^X-Custom-Header.*\", \"value.*\", 2);\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request that includes the required headers\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"x-custom-header-1\", \"value1\")\n .header(\"X-Custom-Header-2\", \"value2\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_excludes": "Sets the requirement that the HTTP request must contain a specific header whose value does not contain a specified substring.\nThis function ensures that the specified header is present and its value does not contain the given substring.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple headers and substrings.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `substring`: The substring that the header value must not contain.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header's value to not contain \"Bearer\"\nlet mock = server.mock(|when, then| {\n when.header_excludes(\"Authorization\", \"Bearer\");\n then.status(200); // Respond with a 200 status code if the header value does not contain the substring\n});\n\n// Make a request that includes the \"Authorization\" header without the forbidden substring in its value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_exists": "Sets the requirement that the HTTP request must contain a specific header.\nThe presence of the header is checked, but its value is not validated.\nFor value validation, refer to [Mock::expect_header](struct.Mock.html#method.expect_header).\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive, as per RFC 2616.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header to be present in the request\nlet mock = server.mock(|when, then| {\n when.header_exists(\"Authorization\");\n then.status(200); // Respond with a 200 status code if the header is present\n});\n\n// Make a request that includes the \"Authorization\" header\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_includes": "Sets the requirement that the HTTP request must contain a specific header whose value contains a specified substring.\nThis function ensures that the specified header is present and its value contains the given substring.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple headers and substrings.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `substring`: The substring that the header value must contain.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header's value to contain \"token\"\nlet mock = server.mock(|when, then| {\n when.header_includes(\"Authorization\", \"token\");\n then.status(200); // Respond with a 200 status code if the header value contains the substring\n});\n\n// Make a request that includes the \"Authorization\" header with the specified substring in its value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_matches": "Sets the requirement that the HTTP request must contain a specific header whose key and value match the specified regular expressions.\nThis function ensures that the specified header is present and both its key and value match the given regular expressions.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple headers and patterns.\n\n# Parameters\n- `key_regex`: The regular expression that the header key must match.\n- `value_regex`: The regular expression that the header value must match.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header's key to match the regex \"^Auth.*\"\n// and its value to match the regex \"token .*\"\nlet mock = server.mock(|when, then| {\n when.header_matches(\"^Auth.*\", \"token .*\");\n then.status(200); // Respond with a 200 status code if the header key and value match the patterns\n});\n\n// Make a request that includes the \"Authorization\" header with a value matching the regex\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_missing": "Sets the requirement that the HTTP request must not contain a specific header.\nThis function ensures that the specified header is absent in the request.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to add multiple excluded headers.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header to be absent in the request\nlet mock = server.mock(|when, then| {\n when.header_missing(\"Authorization\");\n then.status(200); // Respond with a 200 status code if the header is absent\n});\n\n// Make a request that does not include the \"Authorization\" header\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_not": "Sets the requirement that the HTTP request must not contain a specific header with the specified value.\nThis function ensures that the specified header with the given value is absent in the request.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to add multiple excluded headers.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `value`: The value of the HTTP header that must not be present.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header with a specific value to be absent\nlet mock = server.mock(|when, then| {\n when.header_not(\"Authorization\", \"token 1234567890\");\n then.status(200); // Respond with a 200 status code if the header and value are absent\n});\n\n// Make a request that includes the \"Authorization\" header with a different value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token abcdefg\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_prefix": "Sets the requirement that the HTTP request must contain a specific header whose value starts with a specified prefix.\nThis function ensures that the specified header is present and its value starts with the given prefix.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple headers and prefixes.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `prefix`: The prefix that the header value must start with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header's value to start with \"token\"\nlet mock = server.mock(|when, then| {\n when.header_prefix(\"Authorization\", \"token\");\n then.status(200); // Respond with a 200 status code if the header value starts with the prefix\n});\n\n// Make a request that includes the \"Authorization\" header with the specified prefix in its value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_prefix_not": "Sets the requirement that the HTTP request must contain a specific header whose value does not start with a specified prefix.\nThis function ensures that the specified header is present and its value does not start with the given prefix.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple headers and prefixes.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `prefix`: The prefix that the header value must not start with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header's value to not start with \"Bearer\"\nlet mock = server.mock(|when, then| {\n when.header_prefix_not(\"Authorization\", \"Bearer\");\n then.status(200); // Respond with a 200 status code if the header value does not start with the prefix\n});\n\n// Make a request that includes the \"Authorization\" header without the \"Bearer\" prefix in its value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_suffix": "Sets the requirement that the HTTP request must contain a specific header whose value ends with a specified suffix.\nThis function ensures that the specified header is present and its value ends with the given suffix.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple headers and suffixes.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `suffix`: The suffix that the header value must end with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header's value to end with \"7890\"\nlet mock = server.mock(|when, then| {\n when.header_suffix(\"Authorization\", \"7890\");\n then.status(200); // Respond with a 200 status code if the header value ends with the suffix\n});\n\n// Make a request that includes the \"Authorization\" header with the specified suffix in its value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "header_suffix_not": "Sets the requirement that the HTTP request must contain a specific header whose value does not end with a specified suffix.\nThis function ensures that the specified header is present and its value does not end with the given suffix.\nHeader names are case-insensitive, as per RFC 2616.\n\nThis function may be called multiple times to check multiple headers and suffixes.\n\n# Parameters\n- `name`: The HTTP header name. Header names are case-insensitive.\n- `suffix`: The suffix that the header value must not end with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the \"Authorization\" header's value to not end with \"abc\"\nlet mock = server.mock(|when, then| {\n when.header_suffix_not(\"Authorization\", \"abc\");\n then.status(200); // Respond with a 200 status code if the header value does not end with the suffix\n});\n\n// Make a request that includes the \"Authorization\" header without the \"abc\" suffix in its value\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Authorization\", \"token 1234567890\")\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "host": "Sets the expected host name. This constraint is especially useful when working with\nproxy or forwarding rules, but it can also be used to serve mocks (e.g., when using a mock\nserver as a proxy).\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: Both `localhost` and `127.0.0.1` are treated equally.\nIf the provided host is set to either `localhost` or `127.0.0.1`, it will match\nrequests containing either `localhost` or `127.0.0.1`.\n\n* `host` - The host name (should not include a port).\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\nlet server = MockServer::start();\n\nserver.mock(|when, then| {\n when.host(\"github.com\");\n then.body(\"This is a mock response\");\n});\n\nlet client = Client::builder()\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\nlet response = client.get(\"http://github.com\").send().unwrap();\n\nassert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_excludes": "Adds a substring that must not be present within the request's host name for the mock server to respond.\n\nThis method ensures that the mock server does not respond to requests if the host name contains the specified substring.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple excluded substrings, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: This function does not automatically compare with pseudo names, like \"localhost\".\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that excludes any request where the host name contains \"www.google.com\"\nlet mock = server.mock(|when, then| {\n when.host_excludes(\"www.google.com\"); // Exclude hosts containing \"www.google.com\"\n then.status(200); // Respond with status code 200 for other matched requests\n});\n\n// Make a request to a URL whose host name will be \"localhost\" and trigger the mock\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `host`: A string or other type convertible to `String` that will be added as a substring to exclude from matching.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_includes": "Adds a substring to match within the request's host name.\n\nThis method ensures that the mock server only matches requests whose host name contains the specified substring.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple substrings, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: This function does not automatically compare with pseudo names, like \"localhost\".\n\n# Attention\nThis function does not automatically treat 127.0.0.1 like localhost.\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any request where the host name contains \"localhost\"\nlet mock = server.mock(|when, then| {\n when.host_includes(\"0.0\"); // Only match hosts containing \"0.0\" (e.g., 127.0.0.1)\n then.status(200); // Respond with status code 200 for all matched requests\n});\n\n// Make a request to a URL whose host name is \"localhost\" to trigger the mock\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `host`: A string or other type convertible to `String` that will be added as a substring to match against the request's host name.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_matches": "Sets a regular expression pattern that the request's host name must match for the mock server to respond.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple patterns, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: This function does not automatically compare with pseudo names, like \"localhost\".\n\n# Parameters\n- `regex`: A regular expression pattern to match against the host name. Should be a valid regex string.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches requests where the host name is exactly \"localhost\"\nlet mock = server.mock(|when, then| {\n when.host_matches(r\"^127.0.0.1$\");\n then.status(200);\n});\n\n// Make a request with \"127.0.0.1\" as the host name to trigger the mock response.\nlet response = reqwest::blocking::get(server.url(\"/\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_not": "Sets the host name that should **NOT** be responded for.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple suffixes, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n* `host` - The host name (should not include a port).\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\nlet server = MockServer::start();\n\nserver.mock(|when, then| {\n when.host(\"github.com\");\n then.body(\"This is a mock response\");\n});\n\nlet client = Client::builder()\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\nlet response = client.get(\"http://github.com\").send().unwrap();\n\nassert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_prefix": "Adds a prefix that the request's host name must start with for the mock server to respond.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple prefixes, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: This function does not automatically compare with pseudo names, like \"localhost\".\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any request where the host name starts with \"local\"\nlet mock = server.mock(|when, then| {\n when.host_prefix(\"127.0\"); // Only match hosts starting with \"127.0\"\n then.status(200); // Respond with status code 200 for all matched requests\n});\n\n// Make a request to the mock server with a host name of \"127.0.0.1\" to trigger the mock response.\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `prefix`: A string or other type convertible to `String` specifying the prefix that the host name should start with.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_prefix_not": "Adds a prefix that the request's host name must *not* start with for the mock server to respond.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple excluded prefixes, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: This function does not automatically compare with pseudo names, like \"localhost\".\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any request where the host name does not start with \"www.\"\nlet mock = server.mock(|when, then| {\n when.host_prefix_not(\"www.\"); // Exclude hosts starting with \"www\"\n then.status(200); // Respond with status code 200 for all other requests\n});\n\n// Make a request with host name \"localhost\" that does not start with \"www\" and therefore\n// triggers the mock response.\nlet response = reqwest::blocking::get(server.url(\"/example\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `prefix`: A string or other type convertible to `String` specifying the prefix that the host name should *not* start with.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_suffix": "Adds a suffix that the request's host name must end with for the mock server to respond.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple suffixes, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: This function does not automatically compare with pseudo names, like \"localhost\".\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any request where the host name ends with \"host\" (e.g., \"localhost\").\nlet mock = server.mock(|when, then| {\n when.host_suffix(\"0.1\"); // Only match hosts ending with \"0.1\"\n then.status(200); // Respond with status code 200 for all matched requests\n});\n\n// Make a request to the mock server with a host name of \"127.0.0.1\" to trigger the mock response.\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `host`: A string or other type convertible to `String` specifying the suffix that the host name should end with.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "host_suffix_not": "Adds a suffix that the request's host name must *not* end with for the mock server to respond.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple excluded suffixes, invoke this function multiple times.\n\n**Note**: Host matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\nThis standard dictates that all host names are treated equivalently, regardless of character case.\n\n**Note**: This function does not automatically compare with pseudo names, like \"localhost\".\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any request where the host name does not end with \"host\".\nlet mock = server.mock(|when, then| {\n when.host_suffix_not(\"host\"); // Exclude hosts ending with \"host\"\n then.status(200); // Respond with status code 200 for all other requests\n});\n\n// Make a request with a host name that does not end with \"host\" to trigger the mock response.\nlet response = reqwest::blocking::get(server.url(\"/example\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `host`: A string or other type convertible to `String` specifying the suffix that the host name should *not* end with.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "is_false": "Adds a custom matcher for expected HTTP requests. If this function returns false, the request\nis considered a match, and the mock server will respond to the request\n(given all other criteria are also met).\n\nYou can use this function to create custom expectations for your mock server based on any aspect\nof the `HttpMockRequest` object.\n\n# Parameters\n- `matcher`: A function that takes a reference to an `HttpMockRequest` and returns a boolean indicating whether the request matches.\n\n## Example\n```rust\nuse httpmock::prelude::*;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.is_false(|req: &HttpMockRequest| {\n req.uri().path().contains(\"es\")\n });\n then.status(404);\n});\n\n// Act: Send the HTTP request\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 404);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new custom matcher added to the expectations.\n", + "is_true": "Adds a custom matcher for expected HTTP requests. If this function returns true, the request\nis considered a match, and the mock server will respond to the request\n(given all other criteria are also met).\n\nYou can use this function to create custom expectations for your mock server based on any aspect\nof the `HttpMockRequest` object.\n\n# Parameters\n- `matcher`: A function that takes a reference to an `HttpMockRequest` and returns a boolean indicating whether the request matches.\n\n## Example\n```rust\nuse httpmock::prelude::*;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.is_true(|req: &HttpMockRequest| {\n req.uri().path().contains(\"es\")\n });\n then.status(200);\n});\n\n// Act: Send the HTTP request\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 200);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new custom matcher added to the expectations.\n", + "json_body": "Sets the condition that the HTTP request body content must match the specified JSON structure.\nThis method ensures that the request body exactly matches the JSON value provided.\n\n**Note**: The body content is case-sensitive.\n\n**Note**: This method does not automatically verify the `Content-Type` header.\nIf specific content type verification is required (e.g., `application/json`),\nyou must add this expectation manually.\n\n# Parameters\n- `json_value`: The JSON structure that the HTTP request body must match. This parameter accepts any type that can be converted into a `serde_json::Value`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\nuse serde_json::json;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the request body to match a specific JSON structure\nlet mock = server.mock(|when, then| {\n when.json_body(json!({\n \"title\": \"The Great Gatsby\",\n \"author\": \"F. Scott Fitzgerald\"\n }));\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Make a request with a JSON body that matches the expected structure\nClient::new()\n .post(&format!(\"http://{}/test\", server.address()))\n .header(\"Content-Type\", \"application/json\") // It's important to set the Content-Type header manually\n .body(r#\"{\"title\":\"The Great Gatsby\",\"author\":\"F. Scott Fitzgerald\"}\"#)\n .send()\n .unwrap();\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When’ instance to allow method chaining for additional configuration.\n", + "json_body_excludes": "Sets the expected partial JSON body to ensure that specific content is not present within a larger JSON structure.\n\n**Attention:** The partial JSON string must be a valid JSON string and should represent a substructure\nof the full JSON object. It can omit irrelevant attributes but must maintain any necessary object hierarchy.\n\n**Note:** This method does not automatically set the `Content-Type` header to `application/json`.\nYou must explicitly set this header in your requests.\n\n# Parameters\n- `partial_body`: The partial JSON content to check for exclusion. This must be a valid JSON string.\n\n# Example\nSuppose your application sends the following JSON request body:\n```json\n{\n \"parent_attribute\": \"Some parent data goes here\",\n \"child\": {\n \"target_attribute\": \"Example\",\n \"other_attribute\": \"Another value\"\n }\n}\n```\nTo verify the absence of `target_attribute` with the value `Example`:\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\nlet server = MockServer::start();\n\nlet mock = server.mock(|when, then| {\n when.json_body_excludes(r#\"\n {\n \"child\": {\n \"target_attribute\": \"Example\"\n }\n }\n \"#);\n then.status(200);\n});\n\n// Send a POST request with a JSON body\nlet response = Client::new()\n .post(&format!(\"http://{}/some/path\", server.address()))\n .header(\"content-type\", \"application/json\")\n .body(r#\"\n {\n \"parent_attribute\": \"Some parent data goes here\",\n \"child\": {\n \"other_attribute\": \"Another value\"\n }\n }\n \"#)\n .send()\n .unwrap();\n\n// Assert the mock was called and the response status is as expected\nmock.assert();\nassert_eq!(response.status(), 200);\n```\nIt's important that the partial JSON contains the full object hierarchy necessary to reach the target attribute.\nIrrelevant attributes such as `parent_attribute` and `child.other_attribute` in the example can be omitted.\n", + "json_body_includes": "Sets the expected partial JSON body to check for specific content within a larger JSON structure.\n\n**Attention:** The partial JSON string must be a valid JSON string and should represent a substructure\nof the full JSON object. It can omit irrelevant attributes but must maintain any necessary object hierarchy.\n\n**Note:** This method does not automatically set the `Content-Type` header to `application/json`.\nYou must explicitly set this header in your requests.\n\n# Parameters\n- `partial_body`: The partial JSON content to check for. This must be a valid JSON string.\n\n# Example\nSuppose your application sends the following JSON request body:\n```json\n{\n \"parent_attribute\": \"Some parent data goes here\",\n \"child\": {\n \"target_attribute\": \"Example\",\n \"other_attribute\": \"Another value\"\n }\n}\n```\nTo verify the presence of `target_attribute` with the value `Example` without needing the entire JSON object:\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\nlet server = MockServer::start();\n\nlet mock = server.mock(|when, then| {\n when.json_body_includes(r#\"\n {\n \"child\": {\n \"target_attribute\": \"Example\"\n }\n }\n \"#);\n then.status(200);\n});\n\n// Send a POST request with a JSON body\nlet response = Client::new()\n .post(&format!(\"http://{}/some/path\", server.address()))\n .header(\"content-type\", \"application/json\")\n .body(r#\"\n {\n \"parent_attribute\": \"Some parent data goes here\",\n \"child\": {\n \"target_attribute\": \"Example\",\n \"other_attribute\": \"Another value\"\n }\n }\n \"#)\n .send()\n .unwrap();\n\n// Assert the mock was called and the response status is as expected\nmock.assert();\nassert_eq!(response.status(), 200);\n```\nIt's important that the partial JSON contains the full object hierarchy necessary to reach the target attribute.\nIrrelevant attributes such as `parent_attribute` and `child.other_attribute` can be omitted.\n", + "json_body_obj": "Sets the expected JSON body using a serializable serde object.\nThis function automatically serializes the given object into a JSON string using serde.\n\n**Note**: This method does not automatically verify the `Content-Type` header.\nIf specific content type verification is required (e.g., `application/json`),\nyou must add this expectation manually.\n\n# Parameters\n- `body`: The HTTP body object to be serialized to JSON. This object should implement both `serde::Serialize` and `serde::Deserialize`.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\nuse serde_json::json;\nuse serde::{Serialize, Deserialize};\n\n#[derive(Serialize, Deserialize)]\nstruct TestUser {\n name: String,\n}\n\n// Initialize logging (optional, for debugging purposes)\nlet _ = env_logger::try_init();\n\n// Start the mock server\nlet server = MockServer::start();\n\n// Set up a mock endpoint\nlet m = server.mock(|when, then| {\n when.path(\"/user\")\n .header(\"content-type\", \"application/json\")\n .json_body_obj(&TestUser { name: String::from(\"Fred\") });\n then.status(200);\n});\n\n// Send a POST request with a JSON body\nlet response = Client::new()\n .post(&format!(\"http://{}/user\", server.address()))\n .header(\"content-type\", \"application/json\")\n .body(json!(&TestUser { name: \"Fred\".to_string() }).to_string())\n .send()\n .unwrap();\n\n// Assert the mock was called and the response status is as expected\nm.assert();\nassert_eq!(response.status(), 200);\n```\n\nThis method is particularly useful when you need to test server responses to structured JSON data. It helps\nensure that the JSON serialization and deserialization processes are correctly implemented in your API handling logic.\n", + "matches": "Adds a custom matcher for expected HTTP requests. If this function returns true, the request\nis considered a match, and the mock server will respond to the request\n(given all other criteria are also met).\n\nYou can use this function to create custom expectations for your mock server based on any aspect\nof the `HttpMockRequest` object.\n\n# Parameters\n- `matcher`: A function that takes a reference to an `HttpMockRequest` and returns a boolean indicating whether the request matches.\n\n## Example\n```rust\nuse httpmock::prelude::*;\n\n// Arrange\nlet server = MockServer::start();\n\nlet m = server.mock(|when, then| {\n when.matches(|req: &HttpMockRequest| {\n req.uri().path().contains(\"es\")\n });\n then.status(200);\n});\n\n// Act: Send the HTTP request\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Assert\nm.assert();\nassert_eq!(response.status(), 200);\n```\n\n# Returns\n`When`: Returns the modified `When` object with the new custom matcher added to the expectations.\n", + "method": "Sets the expected HTTP method for which the mock server should respond.\n\nThis method ensures that the mock server only matches requests that use the specified HTTP method,\nsuch as `GET`, `POST`, or any other valid method. This allows testing behavior that's specific\nto different types of HTTP requests.\n\n**Note**: Method matching is case-insensitive.\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches only `GET` requests\nlet mock = server.mock(|when, then| {\n when.method(GET); // Match only `GET` HTTP method\n then.status(200); // Respond with status code 200 for all matched requests\n});\n\n// Make a GET request to the server's URL to trigger the mock\nlet response = reqwest::blocking::get(server.url(\"/\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `method`: An HTTP method as either a `Method` enum or a `String` value, specifying the expected method type for matching.\n\n# Returns\nThe updated `When` instance to allow for method chaining.\n\n", + "method_not": "Excludes the specified HTTP method from the requests the mock server will respond to.\n\nThis method ensures that the mock server does not respond to requests using the given HTTP method,\nlike `GET`, `POST`, etc. This allows testing scenarios where a particular method should not\ntrigger a response, and thus testing behaviors like method-based security.\n\n**Note**: Method matching is case-insensitive.\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any request except those using the `POST` method\nlet mock = server.mock(|when, then| {\n when.method_not(POST); // Exclude the `POST` HTTP method from matching\n then.status(200); // Respond with status code 200 for all other matched requests\n});\n\n// Make a GET request to the server's URL, which will trigger the mock\nlet response = reqwest::blocking::get(server.url(\"/\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `method`: An HTTP method as either a `Method` enum or a `String` value, specifying the method type to exclude from matching.\n\n# Returns\nThe updated `When` instance to allow for method chaining.\n\n", + "path": "Specifies the expected URL path that incoming requests must match for the mock server to respond.\nThis is useful for targeting specific endpoints, such as API routes, to ensure only relevant requests trigger the mock response.\n\n# Parameters\n- `path`: A string or other value convertible to `String` that represents the expected URL path.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches requests to `/test`\nlet mock = server.mock(|when, then| {\n when.path(\"/test\");\n then.status(200); // Respond with a 200 status code\n});\n\n// Make a request to the mock server using the specified path\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance, allowing method chaining for additional configuration.\n\n", + "path_excludes": "Specifies a substring that the URL path must *not* contain for the mock server to respond.\nThis constraint is useful for excluding requests to paths containing particular segments or patterns.\n\n# Parameters\n- `substring`: A string or other value convertible to `String` representing the substring that should not appear in the URL path.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any path not containing the substring \"xyz\"\nlet mock = server.mock(|when, then| {\n when.path_excludes(\"xyz\");\n then.status(200); // Respond with status 200 for paths excluding \"xyz\"\n});\n\n// Make a request to a path that does not contain \"xyz\"\nlet response = reqwest::blocking::get(server.url(\"/testpath\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure the mock server returned the expected response\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to enable method chaining for additional configuration.\n\n", + "path_includes": "Specifies a substring that the URL path must contain for the mock server to respond.\nThis constraint is useful for matching URLs based on partial segments, especially when exact path matching isn't required.\n\n# Parameters\n- `substring`: A string or any value convertible to `String` representing the substring that must be present in the URL path.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any path containing the substring \"es\"\nlet mock = server.mock(|when, then| {\n when.path_includes(\"es\");\n then.status(200); // Respond with a 200 status code for matched requests\n});\n\n// Make a request to a path containing \"es\" to trigger the mock response\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for further configuration.\n\n", + "path_matches": "Specifies a regular expression that the URL path must match for the mock server to respond.\nThis method allows flexible matching using regex patterns, making it useful for various matching scenarios.\n\n# Parameters\n- `regex`: An expression that implements `Into`, representing the regex pattern to match against the URL path.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches paths ending with the suffix \"le\"\nlet mock = server.mock(|when, then| {\n when.path_matches(r\"le$\");\n then.status(200); // Respond with a 200 status code for paths matching the pattern\n});\n\n// Make a request to a path ending with \"le\"\nlet response = reqwest::blocking::get(server.url(\"/example\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock server returned the expected response\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n# Errors\nThis function will panic if the provided regex pattern is invalid.\n\n", + "path_not": "Specifies the URL path that incoming requests must *not* match for the mock server to respond.\nThis is helpful when you need to exclude specific endpoints while allowing others through.\n\nTo add multiple excluded paths, invoke this function multiple times.\n\n# Parameters\n- `path`: A string or other value convertible to `String` that represents the URL path to exclude.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that will not match requests to `/exclude`\nlet mock = server.mock(|when, then| {\n when.path_not(\"/exclude\");\n then.status(200); // Respond with status 200 for all other paths\n});\n\n// Make a request to a path that does not match the exclusion\nlet response = reqwest::blocking::get(server.url(\"/include\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance, allowing method chaining for further configuration.\n\n", + "path_prefix": "Specifies a prefix that the URL path must start with for the mock server to respond.\nThis is useful when only the initial segments of a path need to be validated, such as checking specific API routes.\n\n# Parameters\n- `prefix`: A string or other value convertible to `String` representing the prefix that the URL path should start with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any path starting with the prefix \"/api\"\nlet mock = server.mock(|when, then| {\n when.path_prefix(\"/api\");\n then.status(200); // Respond with a 200 status code for matched requests\n});\n\n// Make a request to a path starting with \"/api\"\nlet response = reqwest::blocking::get(server.url(\"/api/v1/resource\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for further configuration.\n\n", + "path_prefix_not": "Specifies a prefix that the URL path must not start with for the mock server to respond.\nThis constraint is useful for excluding paths that begin with particular segments or patterns.\n\n# Parameters\n- `prefix`: A string or other value convertible to `String` representing the prefix that the URL path should not start with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any path not starting with the prefix \"/admin\"\nlet mock = server.mock(|when, then| {\n when.path_prefix_not(\"/admin\");\n then.status(200); // Respond with status 200 for paths excluding \"/admin\"\n});\n\n// Make a request to a path that does not start with \"/admin\"\nlet response = reqwest::blocking::get(server.url(\"/public/home\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock server returned the expected response\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "path_suffix": "Specifies a suffix that the URL path must end with for the mock server to respond.\nThis is useful when the final segments of a path need to be validated, such as file extensions or specific patterns.\n\n# Parameters\n- `suffix`: A string or other value convertible to `String` representing the suffix that the URL path should end with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any path ending with the suffix \".html\"\nlet mock = server.mock(|when, then| {\n when.path_suffix(\".html\");\n then.status(200); // Respond with a 200 status code for matched requests\n});\n\n// Make a request to a path ending with \".html\"\nlet response = reqwest::blocking::get(server.url(\"/about/index.html\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for further configuration.\n\n", + "path_suffix_not": "Specifies a suffix that the URL path must not end with for the mock server to respond.\nThis constraint is useful for excluding paths with specific file extensions or patterns.\n\n# Parameters\n- `suffix`: A string or other value convertible to `String` representing the suffix that the URL path should not end with.\n\n# Example\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that matches any path not ending with the suffix \".json\"\nlet mock = server.mock(|when, then| {\n when.path_suffix_not(\".json\");\n then.status(200); // Respond with a 200 status code for paths excluding \".json\"\n});\n\n// Make a request to a path that does not end with \".json\"\nlet response = reqwest::blocking::get(server.url(\"/about/index.html\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for further configuration.\n\n", + "port": "Specifies the expected port number for incoming requests to match.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\n# Parameters\n- `port`: A value convertible to `u16`, representing the expected port number.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Configure a mock to respond to requests made to `github.com`\n// with a specific port\nserver.mock(|when, then| {\n when.port(80); // Specify the expected port\n then.body(\"This is a mock response\");\n});\n\n// Set up an HTTP client to use the mock server as a proxy\nlet client = Client::builder()\n // Proxy all requests to the mock server\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\n// Send a GET request to `github.com` on port 80.\n// The request will be sent to our mock server due to the HTTP client proxy settings.\nlet response = client.get(\"http://github.com:80\").send().unwrap();\n\n// Validate that the mock server returned the expected response\nassert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n\n# Errors\n- This function will panic if the port number cannot be converted to a valid `u16` value.\n\n# Returns\nThe updated `When` instance to allow method chaining.\n\n", + "port_not": "Specifies the port number that incoming requests must *not* match.\n\nThis constraint is especially useful when working with proxy or forwarding rules, but it\ncan also be used to serve mocks (e.g., when using a mock server as a proxy).\n\nTo add multiple excluded ports, invoke this function multiple times.\n\n# Parameters\n- `port`: A value convertible to `u16`, representing the port number to be excluded.\n\n# Example\n```rust\nuse httpmock::prelude::*;\nuse reqwest::blocking::Client;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Configure a mock to respond to requests not using port 81\nserver.mock(|when, then| {\n when.port_not(81); // Exclude requests on port 81\n then.body(\"This is a mock response\");\n});\n\n// Set up an HTTP client to use the mock server as a proxy\nlet client = Client::builder()\n .proxy(reqwest::Proxy::all(&server.base_url()).unwrap())\n .build()\n .unwrap();\n\n// Make a request to `github.com` on port 80, which will trigger\n// the mock response\nlet response = client.get(\"http://github.com:80\").send().unwrap();\n\n// Validate that the mock server returned the expected response\nassert_eq!(response.text().unwrap(), \"This is a mock response\");\n```\n\n# Errors\n- This function will panic if the port number cannot be converted to a valid `u16` value.\n\n# Returns\nThe updated `When` instance to enable method chaining.\n\n", + "query_param": "Specifies a required query parameter for the request.\nThis function ensures that the specified query parameter (key-value pair) must be included\nin the request URL for the mock server to respond.\n\n**Note**: The request query keys and values are implicitly *allowed but not required* to be URL-encoded.\nHowever, the value passed to this method should always be in plain text (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to match against.\n- `value`: The expected value of the query parameter.\n\n# Example\n```rust\n// Arrange\nuse reqwest::blocking::get;\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query` to have the value \"This is cool\"\nlet m = server.mock(|when, then| {\n when.query_param(\"query\", \"This is cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that includes the specified query parameter and value\nget(&server.url(\"/search?query=This+is+cool\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "query_param_count": "Specifies that the count of query parameters with keys and values matching specific regular\nexpression patterns must equal a specified number for the request to match.\nThis function ensures that the number of query parameters whose keys and values match the\ngiven regex patterns is equal to the specified count in the request URL for the mock\nserver to respond.\n\n# Parameters\n- `key_regex`: A regular expression pattern for the query parameter's key to match against.\n- `value_regex`: A regular expression pattern for the query parameter's value to match against.\n- `expected_count`: The expected number of query parameters whose keys and values match the regex patterns.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects exactly two query parameters with keys matching the regex \"user.*\"\n// and values matching the regex \"admin.*\"\nlet m = server.mock(|when, then| {\n when.query_param_count(r\"user.*\", r\"admin.*\", 2);\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that matches the conditions\nreqwest::blocking::get(&server.url(\"/search?user1=admin1&user2=admin2\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_excludes": "Specifies that a query parameter's value (**not** the key) must not contain a specific substring for the request to match.\n\nThis function ensures that the specified query parameter (key) does exist in the request URL, and\nit does not have a value containing the given substring for the mock server to respond.\n\n**Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded.\nHowever, provide the substring in plain text here (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to match against.\n- `substring`: The substring that must not appear within the value of the query parameter.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query`\n// to have a value that does not contain \"uncool\"\nlet m = server.mock(|when, then| {\n when.query_param_excludes(\"query\", \"uncool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that includes a value not containing the substring \"uncool\"\nreqwest::blocking::get(&server.url(\"/search?query=Something+cool\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_exists": "Specifies that a query parameter must be present in an HTTP request.\nThis function ensures that the specified query parameter key exists in the request URL\nfor the mock server to respond, regardless of the parameter's value.\n\n**Note**: The query key in the request is implicitly *allowed but not required* to be URL-encoded.\nHowever, provide the key in plain text here (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter that must exist in the request.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query` to exist, regardless of its value\nlet m = server.mock(|when, then| {\n when.query_param_exists(\"query\");\n then.status(200); // Respond with a 200 status code if the parameter exists\n});\n\n// Act: Make a request with the specified query parameter\nreqwest::blocking::get(&server.url(\"/search?query=restaurants+near+me\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_includes": "Specifies that a query parameter's value (**not** the key) must contain a specific substring for the request to match.\nThis function ensures that the specified query parameter (key) does exist in the request URL, and\nit does have a value containing the given substring for the mock server to respond.\n\n**Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded.\nHowever, provide the substring in plain text (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to match against.\n- `substring`: The substring that must appear within the value of the query parameter.\n\n# Example\n```rust\n// Arrange\nuse reqwest::blocking::get;\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query`\n// to have a value containing \"cool\"\nlet m = server.mock(|when, then| {\n when.query_param_includes(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that includes a value containing the substring \"cool\"\nget(server.url(\"/search?query=Something+cool\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_matches": "Specifies that a query parameter must match a specific regular expression pattern for the key and another pattern for the value.\nThis function ensures that the specified query parameter key-value pair matches the given patterns\nin the request URL for the mock server to respond.\n\n# Parameters\n- `key_regex`: A regular expression pattern for the query parameter's key to match against.\n- `value_regex`: A regular expression pattern for the query parameter's value to match against.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter key to match the regex \"user.*\"\n// and the value to match the regex \"admin.*\"\nlet m = server.mock(|when, then| {\n when.query_param_matches(r\"user.*\", r\"admin.*\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that matches the regex patterns for both key and value\nreqwest::blocking::get(&server.url(\"/search?user=admin_user\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_missing": "Specifies that a query parameter must *not* be present in an HTTP request.\nThis function ensures that the specified query parameter key is absent in the request URL\nfor the mock server to respond, regardless of the parameter's value.\n\n**Note**: The request query key is implicitly *allowed but not required* to be URL-encoded.\nHowever, provide the key in plain text (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter that should be missing from the request.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query` to be missing\nlet m = server.mock(|when, then| {\n when.query_param_missing(\"query\");\n then.status(200); // Respond with a 200 status code if the parameter is absent\n});\n\n// Act: Make a request without the specified query parameter\nreqwest::blocking::get(&server.url(\"/search\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_not": "This function ensures that the specified query parameter (key) does exist in the request URL,\nand its value is not equal to the specified value.\n\n**Note**: Query keys and values are implicitly *allowed but not required* to be URL-encoded\nin the HTTP request. However, values passed to this method should always be in plain text\n(i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to ensure is not present.\n- `value`: The value of the query parameter to ensure is not present.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query` to NOT have the value \"This is cool\"\nlet m = server.mock(|when, then| {\n when.query_param_not(\"query\", \"This is cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that does not include the specified query parameter and value\nlet response = reqwest::blocking::get(&server.url(\"/search?query=awesome\")).unwrap();\n\n// Assert: Verify that the mock was called\nassert_eq!(response.status(), 200);\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n", + "query_param_prefix": "Specifies that a query parameter's value (**not** the key) must start with a specific prefix for the request to match.\nThis function ensures that the specified query parameter (key) has a value starting with the given prefix\nin the request URL for the mock server to respond.\n\n**Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded.\nProvide the prefix in plain text here (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to match against.\n- `prefix`: The prefix that the query parameter value should start with.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query`\n// to have a value starting with \"cool\"\nlet m = server.mock(|when, then| {\n when.query_param_prefix(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that includes a value starting with the prefix \"cool\"\nreqwest::blocking::get(&server.url(\"/search?query=cool+stuff\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_prefix_not": "Specifies that a query parameter's value (**not** the key) must not start with a specific prefix for the request to match.\nThis function ensures that the specified query parameter (key) has a value not starting with the given prefix\nin the request URL for the mock server to respond.\n\n**Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded.\nProvide the prefix in plain text here (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to match against.\n- `prefix`: The prefix that the query parameter value should not start with.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query`\n// to have a value not starting with \"cool\"\nlet m = server.mock(|when, then| {\n when.query_param_prefix_not(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that does not start with the prefix \"cool\"\nreqwest::blocking::get(&server.url(\"/search?query=warm_stuff\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_suffix": "Specifies that a query parameter's value (**not** the key) must end with a specific suffix for the request to match.\nThis function ensures that the specified query parameter (key) has a value ending with the given suffix\nin the request URL for the mock server to respond.\n\n**Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded.\nProvide the suffix in plain text here (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to match against.\n- `suffix`: The suffix that the query parameter value should end with.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query`\n// to have a value ending with \"cool\"\nlet m = server.mock(|when, then| {\n when.query_param_suffix(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that includes a value ending with the suffix \"cool\"\nreqwest::blocking::get(&server.url(\"/search?query=really_cool\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "query_param_suffix_not": "Specifies that a query parameter's value (**not** the key) must not end with a specific suffix for the request to match.\nThis function ensures that the specified query parameter (key) has a value not ending with the given suffix\nin the request URL for the mock server to respond.\n\n**Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded.\nProvide the suffix in plain text here (i.e., not encoded).\n\n# Parameters\n- `name`: The name of the query parameter to match against.\n- `suffix`: The suffix that the query parameter value should not end with.\n\n# Example\n```rust\n// Arrange\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that expects the query parameter `query`\n// to have a value not ending with \"cool\"\nlet m = server.mock(|when, then| {\n when.query_param_suffix_not(\"query\", \"cool\");\n then.status(200); // Respond with a 200 status code if the condition is met\n});\n\n// Act: Make a request that doesn't end with the suffix \"cool\"\nreqwest::blocking::get(&server.url(\"/search?query=uncool_stuff\")).unwrap();\n\n// Assert: Verify that the mock was called at least once\nm.assert();\n```\n\n# Returns\nThe updated `When` instance to allow method chaining for additional configuration.\n\n", + "scheme": "Specifies the scheme (e.g., \"http\" or \"https\") that requests must match for the mock server to respond.\n\nThis method sets the scheme to filter requests and ensures that the mock server only matches\nrequests with the specified scheme. This allows for more precise testing in environments where\nmultiple protocols are used.\n\n**Note**: Scheme matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that only matches requests with the \"http\" scheme\nlet mock = server.mock(|when, then| {\n when.scheme(\"http\"); // Restrict to the \"http\" scheme\n then.status(200); // Respond with status code 200 for all matched requests\n});\n\n// Make an \"http\" request to the server's URL to trigger the mock\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Verify that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `scheme`: A string specifying the scheme that requests should match. Common values include \"http\" and \"https\".\n\n# Returns\nThe modified `When` instance to allow for method chaining.\n\n", + "scheme_not": "Specifies a scheme (e.g., \"https\") that requests must not match for the mock server to respond.\n\nThis method allows you to exclude specific schemes from matching, ensuring that the mock server\nwon't respond to requests using those protocols. This is useful when you want to mock server\nbehavior based on protocol security requirements or other criteria.\n\n**Note**: Scheme matching is case-insensitive, conforming to\n[RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2).\n\n# Example\n\n```rust\nuse httpmock::prelude::*;\n\n// Start a new mock server\nlet server = MockServer::start();\n\n// Create a mock that will only match requests that do not use the \"https\" scheme\nlet mock = server.mock(|when, then| {\n when.scheme_not(\"https\"); // Exclude the \"https\" scheme from matching\n then.status(200); // Respond with status code 200 for all matched requests\n});\n\n// Make a request to the server's URL with the \"http\" scheme to trigger the mock\nlet response = reqwest::blocking::get(server.url(\"/test\")).unwrap();\n\n// Ensure the request was successful\nassert_eq!(response.status(), 200);\n\n// Ensure that the mock was called at least once\nmock.assert();\n```\n\n# Parameters\n- `scheme`: A string specifying the scheme that requests should not match. Common values include \"http\" and \"https\".\n\n# Returns\nThe modified `When` instance to allow for method chaining.\n\n" + } +} \ No newline at end of file diff --git a/docs/website/generated/example_tests.json b/docs/website/generated/example_tests.json new file mode 100644 index 00000000..81a8eea2 --- /dev/null +++ b/docs/website/generated/example_tests.json @@ -0,0 +1,7 @@ +{ + "record-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn record_github_api_with_forwarding_test() {\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target\n // host instead of answering with a mocked response. The 'when'\n // variable lets you configure rules under which forwarding\n // should take place.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Ensure all requests are forwarded.\n });\n });\n\n let recording = server.record(|rule| {\n rule\n // Specify which headers to record.\n // Only the headers listed here will be captured and stored\n // as part of the recorded mock. This selective recording is\n // necessary because some headers may vary between requests\n // and could cause issues when replaying the mock later.\n // For instance, headers like 'Authorization' or 'Date' may\n // change with each request.\n .record_request_header(\"User-Agent\")\n .filter(|when| {\n when.any_request(); // Ensure all requests are recorded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request\n // will be forwarded to the GitHub API, as we configured before.\n let client = Client::new();\n\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires us to send a user agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Since the request was forwarded, we should see a GitHub API response.\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(true, response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recording to\n // \"target/httpmock/recordings/github-torvalds-scenario_.yaml\".\n recording\n .save(\"github-torvalds-scenario\")\n .expect(\"cannot store scenario on disk\");\n}", + "playback-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn playback_github_api() {\n // Start a mock server for the test\n let server = MockServer::start();\n\n // Configure the mock server to forward requests to the target\n // host (GitHub API) instead of responding with a mock. The 'rule'\n // parameter allows you to define conditions under which forwarding\n // should occur.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Forward all requests.\n });\n });\n\n // Set up recording to capture all forwarded requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests and responses.\n });\n });\n\n // Send an HTTP request to the mock server, which will be forwarded\n // to the GitHub API\n let client = Client::new();\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires a User-Agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Assert that the response from the forwarded request is as expected\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recorded interactions to a file\n let target_path = recording\n .save(\"github-torvalds-scenario\")\n .expect(\"Failed to save the recording to disk\");\n\n // Start a new mock server instance for playback\n let playback_server = MockServer::start();\n\n // Load the recorded interactions into the new mock server\n playback_server.playback(target_path);\n\n // Send a request to the playback server and verify the response\n // matches the recorded data\n let response = client\n .get(playback_server.url(\"/repos/torvalds/linux\"))\n .send()\n .unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}", + "record-proxy-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn record_with_proxy_test() {\n // Start a mock server to act as a proxy for the HTTP client\n let server = MockServer::start();\n\n // Configure the mock server to proxy all incoming requests\n server.proxy(|rule| {\n rule.filter(|when| {\n when.any_request(); // Intercept all requests\n });\n });\n\n // Set up recording on the mock server to capture all proxied\n // requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests\n });\n });\n\n // Create an HTTP client configured to route requests\n // through the mock proxy server\n let github_client = Client::builder()\n // Set the proxy URL to the mock server's URL\n .proxy(reqwest::Proxy::all(server.base_url()).unwrap())\n .build()\n .unwrap();\n\n // Send a GET request using the client, which will be proxied by the mock server\n let response = github_client.get(server.base_url()).send().unwrap();\n\n // Verify that the response matches the expected mock response\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n\n // Save the recorded HTTP interactions to a file for future reference or testing\n recording\n .save(\"my_scenario_name\")\n .expect(\"could not save the recording\");\n}", + "forwarding-github": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forward_to_github_test() {\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target\n // host instead of answering with a mocked response. The 'when'\n // variable lets you configure rules under which forwarding\n // should take place.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Ensure all requests are forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request\n // will be forwarded to the GitHub API, as we configured before.\n let client = Client::new();\n\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires us to send a user agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Since the request was forwarded, we should see a GitHub API response.\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(true, response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}", + "forwarding": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forwarding_test() {\n // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.).\n let target_server = MockServer::start();\n target_server.mock(|when, then| {\n when.any_request();\n then.status(200).body(\"Hi from fake GitHub!\");\n });\n\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target host instead of\n // answering with a mocked response. The 'when' variable lets you configure\n // rules under which forwarding should take place.\n server.forward_to(target_server.base_url(), |rule| {\n rule.filter(|when| {\n when.any_request(); // We want all requests to be forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request will be forwarded\n // to the target host, as we configured before.\n let client = Client::new();\n\n // Since the request was forwarded, we should see the target host's response.\n let response = client.get(server.url(\"/get\")).send().unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(response.text().unwrap(), \"Hi from fake GitHub!\");\n}" +} \ No newline at end of file diff --git a/docs/website/generated/groups.json b/docs/website/generated/groups.json new file mode 100644 index 00000000..cb78fd8c --- /dev/null +++ b/docs/website/generated/groups.json @@ -0,0 +1,398 @@ +{ + "then": [ + { + "group": "Status", + "method": "status" + }, + { + "group": "Body", + "method": "body" + }, + { + "group": "Body", + "method": "body_from_file" + }, + { + "group": "Body", + "method": "json_body" + }, + { + "group": "Body", + "method": "json_body_obj" + }, + { + "group": "Headers", + "method": "header" + }, + { + "group": "Network", + "method": "delay" + }, + { + "group": "Miscellaneous", + "method": "and" + } + ], + "when": [ + { + "group": "Miscellaneous", + "method": "any_request" + }, + { + "group": "Scheme", + "method": "scheme" + }, + { + "group": "Scheme", + "method": "scheme_not" + }, + { + "group": "Method", + "method": "method" + }, + { + "group": "Method", + "method": "method_not" + }, + { + "group": "Host", + "method": "host" + }, + { + "group": "Host", + "method": "host_not" + }, + { + "group": "Host", + "method": "host_includes" + }, + { + "group": "Host", + "method": "host_excludes" + }, + { + "group": "Host", + "method": "host_prefix" + }, + { + "group": "Host", + "method": "host_suffix" + }, + { + "group": "Host", + "method": "host_prefix_not" + }, + { + "group": "Host", + "method": "host_suffix_not" + }, + { + "group": "Host", + "method": "host_matches" + }, + { + "group": "Port", + "method": "port" + }, + { + "group": "Port", + "method": "port_not" + }, + { + "group": "Path", + "method": "path" + }, + { + "group": "Path", + "method": "path_not" + }, + { + "group": "Path", + "method": "path_includes" + }, + { + "group": "Path", + "method": "path_excludes" + }, + { + "group": "Path", + "method": "path_prefix" + }, + { + "group": "Path", + "method": "path_suffix" + }, + { + "group": "Path", + "method": "path_prefix_not" + }, + { + "group": "Path", + "method": "path_suffix_not" + }, + { + "group": "Path", + "method": "path_matches" + }, + { + "group": "Query Parameters", + "method": "query_param" + }, + { + "group": "Query Parameters", + "method": "query_param_not" + }, + { + "group": "Query Parameters", + "method": "query_param_exists" + }, + { + "group": "Query Parameters", + "method": "query_param_missing" + }, + { + "group": "Query Parameters", + "method": "query_param_includes" + }, + { + "group": "Query Parameters", + "method": "query_param_excludes" + }, + { + "group": "Query Parameters", + "method": "query_param_prefix" + }, + { + "group": "Query Parameters", + "method": "query_param_suffix" + }, + { + "group": "Query Parameters", + "method": "query_param_prefix_not" + }, + { + "group": "Query Parameters", + "method": "query_param_suffix_not" + }, + { + "group": "Query Parameters", + "method": "query_param_matches" + }, + { + "group": "Query Parameters", + "method": "query_param_count" + }, + { + "group": "Headers", + "method": "header" + }, + { + "group": "Headers", + "method": "header_not" + }, + { + "group": "Headers", + "method": "header_exists" + }, + { + "group": "Headers", + "method": "header_missing" + }, + { + "group": "Headers", + "method": "header_includes" + }, + { + "group": "Headers", + "method": "header_excludes" + }, + { + "group": "Headers", + "method": "header_prefix" + }, + { + "group": "Headers", + "method": "header_suffix" + }, + { + "group": "Headers", + "method": "header_prefix_not" + }, + { + "group": "Headers", + "method": "header_suffix_not" + }, + { + "group": "Headers", + "method": "header_matches" + }, + { + "group": "Headers", + "method": "header_count" + }, + { + "group": "Cookies", + "method": "cookie" + }, + { + "group": "Cookies", + "method": "cookie_not" + }, + { + "group": "Cookies", + "method": "cookie_exists" + }, + { + "group": "Cookies", + "method": "cookie_missing" + }, + { + "group": "Cookies", + "method": "cookie_includes" + }, + { + "group": "Cookies", + "method": "cookie_excludes" + }, + { + "group": "Cookies", + "method": "cookie_prefix" + }, + { + "group": "Cookies", + "method": "cookie_suffix" + }, + { + "group": "Cookies", + "method": "cookie_prefix_not" + }, + { + "group": "Cookies", + "method": "cookie_suffix_not" + }, + { + "group": "Cookies", + "method": "cookie_matches" + }, + { + "group": "Cookies", + "method": "cookie_count" + }, + { + "group": "Body", + "method": "body" + }, + { + "group": "Body", + "method": "body_not" + }, + { + "group": "Body", + "method": "body_includes" + }, + { + "group": "Body", + "method": "body_excludes" + }, + { + "group": "Body", + "method": "body_prefix" + }, + { + "group": "Body", + "method": "body_suffix" + }, + { + "group": "Body", + "method": "body_prefix_not" + }, + { + "group": "Body", + "method": "body_suffix_not" + }, + { + "group": "Body", + "method": "body_matches" + }, + { + "group": "Body", + "method": "json_body" + }, + { + "group": "Body", + "method": "json_body_obj" + }, + { + "group": "Body", + "method": "json_body_includes" + }, + { + "group": "Body", + "method": "json_body_excludes" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_not" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_exists" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_missing" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_includes" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_excludes" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_prefix" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_prefix_not" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_suffix" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_suffix_not" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_matches" + }, + { + "group": "Body", + "method": "form_urlencoded_tuple_count" + }, + { + "group": "Custom", + "method": "matches" + }, + { + "group": "Custom", + "method": "is_true" + }, + { + "group": "Custom", + "method": "is_false" + }, + { + "group": "Miscellaneous", + "method": "and" + } + ] +} \ No newline at end of file diff --git a/docs/website/package-lock.json b/docs/website/package-lock.json new file mode 100644 index 00000000..c8dd99b9 --- /dev/null +++ b/docs/website/package-lock.json @@ -0,0 +1,8807 @@ +{ + "name": "tutorial", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tutorial", + "version": "0.0.1", + "dependencies": { + "@astrojs/check": "^0.5.10", + "@astrojs/starlight": "^0.21.5", + "astro": "^4.3.5", + "sharp": "^0.32.5", + "typescript": "^5.4.4" + }, + "devDependencies": { + "handlebars": "^4.7.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@astrojs/check": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.5.10.tgz", + "integrity": "sha512-vliHXM9cu/viGeKiksUM4mXfO816ohWtawTl2ADPgTsd4nUMjFiyAl7xFZhF34yy4hq4qf7jvK1F2PlR3b5I5w==", + "dependencies": { + "@astrojs/language-server": "^2.8.4", + "chokidar": "^3.5.3", + "fast-glob": "^3.3.1", + "kleur": "^4.1.5", + "yargs": "^17.7.2" + }, + "bin": { + "astro-check": "dist/bin.js" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.7.1.tgz", + "integrity": "sha512-/POejAYuj8WEw7ZI0J8JBvevjfp9jQ9Wmu/Bg52RiNwGXkMV7JnYpsenVfHvvf1G7R5sXHGKlTcxlQWhoUTiGQ==" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.4.0.tgz", + "integrity": "sha512-6B13lz5n6BrbTqCTwhXjJXuR1sqiX/H6rTxzlXx+lN1NnV4jgnq/KJldCQaUWJzPL5SiWahQyinxAbxQtwgPHA==" + }, + "node_modules/@astrojs/language-server": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.8.4.tgz", + "integrity": "sha512-sJH5vGTBkhgA8+hdhzX78UUp4cFz4Mt7xkEkevD188OS5bDMkaue6hK+dtXWM47mnrXFveXA2u38K7S+5+IRjA==", + "dependencies": { + "@astrojs/compiler": "^2.7.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@volar/kit": "~2.1.5", + "@volar/language-core": "~2.1.5", + "@volar/language-server": "~2.1.5", + "@volar/language-service": "~2.1.5", + "@volar/typescript": "~2.1.5", + "fast-glob": "^3.2.12", + "volar-service-css": "0.0.34", + "volar-service-emmet": "0.0.34", + "volar-service-html": "0.0.34", + "volar-service-prettier": "0.0.34", + "volar-service-typescript": "0.0.34", + "volar-service-typescript-twoslash-queries": "0.0.34", + "vscode-html-languageservice": "^5.1.2", + "vscode-uri": "^3.0.8" + }, + "bin": { + "astro-ls": "bin/nodeServer.js" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-astro": ">=0.11.0" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + } + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-5.0.0.tgz", + "integrity": "sha512-QBXbxXZamVRoqCNN2gjDXa7qYPUkJZq7KYFfg3DX7rze3QL6xiz4N+Wg202dNPRaIkQa16BV6D8+EHibQFubRg==", + "dependencies": { + "@astrojs/prism": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "import-meta-resolve": "^4.0.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.0", + "remark-gfm": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "remark-smartypants": "^2.0.0", + "shiki": "^1.1.2", + "unified": "^11.0.4", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.1" + } + }, + "node_modules/@astrojs/mdx": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-2.2.4.tgz", + "integrity": "sha512-eXCmftMsWj4vTECrc4vgdaXrA8xIvrmJ9rM37BZNK5anQ2PunUm9N8wbnK2VVTE0CAmW5U8v9y3IGps2RYGUvQ==", + "dependencies": { + "@astrojs/markdown-remark": "5.0.0", + "@mdx-js/mdx": "^3.0.0", + "acorn": "^8.11.2", + "es-module-lexer": "^1.4.1", + "estree-util-visit": "^2.0.0", + "github-slugger": "^2.0.0", + "gray-matter": "^4.0.3", + "hast-util-to-html": "^9.0.0", + "kleur": "^4.1.4", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.0", + "remark-smartypants": "^2.0.0", + "source-map": "^0.7.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.1" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "astro": "^4.0.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.0.0.tgz", + "integrity": "sha512-g61lZupWq1bYbcBnYZqdjndShr/J3l/oFobBKPA3+qMat146zce3nz2kdO4giGbhYDt4gYdhmoBz0vZJ4sIurQ==", + "dependencies": { + "prismjs": "^1.29.0" + }, + "engines": { + "node": ">=18.14.1" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.1.2.tgz", + "integrity": "sha512-FxOJldIl5ltZ5CNjocQxHkAO9orwHBjqtaU28o4smobp9vowS0nbGp+I9CrPxkzWdl1crSDm9vjL9tnvG1DSug==", + "dependencies": { + "sitemap": "^7.1.1", + "zod": "^3.22.4" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.21.5.tgz", + "integrity": "sha512-cvftxu7DM4C25KGSxqyIk81DiQGX0zx9s5sfmprd1kKQK1h/MQXaRVDCpJrK4SjrgWtpG1UoKLJZBgD5w4k9kw==", + "dependencies": { + "@astrojs/mdx": "^2.1.1", + "@astrojs/sitemap": "^3.0.5", + "@pagefind/default-ui": "^1.0.3", + "@types/hast": "^3.0.3", + "@types/mdast": "^4.0.3", + "astro-expressive-code": "^0.33.4", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^8.0.0", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "pagefind": "^1.0.3", + "rehype": "^13.0.1", + "remark-directive": "^3.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.1" + }, + "peerDependencies": { + "astro": "^4.2.7" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.0.4.tgz", + "integrity": "sha512-A+0c7k/Xy293xx6odsYZuXiaHO0PL+bnDoXOc47sGDF5ffIKdKQGRPFl2NMlCF4L0NqN4Ynbgnaip+pPF0s7pQ==", + "dependencies": { + "ci-info": "^3.8.0", + "debug": "^4.3.4", + "dlv": "^1.1.3", + "dset": "^3.1.2", + "is-docker": "^3.0.0", + "is-wsl": "^3.0.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": ">=18.14.1" + } + }, + "node_modules/@astrojs/telemetry/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emmetio/abbreviation": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", + "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", + "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", + "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.33.5.tgz", + "integrity": "sha512-KL0EkKAvd7SSIQL3ZIP19xqe4xNjBaQYNvcJC6RmoBUnQpvxaJNFwRxCBEF/X0ftJEMaSG7WTrabZ9c/zFeqmA==", + "dependencies": { + "@ctrl/tinycolor": "^3.6.0", + "hast-util-to-html": "^8.0.4", + "hastscript": "^7.2.0", + "postcss": "^8.4.21", + "postcss-nested": "^6.0.1" + } + }, + "node_modules/@expressive-code/core/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@expressive-code/core/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@expressive-code/core/node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@expressive-code/core/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/@expressive-code/core/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/core/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.33.5.tgz", + "integrity": "sha512-lFt/gbnZscBSxHovg4XiWohp5nrxk4McS6RGABdj6+0gJcX8/YrFTM23GKBIkaDePxdDidVY0jQYGYDL/RrQHw==", + "dependencies": { + "@expressive-code/core": "^0.33.5", + "hastscript": "^7.2.0" + } + }, + "node_modules/@expressive-code/plugin-frames/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@expressive-code/plugin-frames/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@expressive-code/plugin-frames/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/plugin-frames/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.33.5.tgz", + "integrity": "sha512-LWgttQTUrIPE1X+Lya1qFWiX47tH2AS2hkbj/cZoWkdiSjn6zUvtTypK/2Xn6Rgn6z6ClzpgHvkXRqFn7nAB4A==", + "dependencies": { + "@expressive-code/core": "^0.33.5", + "shiki": "^1.1.7" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.33.5.tgz", + "integrity": "sha512-JxSHL1MGrJAPNaUMjFXex3K+9NJDbfew9H6PmX8LQ+fm9VNQdtBYTAz/x7nqOk7bkTrtAZK5RfDqUfb8U5M+2A==", + "dependencies": { + "@expressive-code/core": "^0.33.5", + "hastscript": "^7.2.0", + "unist-util-visit-parents": "^5.1.3" + } + }, + "node_modules/@expressive-code/plugin-text-markers/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@expressive-code/plugin-text-markers/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@expressive-code/plugin-text-markers/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/plugin-text-markers/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/plugin-text-markers/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@expressive-code/plugin-text-markers/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", + "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-to-js": "^2.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-estree": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "periscopic": "^3.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.1.0.tgz", + "integrity": "sha512-SLsXNLtSilGZjvqis8sX42fBWsWAVkcDh1oerxwqbac84HbiwxpxOC2jm8hRwcR0Z55HPZPWO77XeRix/8GwTg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.1.0.tgz", + "integrity": "sha512-QjQSE/L5oS1C8N8GdljGaWtjCBMgMtfrPAoiCmINTu9Y9dp0ggAyXvF8K7Qg3VyIMYJ6v8vg2PN7Z3b+AaAqUA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.1.0.tgz", + "integrity": "sha512-+XiAJAK++C64nQcD7s3Prdmd5S92lT05fwjOxm0L1jj80jbL+tmvcqkkFnPpoqhnicIPgcAX/Y5W0HRZnBt35w==" + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.1.0.tgz", + "integrity": "sha512-8zjYCa2BtNEL7KnXtysPtBELCyv5DSQ4yHeK/nsEq6w4ToAMTBl0K06khqxdSGgjMSwwrxvLzq3so0LC5Q14dA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.1.0.tgz", + "integrity": "sha512-4lsg6VB7A6PWTwaP8oSmXV4O9H0IHX7AlwTDcfyT+YJo/sPXOVjqycD5cdBgqNLfUk8B9bkWcTDCRmJbHrKeCw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.1.0.tgz", + "integrity": "sha512-OboCM76BcMKT9IoSfZuFhiqMRgTde8x4qDDvKulFmycgiJrlL5WnIqBHJLQxZq+o2KyZpoHF97iwsGAm8c32sQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz", + "integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz", + "integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz", + "integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz", + "integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz", + "integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz", + "integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz", + "integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz", + "integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==", + "cpu": [ + "ppc64le" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz", + "integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz", + "integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz", + "integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz", + "integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz", + "integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz", + "integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz", + "integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.3.0.tgz", + "integrity": "sha512-7fedsBfuILDTBmrYZNFI8B6ATTxhQAasUHllHmjvSZPnoq4bULWoTpHwmuQvZ8Aq03/tAa2IGo6RXqWtHdWaCA==" + }, + "node_modules/@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", + "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.12.tgz", + "integrity": "sha512-H9VZ9YqE+H28FQVchC83RCs5xQ2J7mAAv6qdDEaWmXEVl3OpdH+xfrSUzQ1lp7U7oSTRZ0RvW08ASPJsYBi7Cw==" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/nlcst": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-1.0.4.tgz", + "integrity": "sha512-ABoYdNQ/kBSsLvZAekMhIPMQ3YUZvavStpKYs7BjLLuKVmIMA0LUgZ7b54zzuWJRbHF80v1cNf4r90Vd6eMQDg==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/nlcst/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/@volar/kit": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.1.6.tgz", + "integrity": "sha512-dSuXChDGM0nSG/0fxqlNfadjpAeeo1P1SJPBQ+pDf8H1XrqeJq5gIhxRTEbiS+dyNIG69ATq1CArkbCif+oxJw==", + "dependencies": { + "@volar/language-service": "2.1.6", + "@volar/typescript": "2.1.6", + "typesafe-path": "^0.2.2", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@volar/language-core": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.1.6.tgz", + "integrity": "sha512-pAlMCGX/HatBSiDFMdMyqUshkbwWbLxpN/RL7HCQDOo2gYBE+uS+nanosLc1qR6pTQ/U8q00xt8bdrrAFPSC0A==", + "dependencies": { + "@volar/source-map": "2.1.6" + } + }, + "node_modules/@volar/language-server": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.1.6.tgz", + "integrity": "sha512-0w+FV8ro37hVb3qE4ONo3VbS5kEQXv4H/D2xCePyY5dRw6XnbJAPFNKvoxI9mxHTPonvIG1si5rN9MSGSKtgZQ==", + "dependencies": { + "@volar/language-core": "2.1.6", + "@volar/language-service": "2.1.6", + "@volar/snapshot-document": "2.1.6", + "@volar/typescript": "2.1.6", + "@vscode/l10n": "^0.0.16", + "path-browserify": "^1.0.1", + "request-light": "^0.7.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/language-service": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.1.6.tgz", + "integrity": "sha512-1OpbbPQ6wUIumwMP5r45y8utVEmvq1n6BC8JHqGKsuFr9RGFIldDBlvA/xuO3MDKhjmmPGPHKb54kg1/YN78ow==", + "dependencies": { + "@volar/language-core": "2.1.6", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/snapshot-document": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@volar/snapshot-document/-/snapshot-document-2.1.6.tgz", + "integrity": "sha512-YNYk1sCOrGg7VHbZM+1It97q0GWhFxdqIwnxSNFoL0X1LuSRXoCT2DRb/aa1J6aBpPMbKqSFUWHGQEAFUnc4Zw==", + "dependencies": { + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11" + } + }, + "node_modules/@volar/source-map": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.1.6.tgz", + "integrity": "sha512-TeyH8pHHonRCHYI91J7fWUoxi0zWV8whZTVRlsWHSYfjm58Blalkf9LrZ+pj6OiverPTmrHRkBsG17ScQyWECw==", + "dependencies": { + "muggle-string": "^0.4.0" + } + }, + "node_modules/@volar/typescript": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.1.6.tgz", + "integrity": "sha512-JgPGhORHqXuyC3r6skPmPHIZj4LoMmGlYErFTuPNBq9Nhc9VTv7ctHY7A3jMN3ngKEfRrfnUcwXHztvdSQqNfw==", + "dependencies": { + "@volar/language-core": "2.1.6", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.9.2.tgz", + "integrity": "sha512-MaGuyW+fa13q3aYsluKqclmh62Hgp0BpKIqS66fCxfOaBcVQ1OnMQxRRgQUYnCkxFISAQlkJ0qWWPyXjro1Qrg==", + "dependencies": { + "emmet": "^2.4.3", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^2.1.2" + } + }, + "node_modules/@vscode/emmet-helper/node_modules/vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==" + }, + "node_modules/@vscode/l10n": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.16.tgz", + "integrity": "sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg==" + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", + "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "4.5.18", + "resolved": "https://registry.npmjs.org/astro/-/astro-4.5.18.tgz", + "integrity": "sha512-iytLnUfyUneKMjIQdj79zzniByXtcmGNDobIV/gjGsatC9vAyPqeCT8TbMqfkRBMeYGs+S/wCzSoPqaaMJiQnw==", + "dependencies": { + "@astrojs/compiler": "^2.7.1", + "@astrojs/internal-helpers": "0.4.0", + "@astrojs/markdown-remark": "5.0.0", + "@astrojs/telemetry": "3.0.4", + "@babel/core": "^7.24.3", + "@babel/generator": "^7.23.3", + "@babel/parser": "^7.23.3", + "@babel/plugin-transform-react-jsx": "^7.22.5", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", + "@types/babel__core": "^7.20.4", + "acorn": "^8.11.2", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "boxen": "^7.1.1", + "chokidar": "^3.5.3", + "ci-info": "^4.0.0", + "clsx": "^2.0.0", + "common-ancestor-path": "^1.0.1", + "cookie": "^0.6.0", + "cssesc": "^3.0.0", + "debug": "^4.3.4", + "deterministic-object-hash": "^2.0.1", + "devalue": "^4.3.2", + "diff": "^5.1.0", + "dlv": "^1.1.3", + "dset": "^3.1.3", + "es-module-lexer": "^1.4.1", + "esbuild": "^0.19.6", + "estree-walker": "^3.0.3", + "execa": "^8.0.1", + "fast-glob": "^3.3.2", + "flattie": "^1.1.0", + "github-slugger": "^2.0.0", + "gray-matter": "^4.0.3", + "html-escaper": "^3.0.3", + "http-cache-semantics": "^4.1.1", + "js-yaml": "^4.1.0", + "kleur": "^4.1.4", + "magic-string": "^0.30.3", + "mime": "^3.0.0", + "ora": "^7.0.1", + "p-limit": "^5.0.0", + "p-queue": "^8.0.1", + "path-to-regexp": "^6.2.1", + "preferred-pm": "^3.1.2", + "prompts": "^2.4.2", + "rehype": "^13.0.1", + "resolve": "^1.22.4", + "semver": "^7.5.4", + "shiki": "^1.1.2", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0", + "tsconfck": "^3.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.1", + "vite": "^5.1.4", + "vitefu": "^0.2.5", + "which-pm": "^2.1.1", + "yargs-parser": "^21.1.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.4" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": ">=18.14.1", + "npm": ">=6.14.0" + }, + "optionalDependencies": { + "sharp": "^0.32.6" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.33.5.tgz", + "integrity": "sha512-9JAyllueMUN8JTl/h/yTdbKinNmfalEWcV11s3lSf/UJQbAZfWJuy+IlGcArZDI/CmD21GXhFHLqYthpdY33ug==", + "dependencies": { + "hast-util-to-html": "^8.0.4", + "remark-expressive-code": "^0.33.5" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^3.3.0" + } + }, + "node_modules/astro-expressive-code/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/astro-expressive-code/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/astro-expressive-code/node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astro-expressive-code/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/astro-expressive-code/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/astro-expressive-code/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.2.3.tgz", + "integrity": "sha512-amG72llr9pstfXOBOHve1WjiuKKAMnebcmMbPWDZ7BCevAoJLpugjuAPRsDINEyjT0a6tbaVx3DctkXIRbLuJw==", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "streamx": "^2.13.0" + } + }, + "node_modules/bare-os": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.2.1.tgz", + "integrity": "sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.1.tgz", + "integrity": "sha512-OHM+iwRDRMDBsSW7kl3dO62JyHdBKO3B25FB9vNQBPcGHMo4+eA8Yj41Lfbk3pS/seDY+siNge0LdRTulAau/A==", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001608", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz", + "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/ci-info": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-selector-parser": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz", + "integrity": "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", + "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/dset": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz", + "integrity": "sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.731", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.731.tgz", + "integrity": "sha512-+TqVfZjpRz2V/5SPpmJxq9qK620SC5SqCnxQIOi7i/U08ZDcTpKbT7Xjj9FU5CbXTMUb4fywbIr8C7cGv4hcjw==" + }, + "node_modules/emmet": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.7.tgz", + "integrity": "sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==", + "dependencies": { + "@emmetio/abbreviation": "^2.3.3", + "@emmetio/css-abbreviation": "^2.1.8" + } + }, + "node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", + "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==" + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/expressive-code": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.33.5.tgz", + "integrity": "sha512-UPg2jSvZEfXPiCa4MKtMoMQ5Hwiv7In5/LSCa/ukhjzZqPO48iVsCcEBgXWEUmEAQ02P0z00/xFfBmVnUKH+Zw==", + "dependencies": { + "@expressive-code/core": "^0.33.5", + "@expressive-code/plugin-frames": "^0.33.5", + "@expressive-code/plugin-shiki": "^0.33.5", + "@expressive-code/plugin-text-markers": "^0.33.5" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-yarn-workspace-root2": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", + "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", + "dependencies": { + "micromatch": "^4.0.2", + "pkg-dir": "^4.2.0" + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.2.tgz", + "integrity": "sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.2.tgz", + "integrity": "sha512-hT/SD/d/Meu+iobvgkffo1QecV8WeKWxwsNMzcTJsKw1cKTQKSR/7ArJeURLNJF9HDjp9nVoORyNNJxrvBye8Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "not": "^0.1.0", + "nth-check": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", + "integrity": "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.1.tgz", + "integrity": "sha512-hZOofyZANbyWo+9RP75xIDV/gq+OUKx+T46IlwERnKmfpwp81XBFbT9mi26ws+SJchA4RVUQwIBJpqEOBhMzEQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^9.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", + "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/inline-style-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", + "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz", + "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==", + "dependencies": { + "inline-style-parser": "0.2.3" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", + "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.1.tgz", + "integrity": "sha512-RHL7Vo2n06ZocCFWqmbyhZ1pCYX/mSKdywt9YD5U6Hquu5syV+dImCXFKLFt02JoK5QxkQFS0PoVdFdPXuPffQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/load-yaml-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", + "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.13.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/load-yaml-file/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/load-yaml-file/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz", + "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", + "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", + "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz", + "integrity": "sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^5.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz", + "integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", + "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", + "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", + "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", + "integrity": "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz", + "integrity": "sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz", + "integrity": "sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz", + "integrity": "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", + "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nlcst-to-string": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz", + "integrity": "sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw==", + "dependencies": { + "@types/nlcst": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-abi": { + "version": "3.57.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.57.0.tgz", + "integrity": "sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/not": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz", + "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", + "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.3.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "string-width": "^6.1.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz", + "integrity": "sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz", + "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pagefind": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.1.0.tgz", + "integrity": "sha512-1nmj0/vfYcMxNEQj0YDRp6bTVv9hI7HLdPhK/vBBYlrnwjATndQvHyicj5Y7pUHrpCFZpFnLVQXIF829tpFmaw==", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.1.0", + "@pagefind/darwin-x64": "1.1.0", + "@pagefind/linux-arm64": "1.1.0", + "@pagefind/linux-x64": "1.1.0", + "@pagefind/windows-x64": "1.1.0" + } + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/parse-latin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-5.0.1.tgz", + "integrity": "sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg==", + "dependencies": { + "nlcst-to-string": "^3.0.0", + "unist-util-modify-children": "^3.0.0", + "unist-util-visit-children": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/prebuild-install/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/preferred-pm": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.1.3.tgz", + "integrity": "sha512-MkXsENfftWSRpzCzImcp4FRsCc3y1opwB73CfCNWyzMqArju2CrlMHlqB7VexKiPEOjGMbttv1r9fSCn5S610w==", + "dependencies": { + "find-up": "^5.0.0", + "find-yarn-workspace-root2": "1.2.16", + "path-exists": "^4.0.0", + "which-pm": "2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/preferred-pm/node_modules/which-pm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", + "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", + "dependencies": { + "load-yaml-file": "^0.2.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8.15" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rehype": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.1.tgz", + "integrity": "sha512-AcSLS2mItY+0fYu9xKxOu1LhUZeBZZBx8//5HKzF+0XP+eP8+6a5MXn2+DW2kfXR6Dtp1FEXMVrjyKAcvcU8vg==", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.0.tgz", + "integrity": "sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz", + "integrity": "sha512-1TX1i048LooI9QoecrXy7nGFFbFSufxVRAfc6Y9YMRAi56l+oB0zP51mLSV312uRuvVLPV1opSlJmslozR1XHQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz", + "integrity": "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/remark-expressive-code/-/remark-expressive-code-0.33.5.tgz", + "integrity": "sha512-E4CZq3AuUXLu6or0AaDKkgsHYqmnm4ZL8/+1/8YgwtKcogHwTMRJfQtxkZpth90QQoNUpsapvm5x5n3Np2OC9w==", + "dependencies": { + "expressive-code": "^0.33.5", + "hast-util-to-html": "^8.0.4", + "unist-util-visit": "^4.1.2" + } + }, + "node_modules/remark-expressive-code/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/remark-expressive-code/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/remark-expressive-code/node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-expressive-code/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/remark-expressive-code/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-expressive-code/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", + "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", + "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-2.1.0.tgz", + "integrity": "sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw==", + "dependencies": { + "retext": "^8.1.0", + "retext-smartypants": "^5.2.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/request-light": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", + "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/retext": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-8.1.0.tgz", + "integrity": "sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "retext-latin": "^3.0.0", + "retext-stringify": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-3.1.0.tgz", + "integrity": "sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "parse-latin": "^5.0.0", + "unherit": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/retext-latin/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-5.2.0.tgz", + "integrity": "sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/retext-smartypants/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-3.1.0.tgz", + "integrity": "sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w==", + "dependencies": { + "@types/nlcst": "^1.0.0", + "nlcst-to-string": "^3.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/retext-stringify/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/retext/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz", + "integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.14.1", + "@rollup/rollup-android-arm64": "4.14.1", + "@rollup/rollup-darwin-arm64": "4.14.1", + "@rollup/rollup-darwin-x64": "4.14.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.14.1", + "@rollup/rollup-linux-arm64-gnu": "4.14.1", + "@rollup/rollup-linux-arm64-musl": "4.14.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.14.1", + "@rollup/rollup-linux-riscv64-gnu": "4.14.1", + "@rollup/rollup-linux-s390x-gnu": "4.14.1", + "@rollup/rollup-linux-x64-gnu": "4.14.1", + "@rollup/rollup-linux-x64-musl": "4.14.1", + "@rollup/rollup-win32-arm64-msvc": "4.14.1", + "@rollup/rollup-win32-ia32-msvc": "4.14.1", + "@rollup/rollup-win32-x64-msvc": "4.14.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.3.0.tgz", + "integrity": "sha512-9aNdQy/etMXctnPzsje1h1XIGm9YfRcSksKOGqZWXA/qP9G18/8fpz5Bjpma8bOgz3tqIpjERAd6/lLjFyzoww==", + "dependencies": { + "@shikijs/core": "1.3.0" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/sitemap": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz", + "integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/streamx": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-fs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfck": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.0.3.tgz", + "integrity": "sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typesafe-path": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", + "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==" + }, + "node_modules/typescript": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-auto-import-cache": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.2.tgz", + "integrity": "sha512-+laqe5SFL1vN62FPOOJSUDTZxtgsoOXjneYOXIpx5rQ4UMiN89NAtJLpqLqyebv9fgQ/IMeeTX+mQyRnwvJzvg==", + "dependencies": { + "semver": "^7.3.8" + } + }, + "node_modules/uglify-js": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unherit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-3.0.1.tgz", + "integrity": "sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unified": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", + "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-3.1.1.tgz", + "integrity": "sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA==", + "dependencies": { + "@types/unist": "^2.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-2.0.2.tgz", + "integrity": "sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", + "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/volar-service-css": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.34.tgz", + "integrity": "sha512-C7ua0j80ZD7bsgALAz/cA1bykPehoIa5n+3+Ccr+YLpj0fypqw9iLUmGLX11CqzqNCO2XFGe/1eXB/c+SWrF/g==", + "dependencies": { + "vscode-css-languageservice": "^6.2.10", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.1.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-emmet": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.34.tgz", + "integrity": "sha512-ubQvMCmHPp8Ic82LMPkgrp9ot+u2p/RDd0RyT0EykRkZpWsagHUF5HWkVheLfiMyx2rFuWx/+7qZPOgypx6h6g==", + "dependencies": { + "@vscode/emmet-helper": "^2.9.2", + "vscode-html-languageservice": "^5.1.0" + }, + "peerDependencies": { + "@volar/language-service": "~2.1.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-html": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.34.tgz", + "integrity": "sha512-kMEneea1tQbiRcyKavqdrSVt8zV06t+0/3pGkjO3gV6sikXTNShIDkdtB4Tq9vE2cQdM50TuS7utVV7iysUxHw==", + "dependencies": { + "vscode-html-languageservice": "^5.1.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.1.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-prettier": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.34.tgz", + "integrity": "sha512-BNfJ8FwfPi1Wm/JkuzNjraOLdtKieGksNT/bDyquygVawv1QUzO2HB1hiMKfZGdcSFG5ZL9R0j7bBfRTfXA2gg==", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.1.0", + "prettier": "^2.2 || ^3.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + }, + "prettier": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.34.tgz", + "integrity": "sha512-NbAry0w8ZXFgGsflvMwmPDCzgJGx3C+eYxFEbldaumkpTAJiywECWiUbPIOfmEHgpOllUKSnhwtLlWFK4YnfQg==", + "dependencies": { + "path-browserify": "^1.0.1", + "semver": "^7.5.4", + "typescript-auto-import-cache": "^0.3.1", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-nls": "^5.2.0" + }, + "peerDependencies": { + "@volar/language-service": "~2.1.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript-twoslash-queries": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.34.tgz", + "integrity": "sha512-XAY2YtWKUp6ht89gxt3L5Dr46LU45d/VlBkj1KXUwNlinpoWiGN4Nm3B6DRF3VoBThAnQgm4c7WD0S+5yTzh+w==", + "peerDependencies": { + "@volar/language-service": "~2.1.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/vscode-css-languageservice": { + "version": "6.2.13", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.2.13.tgz", + "integrity": "sha512-2rKWXfH++Kxd9Z4QuEgd1IF7WmblWWU7DScuyf1YumoGLkY9DW6wF/OTlhOyO2rN63sWHX2dehIpKBbho4ZwvA==", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/vscode-css-languageservice/node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==" + }, + "node_modules/vscode-html-languageservice": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.2.0.tgz", + "integrity": "sha512-cdNMhyw57/SQzgUUGSIMQ66jikqEN6nBNyhx5YuOyj9310+eY9zw8Q0cXpiKzDX8aHYFewQEXRnigl06j/TVwQ==", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/vscode-html-languageservice/node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + }, + "node_modules/vscode-nls": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", + "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-pm": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.1.1.tgz", + "integrity": "sha512-xzzxNw2wMaoCWXiGE8IJ9wuPMU+EYhFksjHxrRT8kMT5SnocBPRg69YAMtyV4D12fP582RA+k3P8H9J5EMdIxQ==", + "dependencies": { + "load-yaml-file": "^0.2.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8.15" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz", + "integrity": "sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==", + "peerDependencies": { + "zod": "^3.22.4" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/website/package.json b/docs/website/package.json new file mode 100644 index 00000000..219d842b --- /dev/null +++ b/docs/website/package.json @@ -0,0 +1,25 @@ +{ + "name": "tutorial", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro", + "generate-when-method-docs" : "rm -rf src/content/docs/matching_requests && mkdir -p src/content/docs/matching_requests && node tools/generate-docs.cjs generated/docs.json ./templates/matching_requests src/content/docs/matching_requests", + "generate-then-method-docs" : "rm -rf src/content/docs/mocking_responses && mkdir -p src/content/docs/mocking_responses && node tools/generate-docs.cjs generated/docs.json ./templates/mocking_responses src/content/docs/mocking_responses", + "generate-docs": "npm run generate-when-method-docs && npm run generate-then-method-docs" + }, + "dependencies": { + "@astrojs/check": "^0.5.10", + "@astrojs/starlight": "^0.21.5", + "astro": "^4.3.5", + "sharp": "^0.32.5", + "typescript": "^5.4.4" + }, + "devDependencies": { + "handlebars": "^4.7.8" + } +} diff --git a/docs/website/public/favicon.svg b/docs/website/public/favicon.svg new file mode 100644 index 00000000..cba5ac14 --- /dev/null +++ b/docs/website/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/website/src/assets/houston.webp b/docs/website/src/assets/houston.webp new file mode 100644 index 0000000000000000000000000000000000000000..930c164974ad8eb528878f15a98016249b8cf546 GIT binary patch literal 98506 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 b6~t;_wBcJ3AWgga7~Sz~izoKbpAY~5r|m$8 literal 0 HcmV?d00001 diff --git a/docs/website/src/assets/landing.css b/docs/website/src/assets/landing.css new file mode 100644 index 00000000..416f9392 --- /dev/null +++ b/docs/website/src/assets/landing.css @@ -0,0 +1,45 @@ +:root { + --purple-hsl: 255, 60%, 60%; + --overlay-blurple: hsla(var(--purple-hsl), 0.2); +} + +:root[data-theme='light'] { + --purple-hsl: 255, 85%, 65%; +} + +[data-has-hero] .page { + background: + linear-gradient(215deg, var(--overlay-blurple), transparent 40%), + radial-gradient(var(--overlay-blurple), transparent 40%) no-repeat -60vw -40vh / 105vw 200vh, + radial-gradient(var(--overlay-blurple), transparent 65%) no-repeat 50% calc(100% + 20rem) / + 60rem 30rem; +} + +[data-has-hero] header { + border-bottom: 1px solid transparent; + background-color: transparent; + -webkit-backdrop-filter: blur(16px); + backdrop-filter: blur(16px); +} + +[data-has-hero] .hero > img { + filter: drop-shadow(0 0 3rem var(--overlay-blurple)); +} + +[data-has-hero] .hero { + display: block; + text-align: center; +} + +[data-has-hero] .hero h1 { + display: none; +} + +[data-has-hero] .hero .stack { + align-items: center; + text-align: center; +} + +[data-has-hero] .hero .tagline { + font-size: 1.5rem; +} \ No newline at end of file diff --git a/docs/website/src/assets/logo-dark.svg b/docs/website/src/assets/logo-dark.svg new file mode 100644 index 00000000..e6a0490a --- /dev/null +++ b/docs/website/src/assets/logo-dark.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/website/src/assets/logo-light.svg b/docs/website/src/assets/logo-light.svg new file mode 100644 index 00000000..e68eec0e --- /dev/null +++ b/docs/website/src/assets/logo-light.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/website/src/assets/logo.png b/docs/website/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e7e761581ffe425629a97f0d9c69cf675591f7f9 GIT binary patch literal 32762 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

?meS_9 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 literal 0 HcmV?d00001 diff --git a/docs/website/src/assets/logo_color.png b/docs/website/src/assets/logo_color.png new file mode 100644 index 0000000000000000000000000000000000000000..3cf1159bd5fccfdf4d5793d0a32cc8d435671b0e GIT binary patch literal 16611 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\tdaivU=-@@ zmPTYAR#j8=PhU#lLCYdZIG>ochsvDxesUvBU7JOIObAVippFQ8_=OcN$*k5NWU4MO!!0PU^_AbuI4F98*`i8vU_TSlfAJ!3g9c6$uM(j+iWy-=47 zam<{5EE1zijVt|;AxA}gR}Ny?i1nbZTS+1aZ~|1sq~?E9u!ukTqa%?1|Cc0e`CqYL zaPT`}M5*bbi%N-Xff$qr6l7Jw71CzG{|^o3G!_5= literal 0 HcmV?d00001 diff --git a/docs/website/src/assets/logo_color_round.png b/docs/website/src/assets/logo_color_round.png new file mode 100644 index 0000000000000000000000000000000000000000..09a7c24364939793f5cded29ab6bcc7c10a6c5d4 GIT binary patch literal 119882 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

L1+`mP>@Anyj)$tOKQ?z-ByvYpiu?;rUJ3%Mz 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 literal 0 HcmV?d00001 diff --git a/docs/website/src/assets/logo_color_round_sm.png b/docs/website/src/assets/logo_color_round_sm.png new file mode 100644 index 0000000000000000000000000000000000000000..93c5b6f93c5a5704e470a2357a6e02f224eee750 GIT binary patch literal 112381 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

M@iIil%{E8MsNbYWtLN%Czt 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 XX}u_4ZPP}64*VeQ>!_A1TZjJ#FfQXY literal 0 HcmV?d00001 diff --git a/docs/website/src/assets/logo_color_sqare.png b/docs/website/src/assets/logo_color_sqare.png new file mode 100644 index 0000000000000000000000000000000000000000..044d34276cdcc46280a80d5d5df88f1dcbec4945 GIT binary patch literal 49165 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 E4~)0&@c;k- literal 0 HcmV?d00001 diff --git a/docs/website/src/assets/logo_round_sm.png b/docs/website/src/assets/logo_round_sm.png new file mode 100644 index 0000000000000000000000000000000000000000..d3e501b5fb1c112970164834fa3c6157029cb0dd GIT binary patch literal 111167 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

eWSk4!mPXpW=fxa&g;sPf?hEfS!^4z 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�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

O}ow{=qluMDgG-$Yp* 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 GPWlhL(Bkv} literal 0 HcmV?d00001 diff --git a/docs/website/src/assets/logo_square.png b/docs/website/src/assets/logo_square.png new file mode 100644 index 0000000000000000000000000000000000000000..5f08fa6e352d6fc241a018ef4b409b078d95228c GIT binary patch literal 37094 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 + + ```rust + use httpmock::prelude::*; + + // Start a lightweight mock server. + let server = MockServer::start(); + + // Create a mock on the server. + let hello_mock = server.mock(|when, then| { + when.method("GET") + .path("/translate") + .query_param("word", "hello"); + then.status(200) + .header("content-type", "text/html; charset=UTF-8") + .body("Привет"); + }); + + // Send an HTTP request to the mock server. This simulates your code. + let response = reqwest::blocking::get(server.url("/translate?word=hello")).unwrap(); + + // Ensure the specified mock was called. + hello_mock.assert(); + + // Ensure the mock server did respond as specified. + assert_eq!(response.status(), 200); + ``` + + + ```rust + use httpmock::prelude::*; + + // Start a lightweight mock server. + let server = MockServer::start_async().await; + + // Create a mock on the server. + let hello_mock = server.mock_async(|when, then| { + when.method("GET") + .path("/translate") + .query_param("word", "hello"); + then.status(200) + .header("content-type", "text/html; charset=UTF-8") + .body("Привет"); + }).await; + + // Send an HTTP request to the mock server. This simulates your code. + let client = reqwest::Client::new(); + let response = client.get(server.url("/translate?word=hello")).send().await.unwrap(); + + // Ensure the specified mock was called exactly one time (or fail with a + // detailed error description). + hello_mock.assert(); + + // Ensure the mock server did respond as specified. + assert_eq!(response.status(), 200); + ``` + + + + + +The above example will spin up a lightweight HTTP mock server and configure it to respond to all `GET` requests +to path `/translate` with query parameter `word=hello`. The corresponding HTTP response will contain the text body +`Привет`. + +If the mock server receives a request that does not match the specified criteria +(i.e., the request does not have path `/translate` with query parameter `word=hello`), +then the mock server will respond with status code `404 (Not Found)`. diff --git a/docs/website/src/content/docs/getting_started/resources.md b/docs/website/src/content/docs/getting_started/resources.md new file mode 100644 index 00000000..fe605748 --- /dev/null +++ b/docs/website/src/content/docs/getting_started/resources.md @@ -0,0 +1,30 @@ +--- +title: Resources +description: Describes what resources are available to developers. +--- + +To learn more about this project, please refer to the following resources: +- Issue Tracker: https://github.com/alexliesenfeld/httpmock/issues +- Community Forum: https://github.com/alexliesenfeld/httpmock/discussions +- API Reference: https://docs.rs/httpmock/latest/httpmock/ +- GitHub Repository: https://github.com/alexliesenfeld/httpmock +- Official Website: https://alexliesenfeld.github.io/httpmock +- Changelog: https://github.com/alexliesenfeld/httpmock/blob/master/CHANGELOG.md +- Discord Server: https://discord.gg/QrjhRh7A + +## Getting Help + +For assistance with `httpmock`, please join the conversation on +[GitHub Discussions](https://github.com/alexliesenfeld/httpmock/discussions). + +## Create an Issue + +If you think you found a bug or have a feature suggestion, please open a new +[issue on GitHub](https://github.com/alexliesenfeld/httpmock/issues). + +## Contributing + +We welcome contributions from everyone in any capacity. +Please review [our code of conduct](https://github.com/alexliesenfeld/httpmock/blob/master/CODE_OF_CONDUCT.md) +for guidelines on how to contribute. To start contributing, please refer to our +[issue tracker on GitHub](https://github.com/alexliesenfeld/httpmock/issues). diff --git a/docs/website/src/content/docs/getting_started/why_httpmock.md b/docs/website/src/content/docs/getting_started/why_httpmock.md new file mode 100644 index 00000000..ebd0f3bc --- /dev/null +++ b/docs/website/src/content/docs/getting_started/why_httpmock.md @@ -0,0 +1,11 @@ +--- +title: Example Guide +description: A guide in my new Starlight docs site. +--- + +Guides lead a user through a specific task they want to accomplish, often with a sequence of steps. +Writing a good guide requires thinking about what your users are trying to do. + +## Further reading + +- Read [about how-to guides](https://diataxis.fr/how-to-guides/) in the Diátaxis framework diff --git a/docs/website/src/content/docs/index.mdx b/docs/website/src/content/docs/index.mdx new file mode 100644 index 00000000..a233b018 --- /dev/null +++ b/docs/website/src/content/docs/index.mdx @@ -0,0 +1,56 @@ +--- +title: httpmock +description: Get started building your docs site with Starlight. +template: splash +hero: + tagline: Simple yet powerful HTTP mocking library for Rust + image: + alt: The httpmock logo + dark: ../../assets/logo-dark.svg + light: ../../assets/logo-light.svg + actions: + - text: Get started + link: /getting_started/quick_introduction/ + icon: right-arrow + variant: primary + - text: View on GitHub + link: https://github.com/alexliesenfeld/httpmock + icon: github + - text: Support this project + link: https://buymeacoffee.com/alexliesenfeld + icon: external + +head: + - tag: style + content: | + a{} +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + + + + + If you call HTTP services or APIs in your application, `httpmock` makes it **easy** to mock their responses in your tests. + + + `httpmock` offers a range of advanced features, including **record and playback**, **proxy mode**, **standalone mode**, **HTTPS support**, **sharing mocks**, and many more. + + + **Record requests** and responses when interacting with a real service and **play them back** in your tests. + + + `httpmock` is a **Rust testing library**, but it also includes a **standalone server** that runs in a separate process, such as a Docker container. + + + One of the primary goals of `httpmock` is to be **actually helpful** by explaining the reasons when tests fail. It comes with useful logging, error messages, and an advanced request comparison algorithm. + + + `httpmock` allows you to **create and share mocked responses** using YAML-files. + + + Includes an **extensive set of built-in matchers** that let you define rules for matching requests based on method, path, headers, query parameters, body, and more, or use **custom matchers** for complex matching logic. + + + + diff --git a/docs/website/src/content/docs/miscellaneous/faq.mdx b/docs/website/src/content/docs/miscellaneous/faq.mdx new file mode 100644 index 00000000..a32cb4d2 --- /dev/null +++ b/docs/website/src/content/docs/miscellaneous/faq.mdx @@ -0,0 +1,13 @@ +--- +title: FAQ +description: Lists frequently asked questions. +--- + +### Can I start multiple mock servers in one Rust test function? +Yes, you can start multiple mock servers. However, `httpmock` uses a server pool with a default limit of 25 +servers. You can adjust this limit by setting the `HTTPMOCK_MAX_SERVERS` environment variable. + +### In which order are mocks evaluated? +When the `httpmock` server receives a request that matches multiple mocks, they are evaluated in +reverse order of their definition. This means the most recently defined mock takes precedence and will be used. + diff --git a/docs/website/src/content/docs/record-and-playback/playback.mdx b/docs/website/src/content/docs/record-and-playback/playback.mdx new file mode 100644 index 00000000..038968de --- /dev/null +++ b/docs/website/src/content/docs/record-and-playback/playback.mdx @@ -0,0 +1,39 @@ +--- +title: Playback +description: Explains how requests can be replayed. +--- +import codeExamples from "../../../../generated/example_tests.json" +import { Code } from '@astrojs/starlight/components'; + +After creating a recording, you can replay it by loading it into a mock server instance using the +`httpmock` Rust API as follows: + +```rust +// ... + +// Save the recording to +// "target/httpmock/recordings/github-torvalds-scenario_.yaml". +let target_path = recording + .save("github-torvalds-scenario") + .expect("cannot store scenario on disk"); + +let playback_server = MockServer::start(); + +// Play back the recorded interactions from the file. +playback_server.playback(target_path); +``` + +After calling [`MockServer::playback`](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html#method.playback), +the recording will be loaded into the mock server. This allows all previously recorded requests to act as matching +criteria, similar to how you configure normal mocks using +[`MockServer::mock`](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html#method.mock) with the +[`When`](https://docs.rs/httpmock/latest/httpmock/struct.When.html) structure. + +Hereafter, whenever the mock server receives a request that matches any of the recorded requests, it will +respond with the corresponding recorded response. + +## Full Example +The following example demonstrates how you can use the forwarding feature to record and playback +requests sent to the GitHub API and the responses it returns. + + \ No newline at end of file diff --git a/docs/website/src/content/docs/record-and-playback/recording.mdx b/docs/website/src/content/docs/record-and-playback/recording.mdx new file mode 100644 index 00000000..d46252d4 --- /dev/null +++ b/docs/website/src/content/docs/record-and-playback/recording.mdx @@ -0,0 +1,82 @@ +--- +title: Recording +description: Explains how requests can be recorded. +--- + +import codeExamples from "../../../../generated/example_tests.json" +import { Code } from '@astrojs/starlight/components'; + +`httpmock` provides functionality to record both requests to third-party services and their responses. +There are two strategies how you can achieve that: Forwarding and Proxy. + +## Forwarding Strategy + +The forwarding feature is the easier method for intercepting and recording responses from third-party services. +However, it requires you to change the client’s base URL to direct requests to the mock server’s address. + +When using the forwarding strategy, your client sends requests to an `httpmock` mock server. +The mock server forwards requests that match the criteria defined in the +[When](https://docs.rs/httpmock/latest/httpmock/struct.When.html) structure to a predefined target base URL. + +Let's have a look at a basic forwarding example: +```rust +// Initialize the mock server for testing +let server = MockServer::start(); + +// Configure the server to forward all requests to the GitHub API, +// instead of using mocked responses. The 'when' configuration allows +// setting conditions for when forwarding should occur, using the same +// structure familiar from creating mocks. +server.forward_to("https://github.com", |rule| { + rule.filter(|when| { + when.any_request(); // Ensure all requests are forwarded. + }); +}); +``` + +> Hint: If no forwarding rule matches a request, the mock server reverts to its standard mocking strategy and attempts +to serve a configured mock response. + +You can use the forwarding functionality to record requests sent to the remote service. + +### Full Example + +The following example demonstrates how you can use the forwarding feature to record requests sent to the GitHub API and +the responses it returns. + + + +## Proxy Strategy + +> Note: This feature is currently **unstable** and is available only under the `experimental` feature +flag. There is **no guarantee** that it will be included in a future stable release. + +The proxy feature in `httpmock`, while functional on its own, is particularly useful for recording +in scenarios where modifying or injecting the base URL used by the client is not possible. + +Many SDKs, APIs, and HTTP clients support proxy server configuration. For example, +the reqwest crate allows you to set up a proxy server with the following configuration: + +```rust +// Create a client using the reqwest crate with a configured proxy +let client = Client::builder() + .proxy(reqwest::Proxy::all("my-proxy-server:8080").unwrap()) + .build() + .unwrap(); + +// Send a GET request and unwrap the result +let response = client.get("https://github.com").send().unwrap(); +``` + +In this example, each request is routed through the proxy server rather than directly to the requested domain host. +The proxy server then tunnels or forwards the request to the target host, which is `github.com` in this case. + +When configured as a proxy, `httpmock` can intercept, record, and forward both requests and responses. + +> Hint: If no proxy rule matches a request, the mock server reverts to its standard mocking strategy and attempts +to serve a configured mock response. + +### Full Example + + + diff --git a/docs/website/src/content/docs/server/debugging.mdx b/docs/website/src/content/docs/server/debugging.mdx new file mode 100644 index 00000000..6d106d51 --- /dev/null +++ b/docs/website/src/content/docs/server/debugging.mdx @@ -0,0 +1,81 @@ +--- +title: Debugging +description: Describes what features are available to make debugging easier. +--- + +## Test Failure Output + +When your tests don't send the expected data, `httpmock` tries to provide as much information as possible about what +exactly is missing or different. However, to see details about unmet expectations, you need to use one of the +following assertion methods: + +- [Mock::assert](https://docs.rs/httpmock/latest/httpmock/struct.Mock.html#method.assert) / [Mock::assert_async](https://docs.rs/httpmock/latest/httpmock/struct.Mock.html#method.assert_async) +- [Mock::assert_calls](https://docs.rs/httpmock/latest/httpmock/struct.Mock.html#method.assert_calls) / [Mock::assert_calls_async](https://docs.rs/httpmock/latest/httpmock/struct.Mock.html#method.assert_calls_async) + +Let's have a look at an example: +```rust +#[test] +fn getting_started_testxx() { + use httpmock::prelude::*; + + // Start a lightweight mock server. + let server = MockServer::start(); + + // Create a mock on the server. + let hello_mock = server.mock(|when, then| { + when.method("GET") + .path("/translate") + .query_param("word", "hello-rustaceans"); + then.status(200) + .header("content-type", "text/html; charset=UTF-8") + .body("Привет"); + }); + + // Send an HTTP request to the mock server. This simulates your code. + let response = reqwest::blocking::get(server.url("/translate?word=hello")) + .unwrap(); + + // Ensure the specified mock was called. This will fail and print output + // with an explanation of what was expected and provided. + hello_mock.assert(); +} +``` + +Notice how `mock.assert()` is used to verify that the mock you defined earlier has been called **exactly once**. +If you expect a different number of calls, use [Mock::assert_calls](https://docs.rs/httpmock/latest/httpmock/struct.Mock.html#method.assert_calls). + +Since the path of the request that was actually sent to the mock server differs from the expected one, +`hello_mock.assert()` will panic and cause the test to fail with the following message: + +```bash +0 of 1 expected requests matched the mock specification. +Here is a comparison with the most similar unmatched request (request number 1): + +------------------------------------------------------------ +1 : Query Parameter Mismatch +------------------------------------------------------------ +Expected: + key [equals] word + value [equals] hello-rustaceans + +Received (most similar query parameter): + word=hello + +All received query parameter values: + 1. word=hello + +Matcher: query_param +Docs: https://docs.rs/httpmock/0.8.0/httpmock/struct.When.html#method.query_param +``` + +## Logs + +`httpmock` logs through the log crate, so you can see detailed log output about its behavior. +This output is useful for investigating issues, like figuring out why a request doesn't match a mock definition. + +The debug log level is usually the most helpful, but you can use trace to get even more details. + +> **Note**: To see the log output during test execution, add the `--nocapture` argument when running your tests. + +> **Hint**: If you're using the `env_logger` backend, set the `RUST_LOG` environment variable to `httpmock=debug` +to see `httpmock` logs. \ No newline at end of file diff --git a/docs/website/src/content/docs/server/https.mdx b/docs/website/src/content/docs/server/https.mdx new file mode 100644 index 00000000..a4f8676f --- /dev/null +++ b/docs/website/src/content/docs/server/https.mdx @@ -0,0 +1,77 @@ +--- +title: HTTPS +description: Describes how the mock server can be configured to support HTTPS +--- + +> **Note**: This feature is currently **unstable** and there is no guarantee it will be included in a future stable release. +There is progress on stabilizing it, but at the moment, HTTPS **does not work with the proxy feature**. + +By default, `httpmock` does not enable HTTPS support for testing. However, you can enable it on demand by using the +Cargo feature `https`. When this feature is enabled, `httpmock` automatically uses HTTPS for all internal communication +between the Rust API and the mock server, whether it’s a remote standalone server or a local +[`MockServer`](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html) instance. +It also allows your client to send HTTPS requests to the mock server. + +## Unified Port +`httpmock` uses a unified port approach for both HTTP and HTTPS communication. This means you don’t need to change +the port or modify anything in your Rust tests to switch to HTTPS. Your client can send requests using HTTPS as needed, +and `httpmock` will automatically detect HTTPS traffic on the port and transition from HTTP to HTTPS without +any additional configuration. + +## CA Certificate +Since HTTPS requires the use of certificates, you'll need to accept the `httpmock` CA certificate +in your client settings or, more conveniently, in your system preferences when using HTTPS. +You can find the `httpmock` CA certificate in the `httpmock` +[GitHub repository](https://github.com/alexliesenfeld/httpmock/blob/master/certs) (ca.pem file). + + +`httpmock` uses its CA certificate to generate domain-specific certificates for your tests. For instance, if you want +to mock requests from https://wikipedia.org (such as when using the `httpmock` proxy feature), the mock server +will generate and cache a certificate for that domain based on the `httpmock` CA certificate. Since your system +trusts the CA certificate, the self-signed, domain-specific certificate for Wikipedia will also be trusted +automatically. + +### Trusting the CA Certificate + +Here is how you can add the `httpmock` CA crtificate to your system. + +#### MacOS + +```bash +# Download the CA certificate +curl -o httpmock-ca.crt https://github.com/alexliesenfeld/httpmock/raw/master/certs/ca.pem + +# Open Keychain Access manually or use the open command +open /Applications/Utilities/Keychain\ Access.app + +# Import the certificate: +# - Drag the downloaded 'httpmock-ca.crt' file into the "System" keychain. +# - Set the certificate to "Always Trust" under "Get Info". +``` + +#### Windows +``` +# Download the CA certificate +Invoke-WebRequest -Uri "https://github.com/alexliesenfeld/httpmock/raw/master/certs/ca.pem" -OutFile "C:\Path\To\httpmock-ca.crt" + +# Open the Certificate Manager +# Press 'Win + R', type 'mmc', and press Enter. + +# Import the certificate: +# - In MMC, go to File > Add/Remove Snap-in, select "Certificates", and click "Add". +# - Choose "Computer account" and then "Local computer". +# - Under "Trusted Root Certification Authorities", right-click on "Certificates" and choose "All Tasks > Import". +# - Browse to the downloaded 'httpmock-ca.crt' file and complete the import wizard. +``` + +#### Ubuntu +```bash +# Clone the repository to get the CA certificate +git clone git@github.com:alexliesenfeld/httpmock.git + +# Copy the certificate to the system's trusted certificates directory +sudo cp httpmock/certs/ca.pem /usr/local/share/ca-certificates/httpmock.crt + +# Update the system's trusted certificates +sudo update-ca-certificates +``` diff --git a/docs/website/src/content/docs/server/standalone.mdx b/docs/website/src/content/docs/server/standalone.mdx new file mode 100644 index 00000000..9ab08995 --- /dev/null +++ b/docs/website/src/content/docs/server/standalone.mdx @@ -0,0 +1,116 @@ +--- +title: Standalone Server +description: Describes how to set up and use a standalone mock server. +--- + +You can use `httpmock` to run a standalone mock server in a separate process, such as a Docker container. +This setup allows the mock server to be accessible to multiple applications, not just within your Rust tests. +It’s particularly useful for system or end-to-end tests that require mocked services. + +By deploying `httpmock` as an independent service, it becomes available outside of Rust tests, providing +fake responses for services that are unavailable for testing. + +Even when a mock server is running outside your Rust tests in a separate process, such as a Docker container, +you can still use it within your Rust tests just like a local MockServer instance +(see [Connecting to Standalone Mock Servers](#connecting-to-standalone-mock-servers)). This enables your Rust tests +to set up remote, standalone servers for larger, federated end-to-end test scenarios, with your Rust tests +acting as the test runner. + +With this feature, `httpmock` can be used as universal HTTP mocking tool that is useful in all stages +of the development lifecycle. + +## Running Standalone Mock Servers + +### Docker Image +Although you can build the mock server in standalone mode yourself, it is easiest to use the accompanying +[Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock) hosted on Docker Hub. + +You can run it as follows: +```bash +docker run alexliesenfeld/httpmock +```` + +#### Build Docker Image + +If you want to build the Docker image yourself, you can clone the `httpmock` GitHub repository and +build it yourself using the Dockerfile that is contained in the project root directory: + +```bash +# Clone the repository +git clone git@github.com:alexliesenfeld/httpmock.git + +# Build the Docker image +docker build -t my-httpmock-image . + +# Start a Docker container +docker run my-httpmock-image +```` + +### Build Binary +Alternatively, you can clone the GitHub repository and build a binary from the projects root directory +and execute it as follows: + +```bash +# Clone the repository +git clone git@github.com:alexliesenfeld/httpmock.git + +# Build a standalone mock server binary +cargo build --release --all-features + +# Execute the binary +./target/release/httpmock +``` + +### Environment Variables + +Please refer to the [Environment Variables](/getting_started/fundamentals/#environment-variables) section for information what environment variables are available when +using a standalone mock server. + +## Connecting to Standalone Mock Servers + +To be able to use the standalone server from within your tests, you need to change how an instance of the MockServer +instance is created. Instead of using [`MockServer::start`](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html#method.start), +you need to connect to a remote server by using one of the `connect` methods (such as +[`MockServer::connect`](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html#method.connect) or +[`MockServer::connect_from_env`](https://docs.rs/httpmock/latest/httpmock/struct.MockServer.html#method.connect_from_env)). +Note: These are only available with the remote feature enabled. + +## Sequential Test Execution + +To prevent interference with other tests, only one test function can use the remote server at the same time. +This means that test functions may be blocked when connecting to the remote server until it becomes free again. +This is in contrast to tests that use a local mock server where parallel test execution is possible, because +each test uses its own mock server. + +> **Note**: Sequential execution is only enforced on a per-process basis. This means that if multiple test runs or +applications use the remote server simultaneously, interference may still occur. + + +## Usage Without Rust + +You can use a standalone mock server independently, without needing Rust to configure mock behavior. `httpmock` allows +you to define mocks using YAML files, which follow a similar when/then pattern as the Rust API. Here's an example +that defines two mocks (mock definitions are separated using the triple dash separator): + +```yaml +when: + method: GET + path: /static-mock/examples/simple +then: + status: 200 + json_body: '{ "response" : "hello" }' +--- +when: + method: POST + path: /static-mock/examples/submit +then: + status: 201 + json_body: '{ "status" : "created" }' +``` + +> **Note**: Defining mocks with YAML files is straightforward because the field names directly match the corresponding +methods in the Rust API, found in the [`When`](https://docs.rs/httpmock/latest/httpmock/struct.When.html) or +[`Then`](https://docs.rs/httpmock/latest/httpmock/struct.Then.html) data structures. + +Please refer to [this example file](https://github.com/alexliesenfeld/httpmock/blob/master/tests/resources/static_yaml_mock.yaml), +which includes many of the usable fields. \ No newline at end of file diff --git a/docs/website/src/env.d.ts b/docs/website/src/env.d.ts new file mode 100644 index 00000000..acef35f1 --- /dev/null +++ b/docs/website/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/docs/website/templates/matching_requests/body.md b/docs/website/templates/matching_requests/body.md new file mode 100644 index 00000000..fa1eee4a --- /dev/null +++ b/docs/website/templates/matching_requests/body.md @@ -0,0 +1,82 @@ +--- +title: Body +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match HTTP request body content in incoming HTTP requests. + +### body +{{{docs.when.body}}} + +### body_not +{{{docs.when.body_not}}} + +### body_includes +{{{docs.when.body_includes}}} + +### body_excludes +{{{docs.when.body_excludes}}} + +### body_prefix +{{{docs.when.body_prefix}}} + +### body_prefix_not +{{{docs.when.body_prefix_not}}} + +### body_suffix +{{{docs.when.body_suffix}}} + +### body_suffix_not +{{{docs.when.body_suffix_not}}} + +### body_matches +{{{docs.when.body_matches}}} + +## JSON Body + +### json_body +{{{docs.when.json_body}}} + +### json_body_includes +{{{docs.when.json_body_includes}}} + +### json_body_excludes +{{{docs.when.json_body_excludes}}} + +## URL Encoded Body + +### form_urlencoded_tuple +{{{docs.when.form_urlencoded_tuple}}} + +### form_urlencoded_tuple_not +{{{docs.when.form_urlencoded_tuple_not}}} + +### form_urlencoded_tuple_exists +{{{docs.when.form_urlencoded_tuple_exists}}} + +### form_urlencoded_tuple_missing +{{{docs.when.form_urlencoded_tuple_missing}}} + +### form_urlencoded_tuple_includes +{{{docs.when.form_urlencoded_tuple_includes}}} + +### form_urlencoded_tuple_excludes +{{{docs.when.form_urlencoded_tuple_excludes}}} + +### form_urlencoded_tuple_prefix +{{{docs.when.form_urlencoded_tuple_prefix}}} + +### form_urlencoded_tuple_prefix_not +{{{docs.when.form_urlencoded_tuple_prefix_not}}} + +### form_urlencoded_tuple_suffix +{{{docs.when.form_urlencoded_tuple_suffix}}} + +### form_urlencoded_tuple_suffix_not +{{{docs.when.form_urlencoded_tuple_suffix_not}}} + +### form_urlencoded_tuple_matches +{{{docs.when.form_urlencoded_tuple_matches}}} + +### form_urlencoded_tuple_key_value_count +{{{docs.when.form_urlencoded_tuple_key_value_count}}} \ No newline at end of file diff --git a/docs/website/templates/matching_requests/cookies.md b/docs/website/templates/matching_requests/cookies.md new file mode 100644 index 00000000..5834def1 --- /dev/null +++ b/docs/website/templates/matching_requests/cookies.md @@ -0,0 +1,44 @@ +--- +title: Cookie +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match cookies headers in incoming HTTP requests. + +> **Attention:** To use these matchers, enable the cookies feature by adding `--features=cookies` to your Cargo command. For example: `cargo test --features=cookies`. + +## cookie +{{{docs.when.cookie}}} + +## cookie_not +{{{docs.when.cookie_not}}} + +## cookie_exists +{{{docs.when.cookie_exists}}} + +## cookie_missing +{{{docs.when.cookie_missing}}} + +## cookie_includes +{{{docs.when.cookie_includes}}} + +## cookie_excludes +{{{docs.when.cookie_excludes}}} + +## cookie_prefix +{{{docs.when.cookie_prefix}}} + +## cookie_prefix_not +{{{docs.when.cookie_prefix_not}}} + +## cookie_suffix +{{{docs.when.cookie_suffix}}} + +## cookie_suffix_not +{{{docs.when.cookie_suffix_not}}} + +## cookie_matches +{{{docs.when.cookie_matches}}} + +## cookie_count +{{{docs.when.cookie_count}}} diff --git a/docs/website/templates/matching_requests/custom.md b/docs/website/templates/matching_requests/custom.md new file mode 100644 index 00000000..35997f06 --- /dev/null +++ b/docs/website/templates/matching_requests/custom.md @@ -0,0 +1,15 @@ +--- +title: Request Matchers +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions that enable developers to implement custom matchers. +These matchers execute user-defined code to determine if a request meets specific criteria. + +> **Attention:** Custom matchers are **not available** when connecting to standalone mock servers (e.g., by using one of the `connect` methods, such as [`MockServer::connect`](https://docs.rs/httpmock/0.7.0/httpmock/struct.MockServer.html#method.connect)). + +## is_true +{{{docs.when.is_true}}} + +## is_false +{{{docs.when.is_false}}} diff --git a/docs/website/templates/matching_requests/headers.md b/docs/website/templates/matching_requests/headers.md new file mode 100644 index 00000000..c622cb0f --- /dev/null +++ b/docs/website/templates/matching_requests/headers.md @@ -0,0 +1,42 @@ +--- +title: Headers +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match HTTP headers in incoming HTTP requests. + +## header +{{{docs.when.header}}} + +## header_not +{{{docs.when.header_not}}} + +## header_exists +{{{docs.when.header_exists}}} + +## header_missing +{{{docs.when.header_missing}}} + +## header_includes +{{{docs.when.header_includes}}} + +## header_excludes +{{{docs.when.header_excludes}}} + +## header_prefix +{{{docs.when.header_prefix}}} + +## header_prefix_not +{{{docs.when.header_prefix_not}}} + +## header_suffix +{{{docs.when.header_suffix}}} + +## header_suffix_not +{{{docs.when.header_suffix_not}}} + +## header_matches +{{{docs.when.header_matches}}} + +## header_count +{{{docs.when.header_count}}} diff --git a/docs/website/templates/matching_requests/host.md b/docs/website/templates/matching_requests/host.md new file mode 100644 index 00000000..f86624ea --- /dev/null +++ b/docs/website/templates/matching_requests/host.md @@ -0,0 +1,34 @@ +--- +title: Request Matchers +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match the host in incoming HTTP requests. +These matchers are especially useful when using the proxy and record-and-playback features of `httpmock`. + +## host +{{{docs.when.host}}} + +## host_not +{{{docs.when.host_not}}} + +## host_includes +{{{docs.when.host_includes}}} + +## host_excludes +{{{docs.when.host_excludes}}} + +## host_prefix +{{{docs.when.host_prefix}}} + +## host_prefix_not +{{{docs.when.host_prefix_not}}} + +## host_suffix +{{{docs.when.host_suffix}}} + +## host_suffix_not +{{{docs.when.host_suffix_not}}} + +## host_matches +{{{docs.when.host_matches}}} diff --git a/docs/website/templates/matching_requests/method.md b/docs/website/templates/matching_requests/method.md new file mode 100644 index 00000000..ec3c279e --- /dev/null +++ b/docs/website/templates/matching_requests/method.md @@ -0,0 +1,13 @@ +--- +title: Method +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match the method in incoming HTTP requests, +such as `GET`, `POST`, etc. + +## method +{{{docs.when.method}}} + +## method_not +{{{docs.when.method_not}}} diff --git a/docs/website/templates/matching_requests/path.md b/docs/website/templates/matching_requests/path.md new file mode 100644 index 00000000..a7fa19a5 --- /dev/null +++ b/docs/website/templates/matching_requests/path.md @@ -0,0 +1,33 @@ +--- +title: Path +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match the request path, such as `http://localhost:8080/my-path`. + +## path +{{{docs.when.path}}} + +## path_not +{{{docs.when.path_not}}} + +## path_includes +{{{docs.when.path_includes}}} + +## path_excludes +{{{docs.when.path_excludes}}} + +## path_prefix +{{{docs.when.path_prefix}}} + +## path_prefix_not +{{{docs.when.path_prefix_not}}} + +## path_suffix +{{{docs.when.path_suffix}}} + +## path_suffix_not +{{{docs.when.path_suffix_not}}} + +## path_matches +{{{docs.when.path_matches}}} diff --git a/docs/website/templates/matching_requests/port.md b/docs/website/templates/matching_requests/port.md new file mode 100644 index 00000000..137b5e92 --- /dev/null +++ b/docs/website/templates/matching_requests/port.md @@ -0,0 +1,13 @@ +--- +title: Request Matchers +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match the TCP port in incoming HTTP requests. +These matchers are especially useful when using the proxy and record-and-playback features of `httpmock`. + +## port +{{{docs.when.port}}} + +## port_not +{{{docs.when.port_not}}} diff --git a/docs/website/templates/matching_requests/query.md b/docs/website/templates/matching_requests/query.md new file mode 100644 index 00000000..542dd84b --- /dev/null +++ b/docs/website/templates/matching_requests/query.md @@ -0,0 +1,42 @@ +--- +title: Query Parameters +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match query parameters in incoming HTTP requests. + +## query_param +{{{docs.when.query_param}}} + +## query_param_not +{{{docs.when.query_param_not}}} + +## query_param_exists +{{{docs.when.query_param_exists}}} + +## query_param_missing +{{{docs.when.query_param_missing}}} + +## query_param_includes +{{{docs.when.query_param_includes}}} + +## query_param_excludes +{{{docs.when.query_param_excludes}}} + +## query_param_prefix +{{{docs.when.query_param_prefix}}} + +## query_param_prefix_not +{{{docs.when.query_param_prefix_not}}} + +## query_param_suffix +{{{docs.when.query_param_suffix}}} + +## query_param_suffix_not +{{{docs.when.query_param_suffix_not}}} + +## query_param_matches +{{{docs.when.query_param_matches}}} + +## query_param_count +{{{docs.when.query_param_count}}} diff --git a/docs/website/templates/matching_requests/scheme.md b/docs/website/templates/matching_requests/scheme.md new file mode 100644 index 00000000..2023a2ec --- /dev/null +++ b/docs/website/templates/matching_requests/scheme.md @@ -0,0 +1,15 @@ +--- +title: Request Matchers +description: Using request matchers to specify which requests should respond. TODO +--- + +This section describes matcher functions designed to target and match the scheme in incoming HTTP requests +(such as `http` or `https` as in `https://localhost:8080`). + +These matchers are especially useful when using the proxy and record-and-playback features of `httpmock`. + +## scheme +{{{docs.when.scheme}}} + +## scheme_not +{{{docs.when.scheme_not}}} diff --git a/docs/website/templates/mocking_responses/all.md b/docs/website/templates/mocking_responses/all.md new file mode 100644 index 00000000..b9cf8827 --- /dev/null +++ b/docs/website/templates/mocking_responses/all.md @@ -0,0 +1,24 @@ +--- +title: Body +description: Using request matchers to specify which requests should respond. TODO +--- + +This section explains functions designed to set values in the response when a request meets all specified criteria. + +### status +{{{docs.then.status}}} + +### body +{{{docs.then.body}}} + +### body_from_file +{{{docs.then.body_from_file}}} + +### json_body +{{{docs.then.json_body}}} + +### json_body_obj +{{{docs.then.json_body_obj}}} + +### header +{{{docs.then.header}}} diff --git a/docs/website/templates/mocking_responses/delay.md b/docs/website/templates/mocking_responses/delay.md new file mode 100644 index 00000000..69dd6abe --- /dev/null +++ b/docs/website/templates/mocking_responses/delay.md @@ -0,0 +1,9 @@ +--- +title: Network Delay +description: Demonstrates how to simulate network delay. +--- + +This section describes functions designed to simulate network issues, such as latency (delay). + +### delay +{{{docs.then.delay}}} diff --git a/docs/website/tools/generate-docs.cjs b/docs/website/tools/generate-docs.cjs new file mode 100644 index 00000000..12292054 --- /dev/null +++ b/docs/website/tools/generate-docs.cjs @@ -0,0 +1,56 @@ +const fs = require('fs'); +const path = require('path'); + +const Handlebars = require('handlebars'); +Handlebars.registerHelper('eq', (a, b) => a === b); + +function readJsonFile(filename) { + const rawData = fs.readFileSync(filename); + return JSON.parse(rawData); +} + +function deepenMarkdownHeaders(markdownText) { + return markdownText.split('\n').map(line => { + // Check if the line starts with one or more '#' + if (line.startsWith('#')) { + return '###' + line; // Add two more '#' to deepen the header + } + return line; + }).join('\n'); +} + +function generateMarkdownDocs() { + const methodDocs = readJsonFile(process.argv[2]); + Object.keys(methodDocs.then).forEach(key => { + methodDocs.then[key] = deepenMarkdownHeaders(methodDocs.then[key]); + }); + Object.keys(methodDocs.when).forEach(key => { + methodDocs.when[key] = deepenMarkdownHeaders(methodDocs.when[key]); + }); + + const templatesDir = process.argv[3]; + + fs.readdir(templatesDir, (err, files) => { + if (err) return console.error(err); + + files.forEach(file => { + const filePath = path.join(templatesDir, file); + fs.readFile(filePath, 'utf8', (err, content) => { + if (err) return console.error(err); + + const template = Handlebars.compile(content); + const result = template({ docs: methodDocs }); + + const fileName = `${process.argv[4]}/${file}`; + if (fs.existsSync(fileName)) { + fs.unlinkSync(fileName); + } + + console.log("writing: " + fileName) + fs.writeFileSync(fileName, result); + }); + }); + }); +} + +generateMarkdownDocs(); \ No newline at end of file diff --git a/docs/website/tsconfig.json b/docs/website/tsconfig.json new file mode 100644 index 00000000..77da9dd0 --- /dev/null +++ b/docs/website/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "astro/tsconfigs/strict" +} \ No newline at end of file diff --git a/gitlab-ci.yml b/gitlab-ci.yml new file mode 100644 index 00000000..b79523b2 --- /dev/null +++ b/gitlab-ci.yml @@ -0,0 +1,10 @@ +stages: + - build + +rust-latest: + stage: build + image: rust:latest + script: + - make setup + - cargo build --verbose + - make test-powerset diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..78c67260 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +imports_granularity="Crate" +reorder_imports = true \ No newline at end of file diff --git a/scripts/generate_tarpaulin_config.sh b/scripts/generate_tarpaulin_config.sh new file mode 100755 index 00000000..dc868fe3 --- /dev/null +++ b/scripts/generate_tarpaulin_config.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# Populate the features variable +features=$(awk '/^\[features\]/ {flag=1; next} /^\[/ {flag=0} flag {print}' Cargo.toml | \ + sed 's/^[ \t]*//;s/[ \t]*$//' | \ + grep -v '^\s*#' | \ + grep -v '^\s*$' | \ + cut -d'=' -f1 | \ + grep -vE "(default|color)") + +# Convert the multiline string to an array +IFS=$'\n' read -r -d '' -a feature_array <<< "$features" + +generate_powerset() { + local items=("$@") + local len=${#items[@]} + local total=$((1 << len)) + + for ((i=0; i, + state: Arc, } impl LocalMockServerAdapter { - pub fn new(addr: SocketAddr, local_state: Arc) -> Self { - LocalMockServerAdapter { addr, local_state } + pub fn new(addr: SocketAddr, local_state: Arc) -> Self { + LocalMockServerAdapter { + addr, + state: local_state, + } } } #[async_trait] impl MockServerAdapter for LocalMockServerAdapter { + async fn ping(&self) -> Result<(), ServerAdapterError> { + let response = simple_http_get_request(&self.addr, "/__httpmock__/ping").await?; + + if !response.contains("200 OK") { + return Err(PingError(format!( + "Expected server response body to contain '200 OK' but it didn't. Body: '{}'", + response + ))); + } + + Ok(()) + } + fn host(&self) -> String { self.addr.ip().to_string() } @@ -43,72 +63,154 @@ impl MockServerAdapter for LocalMockServerAdapter { &self.addr } - async fn create_mock(&self, mock: &MockDefinition) -> Result { - let id = add_new_mock(&self.local_state, mock.clone(), false)?; - Ok(MockRef::new(id)) + async fn reset(&self) -> Result<(), ServerAdapterError> { + self.state.reset(); + Ok(()) } - async fn fetch_mock(&self, mock_id: usize) -> Result { - match read_one_mock(&self.local_state, mock_id)? { - Some(mock) => Ok(mock), - None => Err("Cannot find mock".to_string()), - } + async fn create_mock(&self, mock: &MockDefinition) -> Result { + let active_mock = self + .state + .add_mock(mock.clone(), false) + .map_err(|e| UpstreamError(e.to_string()))?; + Ok(active_mock) } - async fn delete_mock(&self, mock_id: usize) -> Result<(), String> { - let deleted = delete_one_mock(&self.local_state, mock_id)?; - if deleted { - Ok(()) - } else { - Err("Mock could not deleted".to_string()) - } + async fn fetch_mock(&self, mock_id: usize) -> Result { + let mock = self + .state + .read_mock(mock_id) + .map_err(|e| UpstreamError(e.to_string()))? + .ok_or_else(|| MockNotFound(mock_id))?; + Ok(mock) + } + + async fn delete_mock(&self, mock_id: usize) -> Result<(), ServerAdapterError> { + self.state + .delete_mock(mock_id) + .map_err(|e| UpstreamError(format!("Cannot delete mock: {:?}", e)))?; + Ok(()) } - async fn delete_all_mocks(&self) -> Result<(), String> { - delete_all_mocks(&self.local_state); + async fn delete_all_mocks(&self) -> Result<(), ServerAdapterError> { + self.state.delete_all_mocks(); Ok(()) } - async fn verify(&self, mock_rr: &RequestRequirements) -> Result, String> { - verify(&self.local_state, mock_rr) + async fn verify( + &self, + mock_rr: &RequestRequirements, + ) -> Result, ServerAdapterError> { + let closest_match = self + .state + .verify(mock_rr) + .map_err(|e| UpstreamError(format!("Cannot delete mock: {:?}", e)))?; + Ok(closest_match) } - async fn delete_history(&self) -> Result<(), String> { - delete_history(&self.local_state); + async fn delete_history(&self) -> Result<(), ServerAdapterError> { + self.state.delete_history(); Ok(()) } - async fn ping(&self) -> Result<(), String> { - let addr = self.addr.to_string(); + async fn create_forwarding_rule( + &self, + config: ForwardingRuleConfig, + ) -> Result { + Ok(self.state.create_forwarding_rule(config)) + } - let mut stream = TcpStream::connect(&addr) - .await - .map_err(|err| format!("Cannot connect to mock server: {}", err))?; + async fn delete_forwarding_rule(&self, id: usize) -> Result<(), ServerAdapterError> { + self.state.delete_forwarding_rule(id); + Ok(()) + } - let request = format!( - "GET /__httpmock__/ping HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", - addr - ); + async fn delete_all_forwarding_rules(&self) -> Result<(), ServerAdapterError> { + self.state.delete_all_forwarding_rules(); + Ok(()) + } - stream - .write_all(request.as_bytes()) - .await - .map_err(|err| format!("Cannot send request to mock server: {}", err))?; + async fn create_proxy_rule( + &self, + config: ProxyRuleConfig, + ) -> Result { + Ok(self.state.create_proxy_rule(config)) + } - let mut buf = vec![0u8; 1024]; - stream - .read(&mut buf) - .await - .map_err(|err| format!("Cannot read response from mock server: {}", err))?; + async fn delete_proxy_rule(&self, id: usize) -> Result<(), ServerAdapterError> { + self.state.delete_proxy_rule(id); + Ok(()) + } - let response = String::from_utf8_lossy(&buf); - if !response.contains("200 OK") { - return Err(format!( - "Unexpected mock server response. Expected '{}' to contain '200 OK'", - response - )); - } + async fn delete_all_proxy_rules(&self) -> Result<(), ServerAdapterError> { + self.state.delete_all_proxy_rules(); + Ok(()) + } + async fn create_recording( + &self, + config: RecordingRuleConfig, + ) -> Result { + Ok(self.state.create_recording(config)) + } + + async fn delete_recording(&self, id: usize) -> Result<(), ServerAdapterError> { + self.state.delete_recording(id); + Ok(()) + } + + async fn delete_all_recordings(&self) -> Result<(), ServerAdapterError> { + self.state.delete_all_recordings(); Ok(()) } + + async fn export_recording(&self, id: usize) -> Result, ServerAdapterError> { + Ok(self + .state + .export_recording(id) + .map_err(|err| UpstreamError(err.to_string()))?) + } + + async fn create_mocks_from_recording<'a>( + &self, + recording_file_content: &'a str, + ) -> Result, ServerAdapterError> { + Ok(self + .state + .load_mocks_from_recording(recording_file_content) + .map_err(|err| UpstreamError(err.to_string()))?) + } +} + +pub async fn simple_http_get_request( + addr: &SocketAddr, + path: &str, +) -> Result { + let addr = addr.to_string(); + + let mut stream = TcpStream::connect(&addr) + .await + .map_err(|err| UpstreamError(err.to_string()))?; + + let request = format!( + "GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", + path, addr + ); + + stream + .write_all(request.as_bytes()) + .await + .map_err(|err| UpstreamError(err.to_string()))?; + + let mut buf = vec![0u8; 1024]; + let bytes_read = stream + .read(&mut buf) + .await + .map_err(|err| UpstreamError(err.to_string()))?; + + buf.resize(bytes_read, 0); + + let response = String::from_utf8_lossy(&buf); + + Ok(response.to_string()) } diff --git a/src/api/adapter/mod.rs b/src/api/adapter/mod.rs index 7aca9396..5d40f3ce 100644 --- a/src/api/adapter/mod.rs +++ b/src/api/adapter/mod.rs @@ -1,80 +1,84 @@ -use std::net::SocketAddr; -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; +use std::{net::SocketAddr, str::FromStr}; use async_trait::async_trait; +use bytes::Bytes; use serde::{Deserialize, Serialize}; -use crate::common::data::{ActiveMock, ClosestMatch, MockDefinition, MockRef, RequestRequirements}; -use crate::server::web::handlers::{ - add_new_mock, delete_all_mocks, delete_history, delete_one_mock, read_one_mock, verify, -}; +use crate::common::data::{ActiveForwardingRule, ActiveMock, ActiveProxyRule}; + +use crate::common::data::{ActiveRecording, ClosestMatch, MockDefinition, RequestRequirements}; pub mod local; -#[cfg(feature = "remote")] -pub mod standalone; - -/// Type alias for [regex::Regex](../regex/struct.Regex.html). -pub type Regex = regex::Regex; - -/// Represents an HTTP method. -#[derive(Serialize, Deserialize, Debug)] -pub enum Method { - GET, - HEAD, - POST, - PUT, - DELETE, - CONNECT, - OPTIONS, - TRACE, - PATCH, -} +use crate::common::data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig}; -impl FromStr for Method { - type Err = String; - - fn from_str(input: &str) -> Result { - match input { - "GET" => Ok(Method::GET), - "HEAD" => Ok(Method::HEAD), - "POST" => Ok(Method::POST), - "PUT" => Ok(Method::PUT), - "DELETE" => Ok(Method::DELETE), - "CONNECT" => Ok(Method::CONNECT), - "OPTIONS" => Ok(Method::OPTIONS), - "TRACE" => Ok(Method::TRACE), - "PATCH" => Ok(Method::PATCH), - _ => Err(format!("Invalid HTTP method {}", input)), - } - } -} +use thiserror::Error; -impl From<&str> for Method { - fn from(value: &str) -> Self { - value.parse().expect("Cannot parse HTTP method") - } +#[derive(Error, Debug)] +pub enum ServerAdapterError { + #[error("mock with ID {0} not found")] + MockNotFound(usize), + #[error("invalid mock definition: {0}")] + InvalidMockDefinitionError(String), + #[error("cannot serialize JSON: {0}")] + JsonSerializationError(serde_json::error::Error), + #[error("cannot deserialize JSON: {0}")] + JsonDeserializationError(serde_json::error::Error), + #[error("adapter error: {0}")] + UpstreamError(String), + #[error("cannot ping mock server: {0}")] + PingError(String), + #[error("unknown error")] + Unknown, } -impl std::fmt::Display for Method { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) - } -} +#[cfg(feature = "remote")] +pub mod remote; #[async_trait] pub trait MockServerAdapter { + async fn ping(&self) -> Result<(), ServerAdapterError>; fn host(&self) -> String; fn port(&self) -> u16; fn address(&self) -> &SocketAddr; - async fn create_mock(&self, mock: &MockDefinition) -> Result; - async fn fetch_mock(&self, mock_id: usize) -> Result; - async fn delete_mock(&self, mock_id: usize) -> Result<(), String>; - async fn delete_all_mocks(&self) -> Result<(), String>; - async fn verify(&self, rr: &RequestRequirements) -> Result, String>; - async fn delete_history(&self) -> Result<(), String>; - async fn ping(&self) -> Result<(), String>; + + async fn reset(&self) -> Result<(), ServerAdapterError>; + + async fn create_mock(&self, mock: &MockDefinition) -> Result; + async fn fetch_mock(&self, mock_id: usize) -> Result; + async fn delete_mock(&self, mock_id: usize) -> Result<(), ServerAdapterError>; + async fn delete_all_mocks(&self) -> Result<(), ServerAdapterError>; + + async fn verify( + &self, + rr: &RequestRequirements, + ) -> Result, ServerAdapterError>; + async fn delete_history(&self) -> Result<(), ServerAdapterError>; + + async fn create_forwarding_rule( + &self, + config: ForwardingRuleConfig, + ) -> Result; + async fn delete_forwarding_rule(&self, mock_id: usize) -> Result<(), ServerAdapterError>; + async fn delete_all_forwarding_rules(&self) -> Result<(), ServerAdapterError>; + + async fn create_proxy_rule( + &self, + config: ProxyRuleConfig, + ) -> Result; + async fn delete_proxy_rule(&self, mock_id: usize) -> Result<(), ServerAdapterError>; + async fn delete_all_proxy_rules(&self) -> Result<(), ServerAdapterError>; + + async fn create_recording( + &self, + mock: RecordingRuleConfig, + ) -> Result; + async fn delete_recording(&self, id: usize) -> Result<(), ServerAdapterError>; + async fn delete_all_recordings(&self) -> Result<(), ServerAdapterError>; + async fn export_recording(&self, id: usize) -> Result, ServerAdapterError>; + async fn create_mocks_from_recording<'a>( + &self, + recording_file_content: &'a str, + ) -> Result, ServerAdapterError>; } diff --git a/src/api/adapter/remote.rs b/src/api/adapter/remote.rs new file mode 100644 index 00000000..eab7742a --- /dev/null +++ b/src/api/adapter/remote.rs @@ -0,0 +1,557 @@ +use std::{borrow::Borrow, net::SocketAddr, sync::Arc}; + +use crate::api::{ + adapter::{ + ServerAdapterError, + ServerAdapterError::{ + InvalidMockDefinitionError, JsonDeserializationError, JsonSerializationError, + UpstreamError, + }, + }, + MockServerAdapter, +}; +use async_trait::async_trait; +use bytes::Bytes; +use http::{Request, StatusCode}; + +use crate::{ + common::{ + data::{ + ActiveForwardingRule, ActiveMock, ActiveProxyRule, ActiveRecording, ClosestMatch, + MockDefinition, RequestRequirements, + }, + http::HttpClient, + }, + ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig, +}; + +pub struct RemoteMockServerAdapter { + addr: SocketAddr, + http_client: Arc, +} + +impl RemoteMockServerAdapter { + pub fn new(addr: SocketAddr, http_client: Arc) -> Self { + Self { addr, http_client } + } + + fn validate_request_requirements( + &self, + requirements: &RequestRequirements, + ) -> Result<(), ServerAdapterError> { + match requirements.is_true { + Some(_) => Err(InvalidMockDefinitionError( + "Anonymous function request matchers are not supported when using a remote mock server".to_string(), + )), + None => Ok(()), + } + } + + async fn do_request(&self, req: Request) -> Result<(u16, String), ServerAdapterError> { + let (code, body_bytes) = self.do_request_raw(req).await?; + + let body = + String::from_utf8(body_bytes.to_vec()).map_err(|e| UpstreamError(e.to_string()))?; + + Ok((code, body)) + } + + async fn do_request_raw( + &self, + req: Request, + ) -> Result<(u16, Bytes), ServerAdapterError> { + let mut response = self + .http_client + .send(req) + .await + .map_err(|e| UpstreamError(e.to_string()))?; + + Ok((response.status().as_u16(), response.body().clone())) + } +} + +#[async_trait] +impl MockServerAdapter for RemoteMockServerAdapter { + async fn ping(&self) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("GET") + .uri(format!("http://{}/__httpmock__/ping", &self.addr)) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::OK { + return Err(UpstreamError(format!( + "Could not ping the mock server. Expected response status 202 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + fn host(&self) -> String { + self.addr.ip().to_string() + } + + fn port(&self) -> u16 { + self.addr.port() + } + + fn address(&self) -> &SocketAddr { + &self.addr + } + + async fn reset(&self) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!("http://{}/__httpmock__/state", &self.addr)) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not reset the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn create_mock(&self, mock: &MockDefinition) -> Result { + self.validate_request_requirements(&mock.request)?; + + let json = serde_json::to_string(mock).map_err(|e| JsonSerializationError(e))?; + + let request = Request::builder() + .method("POST") + .uri(format!("http://{}/__httpmock__/mocks", &self.address())) + .header("content-type", "application/json") + .body(Bytes::from(json)) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::CREATED.as_u16() { + return Err(UpstreamError(format!( + "Could not create mock. Expected response status 201 but was {} (response body = '{}')", + status, body + ))); + } + + let response: ActiveMock = + serde_json::from_str(&body).map_err(|e| JsonDeserializationError(e))?; + + Ok(response) + } + + async fn fetch_mock(&self, mock_id: usize) -> Result { + let request = Request::builder() + .method("GET") + .uri(format!( + "http://{}/__httpmock__/mocks/{}", + &self.address(), + mock_id + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::OK { + return Err(UpstreamError(format!( + "Could not fetch mock from the mock server. Expected response status 200 but was {} (response body = '{}')", + status, body + ))); + } + + let response: ActiveMock = + serde_json::from_str(&body).map_err(|e| JsonDeserializationError(e))?; + + Ok(response) + } + + async fn delete_mock(&self, mock_id: usize) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!( + "http://{}/__httpmock__/mocks/{}", + &self.address(), + mock_id + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete mock from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn delete_all_mocks(&self) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!("http://{}/__httpmock__/mocks", &self.address())) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete all mocks from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn verify( + &self, + requirements: &RequestRequirements, + ) -> Result, ServerAdapterError> { + let json = serde_json::to_string(requirements).map_err(|e| JsonSerializationError(e))?; + + let request = Request::builder() + .method("POST") + .uri(format!("http://{}/__httpmock__/verify", &self.address())) + .header("content-type", "application/json") + .body(Bytes::from(json)) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status == StatusCode::NOT_FOUND { + return Ok(None); + } + + if status != StatusCode::OK { + return Err(UpstreamError(format!( + "Could not verify mock. Expected response status 200 but was {} (response body = '{}')", + status, body + ))); + } + + let response: ClosestMatch = + serde_json::from_str(&body).map_err(|e| JsonDeserializationError(e))?; + + Ok(Some(response)) + } + + async fn delete_history(&self) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!("http://{}/__httpmock__/history", &self.address())) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete request history from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn create_forwarding_rule( + &self, + config: ForwardingRuleConfig, + ) -> Result { + self.validate_request_requirements(&config.request_requirements)?; + + let json = serde_json::to_string(&config).map_err(|e| JsonSerializationError(e))?; + + let request = Request::builder() + .method("POST") + .uri(format!( + "http://{}/__httpmock__/forwarding_rules", + &self.address() + )) + .header("content-type", "application/json") + .body(Bytes::from(json)) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::CREATED.as_u16() { + return Err(UpstreamError(format!( + "Could not create forwarding rule. Expected response status 201 but was {} (response body = '{}')", + status, body + ))); + } + + let response: ActiveForwardingRule = + serde_json::from_str(&body).map_err(|e| JsonDeserializationError(e))?; + + Ok(response) + } + + async fn delete_forwarding_rule(&self, id: usize) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!( + "http://{}/__httpmock__/forwarding_rules/{}", + &self.address(), + id + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete forwarding rule from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn delete_all_forwarding_rules(&self) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!( + "http://{}/__httpmock__/forwarding_rules", + &self.address() + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete all forwarding rules from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn create_proxy_rule( + &self, + config: ProxyRuleConfig, + ) -> Result { + self.validate_request_requirements(&config.request_requirements)?; + + let json = serde_json::to_string(&config).map_err(|e| JsonSerializationError(e))?; + + let request = Request::builder() + .method("POST") + .uri(format!( + "http://{}/__httpmock__/proxy_rules", + &self.address() + )) + .header("content-type", "application/json") + .body(Bytes::from(json)) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::CREATED.as_u16() { + return Err(UpstreamError(format!( + "Could not create proxy rule. Expected response status 201 but was {} (response body = '{}')", + status, body + ))); + } + + let response: ActiveProxyRule = + serde_json::from_str(&body).map_err(|e| JsonDeserializationError(e))?; + + Ok(response) + } + + async fn delete_proxy_rule(&self, id: usize) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!( + "http://{}/__httpmock__/proxy_rules/{}", + &self.address(), + id + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete proxy rule from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn delete_all_proxy_rules(&self) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!( + "http://{}/__httpmock__/proxy_rules", + &self.address() + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete all proxy rules from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn create_recording( + &self, + config: RecordingRuleConfig, + ) -> Result { + self.validate_request_requirements(&config.request_requirements)?; + + let json = serde_json::to_string(&config).map_err(|e| JsonSerializationError(e))?; + + let request = Request::builder() + .method("POST") + .uri(format!( + "http://{}/__httpmock__/recordings", + &self.address() + )) + .header("content-type", "application/json") + .body(Bytes::from(json)) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::CREATED.as_u16() { + return Err(UpstreamError(format!( + "Could not create recording. Expected response status 201 but was {} (response body = '{}')", + status, body + ))); + } + + let response: ActiveRecording = + serde_json::from_str(&body).map_err(|e| JsonDeserializationError(e))?; + + Ok(response) + } + + async fn delete_recording(&self, id: usize) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!( + "http://{}/__httpmock__/recordings/{}", + &self.address(), + id + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete recording from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn delete_all_recordings(&self) -> Result<(), ServerAdapterError> { + let request = Request::builder() + .method("DELETE") + .uri(format!( + "http://{}/__httpmock__/recordings", + &self.address() + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::NO_CONTENT { + return Err(UpstreamError(format!( + "Could not delete all recordings from the mock server. Expected response status 204 but was {} (response body = '{}')", + status, body + ))); + } + + Ok(()) + } + + async fn export_recording(&self, id: usize) -> Result, ServerAdapterError> { + let request = Request::builder() + .method("GET") + .uri(format!( + "http://{}/__httpmock__/recordings/{}", + &self.address(), + id + )) + .body(Bytes::new()) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request_raw(request).await?; + + if status == StatusCode::NOT_FOUND { + return Ok(None); + } else if status != StatusCode::OK { + return Err(UpstreamError(format!( + "Could not fetch mock from the mock server. Expected response status 200 but was {}", + status + ))); + } + + Ok(Some(body)) + } + + async fn create_mocks_from_recording<'a>( + &self, + recording_file_content: &'a str, + ) -> Result, ServerAdapterError> { + let request = Request::builder() + .method("POST") + .uri(format!( + "http://{}/__httpmock__/recordings", + &self.address(), + )) + .body(Bytes::from(recording_file_content.to_owned())) + .map_err(|e| UpstreamError(e.to_string()))?; + + let (status, body) = self.do_request(request).await?; + + if status != StatusCode::OK { + return Err(UpstreamError(format!( + "Could not create mocks from recording. Expected response status 200 but was {}", + status + ))); + } + + let response: Vec = + serde_json::from_str(&body).map_err(|e| JsonDeserializationError(e))?; + + Ok(response) + } +} diff --git a/src/api/adapter/standalone.rs b/src/api/adapter/standalone.rs deleted file mode 100644 index fab076a0..00000000 --- a/src/api/adapter/standalone.rs +++ /dev/null @@ -1,298 +0,0 @@ -use std::borrow::Borrow; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::Duration; - -use crate::api::MockServerAdapter; -use async_trait::async_trait; -use isahc::config::Configurable; -use isahc::{AsyncReadResponseExt, Request, ResponseExt}; - -pub type InternalHttpClient = isahc::HttpClient; - -use crate::common::data::{ActiveMock, ClosestMatch, MockDefinition, MockRef, RequestRequirements}; - -#[derive(Debug)] -pub struct RemoteMockServerAdapter { - addr: SocketAddr, - http_client: Arc, -} - -impl RemoteMockServerAdapter { - pub fn new(addr: SocketAddr) -> Self { - Self { - addr, - http_client: build_http_client(), - } - } - - fn validate_mock(&self, mock: &MockDefinition) -> Result<(), String> { - if mock.request.matchers.is_some() { - return Err( - "Anonymous function request matchers are not supported when using a remote mock server".to_string(), - ); - } - Ok(()) - } -} - -#[async_trait] -impl MockServerAdapter for RemoteMockServerAdapter { - fn host(&self) -> String { - self.addr.ip().to_string() - } - - fn port(&self) -> u16 { - self.addr.port() - } - - fn address(&self) -> &SocketAddr { - &self.addr - } - - async fn create_mock(&self, mock: &MockDefinition) -> Result { - // Check if the request can be sent via HTTP - self.validate_mock(mock).expect("Cannot create mock"); - - // Serialize to JSON - let json = match serde_json::to_string(mock) { - Err(err) => return Err(format!("cannot serialize mock object to JSON: {}", err)), - Ok(json) => json, - }; - - // Send the request to the mock server - let request_url = format!("http://{}/__httpmock__/mocks", &self.address()); - let request = Request::builder() - .method("POST") - .uri(request_url) - .header("content-type", "application/json") - .body(json) - .unwrap(); - - let (status, body) = match execute_request(request, &self.http_client).await { - Err(err) => return Err(format!("cannot send request to mock server: {}", err)), - Ok(sb) => sb, - }; - - // Evaluate the response status - if status != 201 { - return Err(format!( - "Could not create mock. Mock server response: status = {}, message = {}", - status, body - )); - } - - // Create response object - let response: serde_json::Result = serde_json::from_str(&body); - if let Err(err) = response { - return Err(format!("Cannot deserialize mock server response: {}", err)); - } - - Ok(response.unwrap()) - } - - async fn fetch_mock(&self, mock_id: usize) -> Result { - // Send the request to the mock server - let request_url = format!("http://{}/__httpmock__/mocks/{}", &self.address(), mock_id); - let request = Request::builder() - .method("GET") - .uri(request_url) - .body("".to_string()) - .unwrap(); - - let (status, body) = match execute_request(request, &self.http_client).await { - Err(err) => return Err(format!("Cannot send request to mock server: {}", err)), - Ok(r) => r, - }; - - // Evaluate response status code - if status != 200 { - return Err(format!( - "Could not create mock. Mock server response: status = {}, message = {}", - status, body - )); - } - - // Create response object - let response: serde_json::Result = serde_json::from_str(&body); - if let Err(err) = response { - return Err(format!("Cannot deserialize mock server response: {}", err)); - } - - Ok(response.unwrap()) - } - - async fn delete_mock(&self, mock_id: usize) -> Result<(), String> { - // Send the request to the mock server - let request_url = format!("http://{}/__httpmock__/mocks/{}", &self.address(), mock_id); - let request = Request::builder() - .method("DELETE") - .uri(request_url) - .body("".to_string()) - .unwrap(); - - let (status, body) = match execute_request(request, &self.http_client).await { - Err(err) => return Err(format!("Cannot send request to mock server: {}", err)), - Ok(sb) => sb, - }; - - // Evaluate response status code - if status != 202 { - return Err(format!( - "Could not delete mocks from server (status = {}, message = {})", - status, body - )); - } - - Ok(()) - } - - async fn delete_all_mocks(&self) -> Result<(), String> { - // Send the request to the mock server - let request_url = format!("http://{}/__httpmock__/mocks", &self.address()); - let request = Request::builder() - .method("DELETE") - .uri(request_url) - .body("".to_string()) - .unwrap(); - - let (status, body) = match execute_request(request, &self.http_client).await { - Err(err) => return Err(format!("Cannot send request to mock server: {}", err)), - Ok(sb) => sb, - }; - - // Evaluate response status code - if status != 202 { - return Err(format!( - "Could not delete mocks from server (status = {}, message = {})", - status, body - )); - } - - Ok(()) - } - - async fn verify(&self, mock_rr: &RequestRequirements) -> Result, String> { - // Serialize to JSON - let json = match serde_json::to_string(mock_rr) { - Err(err) => return Err(format!("Cannot serialize mock object to JSON: {}", err)), - Ok(json) => json, - }; - - // Send the request to the mock server - let request_url = format!("http://{}/__httpmock__/verify", &self.address()); - let request = Request::builder() - .method("POST") - .uri(request_url) - .header("content-type", "application/json") - .body(json) - .unwrap(); - - let (status, body) = match execute_request(request, &self.http_client).await { - Err(err) => return Err(format!("Cannot send request to mock server: {}", err)), - Ok(sb) => sb, - }; - - // Evaluate the response status - if status == 404 { - return Ok(None); - } - - if status != 200 { - return Err(format!( - "Could not execute verification (status = {}, message = {})", - status, body - )); - } - - // Create response object - let response: serde_json::Result = serde_json::from_str(&body); - if let Err(err) = response { - return Err(format!("cannot deserialize mock server response: {}", err)); - } - - Ok(Some(response.unwrap())) - } - - async fn delete_history(&self) -> Result<(), String> { - // Send the request to the mock server - let request_url = format!("http://{}/__httpmock__/history", &self.address()); - let request = Request::builder() - .method("DELETE") - .uri(request_url) - .body("".to_string()) - .unwrap(); - - let (status, body) = match execute_request(request, &self.http_client).await { - Err(err) => return Err(format!("Cannot send request to mock server: {}", err)), - Ok(sb) => sb, - }; - - // Evaluate response status code - if status != 202 { - return Err(format!( - "Could not delete history from server (status = {}, message = {})", - status, body - )); - } - - Ok(()) - } - - async fn ping(&self) -> Result<(), String> { - http_ping(&self.addr, self.http_client.borrow()).await - } -} - -async fn http_ping( - server_addr: &SocketAddr, - http_client: &InternalHttpClient, -) -> Result<(), String> { - let request_url = format!("http://{}/__httpmock__/ping", server_addr); - let request = Request::builder() - .method("GET") - .uri(request_url) - .body("".to_string()) - .unwrap(); - - let (status, _body) = match execute_request(request, http_client).await { - Err(err) => return Err(format!("cannot send request to mock server: {}", err)), - Ok(sb) => sb, - }; - - if status != 200 { - return Err(format!( - "Could not create mock. Mock server response: status = {}", - status - )); - } - - Ok(()) -} - -async fn execute_request( - req: Request, - http_client: &InternalHttpClient, -) -> Result<(u16, String), String> { - let mut response = match http_client.send_async(req).await { - Err(err) => return Err(format!("cannot send request to mock server: {}", err)), - Ok(r) => r, - }; - - // Evaluate the response status - let body = match response.text().await { - Err(err) => return Err(format!("cannot send request to mock server: {}", err)), - Ok(b) => b, - }; - - Ok((response.status().as_u16(), body)) -} - -fn build_http_client() -> Arc { - Arc::new( - InternalHttpClient::builder() - .tcp_keepalive(Duration::from_secs(60 * 60 * 24)) - .build() - .expect("Cannot build HTTP client"), - ) -} diff --git a/src/api/mock.rs b/src/api/mock.rs index 727560da..86f0777d 100644 --- a/src/api/mock.rs +++ b/src/api/mock.rs @@ -1,52 +1,50 @@ -use std::net::SocketAddr; -use std::str::FromStr; -use std::time::Duration; -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, -}; +use std::{io::Write, net::SocketAddr}; +use tabwriter::TabWriter; +use crate::api::output; #[cfg(feature = "color")] use colored::*; use serde::{Deserialize, Serialize}; -use serde_json::Value; use crate::api::server::MockServer; -use crate::api::{Method, Regex}; -use crate::common::data::{ClosestMatch, Diff, DiffResult, Mismatch, Reason}; -use crate::common::util::{get_test_resource_file_path, read_file, Join}; +use crate::common::util::Join; -/// Represents a reference to the mock object on a [MockServer](struct.MockServer.html). -/// It can be used to spy on the mock and also perform some management operations, such as -/// deleting the mock from the [MockServer](struct.MockServer.html). +/// Provides a reference to a mock configuration stored on a [MockServer](struct.MockServer.html). +/// This structure is used for interacting with, monitoring, and managing a specific mock's lifecycle, +/// such as observing call counts or removing the mock from the server. +/// +/// This reference allows you to control and verify the behavior of the server in response to +/// incoming HTTP requests that match the mock criteria. /// /// # Example -/// ``` -/// // Arrange +/// Demonstrates how to create and manipulate a mock on the server. This includes monitoring its usage +/// and effectively managing its lifecycle by removing it when necessary. +/// +/// ```rust /// use httpmock::prelude::*; +/// use reqwest::blocking::get; /// +/// // Arrange /// let server = MockServer::start(); /// -/// let mut mock = server.mock(|when, then|{ +/// // Create and configure a mock +/// let mut mock = server.mock(|when, then| { /// when.path("/test"); /// then.status(202); /// }); /// -/// // Send a first request, then delete the mock from the mock and send another request. -/// let response1 = isahc::get(server.url("/test")).unwrap(); -/// -/// // Fetch how often this mock has been called from the server until now -/// assert_eq!(mock.hits(), 1); +/// // Act by sending a request and verifying the mock's hit count +/// let response1 = get(&server.url("/test")).unwrap(); +/// assert_eq!(mock.hits(), 1); // Verify the mock was triggered /// -/// // Delete the mock from the mock server +/// // Remove the mock and test the server's response to the same path again /// mock.delete(); -/// -/// let response2 = isahc::get(server.url("/test")).unwrap(); +/// let response2 = get(&server.url("/test")).unwrap(); /// /// // Assert /// assert_eq!(response1.status(), 202); -/// assert_eq!(response2.status(), 404); +/// assert_eq!(response2.status(), 404); // Expect a 404 status after the mock is deleted /// ``` pub struct Mock<'a> { // Please find the reason why id is public in @@ -59,137 +57,245 @@ impl<'a> Mock<'a> { pub fn new(id: usize, server: &'a MockServer) -> Self { Self { id, server } } - /// This method asserts that the mock server received **exactly one** HTTP request that matched - /// all the request requirements of this mock. + + /// Verifies that the mock server received exactly one HTTP request matching all specified + /// request conditions for this mock. This method is useful for confirming that a particular + /// operation interacts with the server as expected in test scenarios. /// - /// **Attention**: If you want to assert more than one request, consider using either - /// [Mock::assert_hits](struct.Mock.html#method.assert_hits) or - /// [Mock::hits](struct.Mock.html#method.hits). + /// **Attention**: To assert receipt of multiple requests, use [Mock::assert_hits](struct.Mock.html#method.assert_hits) + /// or [Mock::hits](struct.Mock.html#method.hits) methods instead. /// /// # Example - /// ``` - /// // Arrange: Create mock server and a mock + /// Demonstrates creating a mock to match a specific request path, sending a request to that path, + /// and then verifying that exactly one such request was received. + /// + /// ```rust /// use httpmock::prelude::*; + /// use reqwest::blocking::get; /// + /// // Arrange: Start a mock server and set up a mock /// let server = MockServer::start(); - /// /// let mut mock = server.mock(|when, then| { /// when.path("/hits"); /// then.status(200); /// }); /// - /// // Act: Send a request, then delete the mock from the mock and send another request. - /// isahc::get(server.url("/hits")).unwrap(); + /// // Act: Send a request to the specified path + /// get(&server.url("/hits")).unwrap(); /// - /// // Assert: Make sure the mock server received exactly one request that matched all - /// // the request requirements of the mock. + /// // Assert: Check that the server received exactly one request that matched the mock /// mock.assert(); /// ``` + /// /// # Panics - /// This method will panic if there is a problem with the (standalone) mock server. + /// This method will panic if the mock server did not receive exactly one matching request or if + /// there are issues with the mock server's availability. pub fn assert(&self) { self.assert_async().join() } - /// This method asserts that the mock server received **exactly one** HTTP request that matched - /// all the request requirements of this mock. + /// Asynchronously verifies that the mock server received exactly one HTTP request matching all + /// specified request conditions for this mock. This method is suited for asynchronous testing environments + /// where operations against the mock server occur non-blockingly. /// - /// **Attention**: If you want to assert more than one request, consider using either - /// [Mock::assert_hits](struct.Mock.html#method.assert_hits) or - /// [Mock::hits](struct.Mock.html#method.hits). + /// **Attention**: To assert the receipt of multiple requests asynchronously, consider using + /// [Mock::assert_hits_async](struct.Mock.html#method.assert_hits_async) or + /// [Mock::hits_async](struct.Mock.html#method.hits_async). /// /// # Example - /// ``` - /// // Arrange: Create mock server and a mock + /// Demonstrates setting up an asynchronous mock, sending a request, and verifying that exactly + /// one such request was received. + /// + /// ```rust /// use httpmock::prelude::*; + /// use reqwest::get; + /// use syn::token; /// - /// async_std::task::block_on(async { + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Arrange: Start a mock server asynchronously and set up a mock /// let server = MockServer::start_async().await; - /// /// let mut mock = server.mock_async(|when, then| { /// when.path("/hits"); /// then.status(200); /// }).await; /// - /// // Act: Send a request, then delete the mock from the mock and send another request. - /// isahc::get_async(server.url("/hits")).await.unwrap(); + /// // Act: Send a request to the specified path asynchronously + /// get(&server.url("/hits")).await.unwrap(); /// - /// // Assert: Make sure the mock server received exactly one request that matched all - /// // the request requirements of the mock. + /// // Assert: Check that the server received exactly one request that matched the mock /// mock.assert_async().await; /// }); /// ``` + /// /// # Panics - /// This method will panic if there is a problem with the (standalone) mock server. + /// This method will panic if the mock server did not receive exactly one matching request or if + /// there are issues with the mock server's availability. pub async fn assert_async(&self) { self.assert_hits_async(1).await } - /// This method asserts that the mock server received the provided number of HTTP requests which - /// matched all the request requirements of this mock. + /// Verifies that the mock server received the specified number of HTTP requests matching all + /// the request conditions defined for this mock. /// - /// **Attention**: Consider using the shorthand version - /// [Mock::assert](struct.Mock.html#method.assert) if you want to assert only one hit. + /// This method is useful for confirming that a series of operations interact with the server as expected + /// within test scenarios, especially when specific interaction counts are significant. /// + /// **Attention**: Use [Mock::assert](struct.Mock.html#method.assert) for the common case of asserting exactly one hit. /// /// # Example - /// ``` - /// // Arrange: Create mock server and a mock + /// Demonstrates creating a mock, sending multiple requests, and verifying the number of received requests + /// matches expectations. + /// + /// ```rust /// use httpmock::prelude::*; - /// use isahc::get; + /// use reqwest::blocking::get; /// + /// // Arrange: Start a mock server and configure a mock /// let server = MockServer::start(); - /// /// let mut mock = server.mock(|when, then| { /// when.path("/hits"); /// then.status(200); /// }); /// - /// // Act: Send a request, then delete the mock from the mock and send another request. - /// get(server.url("/hits")).unwrap(); - /// get(server.url("/hits")).unwrap(); + /// // Act: Send multiple requests to the configured path + /// get(&server.url("/hits")).unwrap(); + /// get(&server.url("/hits")).unwrap(); /// - /// // Assert: Make sure the mock server received exactly two requests that matched all - /// // the request requirements of the mock. + /// // Assert: Check that the server received exactly two requests that matched the mock /// mock.assert_hits(2); /// ``` + /// /// # Panics - /// This method will panic if there is a problem with the (standalone) mock server. + /// This method will panic if the actual number of hits differs from the specified `hits`, or if + /// there are issues with the mock server's availability. + #[deprecated(since = "0.8.0", note = "please use `assert_calls` instead")] pub fn assert_hits(&self, hits: usize) { - self.assert_hits_async(hits).join() + self.assert_calls(hits) } - /// This method asserts that the mock server received the provided number of HTTP requests which - /// matched all the request requirements of this mock. + /// Verifies that the mock server received the specified number of HTTP requests matching all + /// the request conditions defined for this mock. + /// + /// This method is useful for confirming that a series of operations interact with the server as expected + /// within test scenarios, especially when specific interaction counts are significant. /// - /// **Attention**: Consider using the shorthand version - /// [Mock::assert_async](struct.Mock.html#method.assert_async) if you want to assert only one hit. + /// **Attention**: Use [Mock::assert](struct.Mock.html#method.assert) for the common case of asserting exactly one hit. /// /// # Example + /// Demonstrates creating a mock, sending multiple requests, and verifying the number of received requests + /// matches expectations. + /// + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::get; + /// + /// // Arrange: Start a mock server and configure a mock + /// let server = MockServer::start(); + /// let mut mock = server.mock(|when, then| { + /// when.path("/hits"); + /// then.status(200); + /// }); + /// + /// // Act: Send multiple requests to the configured path + /// get(&server.url("/hits")).unwrap(); + /// get(&server.url("/hits")).unwrap(); + /// + /// // Assert: Check that the server received exactly two requests that matched the mock + /// mock.assert_calls(2); /// ``` - /// // Arrange: Create mock server and a mock + /// + /// # Panics + /// This method will panic if the actual number of hits differs from the specified `hits`, or if + /// there are issues with the mock server's availability. + pub fn assert_calls(&self, count: usize) { + self.assert_calls_async(count).join() + } + + /// Asynchronously verifies that the mock server received the specified number of HTTP requests + /// matching all defined request conditions for this mock. + /// + /// This method supports asynchronous testing environments, enabling non-blocking verification + /// of multiple interactions with the mock server. It's particularly useful when exact counts + /// of interactions are critical for test assertions. + /// + /// **Attention**: For asserting exactly one request asynchronously, use + /// [Mock::assert_async](struct.Mock.html#method.assert_async) for simpler syntax. + /// + /// # Example + /// Demonstrates setting up an asynchronous mock, sending multiple requests, and verifying the + /// number of requests received matches expectations. + /// + /// ```rust /// use httpmock::prelude::*; + /// use reqwest::get; /// - /// async_std::task::block_on(async { + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Arrange: Start a mock server asynchronously and set up a mock /// let server = MockServer::start_async().await; - /// /// let mut mock = server.mock_async(|when, then| { /// when.path("/hits"); /// then.status(200); /// }).await; /// - /// // Act: Send a request, then delete the mock from the mock and send another request. - /// isahc::get_async(server.url("/hits")).await.unwrap(); - /// isahc::get_async(server.url("/hits")).await.unwrap(); + /// // Act: Send multiple asynchronous requests to the configured path + /// get(&server.url("/hits")).await.unwrap(); + /// get(&server.url("/hits")).await.unwrap(); /// - /// // Assert: Make sure the mock server received exactly two requests that matched all - /// // the request requirements of the mock. + /// // Assert: Check that the server received exactly two requests that matched the mock /// mock.assert_hits_async(2).await; /// }); /// ``` + /// /// # Panics - /// This method will panic if there is a problem with the (standalone) mock server. + /// This method will panic if the actual number of hits differs from the specified `hits`, or if + /// there are issues with the mock server's availability. + #[deprecated(since = "0.8.0", note = "please use `assert_calls_async` instead")] pub async fn assert_hits_async(&self, hits: usize) { + self.assert_calls_async(hits).await + } + + /// Asynchronously verifies that the mock server received the specified number of HTTP requests + /// matching all defined request conditions for this mock. + /// + /// This method supports asynchronous testing environments, enabling non-blocking verification + /// of multiple interactions with the mock server. It's particularly useful when exact counts + /// of interactions are critical for test assertions. + /// + /// **Attention**: For asserting exactly one request asynchronously, use + /// [Mock::assert_async](struct.Mock.html#method.assert_async) for simpler syntax. + /// + /// # Example + /// Demonstrates setting up an asynchronous mock, sending multiple requests, and verifying the + /// number of requests received matches expectations. + /// + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::get; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Arrange: Start a mock server asynchronously and set up a mock + /// let server = MockServer::start_async().await; + /// let mut mock = server.mock_async(|when, then| { + /// when.path("/hits"); + /// then.status(200); + /// }).await; + /// + /// // Act: Send multiple asynchronous requests to the configured path + /// get(&server.url("/hits")).await.unwrap(); + /// get(&server.url("/hits")).await.unwrap(); + /// + /// // Assert: Check that the server received exactly two requests that matched the mock + /// mock.assert_calls_async(2).await; + /// }); + /// ``` + /// + /// # Panics + /// This method will panic if the actual number of hits differs from the specified `hits`, or if + /// there are issues with the mock server's availability. + pub async fn assert_calls_async(&self, hits: usize) { let active_mock = self .server .server_adapter @@ -220,45 +326,94 @@ impl<'a> Mock<'a> { .await .expect("Cannot contact mock server"); - fail_with(active_mock.call_counter, hits, closest_match) + output::fail_with(active_mock.call_counter, hits, closest_match) } - /// This method returns the number of times a mock has been called at the mock server. + /// Returns the number of times the specified mock has been triggered on the mock server. + /// + /// This method is useful for verifying that a mock has been invoked the expected number of times, + /// allowing for precise control and assertion of interactions within test scenarios. /// /// # Example - /// ``` - /// // Arrange: Create mock server and a mock + /// Demonstrates setting up a mock, sending a request, and then verifying that the mock + /// was triggered exactly once. + /// + /// ```rust /// use httpmock::prelude::*; + /// use reqwest::blocking::get; /// + /// // Arrange: Start a mock server and create a mock /// let server = MockServer::start(); - /// /// let mut mock = server.mock(|when, then| { /// when.path("/hits"); /// then.status(200); /// }); /// - /// // Act: Send a request, then delete the mock from the mock and send another request. - /// isahc::get(server.url("/hits")).unwrap(); + /// // Act: Send a request to the mock path + /// get(&server.url("/hits")).unwrap(); /// - /// // Assert: Make sure the mock has been called exactly one time + /// // Assert: Verify the mock was called once /// assert_eq!(1, mock.hits()); /// ``` + /// /// # Panics - /// This method will panic if there is a problem with the (standalone) mock server. + /// This method will panic if there are issues accessing the mock server or retrieving the hit count. + #[deprecated(since = "0.8.0", note = "please use `calls` instead")] pub fn hits(&self) -> usize { - self.hits_async().join() + self.calls() } - /// This method returns the number of times a mock has been called at the mock server. + /// Returns the number of times the specified mock has been triggered on the mock server. + /// + /// This method is useful for verifying that a mock has been invoked the expected number of times, + /// allowing for precise control and assertion of interactions within test scenarios. /// /// # Example + /// Demonstrates setting up a mock, sending a request, and then verifying that the mock + /// was triggered exactly once. + /// + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::get; + /// + /// // Arrange: Start a mock server and create a mock + /// let server = MockServer::start(); + /// let mut mock = server.mock(|when, then| { + /// when.path("/hits"); + /// then.status(200); + /// }); + /// + /// // Act: Send a request to the mock path + /// get(&server.url("/hits")).unwrap(); + /// + /// // Assert: Verify the mock was called once + /// assert_eq!(1, mock.calls()); /// ``` - /// async_std::task::block_on(async { - /// // Arrange: Create mock server and a mock - /// use httpmock::prelude::*; /// - /// let server = MockServer::start_async().await; + /// # Panics + /// This method will panic if there are issues accessing the mock server or retrieving the hit count. + pub fn calls(&self) -> usize { + self.calls_async().join() + } + + /// Asynchronously returns the number of times the specified mock has been triggered on the mock server. + /// + /// This method is particularly useful in asynchronous test setups where non-blocking verifications + /// are needed to confirm that a mock has been invoked the expected number of times. It ensures test + /// assertions align with asynchronous operations. + /// + /// # Example + /// Demonstrates setting up an asynchronous mock, sending a request, and then verifying the number + /// of times the mock was triggered. + /// + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::get; /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Arrange: Start an asynchronous mock server and create a mock + /// let server = MockServer::start_async().await; /// let mut mock = server /// .mock_async(|when, then| { /// when.path("/hits"); @@ -266,16 +421,57 @@ impl<'a> Mock<'a> { /// }) /// .await; /// - /// // Act: Send a request, then delete the mock from the mock and send another request. - /// isahc::get_async(server.url("/hits")).await.unwrap(); + /// // Act: Send an asynchronous request to the mock path + /// get(&server.url("/hits")).await.unwrap(); /// - /// // Assert: Make sure the mock was called with all required attributes exactly one time. + /// // Assert: Verify the mock was called once /// assert_eq!(1, mock.hits_async().await); /// }); /// ``` + /// /// # Panics - /// This method will panic if there is a problem with the (standalone) mock server. + /// This method will panic if there are issues accessing the mock server or retrieving the hit count asynchronously. + #[deprecated(since = "0.8.0", note = "please use `calls_async` instead")] pub async fn hits_async(&self) -> usize { + self.calls_async().await + } + + /// Asynchronously returns the number of times the specified mock has been triggered on the mock server. + /// + /// This method is particularly useful in asynchronous test setups where non-blocking verifications + /// are needed to confirm that a mock has been invoked the expected number of times. It ensures test + /// assertions align with asynchronous operations. + /// + /// # Example + /// Demonstrates setting up an asynchronous mock, sending a request, and then verifying the number + /// of times the mock was triggered. + /// + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::get; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Arrange: Start an asynchronous mock server and create a mock + /// let server = MockServer::start_async().await; + /// let mut mock = server + /// .mock_async(|when, then| { + /// when.path("/hits"); + /// then.status(200); + /// }) + /// .await; + /// + /// // Act: Send an asynchronous request to the mock path + /// get(&server.url("/hits")).await.unwrap(); + /// + /// // Assert: Verify the mock was called once + /// assert_eq!(1, mock.calls_async().await); + /// }); + /// ``` + /// + /// # Panics + /// This method will panic if there are issues accessing the mock server or retrieving the hit count asynchronously. + pub async fn calls_async(&self) -> usize { let response = self .server .server_adapter @@ -288,73 +484,89 @@ impl<'a> Mock<'a> { response.call_counter } - /// Deletes the associated mock object from the mock server. + /// Removes the specified mock from the mock server. This operation is useful for testing scenarios + /// where the mock should no longer intercept requests, effectively simulating an environment + /// where certain endpoints may go offline or change behavior dynamically during testing. /// /// # Example - /// ``` - /// // Arrange + /// Demonstrates creating a mock, verifying its behavior with a request, then deleting the mock and + /// verifying that subsequent requests are not intercepted. + /// + /// ```rust /// use httpmock::prelude::*; + /// use reqwest::blocking::get; /// + /// // Arrange: Start a mock server and set up a mock /// let server = MockServer::start(); - /// - /// let mut mock = server.mock(|when, then|{ + /// let mut mock = server.mock(|when, then| { /// when.path("/test"); /// then.status(202); /// }); /// - /// // Send a first request, then delete the mock from the mock and send another request. - /// let response1 = isahc::get(server.url("/test")).unwrap(); - /// - /// // Fetch how often this mock has been called from the server until now - /// assert_eq!(mock.hits(), 1); + /// // Act: Send a request to the mock and verify its behavior + /// let response1 = get(&server.url("/test")).unwrap(); + /// assert_eq!(mock.hits(), 1); // Verify the mock was called once /// - /// // Delete the mock from the mock server + /// // Delete the mock from the server /// mock.delete(); /// - /// let response2 = isahc::get(server.url("/test")).unwrap(); + /// // Send another request and verify the response now that the mock is deleted + /// let response2 = get(&server.url("/test")).unwrap(); /// - /// // Assert + /// // Assert: The first response should be 202 as the mock was active, the second should be 404 /// assert_eq!(response1.status(), 202); /// assert_eq!(response2.status(), 404); /// ``` + /// + /// This method ensures that the mock is completely removed, and any subsequent requests to the + /// same path will not be intercepted by this mock, typically resulting in a 404 Not Found response + /// unless another active mock matches the request. pub fn delete(&mut self) { self.delete_async().join(); } - /// Deletes this mock from the mock server. This method is the asynchronous equivalent of - /// [Mock::delete](struct.Mock.html#method.delete). + /// Asynchronously deletes this mock from the mock server. This method is the asynchronous equivalent of + /// [Mock::delete](struct.Mock.html#method.delete) and is suited for use in asynchronous testing environments + /// where non-blocking operations are preferred. /// /// # Example - /// ``` - /// async_std::task::block_on(async { - /// // Arrange - /// use httpmock::prelude::*; + /// Demonstrates creating an asynchronous mock, sending a request to verify its behavior, then deleting + /// the mock and verifying that subsequent requests are not intercepted. /// - /// let server = MockServer::start_async().await; + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::get; /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Arrange: Start an asynchronous mock server and create a mock + /// let server = MockServer::start_async().await; /// let mut mock = server - /// .mock_async(|when, then|{ - /// when.path("/test"); - /// then.status(202); - /// }) - /// .await; - /// - /// // Send a first request, then delete the mock from the mock and send another request. - /// let response1 = isahc::get_async(server.url("/test")).await.unwrap(); + /// .mock_async(|when, then| { + /// when.path("/test"); + /// then.status(202); + /// }) + /// .await; /// - /// // Fetch how often this mock has been called from the server until now - /// assert_eq!(mock.hits_async().await, 1); + /// // Act: Send a request to the mock path and verify the mock's hit count + /// let response1 = get(&server.url("/test")).await.unwrap(); + /// assert_eq!(mock.hits_async().await, 1); // Verify the mock was called once /// - /// // Delete the mock from the mock server + /// // Delete the mock asynchronously from the server /// mock.delete_async().await; /// - /// let response2 = isahc::get_async(server.url("/test")).await.unwrap(); + /// // Send another request and check the response now that the mock is deleted + /// let response2 = get(&server.url("/test")).await.unwrap(); /// - /// // Assert + /// // Assert: The first response should be 202 as the mock was active, the second should be 404 /// assert_eq!(response1.status(), 202); /// assert_eq!(response2.status(), 404); /// }); /// ``` + /// + /// This method ensures that the mock is completely removed asynchronously, and any subsequent requests to the + /// same path will not be intercepted by this mock, typically resulting in a 404 Not Found response + /// unless another active mock matches the request. pub async fn delete_async(&self) { self.server .server_adapter @@ -365,18 +577,25 @@ impl<'a> Mock<'a> { .expect("could not delete mock from server"); } - /// Returns the address of the mock server where the associated mock object is store on. + /// Returns the network address of the mock server where the associated mock object is stored. + /// + /// This method provides access to the IP address and port number of the mock server, useful for + /// connecting to it in tests or displaying its address in debugging output. /// /// # Example - /// ``` - /// // Arrange: Create mock server and a mock + /// Demonstrates how to retrieve and print the address of a mock server after it has been started. + /// + /// ```rust /// use httpmock::prelude::*; + /// use std::net::SocketAddr; /// + /// // Arrange: Start a mock server /// let server = MockServer::start(); /// - /// println!("{}", server.address()); - /// // Will print "127.0.0.1:12345", - /// // where 12345 is the port that the mock server is running on. + /// // Print the address of the server + /// let address: &SocketAddr = server.address(); + /// println!("{}", address); + /// // Output will be something like "127.0.0.1:12345", where 12345 is the port the server is running on. /// ``` pub fn server_address(&self) -> &SocketAddr { self.server.server_adapter.as_ref().unwrap().address() @@ -387,45 +606,79 @@ impl<'a> Mock<'a> { /// structure with some additional functionality, that is usually not required. pub trait MockExt<'a> { /// Creates a new [Mock](struct.Mock.html) instance that references an already existing - /// mock on a [MockServer](struct.MockServer.html). This functionality is usually not required. - /// You can use it if for you need to recreate [Mock](struct.Mock.html) instances - ///. - /// * `id` - The ID of the existing mock to the [MockServer](struct.MockServer.html). - /// * `mock_server` - The [MockServer](struct.MockServer.html) to which the - /// [Mock](struct.Mock.html) instance will reference. + /// mock on a [MockServer](struct.MockServer.html). This method is typically used in advanced scenarios + /// where you need to re-establish a reference to a mock after its original instance has been dropped + /// or lost. + /// + /// # Parameters + /// * `id` - The ID of the existing mock on the [MockServer](struct.MockServer.html). + /// * `mock_server` - A reference to the [MockServer](struct.MockServer.html) where the mock is hosted. /// /// # Example - /// ``` + /// Demonstrates how to recreate a [Mock](struct.Mock.html) instance from a mock ID to verify + /// assertions or perform further actions after the original [Mock](struct.Mock.html) reference + /// has been discarded. + /// + /// ```rust /// use httpmock::{MockServer, Mock, MockExt}; - /// use isahc::get; + /// use reqwest::blocking::get; /// /// // Arrange /// let server = MockServer::start(); - /// let mock_ref = server.mock(|when, then| { + /// let initial_mock = server.mock(|when, then| { /// when.path("/test"); /// then.status(202); /// }); /// - /// // Store away the mock ID for later usage and drop the Mock instance. - /// let mock_id = mock_ref.id(); - /// drop(mock_ref); + /// // Store away the mock ID and drop the initial Mock instance + /// let mock_id = initial_mock.id(); + /// drop(initial_mock); /// - /// // Act: Send the HTTP request - /// let response = get(server.url("/test")).unwrap(); + /// // Act: Send an HTTP request to the mock endpoint + /// let response = get(&server.url("/test")).unwrap(); /// - /// // Create a new Mock instance that references the earlier mock at the MockServer. - /// let mock_ref = Mock::new(mock_id, &server); + /// // Recreate the Mock instance using the stored ID + /// let recreated_mock = Mock::new(mock_id, &server); /// - /// // Use the recreated Mock as usual. - /// mock_ref.assert(); + /// // Assert: Use the recreated Mock to check assertions + /// recreated_mock.assert(); /// assert_eq!(response.status(), 202); /// ``` - /// Refer to [`Issue 26`][https://github.com/alexliesenfeld/httpmock/issues/26] for more - /// information. + /// For more detailed use cases, see [`Issue 26`](https://github.com/alexliesenfeld/httpmock/issues/26) on GitHub. fn new(id: usize, mock_server: &'a MockServer) -> Mock<'a>; - /// Returns the ID that the mock was assigned to on the - /// [MockServer](struct.MockServer.html). + /// Returns the unique identifier (ID) assigned to the mock on the [MockServer](struct.MockServer.html). + /// This ID is used internally by the mock server to track and manage the mock throughout its lifecycle. + /// + /// The ID can be particularly useful in advanced testing scenarios where mocks need to be referenced or manipulated + /// programmatically after their creation. + /// + /// # Returns + /// Returns the ID of the mock as a `usize`. + /// + /// # Example + /// Demonstrates how to retrieve the ID of a mock for later reference or manipulation. + /// + /// ```rust + /// use httpmock::MockExt; + /// use httpmock::prelude::*; + /// + /// // Arrange: Start a mock server and create a mock + /// let server = MockServer::start(); + /// let mock = server.mock(|when, then| { + /// when.path("/example"); + /// then.status(200); + /// }); + /// + /// // Act: Retrieve the ID of the mock + /// let mock_id = mock.id(); + /// + /// // The mock_id can now be used to reference or manipulate this specific mock in subsequent operations + /// println!("Mock ID: {}", mock_id); + /// ``` + /// + /// This method is particularly useful when dealing with multiple mocks and needing to assert or modify + /// specific mocks based on their identifiers. fn id(&self) -> usize; } @@ -442,162 +695,25 @@ impl<'a> MockExt<'a> for Mock<'a> { } } -fn create_reason_output(reason: &Reason) -> String { - let mut output = String::new(); - let offsets = match reason.best_match { - true => ("\t".repeat(5), "\t".repeat(2)), - false => ("\t".repeat(1), "\t".repeat(2)), - }; - let actual_text = match reason.best_match { - true => "Actual (closest match):", - false => "Actual:", - }; - output.push_str(&format!( - "Expected:{}[{}]\t\t{}\n", - offsets.0, reason.comparison, &reason.expected - )); - output.push_str(&format!( - "{}{}{}\t{}\n", - actual_text, - offsets.1, - " ".repeat(reason.comparison.len() + 7), - &reason.actual - )); - output +pub struct MockSet<'a> { + pub ids: Vec, + pub(crate) server: &'a MockServer, } -fn create_diff_result_output(dd: &DiffResult) -> String { - let mut output = String::new(); - output.push_str("Diff:"); - if dd.differences.is_empty() { - output.push_str(""); +impl<'a> MockSet<'a> { + pub fn delete(&mut self) { + self.delete_async().join(); } - output.push_str("\n"); - - dd.differences.iter().for_each(|d| { - match d { - Diff::Same(e) => { - output.push_str(&format!(" | {}", e)); - } - Diff::Add(e) => { - #[cfg(feature = "color")] - output.push_str(&format!("+++| {}", e).green().to_string()); - #[cfg(not(feature = "color"))] - output.push_str(&format!("+++| {}", e)); - } - Diff::Rem(e) => { - #[cfg(feature = "color")] - output.push_str(&format!("---| {}", e).red().to_string()); - #[cfg(not(feature = "color"))] - output.push_str(&format!("---| {}", e)); - } - } - output.push_str("\n") - }); - output.push_str("\n"); - output -} - -fn create_mismatch_output(idx: usize, mm: &Mismatch) -> String { - let mut output = String::new(); - - output.push_str(&format!("{} : {}", idx + 1, &mm.title)); - output.push_str("\n"); - output.push_str(&"-".repeat(90)); - output.push_str("\n"); - - mm.reason - .as_ref() - .map(|reason| output.push_str(&create_reason_output(reason))); - - mm.diff - .as_ref() - .map(|diff_result| output.push_str(&create_diff_result_output(diff_result))); - - output.push_str("\n"); - output -} -fn fail_with(actual_hits: usize, expected_hits: usize, closest_match: Option) { - match closest_match { - None => assert!(false, "No request has been received by the mock server."), - Some(closest_match) => { - let mut output = String::new(); - output.push_str(&format!( - "{} of {} expected requests matched the mock specification, .\n", - actual_hits, expected_hits - )); - output.push_str(&format!( - "Here is a comparison with the most similar non-matching request (request number {}): \n\n", - closest_match.request_index + 1 - )); - - for (idx, mm) in closest_match.mismatches.iter().enumerate() { - output.push_str(&create_mismatch_output(idx, &mm)); - } - - closest_match.mismatches.first().map(|mismatch| { - mismatch - .reason - .as_ref() - .map(|reason| assert_eq!(reason.expected, reason.actual, "{}", output)) - }); - - assert!(false, output) + pub async fn delete_async(&self) { + for id in &self.ids { + self.server + .server_adapter + .as_ref() + .unwrap() + .delete_mock(*id) + .await + .expect("could not delete mock from server"); } } } - -#[cfg(test)] -mod test { - use crate::api::mock::fail_with; - use crate::common::data::{ - ClosestMatch, Diff, DiffResult, HttpMockRequest, Mismatch, Reason, Tokenizer, - }; - - #[test] - #[cfg(not(feature = "color"))] - #[should_panic(expected = "1 : This is a title\n\ - ------------------------------------------------------------------------------------------\n\ - Expected: [equals] /toast\n\ - Actual: /test\n\ - Diff:\n | t\n---| e\n+++| oa\n | st")] - fn fail_with_message_test() { - // Arrange - let closest_match = ClosestMatch { - request: HttpMockRequest { - path: "/test".to_string(), - method: "GET".to_string(), - headers: None, - query_params: None, - body: None, - }, - request_index: 0, - mismatches: vec![Mismatch { - title: "This is a title".to_string(), - reason: Some(Reason { - expected: "/toast".to_string(), - actual: "/test".to_string(), - comparison: "equals".to_string(), - best_match: false, - }), - diff: Some(DiffResult { - differences: vec![ - Diff::Same(String::from("t")), - Diff::Rem(String::from("e")), - Diff::Add(String::from("oa")), - Diff::Same(String::from("st")), - ], - distance: 5.0, - tokenizer: Tokenizer::Line, - }), - }], - }; - - // Act - fail_with(1, 2, Some(closest_match)); - - // Assert - // see "should panic" annotation - } -} diff --git a/src/api/mod.rs b/src/api/mod.rs index 68c650ef..8c9b9ff6 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,16 +1,27 @@ // TODO: Remove this at some point #![allow(clippy::needless_lifetimes)] -pub use adapter::{local::LocalMockServerAdapter, Method, MockServerAdapter, Regex}; +pub use adapter::{local::LocalMockServerAdapter, MockServerAdapter}; + +use serde::{Deserialize, Serialize}; +use std::str::FromStr; #[cfg(feature = "remote")] -pub use adapter::standalone::RemoteMockServerAdapter; +pub use adapter::remote::RemoteMockServerAdapter; +use crate::common; pub use mock::{Mock, MockExt}; pub use server::MockServer; pub use spec::{Then, When}; mod adapter; mod mock; +mod output; +mod proxy; mod server; pub mod spec; + +/// Type alias for [regex::Regex](../regex/struct.Regex.html). +pub type Regex = common::data::HttpMockRegex; + +pub use crate::common::data::Method; diff --git a/src/api/output.rs b/src/api/output.rs new file mode 100644 index 00000000..fd8641d9 --- /dev/null +++ b/src/api/output.rs @@ -0,0 +1,382 @@ +use std::io::Write; + +use crate::common::{ + data::{ + ClosestMatch, Diff, DiffResult, FunctionComparison, KeyValueComparison, + KeyValueComparisonKeyValuePair, Mismatch, SingleValueComparison, + }, + util::title_case, +}; + +use tabwriter::TabWriter; + +use crate::server::matchers::generic::MatchingStrategy; +#[cfg(feature = "color")] +use colored::Colorize; + +const QUOTED_TEXT: &'static str = "quoted for better readability"; + +pub fn fail_with(actual_hits: usize, expected_hits: usize, closest_match: Option) { + match closest_match { + None => assert!(false, "No request has been received by the mock server."), + Some(closest_match) => { + let mut output = String::new(); + output.push_str(&format!( + "{} of {} expected requests matched the mock specification.\n", + actual_hits, expected_hits + )); + output.push_str(&format!( + "Here is a comparison with the most similar unmatched request (request number {}): \n\n", + closest_match.request_index + 1 + )); + + let mut fail_text = None; + + for (idx, mm) in closest_match.mismatches.iter().enumerate() { + let (mm_output, fail_text_pair) = create_mismatch_output(idx, &mm); + + if fail_text == None { + if let Some(text) = fail_text_pair { + fail_text = Some(text) + } + } + + output.push_str(&mm_output); + } + + if let Some((left, right)) = fail_text { + assert_eq!(left, right, "{}", output) + } + + assert!(false, "{}", output) + } + } +} + +pub fn create_mismatch_output( + idx: usize, + mismatch: &Mismatch, +) -> (String, Option<(String, String)>) { + let mut tw = TabWriter::new(vec![]); + let mut ide_diff_left = String::new(); + let mut ide_diff_right = String::new(); + + write_header(&mut tw, idx, mismatch); + + if let Some(comparison) = &mismatch.comparison { + let (left, right) = handle_single_value_comparison(&mut tw, mismatch, comparison); + + ide_diff_left.push_str(&left); + ide_diff_right.push_str(&right); + } else if let Some(comparison) = &mismatch.key_value_comparison { + let (left, right) = handle_key_value_comparison(&mut tw, mismatch, comparison); + + ide_diff_left.push_str(&left); + ide_diff_right.push_str(&right); + } else if let Some(comparison) = &mismatch.function_comparison { + handle_function_comparison(&mut tw, mismatch, comparison); + } + + write_footer(&mut tw, mismatch); + + tw.flush().unwrap(); + + let output = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + + if ide_diff_left.len() > 0 && ide_diff_right.len() > 0 { + return (output, Some((ide_diff_left, ide_diff_right))); + } + + (output, None) +} + +fn write_header(tw: &mut TabWriter>, idx: usize, mismatch: &Mismatch) { + writeln!(tw, "{}", &"-".repeat(60)).unwrap(); + writeln!( + tw, + "{}", + &format!("{} : {} Mismatch ", idx + 1, title_case(&mismatch.entity),) + ) + .unwrap(); + writeln!(tw, "{}", &"-".repeat(60)).unwrap(); +} + +fn handle_single_value_comparison( + tw: &mut TabWriter>, + mismatch: &Mismatch, + comparison: &SingleValueComparison, +) -> (String, String) { + writeln!( + tw, + "Expected {} {}:\n{}", + mismatch.entity, comparison.operator, comparison.expected + ) + .unwrap(); + + writeln!(tw, "\nReceived:\n{}", comparison.actual).unwrap(); + + ( + comparison.expected.to_string(), + comparison.actual.to_string(), + ) +} + +fn handle_key_value_comparison( + tw: &mut TabWriter>, + mismatch: &Mismatch, + comparison: &KeyValueComparison, +) -> (String, String) { + let most_similar = match mismatch.best_match { + true => format!(" (most similar {})", mismatch.entity), + false => String::from(" "), + }; + + writeln!(tw, "Expected:").unwrap(); + + if let Some(key) = &comparison.key { + let expected = match quote_if_whitespace(&key.expected) { + (actual, true) => format!("{} ({})", actual, "ED_TEXT), + (actual, false) => format!("{}", actual), + }; + writeln!(tw, "\tkey\t[{}]\t{}", key.operator, expected).unwrap(); + } + + if let Some(value) = &comparison.value { + let expected = match quote_if_whitespace(&value.expected) { + (expected, true) => format!("{} ({})", expected, "ED_TEXT), + (expected, false) => format!("{}", expected), + }; + writeln!(tw, "\tvalue\t[{}]\t{}", value.operator, expected).unwrap(); + } + + if let (Some(expected_count), Some(actual_count)) = + (comparison.expected_count, comparison.actual_count) + { + if comparison.key.is_none() && comparison.value.is_none() { + writeln!( + tw, + "\n{} to appear {} {} but appeared {}", + mismatch.entity, + expected_count, + times_str(expected_count), + actual_count + ) + .unwrap(); + } else { + writeln!( + tw, + "\nto appear {} {} but appeared {}", + expected_count, + times_str(expected_count), + actual_count + ) + .unwrap(); + } + + print_all_request_values(tw, &mismatch.entity, &comparison.all); + + return (expected_count.to_string(), actual_count.to_string()); + } + + if let (Some(key_attr), Some(value_attr)) = (&comparison.key, &comparison.value) { + let result = match (&key_attr.actual, &value_attr.actual) { + (Some(key), Some(value)) => { + writeln!(tw, "\nReceived{}:\n\t{}={}", most_similar, key, value).unwrap(); + (format!("{}\n{}", key, value), format!("{}\n{}", key, value)) + } + (None, Some(value)) => { + writeln!( + tw, + "\nbut{}{} value was\n\t{}", + most_similar, mismatch.entity, value + ) + .unwrap(); + (format!("{}", value), format!("{}", value)) + } + (Some(key), None) => { + writeln!( + tw, + "\nbut{}{} key was\n\t{}", + most_similar, mismatch.entity, key + ) + .unwrap(); + (format!("{}", key), format!("{}", key)) + } + (None, None) => { + let msg = match &mismatch.matching_strategy { + None => "but none was provided", + Some(v) => match v { + MatchingStrategy::Presence => { + "to be in the request, but none was provided." + } + MatchingStrategy::Absence => { + "not to be present, but the request contained it." + } + }, + }; + + writeln!(tw, "\n{}", msg).unwrap(); + (String::new(), String::new()) + } + }; + + // print_value_not_in_request(tw, &mismatch.matching_strategy); + print_all_request_values(tw, &mismatch.entity, &comparison.all); + + return result; + } + + print_value_not_in_request(tw, &mismatch.matching_strategy); + print_all_request_values(tw, &mismatch.entity, &comparison.all); + + (String::new(), String::new()) +} + +fn print_all_request_values( + tw: &mut TabWriter>, + entity: &str, + all: &Vec, +) { + if all.is_empty() { + return; + } + + writeln!(tw, "\nAll received {} values:", entity).unwrap(); + + for (index, pair) in all.iter().enumerate() { + let value = if pair.value.is_some() { + format!("={}", pair.value.clone().unwrap()) + } else { + String::new() + }; + + let text = format!("{}{}", pair.key, value); + writeln!(tw, "\t{}. {}", index + 1, text).unwrap(); + } +} + +fn print_value_not_in_request( + tw: &mut TabWriter>, + matching_strategy: &Option, +) { + writeln!( + tw, + "\n{}", + match matching_strategy { + None => "but none was provided", + Some(v) => match v { + MatchingStrategy::Presence => "to be in the request, but none was provided.", + MatchingStrategy::Absence => "not to be present, but the request contained it.", + }, + } + ) + .unwrap(); +} + +fn handle_function_comparison( + tw: &mut TabWriter>, + mismatch: &Mismatch, + comparison: &FunctionComparison, +) { + writeln!( + tw, + "Custom matcher function {} with index {} did not match the request", + mismatch.matcher_method, comparison.index + ) + .unwrap(); +} + +fn write_footer(tw: &mut TabWriter>, mismatch: &Mismatch) { + let mut version = env!("CARGO_PKG_VERSION"); + if version.trim().is_empty() { + version = "latest"; + } + + let link = format!( + "https://docs.rs/httpmock/{}/httpmock/struct.When.html#method.{}", + version, mismatch.matcher_method + ); + + writeln!(tw).unwrap(); + + if let Some(diff_result) = &mismatch.diff { + writeln!(tw, "{}", &create_diff_result_output(diff_result)).unwrap(); + writeln!(tw).unwrap(); + } + + writeln!(tw, "Matcher:\t{}", mismatch.matcher_method).unwrap(); + writeln!(tw, "Docs:\t{}", link).unwrap(); + writeln!(tw, " ").unwrap(); +} + +fn create_diff_result_output(dd: &DiffResult) -> String { + let mut output = String::new(); + output.push_str("Diff:"); + if dd.differences.is_empty() { + output.push_str(""); + } + output.push_str("\n"); + + dd.differences.iter().enumerate().for_each(|(idx, d)| { + if idx > 0 { + output.push_str("\n") + } + + match d { + Diff::Same(edit) => { + for line in remove_trailing_linebreak(edit).split("\n") { + output.push_str(&format!(" | {}", line)); + } + } + Diff::Add(edit) => { + for line in remove_trailing_linebreak(edit).split("\n") { + #[cfg(feature = "color")] + output.push_str(&format!("+++| {}", line).green().to_string()); + #[cfg(not(feature = "color"))] + output.push_str(&format!("+++| {}", line)); + } + } + Diff::Rem(edit) => { + for line in remove_trailing_linebreak(edit).split("\n") { + #[cfg(feature = "color")] + output.push_str(&format!("---| {}", line).red().to_string()); + #[cfg(not(feature = "color"))] + output.push_str(&format!("---| {}", line)); + } + } + } + }); + output +} + +#[inline] +fn times_str<'a>(v: usize) -> &'a str { + if v == 1 { + return "time"; + } + + return "times"; +} + +fn quote_if_whitespace(s: &str) -> (String, bool) { + if s.is_empty() || s.starts_with(char::is_whitespace) || s.ends_with(char::is_whitespace) { + (format!("\"{}\"", s), true) + } else { + (s.to_string(), false) + } +} + +fn remove_linebreaks(s: &str) -> String { + s.replace("\r\n", "").replace('\n', "").replace('\r', "") +} + +fn remove_trailing_linebreak(s: &str) -> String { + let mut result = s.to_string(); + if result.ends_with('\n') { + result.pop(); + if result.ends_with('\r') { + result.pop(); + } + } + result +} diff --git a/src/api/proxy.rs b/src/api/proxy.rs new file mode 100644 index 00000000..afa3eff0 --- /dev/null +++ b/src/api/proxy.rs @@ -0,0 +1,351 @@ +use crate::{ + api::server::MockServer, + common::{ + data::RequestRequirements, + util::{write_file, Join}, + }, + RecordingRuleConfig, When, +}; +use std::{ + cell::Cell, + path::{Path, PathBuf}, + rc::Rc, +}; + +/// Represents a forwarding rule on a [MockServer](struct.MockServer.html), allowing HTTP requests +/// that meet specific criteria to be redirected to a designated destination. Each rule is +/// uniquely identified by an ID within the server context. +pub struct ForwardingRule<'a> { + pub id: usize, + pub(crate) server: &'a MockServer, +} + +impl<'a> ForwardingRule<'a> { + pub fn new(id: usize, server: &'a MockServer) -> Self { + Self { id, server } + } + + /// Synchronously deletes the forwarding rule from the mock server. + /// This method blocks the current thread until the deletion has been completed, ensuring that the rule is no longer active and will not affect any further requests. + /// + /// # Panics + /// Panics if the deletion fails, typically due to issues such as the rule not existing or server connectivity problems. + pub fn delete(&mut self) { + self.delete_async().join(); + } + + /// Asynchronously deletes the forwarding rule from the mock server. + /// This method performs the deletion without blocking the current thread, + /// making it suitable for use in asynchronous applications where maintaining responsiveness + /// or concurrent execution is necessary. + /// + /// # Panics + /// Panics if the deletion fails, typically due to issues such as the rule not existing or server connectivity problems. + /// This method will raise an immediate panic on such failures, signaling that the operation could not be completed as expected. + pub async fn delete_async(&self) { + self.server + .server_adapter + .as_ref() + .unwrap() + .delete_forwarding_rule(self.id) + .await + .expect("could not delete mock from server"); + } +} +/// Provides methods for managing a proxy rule from the server. +pub struct ProxyRule<'a> { + pub id: usize, + pub(crate) server: &'a MockServer, +} + +impl<'a> ProxyRule<'a> { + pub fn new(id: usize, server: &'a MockServer) -> Self { + Self { id, server } + } + + /// Synchronously deletes the proxy rule from the server. + /// This method blocks the current thread until the deletion is complete, ensuring that + /// the rule is removed and will no longer redirect any requests. + /// + /// # Usage + /// This method is typically used in synchronous environments where immediate removal of the + /// rule is necessary and can afford a blocking operation. + /// + /// # Panics + /// Panics if the deletion fails due to server-related issues such as connectivity problems, + /// or if the rule does not exist on the server. + pub fn delete(&mut self) { + self.delete_async().join(); + } + + /// Asynchronously deletes the proxy rule from the server. + /// This method allows for non-blocking operations, suitable for asynchronous environments + /// where tasks are performed concurrently without interrupting the main workflow. + /// + /// # Usage + /// Ideal for use in modern async/await patterns in Rust, providing a way to handle resource + /// cleanup without stalling other operations. + /// + /// # Panics + /// Panics if the deletion fails due to server-related issues such as connectivity problems, + /// or if the rule does not exist on the server. This method raises an immediate panic to + /// indicate that the operation could not be completed as expected. + pub async fn delete_async(&self) { + self.server + .server_adapter + .as_ref() + .unwrap() + .delete_proxy_rule(self.id) + .await + .expect("could not delete mock from server"); + } +} + +/// Represents a recording of interactions (requests and responses) on a mock server. +/// This structure is used to capture and store detailed information about the HTTP +/// requests received by the server and the corresponding responses sent back. +/// +/// The `Recording` structure can be especially useful in testing scenarios where +/// monitoring and verifying the exact behavior of HTTP interactions is necessary, +/// such as ensuring that a server is responding with the correct headers, body content, +/// and status codes in response to various requests. +pub struct Recording<'a> { + pub id: usize, + pub(crate) server: &'a MockServer, +} + +/// Represents a reference to a recording of HTTP interactions on a mock server. +/// This struct allows for management and retrieval of recorded data, such as viewing, +/// exporting, and deleting the recording. +impl<'a> Recording<'a> { + pub fn new(id: usize, server: &'a MockServer) -> Self { + Self { id, server } + } + + /// Synchronously deletes the recording from the mock server. + /// This method blocks the current thread until the deletion is completed, + /// ensuring that the recording is fully removed before proceeding. + /// + /// # Panics + /// Panics if the deletion fails, which can occur if the recording does not exist, + /// or there are server connectivity issues. + pub fn delete(&mut self) { + self.delete_async().join(); + } + + /// Asynchronously deletes the recording from the mock server. + /// This method allows for non-blocking operations, suitable for asynchronous environments + /// where tasks are performed concurrently without waiting for the deletion to complete. + /// + /// # Panics + /// Panics if the deletion fails, typically due to the recording not existing on the server + /// or connectivity issues with the server. This method provides immediate feedback by + /// raising a panic on such failures. + pub async fn delete_async(&self) { + self.server + .server_adapter + .as_ref() + .unwrap() + .delete_recording(self.id) + .await + .expect("could not delete mock from server"); + } + + /// Synchronously saves the recording to a specified directory with a timestamped filename. + /// The file is named using a combination of the provided scenario name and a UNIX timestamp, formatted as YAML. + /// + /// # Parameters + /// - `dir`: The directory path where the file will be saved. + /// - `scenario_name`: A descriptive name for the scenario, used as part of the filename. + /// + /// # Returns + /// Returns a `Result` containing the `PathBuf` of the created file, or an error if the save operation fails. + /// + /// # Errors + /// Errors if the file cannot be written due to issues like directory permissions, unavailable disk space, or other I/O errors. + pub fn save_to, IntoString: Into>( + &self, + dir: PathRef, + scenario_name: IntoString, + ) -> Result> { + self.save_to_async(dir, scenario_name).join() + } + + /// Asynchronously saves the recording to the specified directory with a scenario-specific and timestamped filename. + /// + /// # Parameters + /// - `dir`: The directory path where the file will be saved. + /// - `scenario`: A string representing the scenario name, used as part of the filename. + /// + /// # Returns + /// Returns an `async` `Result` with the `PathBuf` of the saved file or an error if unable to save. + pub async fn save_to_async, IntoString: Into>( + &self, + dir: PathRef, + scenario: IntoString, + ) -> Result> { + let rec = self + .server + .server_adapter + .as_ref() + .unwrap() + .export_recording(self.id) + .await?; + + let scenario = scenario.into(); + let dir = dir.as_ref(); + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH)? + .as_secs(); + let filename = format!("{}_{}.yaml", scenario, timestamp); + let filepath = dir.join(filename); + + if let Some(bytes) = rec { + return Ok(write_file(&filepath, &bytes, true).await?); + } + + Err("No recording data available".into()) + } + + /// Synchronously saves the recording to the default directory (`target/httpmock/recordings`) with the scenario name. + /// + /// # Parameters + /// - `scenario_name`: A descriptive name for the scenario, which helps identify the recording file. + /// + /// # Returns + /// Returns a `Result` with the `PathBuf` to the saved file or an error. + pub fn save>( + &self, + scenario_name: IntoString, + ) -> Result> { + self.save_async(scenario_name).join() + } + + /// Asynchronously saves the recording to the default directory structured under `target/httpmock/recordings`. + /// + /// # Parameters + /// - `scenario`: A descriptive name for the test scenario, used in naming the saved file. + /// + /// # Returns + /// Returns an `async` `Result` with the `PathBuf` of the saved file or an error. + pub async fn save_async>( + &self, + scenario: IntoString, + ) -> Result> { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("target") + .join("httpmock") + .join("recordings"); + self.save_to_async(path, scenario).await + } +} + +pub struct ForwardingRuleBuilder { + pub(crate) request_requirements: Rc>, + pub(crate) headers: Rc>>, +} + +impl ForwardingRuleBuilder { + pub fn add_request_header, Value: Into>( + mut self, + key: Key, + value: Value, + ) -> Self { + let mut headers = self.headers.take(); + headers.push((key.into(), value.into())); + self.headers.set(headers); + self + } + + pub fn filter(mut self, when: WhenSpecFn) -> Self + where + WhenSpecFn: FnOnce(When), + { + when(When { + expectations: self.request_requirements.clone(), + }); + self + } +} + +pub struct ProxyRuleBuilder { + // TODO: These fields are visible to the user, make them not public + pub(crate) request_requirements: Rc>, + pub(crate) headers: Rc>>, +} + +impl ProxyRuleBuilder { + pub fn add_request_header, Value: Into>( + mut self, + key: Key, + value: Value, + ) -> Self { + let mut headers = self.headers.take(); + headers.push((key.into(), value.into())); + self.headers.set(headers); + self + } + + pub fn filter(mut self, when: WhenSpecFn) -> Self + where + WhenSpecFn: FnOnce(When), + { + when(When { + expectations: self.request_requirements.clone(), + }); + + self + } +} + +pub struct RecordingRuleBuilder { + pub config: Rc>, +} + +impl RecordingRuleBuilder { + pub fn record_request_header>(mut self, header: IntoString) -> Self { + let mut config = self.config.take(); + config.record_headers.push(header.into()); + self.config.set(config); + self + } + + pub fn record_request_headers>( + mut self, + headers: Vec, + ) -> Self { + let mut config = self.config.take(); + config + .record_headers + .extend(headers.into_iter().map(Into::into)); + self.config.set(config); + self + } + + pub fn filter(mut self, when: WhenSpecFn) -> Self + where + WhenSpecFn: FnOnce(When), + { + let mut config = self.config.take(); + + let mut request_requirements = Rc::new(Cell::new(config.request_requirements)); + + when(When { + expectations: request_requirements.clone(), + }); + + config.request_requirements = request_requirements.take(); + + self.config.set(config); + + self + } + + pub fn record_response_delays(mut self, record: bool) -> Self { + let mut config = self.config.take(); + config.record_response_delays = record; + self.config.set(config); + + self + } +} diff --git a/src/api/server.rs b/src/api/server.rs index 7f9dad9b..536430d3 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,21 +1,58 @@ -use crate::api::spec::{Then, When}; #[cfg(feature = "remote")] use crate::api::RemoteMockServerAdapter; -use crate::api::{LocalMockServerAdapter, MockServerAdapter}; -use crate::common::data::{MockDefinition, MockServerHttpResponse, RequestRequirements}; -use crate::common::util::{read_env, with_retry, Join}; -use crate::server::{start_server, MockServerState}; -use crate::Mock; +use crate::api::{ + proxy::{ + ForwardingRule, ForwardingRuleBuilder, ProxyRule, ProxyRuleBuilder, Recording, + RecordingRuleBuilder, + }, + spec::{Then, When}, +}; +#[cfg(feature = "remote")] +use crate::common::http::HttpMockHttpClient; + +use crate::common::data::ForwardingRuleConfig; + +use crate::{ + api::{LocalMockServerAdapter, MockServerAdapter}, + common::{ + data::{MockDefinition, MockServerHttpResponse, RequestRequirements}, + runtime, + util::{read_env, read_file_async, with_retry, Join}, + }, +}; + +use crate::{ + api::mock::MockSet, + server::{state::HttpMockStateManager, HttpMockServerBuilder}, +}; + +#[cfg(feature = "proxy")] +use crate::ProxyRuleConfig; + +use crate::{Mock, RecordingRuleConfig}; use async_object_pool::Pool; -use std::cell::Cell; -use std::future::pending; -use std::net::{SocketAddr, ToSocketAddrs}; -use std::rc::Rc; -use std::sync::Arc; -use std::thread; -use tokio::task::LocalSet; - -/// A mock server that is able to receive and respond to HTTP requests. +use lazy_static::lazy_static; +use std::{ + cell::Cell, + future::pending, + net::{SocketAddr, ToSocketAddrs}, + path::PathBuf, + rc::Rc, + sync::Arc, + thread, +}; +use tokio::sync::oneshot::channel; + +/// Represents a mock server designed to simulate HTTP server behaviors for testing purposes. +/// This server intercepts HTTP requests and can be configured to return predetermined responses. +/// It is used extensively in automated tests to validate client behavior without the need for a live server, +/// ensuring that applications behave as expected in controlled environments. +/// +/// The mock server allows developers to: +/// - Specify expected HTTP requests using a variety of matching criteria such as path, method, headers, and body content. +/// - Define corresponding HTTP responses including status codes, headers, and body data. +/// - Monitor and verify that the expected requests are made by the client under test. +/// - Simulate various network conditions and server responses, including errors and latencies. pub struct MockServer { pub(crate) server_adapter: Option>, pool: Arc>>, @@ -36,10 +73,19 @@ impl MockServer { return server; } - /// Asynchronously connects to a remote mock server that is running in standalone mode using - /// the provided address of the form : (e.g. "127.0.0.1:8080") to establish - /// the connection. - /// **Note**: This method requires the feature `remote` to be enabled. + /// Asynchronously connects to a remote mock server running in standalone mode. + /// + /// # Arguments + /// * `address` - A string slice representing the address in the format ":", e.g., "127.0.0.1:8080". + /// + /// # Returns + /// An instance of `Self` representing the connected mock server. + /// + /// # Panics + /// This method will panic if the address cannot be parsed, resolved to an IPv4 address, or if the mock server is unreachable. + /// + /// # Note + /// This method requires the `remote` feature to be enabled. #[cfg(feature = "remote")] pub async fn connect_async(address: &str) -> Self { let addr = address @@ -49,35 +95,73 @@ impl MockServer { .expect("Not able to resolve the provided host name to an IPv4 address"); let adapter = REMOTE_SERVER_POOL_REF - .take_or_create(|| Arc::new(RemoteMockServerAdapter::new(addr))) + .take_or_create(|| { + Arc::new(RemoteMockServerAdapter::new( + addr, + REMOTE_SERVER_CLIENT.clone(), + )) + }) .await; Self::from(adapter, REMOTE_SERVER_POOL_REF.clone()).await } - /// Synchronously connects to a remote mock server that is running in standalone mode using - /// the provided address of the form : (e.g. "127.0.0.1:8080") to establish - /// the connection. - /// **Note**: This method requires the feature `remote` to be enabled. + /// Synchronously connects to a remote mock server running in standalone mode. + /// + /// # Arguments + /// * `address` - A string slice representing the address in the format ":", e.g., "127.0.0.1:8080". + /// + /// # Returns + /// An instance of `Self` representing the connected mock server. + /// + /// # Panics + /// This method will panic if the address cannot be parsed, resolved to an IPv4 address, or if the mock server is unreachable. + /// + /// # Note + /// This method requires the `remote` feature to be enabled. #[cfg(feature = "remote")] pub fn connect(address: &str) -> Self { Self::connect_async(address).join() } - /// Asynchronously connects to a remote mock server that is running in standalone mode using - /// connection parameters stored in `HTTPMOCK_HOST` and `HTTPMOCK_PORT` environment variables. - /// **Note**: This method requires the feature `remote` to be enabled. + /// Asynchronously connects to a remote mock server running in standalone mode + /// using connection parameters stored in the `HTTPMOCK_HOST` and `HTTPMOCK_PORT` + /// environment variables. + /// + /// # Returns + /// An instance of `Self` representing the connected mock server. + /// + /// # Panics + /// This method will panic if the `HTTPMOCK_PORT` environment variable cannot be + /// parsed to an integer or if the connection fails. + /// + /// # Note + /// This method requires the `remote` feature to be enabled. + /// + /// # Environment Variables + /// * `HTTPMOCK_HOST` - The hostname or IP address of the mock server (default: "127.0.0.1"). + /// * `HTTPMOCK_PORT` - The port number of the mock server (default: "5050"). #[cfg(feature = "remote")] pub async fn connect_from_env_async() -> Self { let host = read_env("HTTPMOCK_HOST", "127.0.0.1"); - let port = read_env("HTTPMOCK_PORT", "5000") + let port = read_env("HTTPMOCK_PORT", "5050") .parse::() .expect("Cannot parse environment variable HTTPMOCK_PORT to an integer"); Self::connect_async(&format!("{}:{}", host, port)).await } - /// Synchronously connects to a remote mock server that is running in standalone mode using - /// connection parameters stored in `HTTPMOCK_HOST` and `HTTPMOCK_PORT` environment variables. - /// **Note**: This method requires the feature `remote` to be enabled. + /// Synchronously connects to a remote mock server running in standalone mode + /// using connection parameters stored in the `HTTPMOCK_HOST` and `HTTPMOCK_PORT` + /// environment variables. + /// + /// # Returns + /// An instance of `Self` representing the connected mock server. + /// + /// # Panics + /// This method will panic if the `HTTPMOCK_PORT` environment variable cannot be + /// parsed to an integer or if the connection fails. + /// + /// # Note + /// This method requires the `remote` feature to be enabled. #[cfg(feature = "remote")] pub fn connect_from_env() -> Self { Self::connect_from_env_async().join() @@ -85,18 +169,23 @@ impl MockServer { /// Starts a new `MockServer` asynchronously. /// - /// Attention: This library manages a pool of `MockServer` instances in the background. - /// Instead of always starting a new mock server, a `MockServer` instance is only created - /// on demand if there is no free `MockServer` instance in the pool and the pool has not - /// reached a maximum size yet. Otherwise, *THIS METHOD WILL BLOCK* the executing function - /// until a free mock server is available. + /// # Attention + /// This library manages a pool of `MockServer` instances in the background. + /// Instead of always starting a new mock server, a `MockServer` instance is + /// only created on demand if there is no free `MockServer` instance in the pool + /// and the pool has not reached its maximum size yet. Otherwise, **THIS METHOD WILL BLOCK** + /// the executing function until a free mock server is available. /// - /// This allows to run many tests in parallel, but will prevent exhaust the executing - /// machine by creating too many mock servers. + /// This approach allows running many tests in parallel without exhausting + /// the executing machine by creating too many mock servers. /// /// A `MockServer` instance is automatically taken from the pool whenever this method is called. /// The instance is put back into the pool automatically when the corresponding - /// 'MockServer' variable gets out of scope. + /// `MockServer` variable goes out of scope. + /// + /// # Returns + /// An instance of `Self` representing the started mock server. + /// ``` pub async fn start_async() -> Self { let adapter = LOCAL_SERVER_POOL_REF .take_or_create(LOCAL_SERVER_ADAPTER_GENERATOR) @@ -122,22 +211,52 @@ impl MockServer { Self::start_async().join() } - /// The hostname of the `MockServer`. By default, this is `127.0.0.1`. - /// In standalone mode, the hostname will be the host where the standalone mock server is - /// running. + /// Returns the hostname of the `MockServer`. + /// + /// By default, this is `127.0.0.1`. In standalone mode, the hostname will be + /// the host where the standalone mock server is running. + /// + /// # Returns + /// A `String` representing the hostname of the `MockServer`. + /// + /// # Example + /// ```rust + /// use httpmock::MockServer; + /// + /// let server = MockServer::start(); + /// let host = server.host(); + /// + /// assert_eq!(host, "127.0.0.1"); + /// ``` pub fn host(&self) -> String { self.server_adapter.as_ref().unwrap().host() } - /// The TCP port that the mock server is listening on. + /// Returns the TCP port that the mock server is listening on. + /// + /// # Returns + /// A `u16` representing the port number of the `MockServer`. + /// + /// # Example + /// ```rust + /// use httpmock::MockServer; + /// + /// let server = MockServer::start(); + /// let port = server.port(); + /// + /// assert!(port > 0); + /// ``` pub fn port(&self) -> u16 { self.server_adapter.as_ref().unwrap().port() } /// Builds the address for a specific path on the mock server. /// - /// **Example**: - /// ``` + /// # Returns + /// A reference to the `SocketAddr` representing the address of the `MockServer`. + /// + /// # Example + /// ```rust /// // Start a local mock server for exclusive use by this test function. /// let server = httpmock::MockServer::start(); /// @@ -146,7 +265,7 @@ impl MockServer { /// // Get the address of the MockServer. /// let addr = server.address(); /// - /// // Ensure the returned URL is as expected + /// // Ensure the returned URL is as expected. /// assert_eq!(expected_addr_str, addr.to_string()); /// ``` pub fn address(&self) -> &SocketAddr { @@ -155,8 +274,42 @@ impl MockServer { /// Builds the URL for a specific path on the mock server. /// - /// **Example**: + /// # Arguments + /// * `path` - A string slice representing the specific path on the mock server. + /// + /// # Returns + /// A `String` representing the full URL for the given path on the `MockServer`. + /// + /// # Example + /// ```rust + /// // Start a local mock server for exclusive use by this test function. + /// let server = httpmock::MockServer::start(); + /// + /// let expected_url = format!("https://127.0.0.1:{}/hello", server.port()); + /// + /// // Get the URL for path "/hello". + /// let url = server.url("/hello"); + /// + /// // Ensure the returned URL is as expected. + /// assert_eq!(expected_url, url); /// ``` + #[cfg(feature = "https")] + pub fn url>(&self, path: S) -> String { + return format!("https://{}{}", self.address(), path.into()); + } + + /// Builds the URL for a specific path on the mock server. + /// + /// # Arguments + /// * `path` - A string slice representing the specific path on the mock server. + /// + /// # Returns + /// A `String` representing the full URL for the given path on the `MockServer`. + /// + /// # Example + /// ```rust + /// use httpmock::MockServer; + /// /// // Start a local mock server for exclusive use by this test function. /// let server = httpmock::MockServer::start(); /// @@ -165,27 +318,33 @@ impl MockServer { /// // Get the URL for path "/hello". /// let url = server.url("/hello"); /// - /// // Ensure the returned URL is as expected + /// // Ensure the returned URL is as expected. /// assert_eq!(expected_url, url); /// ``` + #[cfg(not(feature = "https"))] pub fn url>(&self, path: S) -> String { - format!("http://{}{}", self.address(), path.into()) + return format!("http://{}{}", self.address(), path.into()); } /// Builds the base URL for the mock server. /// - /// **Example**: - /// ``` + /// # Returns + /// A `String` representing the base URL of the `MockServer`. + /// + /// # Example + /// ```rust + /// use httpmock::MockServer; + /// /// // Start a local mock server for exclusive use by this test function. /// let server = httpmock::MockServer::start(); /// /// let expected_url = format!("http://127.0.0.1:{}", server.port()); /// - /// // Get the URL for path "/hello". - /// let url = server.base_url(); + /// // Get the base URL of the MockServer. + /// let base_url = server.base_url(); /// - /// // Ensure the returned URL is as expected - /// assert_eq!(expected_url, url); + /// // Ensure the returned URL is as expected. + /// assert_eq!(expected_url, base_url); /// ``` pub fn base_url(&self) -> String { self.url("") @@ -193,19 +352,30 @@ impl MockServer { /// Creates a [Mock](struct.Mock.html) object on the mock server. /// - /// **Example**: - /// ``` - /// use isahc::get; + /// # Arguments + /// * `config_fn` - A closure that takes a `When` and `Then` to configure the mock. /// - /// let server = httpmock::MockServer::start(); + /// # Returns + /// A `Mock` object representing the created mock on the server. /// + /// # Example + /// ```rust + /// use reqwest::blocking::get; + /// use httpmock::MockServer; + /// + /// // Start a local mock server for exclusive use by this test function. + /// let server = MockServer::start(); + /// + /// // Create a mock on the server. /// let mock = server.mock(|when, then| { /// when.path("/hello"); /// then.status(200); /// }); /// - /// get(server.url("/hello")).unwrap(); + /// // Send an HTTP request to the mock server. This simulates your code. + /// get(&server.url("/hello")).unwrap(); /// + /// // Ensure the mock was called as expected. /// mock.assert(); /// ``` pub fn mock(&self, config_fn: F) -> Mock @@ -215,13 +385,22 @@ impl MockServer { self.mock_async(config_fn).join() } - /// Creates a [Mock](struct.Mock.html) object on the mock server. + /// Creates a [Mock](struct.Mock.html) object on the mock server asynchronously. /// - /// **Example**: - /// ``` - /// use isahc::{get_async}; - /// async_std::task::block_on(async { - /// let server = httpmock::MockServer::start(); + /// # Arguments + /// * `spec_fn` - A closure that takes a `When` and `Then` to configure the mock. + /// + /// # Returns + /// A `Mock` object representing the created mock on the server. + /// + /// # Example + /// ```rust + /// use reqwest::get; + /// use httpmock::MockServer; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// let server = MockServer::start(); /// /// let mock = server /// .mock_async(|when, then| { @@ -230,14 +409,14 @@ impl MockServer { /// }) /// .await; /// - /// get_async(server.url("/hello")).await.unwrap(); + /// get(&server.url("/hello")).await.unwrap(); /// /// mock.assert_async().await; /// }); /// ``` - pub async fn mock_async<'a, F>(&'a self, spec_fn: F) -> Mock<'a> + pub async fn mock_async<'a, SpecFn>(&'a self, spec_fn: SpecFn) -> Mock<'a> where - F: FnOnce(When, Then), + SpecFn: FnOnce(When, Then), { let mut req = Rc::new(Cell::new(RequestRequirements::new())); let mut res = Rc::new(Cell::new(MockServerHttpResponse::new())); @@ -263,7 +442,7 @@ impl MockServer { .expect("Cannot deserialize mock server response"); Mock { - id: response.mock_id, + id: response.id, server: self, } } @@ -271,23 +450,25 @@ impl MockServer { /// Resets the mock server. More specifically, it deletes all [Mock](struct.Mock.html) objects /// from the mock server and clears its request history. /// - /// **Example**: - /// ``` - /// use isahc::get; - /// let server = httpmock::MockServer::start(); + /// # Example + /// ```rust + /// use reqwest::blocking::get; + /// use httpmock::MockServer; /// - /// let mock = server.mock(|when, then| { + /// let server = MockServer::start(); + /// + /// let mock = server.mock(|when, then| { /// when.path("/hello"); /// then.status(200); - /// }); + /// }); /// - /// let mut response = get(server.url("/hello")).unwrap(); - /// assert_eq!(response.status(), 200); + /// let response = get(&server.url("/hello")).unwrap(); + /// assert_eq!(response.status(), 200); /// - /// server.reset(); + /// server.reset(); /// - /// let mut response = get(server.url("/hello")).unwrap(); - /// assert_eq!(response.status(), 404); + /// let response = get(&server.url("/hello")).unwrap(); + /// assert_eq!(response.status(), 404); /// ``` pub fn reset(&self) { self.reset_async().join() @@ -296,39 +477,860 @@ impl MockServer { /// Resets the mock server. More specifically, it deletes all [Mock](struct.Mock.html) objects /// from the mock server and clears its request history. /// - /// **Example**: - /// ``` - /// use isahc::get; - /// async_std::task::block_on(async { - /// let server = httpmock::MockServer::start_async().await; + /// # Example + /// ```rust + /// use reqwest::get; + /// use httpmock::MockServer; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// let server = MockServer::start_async().await; /// /// let mock = server.mock_async(|when, then| { - /// when.path("/hello"); - /// then.status(200); + /// when.path("/hello"); + /// then.status(200); /// }).await; /// - /// let mut response = get(server.url("/hello")).unwrap(); + /// let response = get(&server.url("/hello")).await.unwrap(); /// assert_eq!(response.status(), 200); /// /// server.reset_async().await; /// - /// let mut response = get(server.url("/hello")).unwrap(); + /// let response = get(&server.url("/hello")).await.unwrap(); /// assert_eq!(response.status(), 404); /// }); /// ``` pub async fn reset_async(&self) { if let Some(server_adapter) = &self.server_adapter { - with_retry(5, || server_adapter.delete_all_mocks()) + with_retry(3, || server_adapter.reset()) .await .expect("Cannot reset mock server (task: delete mocks)."); - with_retry(5, || server_adapter.delete_history()) - .await - .expect("Cannot reset mock server (task: delete request history)."); + } + } + + /// Configures the mock server to forward the request to the target host by replacing the host name, + /// but only if the request expectations are met. If the request is recorded, the recording will + /// **NOT** contain the host name as an expectation to allow the recording to be reused. + /// + /// # Arguments + /// * `to_base_url` - A string that represents the base URL to which the request should be forwarded. + /// * `rule` - A closure that takes a `ForwardingRuleBuilder` to configure the forwarding rule. + /// + /// # Returns + /// A `ForwardingRule` object representing the configured forwarding rule. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// let target_server = MockServer::start(); + /// target_server.mock(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }); + /// + /// // Let's create our mock server for the test + /// let server = MockServer::start(); + /// + /// // We configure our server to forward the request to the target host instead of + /// // answering with a mocked response. The 'rule' variable lets you configure + /// // rules under which forwarding should take place. + /// server.forward_to(target_server.base_url(), |rule| { + /// rule.filter(|when| { + /// when.any_request(); // We want all requests to be forwarded. + /// }); + /// }); + /// + /// // Now let's send an HTTP request to the mock server. The request will be forwarded + /// // to the target host, as we configured before. + /// let client = Client::new(); + /// + /// // Since the request was forwarded, we should see the target host's response. + /// let response = client.get(&server.url("/get")).send().unwrap(); + /// let status = response.status(); + /// + /// assert_eq!("Hi from fake GitHub!", response.text().unwrap()); + /// assert_eq!(status, 200); + /// ``` + /// + /// # Feature + /// This method is only available when the `proxy` feature is enabled. + #[cfg(feature = "proxy")] + pub fn forward_to( + &self, + to_base_url: IntoString, + rule: ForwardingRuleBuilderFn, + ) -> ForwardingRule + where + ForwardingRuleBuilderFn: FnOnce(ForwardingRuleBuilder), + IntoString: Into, + { + self.forward_to_async(to_base_url, rule).join() + } + + /// Asynchronously configures the mock server to forward the request to the target host by replacing the host name, + /// but only if the request expectations are met. If the request is recorded, the recording will + /// contain the host name as an expectation to allow the recording to be reused. + /// + /// # Arguments + /// * `target_base_url` - A string that represents the base URL to which the request should be forwarded. + /// * `rule` - A closure that takes a `ForwardingRuleBuilder` to configure the forwarding rule. + /// + /// # Returns + /// A `ForwardingRule` object representing the configured forwarding rule. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::Client; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// let target_server = MockServer::start_async().await; + /// target_server.mock_async(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }).await; + /// + /// // Let's create our mock server for the test + /// let server = MockServer::start_async().await; + /// + /// // We configure our server to forward the request to the target host instead of + /// // answering with a mocked response. The 'rule' variable lets you configure + /// // rules under which forwarding should take place. + /// server.forward_to_async(target_server.base_url(), |rule| { + /// rule.filter(|when| { + /// when.any_request(); // We want all requests to be forwarded. + /// }); + /// }).await; + /// + /// // Now let's send an HTTP request to the mock server. The request will be forwarded + /// // to the target host, as we configured before. + /// let client = Client::new(); + /// + /// // Since the request was forwarded, we should see the target host's response. + /// let response = client.get(&server.url("/get")).send().await.unwrap(); + /// let status = response.status(); + /// assert_eq!(status, 200); + /// assert_eq!("Hi from fake GitHub!", response.text().await.unwrap()); + /// }); + /// ``` + /// + /// # Feature + /// This method is only available when the `proxy` feature is enabled. + #[cfg(feature = "proxy")] + pub async fn forward_to_async<'a, IntoString, ForwardingRuleBuilderFn>( + &'a self, + target_base_url: IntoString, + rule: ForwardingRuleBuilderFn, + ) -> ForwardingRule<'a> + where + ForwardingRuleBuilderFn: FnOnce(ForwardingRuleBuilder), + IntoString: Into, + { + let mut headers = Rc::new(Cell::new(Vec::new())); + let mut req = Rc::new(Cell::new(RequestRequirements::new())); + + rule(ForwardingRuleBuilder { + headers: headers.clone(), + request_requirements: req.clone(), + }); + + let response = self + .server_adapter + .as_ref() + .unwrap() + .create_forwarding_rule(ForwardingRuleConfig { + target_base_url: target_base_url.into(), + request_requirements: req.take(), + request_header: headers.take(), + }) + .await + .expect("Cannot deserialize mock server response"); + + ForwardingRule { + id: response.id, + server: self, + } + } + + /// Configures the mock server to proxy HTTP requests based on specified criteria. + /// + /// This method configures the mock server to forward incoming requests to the target host + /// when the requests meet the defined criteria. If a request matches the criteria, it will be + /// proxied to the target host. + /// + /// When a recording is active (which records requests and responses), the host name of the request + /// will be stored with the recording as a request expectation. + /// + /// # Arguments + /// * `rule` - A closure that takes a `ProxyRuleBuilder` to configure the proxy rule. + /// + /// # Returns + /// A `ProxyRule` object representing the configured proxy rule that is stored on the mock server. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Create a mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// let target_server = MockServer::start(); + /// target_server.mock(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }); + /// + /// // Create a proxy mock server for the test. + /// let proxy_server = MockServer::start(); + /// + /// // Configure the proxy server to forward requests to the target server. + /// // The `rule` closure allows specifying criteria for requests that should be proxied. + /// proxy_server.proxy(|rule| { + /// rule.filter(|when| { + /// // Only allow requests to the target server to be proxied. + /// when.host(target_server.host()).port(target_server.port()); + /// }); + /// }); + /// + /// // Create an HTTP client configured to use the proxy server. + /// let client = Client::builder() + /// .proxy(reqwest::Proxy::all(proxy_server.base_url()).unwrap()) // Set the proxy server + /// .build() + /// .unwrap(); + /// + /// // Send a request to the target server through the proxy server. + /// // The request will be forwarded to the target server as configured. + /// let response = client.get(&target_server.url("/get")).send().unwrap(); + /// let status = response.status(); + /// + /// // Verify that the response comes from the target server. + /// assert_eq!(status, 200); + /// assert_eq!("Hi from fake GitHub!", response.text().unwrap()); + /// ``` + /// + /// # Feature + /// This method is only available when the `proxy` feature is enabled. + #[cfg(feature = "proxy")] + pub fn proxy(&self, rule: ProxyRuleBuilderFn) -> ProxyRule + where + ProxyRuleBuilderFn: FnOnce(ProxyRuleBuilder), + { + self.proxy_async(rule).join() + } + + /// Asynchronously configures the mock server to proxy HTTP requests based on specified criteria. + /// + /// This method configures the mock server to forward incoming requests to the target host + /// when the requests meet the defined criteria. If a request matches the criteria, it will be + /// proxied to the target host. + /// + /// When a recording is active (which records requests and responses), the host name of the request + /// will be stored with the recording to allow the recording to be reused. + /// + /// # Arguments + /// * `rule` - A closure that takes a `ProxyRuleBuilder` to configure the proxy rule. + /// + /// # Returns + /// A `ProxyRule` object representing the configured proxy rule. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::Client; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// let target_server = MockServer::start_async().await; + /// target_server.mock_async(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }).await; + /// + /// // Let's create our proxy mock server for the test + /// let proxy_server = MockServer::start_async().await; + /// + /// // We configure our proxy server to forward requests to the target server + /// // The 'rule' closure allows specifying criteria for requests that should be proxied + /// proxy_server.proxy_async(|rule| { + /// rule.filter(|when| { + /// // Only allow requests to the target server to be proxied + /// when.host(target_server.host()).port(target_server.port()); + /// }); + /// }).await; + /// + /// // Create an HTTP client configured to use the proxy server + /// let client = Client::builder() + /// .proxy(reqwest::Proxy::all(proxy_server.base_url()).unwrap()) + /// .build() + /// .unwrap(); + /// + /// // Send a request to the target server through the proxy server + /// // The request will be forwarded to the target server as configured + /// let response = client.get(&target_server.url("/get")).send().await.unwrap(); + /// let status = response.status(); + /// + /// // Verify that the response comes from the target server + /// assert_eq!(status, 200); + /// assert_eq!("Hi from fake GitHub!", response.text().await.unwrap()); + /// }); + /// ``` + /// + /// # Feature + /// This method is only available when the `proxy` feature is enabled. + #[cfg(feature = "proxy")] + pub async fn proxy_async<'a, ProxyRuleBuilderFn>( + &'a self, + rule: ProxyRuleBuilderFn, + ) -> ProxyRule<'a> + where + ProxyRuleBuilderFn: FnOnce(ProxyRuleBuilder), + { + let mut headers = Rc::new(Cell::new(Vec::new())); + let mut req = Rc::new(Cell::new(RequestRequirements::new())); + + rule(ProxyRuleBuilder { + headers: headers.clone(), + request_requirements: req.clone(), + }); + + let response = self + .server_adapter + .as_ref() + .unwrap() + .create_proxy_rule(ProxyRuleConfig { + request_requirements: req.take(), + request_header: headers.take(), + }) + .await + .expect("Cannot deserialize mock server response"); + + ProxyRule { + id: response.id, + server: self, + } + } + + /// Records all requests matching a given rule and the corresponding responses + /// sent back by the mock server. If requests are forwarded or proxied to another + /// host, the original responses from those target hosts will also be recorded. + /// + /// # Parameters + /// + /// * `rule`: A closure that takes a `RecordingRuleBuilder` as an argument, + /// which defines the conditions under which HTTP requests and + /// their corresponding responses will be recorded. + /// + /// # Returns + /// + /// * `Recording`: A reference to the recording object stored on the mock server, + /// which can be used to manage the recording, such as downloading + /// or deleting it. The `Recording` object provides functionality + /// to download the recording and store it under a file. Users can + /// use these files for later playback by calling the `playback` + /// method of the mock server. + /// + /// # Example + /// + /// ```rust + /// // Create a mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// use reqwest::blocking::Client; + /// use httpmock::MockServer; + /// + /// let target_server = MockServer::start(); + /// target_server.mock(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }); + /// + /// // Create the recording server for the test. + /// let recording_server = MockServer::start(); + /// + /// // Configure the recording server to forward requests to the target host. + /// recording_server.forward_to(target_server.base_url(), |rule| { + /// rule.filter(|when| { + /// when.path("/hello"); // Forward all requests with path "/hello". + /// }); + /// }); + /// + /// // Record the target server's response. + /// let recording = recording_server.record(|rule| { + /// rule.record_response_delays(true) + /// .record_request_headers(vec!["Accept", "Content-Type"]) // Record specific headers. + /// .filter(|when| { + /// when.path("/hello"); // Only record requests with path "/hello". + /// }); + /// }); + /// + /// // Use httpmock as a proxy server. + /// let github_client = Client::new(); + /// + /// let response = github_client + /// .get(&format!("{}/hello", recording_server.base_url())) + /// .send() + /// .unwrap(); + /// assert_eq!(response.text().unwrap(), "Hi from fake GitHub!"); + /// + /// // Store the recording to a file and create a new mock server to playback the recording. + /// let target_path = recording.save("my_test_scenario").unwrap(); + /// + /// let playback_server = MockServer::start(); + /// + /// playback_server.playback(target_path); + /// + /// let response = github_client + /// .get(&format!("{}/hello", playback_server.base_url())) + /// .send() + /// .unwrap(); + /// assert_eq!(response.text().unwrap(), "Hi from fake GitHub!"); + /// ``` + /// + /// # Feature + /// + /// This method is only available when the `record` feature is enabled. + #[cfg(feature = "record")] + pub fn record(&self, rule: RecordingRuleBuilderFn) -> Recording + where + RecordingRuleBuilderFn: FnOnce(RecordingRuleBuilder), + { + self.record_async(rule).join() + } + + /// Asynchronously records all requests matching a given rule and the corresponding responses + /// sent back by the mock server. If requests are forwarded or proxied to another + /// host, the original responses from those target hosts will also be recorded. + /// + /// # Parameters + /// + /// * `rule`: A closure that takes a `RecordingRuleBuilder` as an argument, + /// which defines the conditions under which requests will be recorded. + /// + /// # Returns + /// + /// * `Recording`: A reference to the recording object stored on the mock server, + /// which can be used to manage the recording, such as downloading + /// or deleting it. The `Recording` object provides functionality + /// to download the recording and store it under a file. Users can + /// use these files for later playback by calling the `playback` + /// method of the mock server. + /// + /// # Example + /// + /// ```rust + /// use httpmock::MockServer; + /// use reqwest::Client; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Create a mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// let target_server = MockServer::start_async().await; + /// target_server.mock_async(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }).await; + /// + /// // Create the recording server for the test. + /// let recording_server = MockServer::start_async().await; + /// + /// // Configure the recording server to forward requests to the target host. + /// recording_server.forward_to_async(target_server.base_url(), |rule| { + /// rule.filter(|when| { + /// when.path("/hello"); // Forward all requests with path "/hello". + /// }); + /// }).await; + /// + /// // Record the target server's response. + /// let recording = recording_server.record_async(|rule| { + /// rule.record_response_delays(true) + /// .record_request_headers(vec!["Accept", "Content-Type"]) // Record specific headers. + /// .filter(|when| { + /// when.path("/hello"); // Only record requests with path "/hello". + /// }); + /// }).await; + /// + /// // Use httpmock as a proxy server. + /// let client = Client::new(); + /// + /// let response = client + /// .get(&format!("{}/hello", recording_server.base_url())) + /// .send() + /// .await + /// .unwrap(); + /// assert_eq!(response.text().await.unwrap(), "Hi from fake GitHub!"); + /// + /// // Store the recording to a file and create a new mock server to playback the recording. + /// let target_path = recording.save_async("my_test_scenario").await.unwrap(); + /// + /// let playback_server = MockServer::start_async().await; + /// + /// playback_server.playback_async(target_path).await; + /// + /// let response = client + /// .get(&format!("{}/hello", playback_server.base_url())) + /// .send() + /// .await + /// .unwrap(); + /// assert_eq!(response.text().await.unwrap(), "Hi from fake GitHub!"); + /// }); + /// ``` + /// + /// # Feature + /// + /// This method is only available when the `record` feature is enabled. + #[cfg(feature = "record")] + pub async fn record_async<'a, RecordingRuleBuilderFn>( + &'a self, + rule: RecordingRuleBuilderFn, + ) -> Recording<'a> + where + RecordingRuleBuilderFn: FnOnce(RecordingRuleBuilder), + { + let mut config = Rc::new(Cell::new(RecordingRuleConfig { + request_requirements: RequestRequirements::new(), + record_headers: Vec::new(), + record_response_delays: false, + })); + + rule(RecordingRuleBuilder { + config: config.clone(), + }); + + let response = self + .server_adapter + .as_ref() + .unwrap() + .create_recording(config.take()) + .await + .expect("Cannot deserialize mock server response"); + + Recording { + id: response.id, + server: self, + } + } + + /// Reads a recording file and configures the mock server to respond with the + /// recorded responses when an incoming request matches the corresponding recorded HTTP request. + /// This allows users to record responses from a real service and use these recordings for testing later, + /// without needing to be online or having access to the real service during subsequent tests. + /// + /// # Parameters + /// + /// * `path`: A path to the file containing the recording. This can be any type + /// that implements `Into`, such as a `&str` or `String`. + /// + /// # Returns + /// + /// * `MockSet`: An object representing the set of mocks that were loaded from the recording file. + /// + /// # Example + /// + /// ```rust + /// // Create a mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// use reqwest::blocking::Client; + /// use httpmock::MockServer; + /// + /// let target_server = MockServer::start(); + /// target_server.mock(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }); + /// + /// // Create the recording server for the test. + /// let recording_server = MockServer::start(); + /// + /// // Configure the recording server to forward requests to the target host. + /// recording_server.forward_to(target_server.base_url(), |rule| { + /// rule.filter(|when| { + /// when.path("/hello"); // Forward all requests with path "/hello". + /// }); + /// }); + /// + /// // Record the target server's response. + /// let recording = recording_server.record(|rule| { + /// rule.record_response_delays(true) + /// .record_request_headers(vec!["Accept", "Content-Type"]) // Record specific headers. + /// .filter(|when| { + /// when.path("/hello"); // Only record requests with path "/hello". + /// }); + /// }); + /// + /// // Use httpmock as a proxy server. + /// let client = Client::new(); + /// + /// let response = client + /// .get(&format!("{}/hello", recording_server.base_url())) + /// .send() + /// .unwrap(); + /// assert_eq!(response.text().unwrap(), "Hi from fake GitHub!"); + /// + /// // Store the recording to a file and create a new mock server to play back the recording. + /// let target_path = recording.save("my_test_scenario").unwrap(); + /// + /// let playback_server = MockServer::start(); + /// + /// // Play back the recorded interactions from the file. + /// playback_server.playback(target_path); + /// + /// let response = client + /// .get(&format!("{}/hello", playback_server.base_url())) + /// .send() + /// .unwrap(); + /// assert_eq!(response.text().unwrap(), "Hi from fake GitHub!"); + /// ``` + /// + /// # Feature + /// + /// This method is only available when the `record` feature is enabled. + #[cfg(feature = "record")] + pub fn playback>(&self, path: IntoPathBuf) -> MockSet { + self.playback_async(path).join() + } + + /// Asynchronously reads a recording file and configures the mock server to respond with the + /// recorded responses when an incoming request matches the corresponding recorded HTTP request. + /// This allows users to record responses from a real service and use these recordings for testing later, + /// without needing to be online or having access to the real service during subsequent tests. + /// + /// # Parameters + /// + /// * `path`: A path to the file containing the recorded interactions. This can be any type + /// that implements `Into`, such as a `&str` or `String`. + /// + /// # Returns + /// + /// * `MockSet`: An object representing the set of mocks that were loaded from the recording file. + /// + /// # Example + /// + /// ```rust + /// use httpmock::MockServer; + /// use reqwest::Client; + /// + /// let rt = tokio::runtime::Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Create a mock server to simulate a real service (e.g., GitHub, AWS, etc.). + /// let target_server = MockServer::start_async().await; + /// target_server.mock_async(|when, then| { + /// when.any_request(); + /// then.status(200).body("Hi from fake GitHub!"); + /// }).await; + /// + /// // Create the recording server for the test. + /// let recording_server = MockServer::start_async().await; + /// + /// // Configure the recording server to forward requests to the target host. + /// recording_server.forward_to_async(target_server.base_url(), |rule| { + /// rule.filter(|when| { + /// when.path("/hello"); // Forward all requests with path "/hello". + /// }); + /// }).await; + /// + /// // Record the target server's response. + /// let recording = recording_server.record_async(|rule| { + /// rule.record_response_delays(true) + /// .record_request_headers(vec!["Accept", "Content-Type"]) // Record specific headers. + /// .filter(|when| { + /// when.path("/hello"); // Only record requests with path "/hello". + /// }); + /// }).await; + /// + /// // Use httpmock as a proxy server. + /// let client = Client::new(); + /// + /// let response = client + /// .get(&format!("{}/hello", recording_server.base_url())) + /// .send() + /// .await + /// .unwrap(); + /// assert_eq!(response.text().await.unwrap(), "Hi from fake GitHub!"); + /// + /// // Store the recording to a file and create a new mock server to play back the recording. + /// let target_path = recording.save("my_test_scenario").unwrap(); + /// + /// let playback_server = MockServer::start_async().await; + /// + /// playback_server.playback_async(target_path).await; + /// + /// let response = client + /// .get(&format!("{}/hello", playback_server.base_url())) + /// .send() + /// .await + /// .unwrap(); + /// assert_eq!(response.text().await.unwrap(), "Hi from fake GitHub!"); + /// }); + /// ``` + /// + /// # Feature + /// + /// This method is only available when the `record` feature is enabled. + #[cfg(feature = "record")] + pub async fn playback_async>(&self, path: IntoPathBuf) -> MockSet { + let path = path.into(); + let content = read_file_async(&path).await.expect(&format!( + "could not read from file {}", + path.as_os_str() + .to_str() + .map_or(String::new(), |p| p.to_string()) + )); + + return self + .playback_from_yaml_async( + String::from_utf8(content).expect("cannot convert file content to UTF-8"), + ) + .await; + } + + /// Configures the mock server to respond with the recorded responses based on a provided recording + /// in the form of a YAML string. This allows users to directly use a YAML string representing + /// the recorded interactions, which can be useful for testing and debugging without needing a physical file. + /// + /// # Parameters + /// + /// * `content`: A YAML string that represents the contents of the recording file. + /// This can be any type that implements `AsRef`, such as a `&str` or `String`. + /// + /// # Returns + /// + /// * `MockSet`: An object representing the set of mocks that were loaded from the YAML string. + /// + /// # Example + /// + /// ```rust + /// use httpmock::MockServer; + /// use reqwest::blocking::Client; + /// + /// // Example YAML content representing recorded interactions. + /// let yaml_content = r#" + /// when: + /// method: GET + /// path: /recorded-mock + /// then: + /// status: 200 + /// header: + /// - name: Content-Type + /// value: application/json + /// body: '{ "response" : "hello" }' + /// "#; + /// + /// // Create the mock server. + /// let mock_server = MockServer::start(); + /// + /// // Play back the recorded interactions from the YAML string. + /// mock_server.playback_from_yaml(yaml_content); + /// + /// // Use the reqwest HTTP client to send a request to the mock server. + /// let client = Client::new(); + /// + /// let response = client + /// .get(&format!("{}/recorded-mock", mock_server.base_url())) // Build the full URL using the mock server's base URL + /// .send() // Send the GET request + /// .unwrap(); // Unwrap the result, assuming the request is successful + /// + /// assert_eq!(response.headers().get("Content-Type").unwrap(), "application/json"); + /// assert_eq!(response.text().unwrap(), r#"{ "response" : "hello" }"#); + /// ``` + /// + /// # Feature + /// + /// This method is only available when the `record` feature is enabled. + #[cfg(feature = "record")] + pub fn playback_from_yaml>(&self, content: AsStrRef) -> MockSet { + self.playback_from_yaml_async(content).join() + } + + /// Asynchronously configures the mock server to respond with the recorded responses based on a provided recording + /// in the form of a YAML string. This allows users to directly use a YAML string representing + /// the recorded interactions, which can be useful for testing and debugging without needing a physical file. + /// + /// # Parameters + /// + /// * `content`: A YAML string that represents the contents of the recording file. + /// This can be any type that implements `AsRef`, such as a `&str` or `String`. + /// + /// # Returns + /// + /// * `MockSet`: An object representing the set of mocks that were loaded from the YAML string. + /// + /// # Example + /// + /// ```rust + /// use tokio::runtime::Runtime; // Import tokio for asynchronous runtime + /// use httpmock::MockServer; + /// use reqwest::Client; + /// + /// // Example YAML content representing a recording. + /// let yaml_content = r#" + /// when: + /// method: GET + /// path: /recorded-mock + /// then: + /// status: 200 + /// body: '{ "response" : "hello" }' + /// "#; + /// + /// let rt = Runtime::new().unwrap(); + /// rt.block_on(async { + /// // Create the mock server. + /// let mock_server = MockServer::start_async().await; + /// + /// // Play back the recorded interactions from the YAML string. + /// mock_server.playback_from_yaml_async(yaml_content).await; + /// + /// // Use reqwest to send an asynchronous request to the mock server. + /// let client = Client::new(); + /// + /// let response = client + /// .get(&format!("{}/recorded-mock", mock_server.base_url())) + /// .send() + /// .await + /// .unwrap(); + /// + /// assert_eq!(response.text().await.unwrap(), r#"{ "response" : "hello" }"#); + /// }); + /// ``` + /// + /// # Feature + /// + /// This method is only available when the `record` feature is enabled. + #[cfg(feature = "record")] + pub async fn playback_from_yaml_async>( + &self, + content: AsStrRef, + ) -> MockSet { + let response = self + .server_adapter + .as_ref() + .unwrap() + .create_mocks_from_recording(content.as_ref()) + .await + .expect("Cannot deserialize mock server response"); + + MockSet { + ids: response, + server: self, } } } +/// Implements the `Drop` trait for `MockServer`. +/// When a `MockServer` instance goes out of scope, this method is called automatically to manage resources. impl Drop for MockServer { + /// This method will returns the mock server to the pool of mock servers. The mock server is not cleaned immediately. + /// Instead, it will be reset and cleaned when `MockServer::start()` is called again, preparing it for reuse by another test. + /// + /// # Important Considerations + /// + /// Users should be aware that when a `MockServer` instance is dropped, the server is not immediately cleaned. + /// The actual reset and cleaning of the server happen when `MockServer::start()` is called again, making it ready for reuse. + /// + /// # Feature + /// + /// This behavior is part of the `MockServer` struct and does not require any additional features to be enabled. fn drop(&mut self) { let adapter = self.server_adapter.take().unwrap(); self.pool.put(adapter).join(); @@ -336,33 +1338,48 @@ impl Drop for MockServer { } const LOCAL_SERVER_ADAPTER_GENERATOR: fn() -> Arc = || { - let (addr_sender, addr_receiver) = tokio::sync::oneshot::channel::(); - let state = Arc::new(MockServerState::default()); - let server_state = state.clone(); + let (addr_sender, addr_receiver) = channel::(); + let state_manager = Arc::new(HttpMockStateManager::default()); + let srv = HttpMockServerBuilder::new() + .build_with_state(state_manager.clone()) + .expect("cannot build mock server"); + // TODO: Check how we can improve here to not create a Tokio runtime on the current thread per MockServer. + // Can we create one runtime and use it for all servers? thread::spawn(move || { - let server_state = server_state.clone(); - let srv = start_server(0, false, &server_state, Some(addr_sender), false, pending()); - - let mut runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("Cannot build local tokio runtime"); - - LocalSet::new().block_on(&mut runtime, srv) + let server_fn = srv.start_with_signals(Some(addr_sender), pending()); + runtime::block_on_current_thread(server_fn).expect("Server execution failed"); }); let addr = addr_receiver.join().expect("Cannot get server address"); - Arc::new(LocalMockServerAdapter::new(addr, state)) + Arc::new(LocalMockServerAdapter::new(addr, state_manager)) }; lazy_static! { static ref LOCAL_SERVER_POOL_REF: Arc>> = { let max_servers = read_env("HTTPMOCK_MAX_SERVERS", "25") .parse::() - .expect("Cannot parse environment variable HTTPMOCK_MAX_SERVERS to an integer"); + .expect("Cannot parse environment variable HTTPMOCK_MAX_SERVERS as an integer"); Arc::new(Pool::new(max_servers)) }; static ref REMOTE_SERVER_POOL_REF: Arc>> = Arc::new(Pool::new(1)); } + +#[cfg(feature = "remote")] +lazy_static! { + // TODO: REFACTOR to use a runtime agnostic HTTP client for remote access. + // This solution does not require OpenSSL and less dependencies compared to + // other HTTP clients (tested: isahc, surf). Curl seems to use OpenSSL by default, + // so this is not an option. Optimally, the HTTP client uses rustls to avoid the + // dependency on OpenSSL installed on the OS. + static ref REMOTE_SERVER_CLIENT: Arc = { + let max_workers = read_env("HTTPMOCK_HTTP_CLIENT_WORKER_THREADS", "1") + .parse::() + .expect("Cannot parse environment variable HTTPMOCK_HTTP_CLIENT_WORKER_THREADS as an integer"); + let max_blocking_threads = read_env("HTTPMOCK_HTTP_CLIENT_MAX_BLOCKING_THREADS", "10") + .parse::() + .expect("Cannot parse environment variable HTTPMOCK_HTTP_CLIENT_MAX_BLOCKING_THREADS to an integer"); + Arc::new(HttpMockHttpClient::new(Some(Arc::new(runtime::new(max_workers,max_blocking_threads).unwrap())))) + }; +} diff --git a/src/api/spec.rs b/src/api/spec.rs index 2071f6b2..c8afe754 100644 --- a/src/api/spec.rs +++ b/src/api/spec.rs @@ -1,15 +1,17 @@ -use crate::common::data::{ - MockMatcherFunction, MockServerHttpResponse, Pattern, RequestRequirements, +use crate::{ + common::{ + data::{MockServerHttpResponse, RequestRequirements}, + util::{get_test_resource_file_path, read_file, update_cell, HttpMockBytes}, + }, + prelude::HttpMockRequest, + Method, Regex, }; -use crate::common::util::{get_test_resource_file_path, read_file, update_cell}; -use crate::{Method, Regex}; +use bytes::Bytes; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::cell::Cell; -use std::path::Path; -use std::rc::Rc; -use std::str::FromStr; -use std::time::Duration; +use std::{ + cell::Cell, convert::TryInto, path::Path, rc::Rc, str::FromStr, sync::Arc, time::Duration, +}; /// A function that encapsulates one or more /// [`When`](When) method calls as an abstraction @@ -21,910 +23,5036 @@ pub type AndWhenFunction = fn(When) -> When; /// or convenience pub type AndThenFunction = fn(Then) -> Then; -/// A type that allows the specification of HTTP request values. +/// Represents the conditions that an incoming HTTP request must satisfy to be handled by the mock server. +/// +/// The `When` structure is used exclusively to define the expectations for HTTP requests. It allows +/// the configuration of various aspects of the request such as paths, headers, methods, and more. +/// These specifications determine whether a request matches the mock setup and should be handled accordingly. +/// This structure is part of the setup process in creating a mock server, typically used before defining the response +/// behavior with a `Then` structure. pub struct When { pub(crate) expectations: Rc>, } impl When { - /// Sets the mock server to respond to any incoming request. + /// Configures the mock server to respond to any incoming request, regardless of the URL path, + /// query parameters, headers, or method. + /// + /// This method doesn't directly alter the behavior of the mock server, as it already responds to any + /// request by default. However, it serves as an explicit indication in your code that the + /// server will respond to any request. /// /// # Example - /// ``` + /// + /// ```rust /// use httpmock::prelude::*; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.any_request(); - /// then.status(200); + /// // Configure the mock server to respond to any request + /// let mock = server.mock(|when, then| { + /// when.any_request(); // Explicitly specify that any request should match + /// then.status(200); // Respond with status code 200 for all matched requests /// }); /// - /// isahc::get(server.url("/anyPath")).unwrap(); + /// // Make a request to the server's URL and ensure the mock is triggered + /// let response = reqwest::blocking::get(server.url("/anyPath")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); /// + /// // Assert that the mock was called at least once /// mock.assert(); /// ``` + /// + /// # Note + /// This is the default behavior as of now, but it may change in future versions. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// pub fn any_request(self) -> Self { // This method does nothing. It only exists to make it very explicit that // the mock server will respond to any request. This is the default at this time, but // may change in the future. self } + // @docs-group: Miscellaneous - /// Sets the expected HTTP method. + /// Specifies the scheme (e.g., "http" or "https") that requests must match for the mock server to respond. + /// + /// This method sets the scheme to filter requests and ensures that the mock server only matches + /// requests with the specified scheme. This allows for more precise testing in environments where + /// multiple protocols are used. /// - /// * `method` - The HTTP method (a [Method](enum.Method.html) or a `String`). + /// **Note**: Scheme matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). /// /// # Example - /// ``` + /// + /// ```rust /// use httpmock::prelude::*; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.method(GET); - /// then.status(200); + /// // Create a mock that only matches requests with the "http" scheme + /// let mock = server.mock(|when, then| { + /// when.scheme("http"); // Restrict to the "http" scheme + /// then.status(200); // Respond with status code 200 for all matched requests /// }); /// - /// isahc::get(server.url("/")).unwrap(); + /// // Make an "http" request to the server's URL to trigger the mock + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock was called at least once /// mock.assert(); /// ``` - pub fn method>(mut self, method: M) -> Self { + /// + /// # Parameters + /// - `scheme`: A string specifying the scheme that requests should match. Common values include "http" and "https". + /// + /// # Returns + /// The modified `When` instance to allow for method chaining. + /// + pub fn scheme>(mut self, scheme: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let scheme = scheme + .try_into() + .expect("cannot convert scheme into a string"); update_cell(&self.expectations, |e| { - e.method = Some(method.into().to_string()) + e.scheme = Some(scheme); }); self } + // @docs-group: Scheme - /// Sets the expected URL path. - /// * `path` - The URL path. + /// Specifies a scheme (e.g., "https") that requests must not match for the mock server to respond. + /// + /// This method allows you to exclude specific schemes from matching, ensuring that the mock server + /// won't respond to requests using those protocols. This is useful when you want to mock server + /// behavior based on protocol security requirements or other criteria. + /// + /// **Note**: Scheme matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). /// /// # Example - /// ``` + /// + /// ```rust /// use httpmock::prelude::*; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.path("/test"); - /// then.status(200); + /// // Create a mock that will only match requests that do not use the "https" scheme + /// let mock = server.mock(|when, then| { + /// when.scheme_not("https"); // Exclude the "https" scheme from matching + /// then.status(200); // Respond with status code 200 for all matched requests /// }); /// - /// isahc::get(server.url("/test")).unwrap(); + /// // Make a request to the server's URL with the "http" scheme to trigger the mock + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); /// + /// // Ensure that the mock was called at least once /// mock.assert(); /// ``` - pub fn path>(mut self, path: S) -> Self { + /// + /// # Parameters + /// - `scheme`: A string specifying the scheme that requests should not match. Common values include "http" and "https". + /// + /// # Returns + /// The modified `When` instance to allow for method chaining. + /// + pub fn scheme_not>(mut self, scheme: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let scheme = scheme + .try_into() + .expect("cannot convert scheme into a string"); update_cell(&self.expectations, |e| { - e.path = Some(path.into()); + e.scheme_not = Some(scheme); }); self } + // @docs-group: Scheme - /// Sets an substring that the URL path needs to contain. - /// * `substring` - The substring to match against. + /// Sets the expected HTTP method for which the mock server should respond. + /// + /// This method ensures that the mock server only matches requests that use the specified HTTP method, + /// such as `GET`, `POST`, or any other valid method. This allows testing behavior that's specific + /// to different types of HTTP requests. + /// + /// **Note**: Method matching is case-insensitive. /// /// # Example - /// ``` + /// + /// ```rust /// use httpmock::prelude::*; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.path_contains("es"); - /// then.status(200); + /// // Create a mock that matches only `GET` requests + /// let mock = server.mock(|when, then| { + /// when.method(GET); // Match only `GET` HTTP method + /// then.status(200); // Respond with status code 200 for all matched requests /// }); /// - /// isahc::get(server.url("/test")).unwrap(); + /// // Make a GET request to the server's URL to trigger the mock + /// let response = reqwest::blocking::get(server.url("/")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); /// + /// // Verify that the mock was called at least once /// mock.assert(); /// ``` - pub fn path_contains>(mut self, substring: S) -> Self { - update_cell(&self.expectations, |e| { - if e.path_contains.is_none() { - e.path_contains = Some(Vec::new()); - } - e.path_contains.as_mut().unwrap().push(substring.into()); - }); + /// + /// # Parameters + /// - `method`: An HTTP method as either a `Method` enum or a `String` value, specifying the expected method type for matching. + /// + /// # Returns + /// The updated `When` instance to allow for method chaining. + /// + pub fn method>(mut self, method: TryIntoMethod) -> Self + where + >::Error: std::fmt::Debug, + { + let method = method + .try_into() + .expect("cannot convert method into httpmock::Method"); + + update_cell(&self.expectations, |e| e.method = Some(method.to_string())); self } + // @docs-group: Method - /// Sets a regex that the URL path needs to match. - /// * `regex` - The regex to match against. + /// Excludes the specified HTTP method from the requests the mock server will respond to. + /// + /// This method ensures that the mock server does not respond to requests using the given HTTP method, + /// like `GET`, `POST`, etc. This allows testing scenarios where a particular method should not + /// trigger a response, and thus testing behaviors like method-based security. + /// + /// **Note**: Method matching is case-insensitive. /// /// # Example - /// ``` + /// + /// ```rust /// use httpmock::prelude::*; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.path_matches(Regex::new("le$").unwrap()); - /// then.status(200); + /// // Create a mock that matches any request except those using the `POST` method + /// let mock = server.mock(|when, then| { + /// when.method_not(POST); // Exclude the `POST` HTTP method from matching + /// then.status(200); // Respond with status code 200 for all other matched requests /// }); /// - /// isahc::get(server.url("/example")).unwrap(); + /// // Make a GET request to the server's URL, which will trigger the mock + /// let response = reqwest::blocking::get(server.url("/")).unwrap(); /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once /// mock.assert(); /// ``` - pub fn path_matches>(mut self, regex: R) -> Self { + /// + /// # Parameters + /// - `method`: An HTTP method as either a `Method` enum or a `String` value, specifying the method type to exclude from matching. + /// + /// # Returns + /// The updated `When` instance to allow for method chaining. + /// + pub fn method_not>(mut self, method: IntoMethod) -> Self { update_cell(&self.expectations, |e| { - if e.path_matches.is_none() { - e.path_matches = Some(Vec::new()); + if e.method_not.is_none() { + e.method_not = Some(Vec::new()); } - e.path_matches + e.method_not .as_mut() .unwrap() - .push(Pattern::from_regex(regex.into())); + .push(method.into().to_string()); }); + self } + // @docs-group: Method - /// Sets a query parameter that needs to be provided. + /// Sets the expected host name. This constraint is especially useful when working with + /// proxy or forwarding rules, but it can also be used to serve mocks (e.g., when using a mock + /// server as a proxy). /// - /// Attention!: The request query keys and values are implicitly *allowed, but is not required* - /// to be urlencoded! The value you pass here, however, must be in plain text (i.e. not encoded)! + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. /// - /// * `name` - The query parameter name that will matched against. - /// * `value` - The value parameter name that will matched against. + /// **Note**: Both `localhost` and `127.0.0.1` are treated equally. + /// If the provided host is set to either `localhost` or `127.0.0.1`, it will match + /// requests containing either `localhost` or `127.0.0.1`. /// - /// ``` - /// // Arrange - /// use isahc::get; + /// * `host` - The host name (should not include a port). + /// + /// # Example + /// ```rust /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; /// - /// let _ = env_logger::try_init(); /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ - /// when.query_param("query", "Metallica is cool"); - /// then.status(200); + /// server.mock(|when, then| { + /// when.host("github.com"); + /// then.body("This is a mock response"); /// }); /// - /// // Act - /// get(server.url("/search?query=Metallica+is+cool")).unwrap(); + /// let client = Client::builder() + /// .proxy(reqwest::Proxy::all(&server.base_url()).unwrap()) + /// .build() + /// .unwrap(); /// - /// // Assert - /// m.assert(); + /// let response = client.get("http://github.com").send().unwrap(); + /// + /// assert_eq!(response.text().unwrap(), "This is a mock response"); /// ``` - pub fn query_param, SV: Into>(mut self, name: SK, value: SV) -> Self { - update_cell(&self.expectations, |e| { - if e.query_param.is_none() { - e.query_param = Some(Vec::new()); - } - e.query_param - .as_mut() - .unwrap() - .push((name.into(), value.into())); - }); + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host>(mut self, host: IntoString) -> Self { + update_cell(&self.expectations, |e| e.host = Some(host.into())); self } + // @docs-group: Host - /// Sets a query parameter that needs to exist in an HTTP request. + /// Sets the host name that should **NOT** be responded for. /// - /// Attention!: The request query key is implicitly *allowed, but is not required* to be - /// urlencoded! The value you pass here, however, must be in plain text (i.e. not encoded)! + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). /// - /// * `name` - The query parameter name that will matched against. + /// To add multiple suffixes, invoke this function multiple times. /// - /// ``` - /// // Arrange - /// use isahc::get; + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. + /// + /// * `host` - The host name (should not include a port). + /// + /// # Example + /// ```rust /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; /// - /// let _ = env_logger::try_init(); /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then| { - /// when.query_param_exists("query"); - /// then.status(200); + /// server.mock(|when, then| { + /// when.host("github.com"); + /// then.body("This is a mock response"); /// }); /// - /// // Act - /// get(server.url("/search?query=Metallica")).unwrap(); + /// let client = Client::builder() + /// .proxy(reqwest::Proxy::all(&server.base_url()).unwrap()) + /// .build() + /// .unwrap(); /// - /// // Assert - /// m.assert(); + /// let response = client.get("http://github.com").send().unwrap(); + /// + /// assert_eq!(response.text().unwrap(), "This is a mock response"); /// ``` - pub fn query_param_exists>(mut self, name: S) -> Self { + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_not>(mut self, host: IntoString) -> Self { update_cell(&self.expectations, |e| { - if e.query_param_exists.is_none() { - e.query_param_exists = Some(Vec::new()); + if e.host_not.is_none() { + e.host_not = Some(Vec::new()); } - e.query_param_exists.as_mut().unwrap().push(name.into()); + e.host_not.as_mut().unwrap().push(host.into()); }); self } + // @docs-group: Host - /// Sets a requirement for a tuple in an x-www-form-urlencoded request body. - /// Please refer to https://url.spec.whatwg.org/#application/x-www-form-urlencoded for more - /// information. - /// ``` + /// Adds a substring to match within the request's host name. + /// + /// This method ensures that the mock server only matches requests whose host name contains the specified substring. + /// + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). + /// + /// To add multiple substrings, invoke this function multiple times. + /// + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. + /// + /// **Note**: This function does not automatically compare with pseudo names, like "localhost". + /// + /// # Attention + /// This function does not automatically treat 127.0.0.1 like localhost. + /// + /// # Example + /// + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; /// - /// // Arrange + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then| { - /// when.method(POST) - /// .path("/example") - /// .header("content-type", "application/x-www-form-urlencoded") - /// .x_www_form_urlencoded_tuple("name", "Peter Griffin") - /// .x_www_form_urlencoded_tuple("town", "Quahog"); - /// then.status(202); + /// // Create a mock that matches any request where the host name contains "localhost" + /// let mock = server.mock(|when, then| { + /// when.host_includes("0.0"); // Only match hosts containing "0.0" (e.g., 127.0.0.1) + /// then.status(200); // Respond with status code 200 for all matched requests /// }); /// - /// let response = Request::post(server.url("/example")) - /// .header("content-type", "application/x-www-form-urlencoded") - /// .body("name=Peter%20Griffin&town=Quahog") - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Make a request to a URL whose host name is "localhost" to trigger the mock + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); /// - /// // Assert - /// m.assert(); - /// assert_eq!(response.status(), 202); + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once + /// mock.assert(); /// ``` - pub fn x_www_form_urlencoded_tuple, SV: Into>( - mut self, - key: SK, - value: SV, - ) -> Self { + /// + /// # Parameters + /// - `host`: A string or other type convertible to `String` that will be added as a substring to match against the request's host name. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_includes>(mut self, host: IntoString) -> Self { update_cell(&self.expectations, |e| { - if e.x_www_form_urlencoded.is_none() { - e.x_www_form_urlencoded = Some(Vec::new()); + if e.host_contains.is_none() { + e.host_contains = Some(Vec::new()); } - e.x_www_form_urlencoded - .as_mut() - .unwrap() - .push((key.into(), value.into())); + e.host_contains.as_mut().unwrap().push(host.into()); }); self } + // @docs-group: Host - /// Sets a requirement for a tuple key in an x-www-form-urlencoded request body. - /// Please refer to https://url.spec.whatwg.org/#application/x-www-form-urlencoded for more - /// information. - /// ``` + /// Adds a substring that must not be present within the request's host name for the mock server to respond. + /// + /// This method ensures that the mock server does not respond to requests if the host name contains the specified substring. + /// + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). + /// + /// To add multiple excluded substrings, invoke this function multiple times. + /// + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. + /// + /// **Note**: This function does not automatically compare with pseudo names, like "localhost". + /// + /// # Example + /// + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; /// - /// // Arrange + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then| { - /// when.method(POST) - /// .path("/example") - /// .header("content-type", "application/x-www-form-urlencoded") - /// .x_www_form_urlencoded_key_exists("name") - /// .x_www_form_urlencoded_key_exists("town"); - /// then.status(202); + /// // Create a mock that excludes any request where the host name contains "www.google.com" + /// let mock = server.mock(|when, then| { + /// when.host_excludes("www.google.com"); // Exclude hosts containing "www.google.com" + /// then.status(200); // Respond with status code 200 for other matched requests /// }); /// - /// let response = Request::post(server.url("/example")) - /// .header("content-type", "application/x-www-form-urlencoded") - /// .body("name=Peter%20Griffin&town=Quahog") - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Make a request to a URL whose host name will be "localhost" and trigger the mock + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); /// - /// // Assert - /// m.assert(); - /// assert_eq!(response.status(), 202); + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once + /// mock.assert(); /// ``` - pub fn x_www_form_urlencoded_key_exists>(mut self, key: S) -> Self { + /// + /// # Parameters + /// - `host`: A string or other type convertible to `String` that will be added as a substring to exclude from matching. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_excludes>(mut self, host: IntoString) -> Self { update_cell(&self.expectations, |e| { - if e.x_www_form_urlencoded_key_exists.is_none() { - e.x_www_form_urlencoded_key_exists = Some(Vec::new()); + if e.host_excludes.is_none() { + e.host_excludes = Some(Vec::new()); } - e.x_www_form_urlencoded_key_exists - .as_mut() - .unwrap() - .push(key.into()); + e.host_excludes.as_mut().unwrap().push(host.into()); }); self } + // @docs-group: Host - /// Sets the required HTTP request body content. + /// Adds a prefix that the request's host name must start with for the mock server to respond. + /// + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). + /// + /// To add multiple prefixes, invoke this function multiple times. + /// + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. /// - /// * `body` - The required HTTP request body. + /// **Note**: This function does not automatically compare with pseudo names, like "localhost". /// /// # Example - /// ``` + /// + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.body("The Great Gatsby"); - /// then.status(200); + /// // Create a mock that matches any request where the host name starts with "local" + /// let mock = server.mock(|when, then| { + /// when.host_prefix("127.0"); // Only match hosts starting with "127.0" + /// then.status(200); // Respond with status code 200 for all matched requests /// }); /// - /// Request::post(&format!("http://{}/test", server.address())) - /// .body("The Great Gatsby") - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Make a request to the mock server with a host name of "127.0.0.1" to trigger the mock response. + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once /// mock.assert(); /// ``` - pub fn body>(mut self, body: S) -> Self { + /// + /// # Parameters + /// - `prefix`: A string or other type convertible to `String` specifying the prefix that the host name should start with. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_prefix>(mut self, host: IntoString) -> Self { update_cell(&self.expectations, |e| { - e.body = Some(body.into()); + if e.host_prefix.is_none() { + e.host_prefix = Some(Vec::new()); + } + e.host_prefix.as_mut().unwrap().push(host.into()); }); self } + // @docs-group: Host - /// Sets a [Regex](type.Regex.html) for the expected HTTP body. + /// Adds a suffix that the request's host name must end with for the mock server to respond. /// - /// * `regex` - The regex that the HTTP request body will matched against. + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). /// - /// ``` - /// use isahc::{prelude::*, Request}; - /// use httpmock::prelude::*; + /// To add multiple suffixes, invoke this function multiple times. /// - /// // Arrange - /// let _ = env_logger::try_init(); + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. + /// + /// **Note**: This function does not automatically compare with pseudo names, like "localhost". + /// + /// # Example + /// + /// ```rust + /// use httpmock::prelude::*; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ - /// when.method(POST) - /// .path("/books") - /// .body_matches(Regex::new("Fellowship").unwrap()); - /// then.status(201); + /// // Create a mock that matches any request where the host name ends with "host" (e.g., "localhost"). + /// let mock = server.mock(|when, then| { + /// when.host_suffix("0.1"); // Only match hosts ending with "0.1" + /// then.status(200); // Respond with status code 200 for all matched requests /// }); /// - /// // Act: Send the request - /// let response = Request::post(server.url("/books")) - /// .body("The Fellowship of the Ring") - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Make a request to the mock server with a host name of "127.0.0.1" to trigger the mock response. + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); /// - /// // Assert - /// m.assert(); - /// assert_eq!(response.status(), 201); + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once + /// mock.assert(); /// ``` - pub fn body_matches>(mut self, regex: R) -> Self { + /// + /// # Parameters + /// - `host`: A string or other type convertible to `String` specifying the suffix that the host name should end with. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_suffix>(mut self, host: IntoString) -> Self { update_cell(&self.expectations, |e| { - if e.body_matches.is_none() { - e.body_matches = Some(Vec::new()); + if e.host_suffix.is_none() { + e.host_suffix = Some(Vec::new()); } - e.body_matches - .as_mut() - .unwrap() - .push(Pattern::from_regex(regex.into())); + e.host_suffix.as_mut().unwrap().push(host.into()); }); self } + // @docs-group: Host - /// Sets the expected HTTP body substring. + /// Adds a prefix that the request's host name must *not* start with for the mock server to respond. /// - /// * `substring` - The substring that will matched against. + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). /// - /// ``` - /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; + /// To add multiple excluded prefixes, invoke this function multiple times. /// - /// // Arrange - /// let _ = env_logger::try_init(); + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. + /// + /// **Note**: This function does not automatically compare with pseudo names, like "localhost". /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ - /// when.path("/books") - /// .body_contains("Ring"); - /// then.status(201); + /// // Create a mock that matches any request where the host name does not start with "www." + /// let mock = server.mock(|when, then| { + /// when.host_prefix_not("www."); // Exclude hosts starting with "www" + /// then.status(200); // Respond with status code 200 for all other requests /// }); /// - /// // Act: Send the request - /// let response = Request::post(server.url("/books")) - /// .body("The Fellowship of the Ring") - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Make a request with host name "localhost" that does not start with "www" and therefore + /// // triggers the mock response. + /// let response = reqwest::blocking::get(server.url("/example")).unwrap(); /// - /// // Assert - /// m.assert(); - /// assert_eq!(response.status(), 201); + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once + /// mock.assert(); /// ``` - pub fn body_contains>(mut self, substring: S) -> Self { + /// + /// # Parameters + /// - `prefix`: A string or other type convertible to `String` specifying the prefix that the host name should *not* start with. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_prefix_not>(mut self, prefix: IntoString) -> Self { update_cell(&self.expectations, |e| { - if e.body_contains.is_none() { - e.body_contains = Some(Vec::new()); + if e.host_prefix_not.is_none() { + e.host_prefix_not = Some(Vec::new()); } - e.body_contains.as_mut().unwrap().push(substring.into()); + e.host_prefix_not.as_mut().unwrap().push(prefix.into()); }); self } + // @docs-group: Host - /// Sets the expected JSON body. This method expects a [serde_json::Value](../serde_json/enum.Value.html) - /// that will be serialized/deserialized to/from a JSON string. + /// Adds a suffix that the request's host name must *not* end with for the mock server to respond. /// - /// Note that this method does not set the `content-type` header automatically, so you - /// need to provide one yourself! + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). /// - /// * `body` - The HTTP body object that will be serialized to JSON using serde. + /// To add multiple excluded suffixes, invoke this function multiple times. /// - /// ``` + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. + /// + /// **Note**: This function does not automatically compare with pseudo names, like "localhost". + /// + /// # Example + /// ```rust /// use httpmock::prelude::*; - /// use serde_json::json; - /// use isahc::{prelude::*, Request}; /// - /// // Arrange - /// let _ = env_logger::try_init(); + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ - /// when.path("/user") - /// .header("content-type", "application/json") - /// .json_body(json!({ "name": "Hans" })); - /// then.status(201); + /// // Create a mock that matches any request where the host name does not end with "host". + /// let mock = server.mock(|when, then| { + /// when.host_suffix_not("host"); // Exclude hosts ending with "host" + /// then.status(200); // Respond with status code 200 for all other requests /// }); /// - /// // Act: Send the request and deserialize the response to JSON - /// let mut response = Request::post(&format!("http://{}/user", server.address())) - /// .header("content-type", "application/json") - /// .body(json!({ "name": "Hans" }).to_string()) - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Make a request with a host name that does not end with "host" to trigger the mock response. + /// let response = reqwest::blocking::get(server.url("/example")).unwrap(); /// - /// // Assert - /// m.assert(); - /// assert_eq!(response.status(), 201); - /// ``` - pub fn json_body>(mut self, value: V) -> Self { - update_cell(&self.expectations, |e| { - e.json_body = Some(value.into()); + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Parameters + /// - `host`: A string or other type convertible to `String` specifying the suffix that the host name should *not* end with. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_suffix_not>(mut self, host: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.host_suffix_not.is_none() { + e.host_suffix_not = Some(Vec::new()); + } + e.host_suffix_not.as_mut().unwrap().push(host.into()); }); self } + // @docs-group: Host - /// Sets the expected JSON body. This method expects a serializable serde object - /// that will be serialized/deserialized to/from a JSON string. + /// Sets a regular expression pattern that the request's host name must match for the mock server to respond. + /// + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). + /// + /// To add multiple patterns, invoke this function multiple times. + /// + /// **Note**: Host matching is case-insensitive, conforming to + /// [RFC 3986, Section 3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2). + /// This standard dictates that all host names are treated equivalently, regardless of character case. + /// + /// **Note**: This function does not automatically compare with pseudo names, like "localhost". + /// + /// # Parameters + /// - `regex`: A regular expression pattern to match against the host name. Should be a valid regex string. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches requests where the host name is exactly "localhost" + /// let mock = server.mock(|when, then| { + /// when.host_matches(r"^127.0.0.1$"); + /// then.status(200); + /// }); /// - /// Note that this method does not set the "content-type" header automatically, so you - /// need to provide one yourself! + /// // Make a request with "127.0.0.1" as the host name to trigger the mock response. + /// let response = reqwest::blocking::get(server.url("/")).unwrap(); /// - /// * `body` - The HTTP body object that will be serialized to JSON using serde. + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); /// + /// // Verify that the mock was called at least once + /// mock.assert(); /// ``` + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn host_matches>(mut self, regex: IntoRegex) -> Self { + update_cell(&self.expectations, |e| { + if e.host_matches.is_none() { + e.host_matches = Some(Vec::new()); + } + e.host_matches.as_mut().unwrap().push(regex.into()); + }); + self + } + // @docs-group: Host + + /// Specifies the expected port number for incoming requests to match. + /// + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). + /// + /// # Parameters + /// - `port`: A value convertible to `u16`, representing the expected port number. + /// + /// # Example + /// ```rust /// use httpmock::prelude::*; - /// use serde_json::json; - /// use isahc::{prelude::*, Request}; + /// use reqwest::blocking::Client; /// - /// // This is a temporary type that we will use for this test - /// #[derive(serde::Serialize, serde::Deserialize)] - /// struct TestUser { - /// name: String, - /// } + /// // Start a new mock server + /// let server = MockServer::start(); /// - /// // Arrange - /// let _ = env_logger::try_init(); + /// // Configure a mock to respond to requests made to `github.com` + /// // with a specific port + /// server.mock(|when, then| { + /// when.port(80); // Specify the expected port + /// then.body("This is a mock response"); + /// }); + /// + /// // Set up an HTTP client to use the mock server as a proxy + /// let client = Client::builder() + /// // Proxy all requests to the mock server + /// .proxy(reqwest::Proxy::all(&server.base_url()).unwrap()) + /// .build() + /// .unwrap(); + /// + /// // Send a GET request to `github.com` on port 80. + /// // The request will be sent to our mock server due to the HTTP client proxy settings. + /// let response = client.get("http://github.com:80").send().unwrap(); + /// + /// // Validate that the mock server returned the expected response + /// assert_eq!(response.text().unwrap(), "This is a mock response"); + /// ``` + /// + /// # Errors + /// - This function will panic if the port number cannot be converted to a valid `u16` value. + /// + /// # Returns + /// The updated `When` instance to allow method chaining. + /// + pub fn port>(mut self, port: U16) -> Self + where + >::Error: std::fmt::Debug, + { + let port: u16 = port.try_into().expect("Port value is out of range for u16"); + + update_cell(&self.expectations, |e| e.port = Some(port)); + self + } + // @docs-group: Port + + /// Specifies the port number that incoming requests must *not* match. + /// + /// This constraint is especially useful when working with proxy or forwarding rules, but it + /// can also be used to serve mocks (e.g., when using a mock server as a proxy). + /// + /// To add multiple excluded ports, invoke this function multiple times. + /// + /// # Parameters + /// - `port`: A value convertible to `u16`, representing the port number to be excluded. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ - /// when.path("/user") - /// .header("content-type", "application/json") - /// .json_body_obj(&TestUser { - /// name: String::from("Fred"), - /// }); - /// then.status(200); + /// // Configure a mock to respond to requests not using port 81 + /// server.mock(|when, then| { + /// when.port_not(81); // Exclude requests on port 81 + /// then.body("This is a mock response"); /// }); /// - /// // Act: Send the request and deserialize the response to JSON - /// let mut response = Request::post(&format!("http://{}/user", server.address())) - /// .header("content-type", "application/json") - /// .body(json!(&TestUser { - /// name: "Fred".to_string() - /// }).to_string()) - /// .unwrap() - /// .send() + /// // Set up an HTTP client to use the mock server as a proxy + /// let client = Client::builder() + /// .proxy(reqwest::Proxy::all(&server.base_url()).unwrap()) + /// .build() /// .unwrap(); /// - /// // Assert - /// m.assert(); + /// // Make a request to `github.com` on port 80, which will trigger + /// // the mock response + /// let response = client.get("http://github.com:80").send().unwrap(); + /// + /// // Validate that the mock server returned the expected response + /// assert_eq!(response.text().unwrap(), "This is a mock response"); + /// ``` + /// + /// # Errors + /// - This function will panic if the port number cannot be converted to a valid `u16` value. + /// + /// # Returns + /// The updated `When` instance to enable method chaining. + /// + pub fn port_not>(mut self, port: U16) -> Self + where + >::Error: std::fmt::Debug, + { + let port: u16 = port.try_into().expect("Port value is out of range for u16"); + + update_cell(&self.expectations, |e| { + if e.port_not.is_none() { + e.port_not = Some(Vec::new()); + } + e.port_not.as_mut().unwrap().push(port); + }); + self + } + // @docs-group: Port + + /// Specifies the expected URL path that incoming requests must match for the mock server to respond. + /// This is useful for targeting specific endpoints, such as API routes, to ensure only relevant requests trigger the mock response. + /// + /// # Parameters + /// - `path`: A string or other value convertible to `String` that represents the expected URL path. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches requests to `/test` + /// let mock = server.mock(|when, then| { + /// when.path("/test"); + /// then.status(200); // Respond with a 200 status code + /// }); + /// + /// // Make a request to the mock server using the specified path + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); + /// + /// // Ensure the request was successful /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); /// ``` - pub fn json_body_obj<'a, T>(self, body: &T) -> Self + /// + /// # Returns + /// The updated `When` instance, allowing method chaining for additional configuration. + /// + pub fn path>(mut self, path: TryIntoString) -> Self where - T: Serialize + Deserialize<'a>, + >::Error: std::fmt::Debug, { - let json_value = serde_json::to_value(body).expect("Cannot serialize json body to JSON"); - self.json_body(json_value) + let path = path.try_into().expect("cannot convert path into a string"); + update_cell(&self.expectations, |e| { + e.path = Some(path); + }); + self } + // @docs-group: Path - /// Sets the expected partial JSON body. + /// Specifies the URL path that incoming requests must *not* match for the mock server to respond. + /// This is helpful when you need to exclude specific endpoints while allowing others through. /// - /// **Attention: The partial string needs to be a valid JSON string. It must contain - /// the full object hierarchy from the original JSON object but can leave out irrelevant - /// attributes (see example).** + /// To add multiple excluded paths, invoke this function multiple times. /// - /// Note that this method does not set the `content-type` header automatically, so you - /// need to provide one yourself! + /// # Parameters + /// - `path`: A string or other value convertible to `String` that represents the URL path to exclude. /// - /// String format and attribute order are irrelevant. + /// # Example + /// ```rust + /// use httpmock::prelude::*; /// - /// * `partial_body` - The HTTP body object that will be serialized to JSON using serde. + /// // Start a new mock server + /// let server = MockServer::start(); /// - /// ## Example - /// Suppose your application sends the following JSON request body: - /// ```json - /// { - /// "parent_attribute" : "Some parent data goes here", - /// "child" : { - /// "target_attribute" : "Example", - /// "other_attribute" : "Another value" - /// } - /// } + /// // Create a mock that will not match requests to `/exclude` + /// let mock = server.mock(|when, then| { + /// when.path_not("/exclude"); + /// then.status(200); // Respond with status 200 for all other paths + /// }); + /// + /// // Make a request to a path that does not match the exclusion + /// let response = reqwest::blocking::get(server.url("/include")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); /// ``` - /// If we only want to verify that `target_attribute` has value `Example` without the need - /// to provide a full JSON object, we can use this method as follows: + /// + /// # Returns + /// The updated `When` instance, allowing method chaining for further configuration. + /// + pub fn path_not>(mut self, path: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let path = path.try_into().expect("cannot convert path into string"); + update_cell(&self.expectations, |e| { + if e.path_not.is_none() { + e.path_not = Some(Vec::new()); + } + e.path_not.as_mut().unwrap().push(path); + }); + self + } + // @docs-group: Path + + /// Specifies a substring that the URL path must contain for the mock server to respond. + /// This constraint is useful for matching URLs based on partial segments, especially when exact path matching isn't required. + /// + /// # Parameters + /// - `substring`: A string or any value convertible to `String` representing the substring that must be present in the URL path. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches any path containing the substring "es" + /// let mock = server.mock(|when, then| { + /// when.path_includes("es"); + /// then.status(200); // Respond with a 200 status code for matched requests + /// }); + /// + /// // Make a request to a path containing "es" to trigger the mock response + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure that the mock was called at least once + /// mock.assert(); /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for further configuration. + /// + pub fn path_includes>(mut self, substring: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let substring = substring + .try_into() + .expect("cannot convert substring into string"); + update_cell(&self.expectations, |e| { + if e.path_includes.is_none() { + e.path_includes = Some(Vec::new()); + } + e.path_includes.as_mut().unwrap().push(substring); + }); + self + } + // @docs-group: Path + + /// Specifies a substring that the URL path must *not* contain for the mock server to respond. + /// This constraint is useful for excluding requests to paths containing particular segments or patterns. + /// + /// # Parameters + /// - `substring`: A string or other value convertible to `String` representing the substring that should not appear in the URL path. + /// + /// # Example + /// ```rust /// use httpmock::prelude::*; /// + /// // Start a new mock server /// let server = MockServer::start(); /// - /// let mut mock = server.mock(|when, then|{ - /// when.json_body_partial(r#" - /// { - /// "child" : { - /// "target_attribute" : "Example" - /// } - /// } - /// "#); - /// then.status(200); + /// // Create a mock that matches any path not containing the substring "xyz" + /// let mock = server.mock(|when, then| { + /// when.path_excludes("xyz"); + /// then.status(200); // Respond with status 200 for paths excluding "xyz" /// }); + /// + /// // Make a request to a path that does not contain "xyz" + /// let response = reqwest::blocking::get(server.url("/testpath")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Ensure the mock server returned the expected response + /// mock.assert(); /// ``` - /// Please note that the JSON partial contains the full object hierarchy, i.e. it needs to start - /// from the root! It leaves out irrelevant attributes, however (`parent_attribute` - /// and `child.other_attribute`). - pub fn json_body_partial>(mut self, partial: S) -> Self { + /// + /// # Returns + /// The updated `When` instance to enable method chaining for additional configuration. + /// + pub fn path_excludes>(mut self, substring: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let substring = substring + .try_into() + .expect("cannot convert substring into string"); update_cell(&self.expectations, |e| { - if e.json_body_includes.is_none() { - e.json_body_includes = Some(Vec::new()); + if e.path_excludes.is_none() { + e.path_excludes = Some(Vec::new()); } - let value = Value::from_str(&partial.into()) - .expect("cannot convert JSON string to serde value"); - e.json_body_includes.as_mut().unwrap().push(value); + e.path_excludes.as_mut().unwrap().push(substring); + }); + self + } + // @docs-group: Path + + /// Specifies a prefix that the URL path must start with for the mock server to respond. + /// This is useful when only the initial segments of a path need to be validated, such as checking specific API routes. + /// + /// # Parameters + /// - `prefix`: A string or other value convertible to `String` representing the prefix that the URL path should start with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches any path starting with the prefix "/api" + /// let mock = server.mock(|when, then| { + /// when.path_prefix("/api"); + /// then.status(200); // Respond with a 200 status code for matched requests + /// }); + /// + /// // Make a request to a path starting with "/api" + /// let response = reqwest::blocking::get(server.url("/api/v1/resource")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for further configuration. + /// + pub fn path_prefix>(mut self, prefix: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let prefix = prefix + .try_into() + .expect("cannot convert prefix into string"); + update_cell(&self.expectations, |e| { + if e.path_prefix.is_none() { + e.path_prefix = Some(Vec::new()); + } + e.path_prefix.as_mut().unwrap().push(prefix); + }); + self + } + // @docs-group: Path + + /// Specifies a suffix that the URL path must end with for the mock server to respond. + /// This is useful when the final segments of a path need to be validated, such as file extensions or specific patterns. + /// + /// # Parameters + /// - `suffix`: A string or other value convertible to `String` representing the suffix that the URL path should end with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches any path ending with the suffix ".html" + /// let mock = server.mock(|when, then| { + /// when.path_suffix(".html"); + /// then.status(200); // Respond with a 200 status code for matched requests + /// }); + /// + /// // Make a request to a path ending with ".html" + /// let response = reqwest::blocking::get(server.url("/about/index.html")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for further configuration. + /// + pub fn path_suffix>(mut self, suffix: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let suffix = suffix + .try_into() + .expect("cannot convert suffix into string"); + update_cell(&self.expectations, |e| { + if e.path_suffix.is_none() { + e.path_suffix = Some(Vec::new()); + } + e.path_suffix.as_mut().unwrap().push(suffix); + }); + self + } + // @docs-group: Path + + /// Specifies a prefix that the URL path must not start with for the mock server to respond. + /// This constraint is useful for excluding paths that begin with particular segments or patterns. + /// + /// # Parameters + /// - `prefix`: A string or other value convertible to `String` representing the prefix that the URL path should not start with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches any path not starting with the prefix "/admin" + /// let mock = server.mock(|when, then| { + /// when.path_prefix_not("/admin"); + /// then.status(200); // Respond with status 200 for paths excluding "/admin" + /// }); + /// + /// // Make a request to a path that does not start with "/admin" + /// let response = reqwest::blocking::get(server.url("/public/home")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock server returned the expected response + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn path_prefix_not>(mut self, prefix: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let prefix = prefix + .try_into() + .expect("cannot convert prefix into string"); + update_cell(&self.expectations, |e| { + if e.path_prefix_not.is_none() { + e.path_prefix_not = Some(Vec::new()); + } + e.path_prefix_not.as_mut().unwrap().push(prefix); + }); + self + } + // @docs-group: Path + + /// Specifies a suffix that the URL path must not end with for the mock server to respond. + /// This constraint is useful for excluding paths with specific file extensions or patterns. + /// + /// # Parameters + /// - `suffix`: A string or other value convertible to `String` representing the suffix that the URL path should not end with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches any path not ending with the suffix ".json" + /// let mock = server.mock(|when, then| { + /// when.path_suffix_not(".json"); + /// then.status(200); // Respond with a 200 status code for paths excluding ".json" + /// }); + /// + /// // Make a request to a path that does not end with ".json" + /// let response = reqwest::blocking::get(server.url("/about/index.html")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for further configuration. + /// + pub fn path_suffix_not>(mut self, suffix: TryIntoString) -> Self + where + >::Error: std::fmt::Debug, + { + let suffix = suffix + .try_into() + .expect("cannot convert suffix into string"); + update_cell(&self.expectations, |e| { + if e.path_suffix_not.is_none() { + e.path_suffix_not = Some(Vec::new()); + } + e.path_suffix_not.as_mut().unwrap().push(suffix); + }); + self + } + // @docs-group: Path + + /// Specifies a regular expression that the URL path must match for the mock server to respond. + /// This method allows flexible matching using regex patterns, making it useful for various matching scenarios. + /// + /// # Parameters + /// - `regex`: An expression that implements `Into`, representing the regex pattern to match against the URL path. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that matches paths ending with the suffix "le" + /// let mock = server.mock(|when, then| { + /// when.path_matches(r"le$"); + /// then.status(200); // Respond with a 200 status code for paths matching the pattern + /// }); + /// + /// // Make a request to a path ending with "le" + /// let response = reqwest::blocking::get(server.url("/example")).unwrap(); + /// + /// // Ensure the request was successful + /// assert_eq!(response.status(), 200); + /// + /// // Verify that the mock server returned the expected response + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + /// # Errors + /// This function will panic if the provided regex pattern is invalid. + /// + pub fn path_matches>(mut self, regex: TryIntoRegex) -> Self + where + >::Error: std::fmt::Debug, + { + let regex = regex + .try_into() + .expect("cannot convert provided value into regex"); + update_cell(&self.expectations, |e| { + if e.path_matches.is_none() { + e.path_matches = Some(Vec::new()); + } + e.path_matches.as_mut().unwrap().push(regex) + }); + self + } + // @docs-group: Path + + /// Specifies a required query parameter for the request. + /// This function ensures that the specified query parameter (key-value pair) must be included + /// in the request URL for the mock server to respond. + /// + /// **Note**: The request query keys and values are implicitly *allowed but not required* to be URL-encoded. + /// However, the value passed to this method should always be in plain text (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to match against. + /// - `value`: The expected value of the query parameter. + /// + /// # Example + /// ```rust + /// // Arrange + /// use reqwest::blocking::get; + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` to have the value "This is cool" + /// let m = server.mock(|when, then| { + /// when.query_param("query", "This is cool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that includes the specified query parameter and value + /// get(&server.url("/search?query=This+is+cool")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn query_param, ValueString: Into>( + mut self, + name: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param.is_none() { + e.query_param = Some(Vec::new()); + } + e.query_param + .as_mut() + .unwrap() + .push((name.into(), value.into())); + }); + self + } + // @docs-group: Query Parameters + + /// This function ensures that the specified query parameter (key) does exist in the request URL, + /// and its value is not equal to the specified value. + /// + /// **Note**: Query keys and values are implicitly *allowed but not required* to be URL-encoded + /// in the HTTP request. However, values passed to this method should always be in plain text + /// (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to ensure is not present. + /// - `value`: The value of the query parameter to ensure is not present. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` to NOT have the value "This is cool" + /// let m = server.mock(|when, then| { + /// when.query_param_not("query", "This is cool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that does not include the specified query parameter and value + /// let response = reqwest::blocking::get(&server.url("/search?query=awesome")).unwrap(); + /// + /// // Assert: Verify that the mock was called + /// assert_eq!(response.status(), 200); + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn query_param_not, ValueString: Into>( + mut self, + name: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_not.is_none() { + e.query_param_not = Some(Vec::new()); + } + e.query_param_not + .as_mut() + .unwrap() + .push((name.into(), value.into())); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter must be present in an HTTP request. + /// This function ensures that the specified query parameter key exists in the request URL + /// for the mock server to respond, regardless of the parameter's value. + /// + /// **Note**: The query key in the request is implicitly *allowed but not required* to be URL-encoded. + /// However, provide the key in plain text here (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter that must exist in the request. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` to exist, regardless of its value + /// let m = server.mock(|when, then| { + /// when.query_param_exists("query"); + /// then.status(200); // Respond with a 200 status code if the parameter exists + /// }); + /// + /// // Act: Make a request with the specified query parameter + /// reqwest::blocking::get(&server.url("/search?query=restaurants+near+me")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_exists>(mut self, name: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_exists.is_none() { + e.query_param_exists = Some(Vec::new()); + } + e.query_param_exists.as_mut().unwrap().push(name.into()); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter must *not* be present in an HTTP request. + /// This function ensures that the specified query parameter key is absent in the request URL + /// for the mock server to respond, regardless of the parameter's value. + /// + /// **Note**: The request query key is implicitly *allowed but not required* to be URL-encoded. + /// However, provide the key in plain text (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter that should be missing from the request. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` to be missing + /// let m = server.mock(|when, then| { + /// when.query_param_missing("query"); + /// then.status(200); // Respond with a 200 status code if the parameter is absent + /// }); + /// + /// // Act: Make a request without the specified query parameter + /// reqwest::blocking::get(&server.url("/search")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_missing>(mut self, name: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_missing.is_none() { + e.query_param_missing = Some(Vec::new()); + } + e.query_param_missing.as_mut().unwrap().push(name.into()); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter's value (**not** the key) must contain a specific substring for the request to match. + /// This function ensures that the specified query parameter (key) does exist in the request URL, and + /// it does have a value containing the given substring for the mock server to respond. + /// + /// **Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded. + /// However, provide the substring in plain text (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to match against. + /// - `substring`: The substring that must appear within the value of the query parameter. + /// + /// # Example + /// ```rust + /// // Arrange + /// use reqwest::blocking::get; + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` + /// // to have a value containing "cool" + /// let m = server.mock(|when, then| { + /// when.query_param_includes("query", "cool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that includes a value containing the substring "cool" + /// get(server.url("/search?query=Something+cool")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_includes, ValueString: Into>( + mut self, + name: KeyString, + substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_includes.is_none() { + e.query_param_includes = Some(Vec::new()); + } + e.query_param_includes + .as_mut() + .unwrap() + .push((name.into(), substring.into())); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter's value (**not** the key) must not contain a specific substring for the request to match. + /// + /// This function ensures that the specified query parameter (key) does exist in the request URL, and + /// it does not have a value containing the given substring for the mock server to respond. + /// + /// **Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded. + /// However, provide the substring in plain text here (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to match against. + /// - `substring`: The substring that must not appear within the value of the query parameter. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` + /// // to have a value that does not contain "uncool" + /// let m = server.mock(|when, then| { + /// when.query_param_excludes("query", "uncool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that includes a value not containing the substring "uncool" + /// reqwest::blocking::get(&server.url("/search?query=Something+cool")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_excludes, ValueString: Into>( + mut self, + name: KeyString, + substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_excludes.is_none() { + e.query_param_excludes = Some(Vec::new()); + } + e.query_param_excludes + .as_mut() + .unwrap() + .push((name.into(), substring.into())); + }); + + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter's value (**not** the key) must start with a specific prefix for the request to match. + /// This function ensures that the specified query parameter (key) has a value starting with the given prefix + /// in the request URL for the mock server to respond. + /// + /// **Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded. + /// Provide the prefix in plain text here (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to match against. + /// - `prefix`: The prefix that the query parameter value should start with. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` + /// // to have a value starting with "cool" + /// let m = server.mock(|when, then| { + /// when.query_param_prefix("query", "cool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that includes a value starting with the prefix "cool" + /// reqwest::blocking::get(&server.url("/search?query=cool+stuff")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_prefix, ValueString: Into>( + mut self, + name: KeyString, + prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_prefix.is_none() { + e.query_param_prefix = Some(Vec::new()); + } + e.query_param_prefix + .as_mut() + .unwrap() + .push((name.into(), prefix.into())); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter's value (**not** the key) must end with a specific suffix for the request to match. + /// This function ensures that the specified query parameter (key) has a value ending with the given suffix + /// in the request URL for the mock server to respond. + /// + /// **Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded. + /// Provide the suffix in plain text here (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to match against. + /// - `suffix`: The suffix that the query parameter value should end with. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` + /// // to have a value ending with "cool" + /// let m = server.mock(|when, then| { + /// when.query_param_suffix("query", "cool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that includes a value ending with the suffix "cool" + /// reqwest::blocking::get(&server.url("/search?query=really_cool")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_suffix, ValueString: Into>( + mut self, + name: KeyString, + suffix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_suffix.is_none() { + e.query_param_suffix = Some(Vec::new()); + } + e.query_param_suffix + .as_mut() + .unwrap() + .push((name.into(), suffix.into())); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter's value (**not** the key) must not start with a specific prefix for the request to match. + /// This function ensures that the specified query parameter (key) has a value not starting with the given prefix + /// in the request URL for the mock server to respond. + /// + /// **Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded. + /// Provide the prefix in plain text here (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to match against. + /// - `prefix`: The prefix that the query parameter value should not start with. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` + /// // to have a value not starting with "cool" + /// let m = server.mock(|when, then| { + /// when.query_param_prefix_not("query", "cool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that does not start with the prefix "cool" + /// reqwest::blocking::get(&server.url("/search?query=warm_stuff")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_prefix_not, ValueString: Into>( + mut self, + name: KeyString, + prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_prefix_not.is_none() { + e.query_param_prefix_not = Some(Vec::new()); + } + e.query_param_prefix_not + .as_mut() + .unwrap() + .push((name.into(), prefix.into())); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter's value (**not** the key) must not end with a specific suffix for the request to match. + /// This function ensures that the specified query parameter (key) has a value not ending with the given suffix + /// in the request URL for the mock server to respond. + /// + /// **Note**: The request query key-value pairs are implicitly *allowed but not required* to be URL-encoded. + /// Provide the suffix in plain text here (i.e., not encoded). + /// + /// # Parameters + /// - `name`: The name of the query parameter to match against. + /// - `suffix`: The suffix that the query parameter value should not end with. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter `query` + /// // to have a value not ending with "cool" + /// let m = server.mock(|when, then| { + /// when.query_param_suffix_not("query", "cool"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that doesn't end with the suffix "cool" + /// reqwest::blocking::get(&server.url("/search?query=uncool_stuff")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_suffix_not, ValueString: Into>( + mut self, + name: KeyString, + suffix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.query_param_suffix_not.is_none() { + e.query_param_suffix_not = Some(Vec::new()); + } + e.query_param_suffix_not + .as_mut() + .unwrap() + .push((name.into(), suffix.into())); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that a query parameter must match a specific regular expression pattern for the key and another pattern for the value. + /// This function ensures that the specified query parameter key-value pair matches the given patterns + /// in the request URL for the mock server to respond. + /// + /// # Parameters + /// - `key_regex`: A regular expression pattern for the query parameter's key to match against. + /// - `value_regex`: A regular expression pattern for the query parameter's value to match against. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the query parameter key to match the regex "user.*" + /// // and the value to match the regex "admin.*" + /// let m = server.mock(|when, then| { + /// when.query_param_matches(r"user.*", r"admin.*"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that matches the regex patterns for both key and value + /// reqwest::blocking::get(&server.url("/search?user=admin_user")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_matches, ValueRegex: Into>( + mut self, + key_regex: KeyRegex, + value_regex: ValueRegex, + ) -> Self { + let key_regex = key_regex.into(); + let value_regex = value_regex.into(); + + update_cell(&self.expectations, |e| { + if e.query_param_matches.is_none() { + e.query_param_matches = Some(Vec::new()); + } + e.query_param_matches + .as_mut() + .unwrap() + .push((key_regex, value_regex)); + }); + self + } + // @docs-group: Query Parameters + + /// Specifies that the count of query parameters with keys and values matching specific regular + /// expression patterns must equal a specified number for the request to match. + /// This function ensures that the number of query parameters whose keys and values match the + /// given regex patterns is equal to the specified count in the request URL for the mock + /// server to respond. + /// + /// # Parameters + /// - `key_regex`: A regular expression pattern for the query parameter's key to match against. + /// - `value_regex`: A regular expression pattern for the query parameter's value to match against. + /// - `expected_count`: The expected number of query parameters whose keys and values match the regex patterns. + /// + /// # Example + /// ```rust + /// // Arrange + /// use httpmock::prelude::*; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects exactly two query parameters with keys matching the regex "user.*" + /// // and values matching the regex "admin.*" + /// let m = server.mock(|when, then| { + /// when.query_param_count(r"user.*", r"admin.*", 2); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Act: Make a request that matches the conditions + /// reqwest::blocking::get(&server.url("/search?user1=admin1&user2=admin2")).unwrap(); + /// + /// // Assert: Verify that the mock was called at least once + /// m.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn query_param_count, ValueRegex: Into>( + mut self, + key_regex: KeyRegex, + value_regex: ValueRegex, + expected_count: usize, + ) -> Self { + let key_regex = key_regex.into(); + let value_regex = value_regex.into(); + + update_cell(&self.expectations, |e| { + if e.query_param_count.is_none() { + e.query_param_count = Some(Vec::new()); + } + e.query_param_count + .as_mut() + .unwrap() + .push((key_regex, value_regex, expected_count)); + }); + self + } + // @docs-group: Query Parameters + + /// Sets the expected HTTP header and its value for the request to match. + /// This function ensures that the specified header with the given value is present in the request. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `value`: The expected value of the HTTP header. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header with a specific value + /// let mock = server.mock(|when, then| { + /// when.header("Authorization", "token 1234567890"); + /// then.status(200); // Respond with a 200 status code if the header and value are present + /// }); + /// + /// // Make a request that includes the "Authorization" header with the specified value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header, ValueString: Into>( + mut self, + name: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header.is_none() { + e.header = Some(Vec::new()); + } + e.header.as_mut().unwrap().push((name.into(), value.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must not contain a specific header with the specified value. + /// This function ensures that the specified header with the given value is absent in the request. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to add multiple excluded headers. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `value`: The value of the HTTP header that must not be present. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header with a specific value to be absent + /// let mock = server.mock(|when, then| { + /// when.header_not("Authorization", "token 1234567890"); + /// then.status(200); // Respond with a 200 status code if the header and value are absent + /// }); + /// + /// // Make a request that includes the "Authorization" header with a different value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token abcdefg") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_not, ValueString: Into>( + mut self, + name: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_not.is_none() { + e.header_not = Some(Vec::new()); + } + e.header_not + .as_mut() + .unwrap() + .push((name.into(), value.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header. + /// The presence of the header is checked, but its value is not validated. + /// For value validation, refer to [Mock::expect_header](struct.Mock.html#method.expect_header). + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive, as per RFC 2616. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header to be present in the request + /// let mock = server.mock(|when, then| { + /// when.header_exists("Authorization"); + /// then.status(200); // Respond with a 200 status code if the header is present + /// }); + /// + /// // Make a request that includes the "Authorization" header + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_exists>(mut self, name: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.header_exists.is_none() { + e.header_exists = Some(Vec::new()); + } + e.header_exists.as_mut().unwrap().push(name.into()); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must not contain a specific header. + /// This function ensures that the specified header is absent in the request. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to add multiple excluded headers. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header to be absent in the request + /// let mock = server.mock(|when, then| { + /// when.header_missing("Authorization"); + /// then.status(200); // Respond with a 200 status code if the header is absent + /// }); + /// + /// // Make a request that does not include the "Authorization" header + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_missing>(mut self, name: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.header_missing.is_none() { + e.header_missing = Some(Vec::new()); + } + e.header_missing.as_mut().unwrap().push(name.into()); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header whose value contains a specified substring. + /// This function ensures that the specified header is present and its value contains the given substring. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple headers and substrings. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `substring`: The substring that the header value must contain. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header's value to contain "token" + /// let mock = server.mock(|when, then| { + /// when.header_includes("Authorization", "token"); + /// then.status(200); // Respond with a 200 status code if the header value contains the substring + /// }); + /// + /// // Make a request that includes the "Authorization" header with the specified substring in its value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_includes, ValueString: Into>( + mut self, + name: KeyString, + substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_includes.is_none() { + e.header_includes = Some(Vec::new()); + } + e.header_includes + .as_mut() + .unwrap() + .push((name.into(), substring.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header whose value does not contain a specified substring. + /// This function ensures that the specified header is present and its value does not contain the given substring. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple headers and substrings. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `substring`: The substring that the header value must not contain. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header's value to not contain "Bearer" + /// let mock = server.mock(|when, then| { + /// when.header_excludes("Authorization", "Bearer"); + /// then.status(200); // Respond with a 200 status code if the header value does not contain the substring + /// }); + /// + /// // Make a request that includes the "Authorization" header without the forbidden substring in its value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_excludes, ValueString: Into>( + mut self, + name: KeyString, + substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_excludes.is_none() { + e.header_excludes = Some(Vec::new()); + } + e.header_excludes + .as_mut() + .unwrap() + .push((name.into(), substring.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header whose value starts with a specified prefix. + /// This function ensures that the specified header is present and its value starts with the given prefix. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple headers and prefixes. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `prefix`: The prefix that the header value must start with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header's value to start with "token" + /// let mock = server.mock(|when, then| { + /// when.header_prefix("Authorization", "token"); + /// then.status(200); // Respond with a 200 status code if the header value starts with the prefix + /// }); + /// + /// // Make a request that includes the "Authorization" header with the specified prefix in its value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_prefix, ValueString: Into>( + mut self, + name: KeyString, + prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_prefix.is_none() { + e.header_prefix = Some(Vec::new()); + } + e.header_prefix + .as_mut() + .unwrap() + .push((name.into(), prefix.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header whose value ends with a specified suffix. + /// This function ensures that the specified header is present and its value ends with the given suffix. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple headers and suffixes. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `suffix`: The suffix that the header value must end with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header's value to end with "7890" + /// let mock = server.mock(|when, then| { + /// when.header_suffix("Authorization", "7890"); + /// then.status(200); // Respond with a 200 status code if the header value ends with the suffix + /// }); + /// + /// // Make a request that includes the "Authorization" header with the specified suffix in its value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_suffix, ValueString: Into>( + mut self, + name: KeyString, + suffix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_suffix.is_none() { + e.header_suffix = Some(Vec::new()); + } + e.header_suffix + .as_mut() + .unwrap() + .push((name.into(), suffix.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header whose value does not start with a specified prefix. + /// This function ensures that the specified header is present and its value does not start with the given prefix. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple headers and prefixes. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `prefix`: The prefix that the header value must not start with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header's value to not start with "Bearer" + /// let mock = server.mock(|when, then| { + /// when.header_prefix_not("Authorization", "Bearer"); + /// then.status(200); // Respond with a 200 status code if the header value does not start with the prefix + /// }); + /// + /// // Make a request that includes the "Authorization" header without the "Bearer" prefix in its value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_prefix_not, ValueString: Into>( + mut self, + name: KeyString, + prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_prefix_not.is_none() { + e.header_prefix_not = Some(Vec::new()); + } + e.header_prefix_not + .as_mut() + .unwrap() + .push((name.into(), prefix.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header whose value does not end with a specified suffix. + /// This function ensures that the specified header is present and its value does not end with the given suffix. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple headers and suffixes. + /// + /// # Parameters + /// - `name`: The HTTP header name. Header names are case-insensitive. + /// - `suffix`: The suffix that the header value must not end with. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header's value to not end with "abc" + /// let mock = server.mock(|when, then| { + /// when.header_suffix_not("Authorization", "abc"); + /// then.status(200); // Respond with a 200 status code if the header value does not end with the suffix + /// }); + /// + /// // Make a request that includes the "Authorization" header without the "abc" suffix in its value + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_suffix_not, ValueString: Into>( + mut self, + name: KeyString, + suffix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_suffix_not.is_none() { + e.header_suffix_not = Some(Vec::new()); + } + e.header_suffix_not + .as_mut() + .unwrap() + .push((name.into(), suffix.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific header whose key and value match the specified regular expressions. + /// This function ensures that the specified header is present and both its key and value match the given regular expressions. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple headers and patterns. + /// + /// # Parameters + /// - `key_regex`: The regular expression that the header key must match. + /// - `value_regex`: The regular expression that the header value must match. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the "Authorization" header's key to match the regex "^Auth.*" + /// // and its value to match the regex "token .*" + /// let mock = server.mock(|when, then| { + /// when.header_matches("^Auth.*", "token .*"); + /// then.status(200); // Respond with a 200 status code if the header key and value match the patterns + /// }); + /// + /// // Make a request that includes the "Authorization" header with a value matching the regex + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Authorization", "token 1234567890") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_matches, ValueString: Into>( + mut self, + key_regex: KeyString, + value_regex: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.header_matches.is_none() { + e.header_matches = Some(Vec::new()); + } + e.header_matches + .as_mut() + .unwrap() + .push((key_regex.into(), value_regex.into())); + }); + self + } + // @docs-group: Headers + + /// Sets the requirement that the HTTP request must contain a specific number of headers whose keys and values match specified patterns. + /// This function ensures that the specified number of headers with keys and values matching the given patterns are present in the request. + /// Header names are case-insensitive, as per RFC 2616. + /// + /// This function may be called multiple times to check multiple patterns and counts. + /// + /// # Parameters + /// - `key_pattern`: The pattern that the header keys must match. + /// - `value_pattern`: The pattern that the header values must match. + /// - `count`: The number of headers with keys and values matching the patterns that must be present. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects at least 2 headers whose keys match the regex "^X-Custom-Header.*" + /// // and values match the regex "value.*" + /// let mock = server.mock(|when, then| { + /// when.header_count("^X-Custom-Header.*", "value.*", 2); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required headers + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("x-custom-header-1", "value1") + /// .header("X-Custom-Header-2", "value2") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + /// + pub fn header_count< + KeyRegex: TryInto, + ValueRegex: TryInto, + IntoUsize: TryInto, + >( + mut self, + key_pattern: KeyRegex, + value_pattern: ValueRegex, + count: IntoUsize, + ) -> Self + where + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + { + let count = match count.try_into() { + Ok(c) => c, + Err(_) => panic!("parameter count must be a positive integer that fits into a usize"), + }; + + let key_pattern = key_pattern.try_into().expect("cannot convert key to regex"); + let value_pattern = value_pattern + .try_into() + .expect("cannot convert key to regex"); + + update_cell(&self.expectations, |e| { + if e.header_count.is_none() { + e.header_count = Some(Vec::new()); + } + e.header_count.as_mut().unwrap().push(( + key_pattern.into(), + value_pattern.into(), + count, + )); + }); + self + } + // @docs-group: Headers + + /// Sets the cookie that needs to exist in the HTTP request. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie. Must be a case-sensitive match. + /// - `value`: The expected value of the cookie. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" with the value "1234567890" + /// let mock = server.mock(|when, then| { + /// when.cookie("SESSIONID", "1234567890"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=1234567890; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie, ValueString: Into>( + mut self, + name: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie.is_none() { + e.cookie = Some(Vec::new()); + } + e.cookie.as_mut().unwrap().push((name.into(), value.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the cookie that should not exist or should not have a specific value in the HTTP request. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie. Must be a case-sensitive match. + /// - `value`: The value that the cookie should not have. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" to not have the value "1234567890" + /// let mock = server.mock(|when, then| { + /// when.cookie_not("SESSIONID", "1234567890"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=0987654321; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_not, ValueString: Into>( + mut self, + name: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_not.is_none() { + e.cookie_not = Some(Vec::new()); + } + e.cookie_not + .as_mut() + .unwrap() + .push((name.into(), value.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must exist in the HTTP request. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must exist. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" + /// let mock = server.mock(|when, then| { + /// when.cookie_exists("SESSIONID"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=1234567890; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_exists>(mut self, name: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_exists.is_none() { + e.cookie_exists = Some(Vec::new()); + } + e.cookie_exists.as_mut().unwrap().push(name.into()); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must not exist in the HTTP request. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must not exist. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" not to exist + /// let mock = server.mock(|when, then| { + /// when.cookie_missing("SESSIONID"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that does not include the excluded cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_missing>(mut self, name: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_missing.is_none() { + e.cookie_missing = Some(Vec::new()); + } + e.cookie_missing.as_mut().unwrap().push(name.into()); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must exist and its value must contain the specified substring. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must exist. + /// - `value_substring`: The substring that must be present in the cookie value. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" with a value containing "1234" + /// let mock = server.mock(|when, then| { + /// when.cookie_includes("SESSIONID", "1234"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=abc1234def; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_includes, ValueString: Into>( + mut self, + name: KeyString, + value_substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_includes.is_none() { + e.cookie_includes = Some(Vec::new()); + } + e.cookie_includes + .as_mut() + .unwrap() + .push((name.into(), value_substring.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must exist and its value must not contain the specified substring. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must exist. + /// - `value_substring`: The substring that must not be present in the cookie value. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" with a value not containing "1234" + /// let mock = server.mock(|when, then| { + /// when.cookie_excludes("SESSIONID", "1234"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=abcdef; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_excludes, ValueString: Into>( + mut self, + name: KeyString, + value_substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_excludes.is_none() { + e.cookie_excludes = Some(Vec::new()); + } + e.cookie_excludes + .as_mut() + .unwrap() + .push((name.into(), value_substring.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must exist and its value must start with the specified substring. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must exist. + /// - `value_prefix`: The substring that must be at the start of the cookie value. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" with a value starting with "1234" + /// let mock = server.mock(|when, then| { + /// when.cookie_prefix("SESSIONID", "1234"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=1234abcdef; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_prefix, ValueString: Into>( + mut self, + name: KeyString, + value_prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_prefix.is_none() { + e.cookie_prefix = Some(Vec::new()); + } + e.cookie_prefix + .as_mut() + .unwrap() + .push((name.into(), value_prefix.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must exist and its value must end with the specified substring. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must exist. + /// - `value_suffix`: The substring that must be at the end of the cookie value. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" with a value ending with "7890" + /// let mock = server.mock(|when, then| { + /// when.cookie_suffix("SESSIONID", "7890"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=abcdef7890; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_suffix, ValueString: Into>( + mut self, + name: KeyString, + value_suffix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_suffix.is_none() { + e.cookie_suffix = Some(Vec::new()); + } + e.cookie_suffix + .as_mut() + .unwrap() + .push((name.into(), value_suffix.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must exist and its value must not start with the specified substring. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must exist. + /// - `value_prefix`: The substring that must not be at the start of the cookie value. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" with a value not starting with "1234" + /// let mock = server.mock(|when, then| { + /// when.cookie_prefix_not("SESSIONID", "1234"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=abcd1234; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_prefix_not, ValueString: Into>( + mut self, + name: KeyString, + value_prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_prefix_not.is_none() { + e.cookie_prefix_not = Some(Vec::new()); + } + e.cookie_prefix_not + .as_mut() + .unwrap() + .push((name.into(), value_prefix.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with the specified name must exist and its value must not end with the specified substring. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `name`: The name of the cookie that must exist. + /// - `value_suffix`: The substring that must not be at the end of the cookie value. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie named "SESSIONID" with a value not ending with "7890" + /// let mock = server.mock(|when, then| { + /// when.cookie_suffix_not("SESSIONID", "7890"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=abcdef1234; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_suffix_not, ValueString: Into>( + mut self, + name: KeyString, + value_suffix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_suffix_not.is_none() { + e.cookie_suffix_not = Some(Vec::new()); + } + e.cookie_suffix_not + .as_mut() + .unwrap() + .push((name.into(), value_suffix.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with a name matching the specified regex must exist and its value must match the specified regex. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `key_regex`: The regex pattern that the cookie name must match. + /// - `value_regex`: The regex pattern that the cookie value must match. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie with a name matching the regex "^SESSION" + /// // and a value matching the regex "^[0-9]{10}$" + /// let mock = server.mock(|when, then| { + /// when.cookie_matches(r"^SESSION", r"^[0-9]{10}$"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookie + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "TRACK=12345; SESSIONID=1234567890; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_matches, ValueRegex: Into>( + mut self, + key_regex: KeyRegex, + value_regex: ValueRegex, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_matches.is_none() { + e.cookie_matches = Some(Vec::new()); + } + e.cookie_matches + .as_mut() + .unwrap() + .push((key_regex.into(), value_regex.into())); + }); + self + } + // @docs-group: Cookies + + /// Sets the requirement that a cookie with a name and value matching the specified regexes must appear a specified number of times in the HTTP request. + /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). + /// **Attention**: Cookie names are **case-sensitive**. + /// + /// # Parameters + /// - `key_regex`: The regex pattern that the cookie name must match. + /// - `value_regex`: The regex pattern that the cookie value must match. + /// - `count`: The number of times a cookie with a matching name and value must appear. + /// + /// > Note: This function is only available when the `cookies` feature is enabled. This feature is enabled by default. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects a cookie with a name matching the regex "^SESSION" + /// // and a value matching the regex "^[0-9]{10}$" to appear exactly twice + /// let mock = server.mock(|when, then| { + /// when.cookie_count(r"^SESSION", r"^[0-9]{10}$", 2); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request that includes the required cookies + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Cookie", "SESSIONID=1234567890; TRACK=12345; SESSIONTOKEN=0987654321; CONSENT=1") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn cookie_count, ValueRegex: Into>( + mut self, + key_regex: KeyRegex, + value_regex: ValueRegex, + count: usize, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.cookie_count.is_none() { + e.cookie_count = Some(Vec::new()); + } + e.cookie_count + .as_mut() + .unwrap() + .push((key_regex.into(), value_regex.into(), count)); + }); + self + } + // @docs-group: Cookies + + /// Sets the required HTTP request body content. + /// This method specifies that the HTTP request body must match the provided content exactly. + /// + /// **Note**: The body content is case-sensitive and must be an exact match. + /// + /// # Parameters + /// - `body`: The required HTTP request body content. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to be "The Great Gatsby" + /// let mock = server.mock(|when, then| { + /// when.body("The Great Gatsby"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with the required body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("The Great Gatsby") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn body>(mut self, body: IntoString) -> Self { + update_cell(&self.expectations, |e| { + e.body = Some(HttpMockBytes::from(Bytes::from(body.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must not match the specified value. + /// This method ensures that the request body does not contain the provided content exactly. + /// + /// **Note**: The body content is case-sensitive and must be an exact mismatch. + /// + /// # Parameters + /// - `body`: The body content that the HTTP request must not contain. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to not be "The Great Gatsby" + /// let mock = server.mock(|when, then| { + /// when.body_not("The Great Gatsby"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with a different body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("A Tale of Two Cities") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn body_not>(mut self, body: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.body_not.is_none() { + e.body_not = Some(Vec::new()); + } + e.body_not + .as_mut() + .unwrap() + .push(HttpMockBytes::from(Bytes::from(body.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must contain the specified substring. + /// This method ensures that the request body includes the provided content as a substring. + /// + /// **Note**: The body content is case-sensitive. + /// + /// # Parameters + /// - `substring`: The substring that the HTTP request body must contain. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to contain the substring "Gatsby" + /// let mock = server.mock(|when, then| { + /// when.body_includes("Gatsby"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with the required substring in the body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("The Great Gatsby is a novel.") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn body_includes>(mut self, substring: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.body_includes.is_none() { + e.body_includes = Some(Vec::new()); + } + e.body_includes + .as_mut() + .unwrap() + .push(HttpMockBytes::from(Bytes::from(substring.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must not contain the specified substring. + /// This method ensures that the request body does not include the provided content as a substring. + /// + /// **Note**: The body content is case-sensitive. + /// + /// # Parameters + /// - `substring`: The substring that the HTTP request body must not contain. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to not contain the substring "Gatsby" + /// let mock = server.mock(|when, then| { + /// when.body_excludes("Gatsby"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with a different body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("A Tale of Two Cities is a novel.") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn body_excludes>(mut self, substring: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.body_excludes.is_none() { + e.body_excludes = Some(Vec::new()); + } + e.body_excludes + .as_mut() + .unwrap() + .push(HttpMockBytes::from(Bytes::from(substring.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must begin with the specified substring. + /// This method ensures that the request body starts with the provided content as a substring. + /// + /// **Note**: The body content is case-sensitive. + /// + /// # Parameters + /// - `prefix`: The substring that the HTTP request body must begin with. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to begin with the substring "The Great" + /// let mock = server.mock(|when, then| { + /// when.body_prefix("The Great"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with the required prefix in the body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("The Great Gatsby is a novel.") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When` instance to allow method chaining for additional configuration. + pub fn body_prefix>(mut self, prefix: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.body_prefix.is_none() { + e.body_prefix = Some(Vec::new()); + } + e.body_prefix + .as_mut() + .unwrap() + .push(HttpMockBytes::from(Bytes::from(prefix.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must end with the specified substring. + /// This method ensures that the request body concludes with the provided content as a substring. + /// + /// **Note**: The body content is case-sensitive. + /// + /// # Parameters + /// - `suffix`: The substring that the HTTP request body must end with. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to end with the substring "a novel." + /// let mock = server.mock(|when, then| { + /// when.body_suffix("a novel."); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with the required suffix in the body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("The Great Gatsby is a novel.") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When’ instance to allow method chaining for additional configuration. + pub fn body_suffix>(mut self, suffix: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.body_suffix.is_none() { + e.body_suffix = Some(Vec::new()); + } + e.body_suffix + .as_mut() + .unwrap() + .push(HttpMockBytes::from(Bytes::from(suffix.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must not begin with the specified substring. + /// This method ensures that the request body does not start with the provided content as a substring. + /// + /// **Note**: The body content is case-sensitive. + /// + /// # Parameters + /// - `prefix`: The substring that the HTTP request body must not begin with. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to not begin with the substring "Error:" + /// let mock = server.mock(|when, then| { + /// when.body_prefix_not("Error:"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with a different body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("Success: Operation completed.") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When’ instance to allow method chaining for additional configuration. + pub fn body_prefix_not>(mut self, prefix: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.body_prefix_not.is_none() { + e.body_prefix_not = Some(Vec::new()); + } + e.body_prefix_not + .as_mut() + .unwrap() + .push(HttpMockBytes::from(Bytes::from(prefix.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must not end with the specified substring. + /// This method ensures that the request body does not conclude with the provided content as a substring. + /// + /// **Note**: The body content is case-sensitive. + /// + /// # Parameters + /// - `suffix`: The substring that the HTTP request body must not end with. This parameter accepts any type that can be converted into a `String`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to not end with the substring "a novel." + /// let mock = server.mock(|when, then| { + /// when.body_suffix_not("a novel."); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with a different body content + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("The Great Gatsby is a story.") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When’ instance to allow method chaining for additional configuration. + pub fn body_suffix_not>(mut self, suffix: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.body_suffix_not.is_none() { + e.body_suffix_not = Some(Vec::new()); + } + e.body_suffix_not + .as_mut() + .unwrap() + .push(HttpMockBytes::from(Bytes::from(suffix.into()))); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must match the specified regular expression. + /// This method ensures that the request body fully conforms to the provided regex pattern. + /// + /// **Note**: The regex matching is case-sensitive unless the regex is explicitly defined to be case-insensitive. + /// + /// # Parameters + /// - `pattern`: The regular expression pattern that the HTTP request body must match. This parameter accepts any type that can be converted into a `Regex`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to match the regex pattern "^The Great Gatsby.*" + /// let mock = server.mock(|when, then| { + /// when.body_matches("^The Great Gatsby.*"); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with a body that matches the regex pattern + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .body("The Great Gatsby is a novel by F. Scott Fitzgerald.") + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When’ instance to allow method chaining for additional configuration. + pub fn body_matches>(mut self, pattern: IntoRegex) -> Self { + update_cell(&self.expectations, |e| { + if e.body_matches.is_none() { + e.body_matches = Some(Vec::new()); + } + e.body_matches.as_mut().unwrap().push(pattern.into()); + }); + self + } + // @docs-group: Body + + /// Sets the condition that the HTTP request body content must match the specified JSON structure. + /// This method ensures that the request body exactly matches the JSON value provided. + /// + /// **Note**: The body content is case-sensitive. + /// + /// **Note**: This method does not automatically verify the `Content-Type` header. + /// If specific content type verification is required (e.g., `application/json`), + /// you must add this expectation manually. + /// + /// # Parameters + /// - `json_value`: The JSON structure that the HTTP request body must match. This parameter accepts any type that can be converted into a `serde_json::Value`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// use serde_json::json; + /// + /// // Start a new mock server + /// let server = MockServer::start(); + /// + /// // Create a mock that expects the request body to match a specific JSON structure + /// let mock = server.mock(|when, then| { + /// when.json_body(json!({ + /// "title": "The Great Gatsby", + /// "author": "F. Scott Fitzgerald" + /// })); + /// then.status(200); // Respond with a 200 status code if the condition is met + /// }); + /// + /// // Make a request with a JSON body that matches the expected structure + /// Client::new() + /// .post(&format!("http://{}/test", server.address())) + /// .header("Content-Type", "application/json") // It's important to set the Content-Type header manually + /// .body(r#"{"title":"The Great Gatsby","author":"F. Scott Fitzgerald"}"#) + /// .send() + /// .unwrap(); + /// + /// // Verify that the mock was called at least once + /// mock.assert(); + /// ``` + /// + /// # Returns + /// The updated `When’ instance to allow method chaining for additional configuration. + pub fn json_body>(mut self, json_value: JsonValue) -> Self { + update_cell(&self.expectations, |e| { + e.json_body = Some(json_value.into()); + }); + self + } + // @docs-group: Body + + /// Sets the expected JSON body using a serializable serde object. + /// This function automatically serializes the given object into a JSON string using serde. + /// + /// **Note**: This method does not automatically verify the `Content-Type` header. + /// If specific content type verification is required (e.g., `application/json`), + /// you must add this expectation manually. + /// + /// # Parameters + /// - `body`: The HTTP body object to be serialized to JSON. This object should implement both `serde::Serialize` and `serde::Deserialize`. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// use serde_json::json; + /// use serde::{Serialize, Deserialize}; + /// + /// #[derive(Serialize, Deserialize)] + /// struct TestUser { + /// name: String, + /// } + /// + /// // Initialize logging (optional, for debugging purposes) + /// let _ = env_logger::try_init(); + /// + /// // Start the mock server + /// let server = MockServer::start(); + /// + /// // Set up a mock endpoint + /// let m = server.mock(|when, then| { + /// when.path("/user") + /// .header("content-type", "application/json") + /// .json_body_obj(&TestUser { name: String::from("Fred") }); + /// then.status(200); + /// }); + /// + /// // Send a POST request with a JSON body + /// let response = Client::new() + /// .post(&format!("http://{}/user", server.address())) + /// .header("content-type", "application/json") + /// .body(json!(&TestUser { name: "Fred".to_string() }).to_string()) + /// .send() + /// .unwrap(); + /// + /// // Assert the mock was called and the response status is as expected + /// m.assert(); + /// assert_eq!(response.status(), 200); + /// ``` + /// + /// This method is particularly useful when you need to test server responses to structured JSON data. It helps + /// ensure that the JSON serialization and deserialization processes are correctly implemented in your API handling logic. + pub fn json_body_obj<'a, T>(self, body: &T) -> Self + where + T: Serialize + Deserialize<'a>, + { + let json_value = serde_json::to_value(body).expect("Cannot serialize json body to JSON"); + self.json_body(json_value) + } + // @docs-group: Body + + /// Sets the expected partial JSON body to check for specific content within a larger JSON structure. + /// + /// **Attention:** The partial JSON string must be a valid JSON string and should represent a substructure + /// of the full JSON object. It can omit irrelevant attributes but must maintain any necessary object hierarchy. + /// + /// **Note:** This method does not automatically set the `Content-Type` header to `application/json`. + /// You must explicitly set this header in your requests. + /// + /// # Parameters + /// - `partial_body`: The partial JSON content to check for. This must be a valid JSON string. + /// + /// # Example + /// Suppose your application sends the following JSON request body: + /// ```json + /// { + /// "parent_attribute": "Some parent data goes here", + /// "child": { + /// "target_attribute": "Example", + /// "other_attribute": "Another value" + /// } + /// } + /// ``` + /// To verify the presence of `target_attribute` with the value `Example` without needing the entire JSON object: + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// let server = MockServer::start(); + /// + /// let mock = server.mock(|when, then| { + /// when.json_body_includes(r#" + /// { + /// "child": { + /// "target_attribute": "Example" + /// } + /// } + /// "#); + /// then.status(200); + /// }); + /// + /// // Send a POST request with a JSON body + /// let response = Client::new() + /// .post(&format!("http://{}/some/path", server.address())) + /// .header("content-type", "application/json") + /// .body(r#" + /// { + /// "parent_attribute": "Some parent data goes here", + /// "child": { + /// "target_attribute": "Example", + /// "other_attribute": "Another value" + /// } + /// } + /// "#) + /// .send() + /// .unwrap(); + /// + /// // Assert the mock was called and the response status is as expected + /// mock.assert(); + /// assert_eq!(response.status(), 200); + /// ``` + /// It's important that the partial JSON contains the full object hierarchy necessary to reach the target attribute. + /// Irrelevant attributes such as `parent_attribute` and `child.other_attribute` can be omitted. + pub fn json_body_includes>(mut self, partial: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.json_body_includes.is_none() { + e.json_body_includes = Some(Vec::new()); + } + let value = Value::from_str(&partial.into()) + .expect("cannot convert JSON string to serde value"); + e.json_body_includes.as_mut().unwrap().push(value); + }); + self + } + // @docs-group: Body + + /// Sets the expected partial JSON body to ensure that specific content is not present within a larger JSON structure. + /// + /// **Attention:** The partial JSON string must be a valid JSON string and should represent a substructure + /// of the full JSON object. It can omit irrelevant attributes but must maintain any necessary object hierarchy. + /// + /// **Note:** This method does not automatically set the `Content-Type` header to `application/json`. + /// You must explicitly set this header in your requests. + /// + /// # Parameters + /// - `partial_body`: The partial JSON content to check for exclusion. This must be a valid JSON string. + /// + /// # Example + /// Suppose your application sends the following JSON request body: + /// ```json + /// { + /// "parent_attribute": "Some parent data goes here", + /// "child": { + /// "target_attribute": "Example", + /// "other_attribute": "Another value" + /// } + /// } + /// ``` + /// To verify the absence of `target_attribute` with the value `Example`: + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// let server = MockServer::start(); + /// + /// let mock = server.mock(|when, then| { + /// when.json_body_excludes(r#" + /// { + /// "child": { + /// "target_attribute": "Example" + /// } + /// } + /// "#); + /// then.status(200); + /// }); + /// + /// // Send a POST request with a JSON body + /// let response = Client::new() + /// .post(&format!("http://{}/some/path", server.address())) + /// .header("content-type", "application/json") + /// .body(r#" + /// { + /// "parent_attribute": "Some parent data goes here", + /// "child": { + /// "other_attribute": "Another value" + /// } + /// } + /// "#) + /// .send() + /// .unwrap(); + /// + /// // Assert the mock was called and the response status is as expected + /// mock.assert(); + /// assert_eq!(response.status(), 200); + /// ``` + /// It's important that the partial JSON contains the full object hierarchy necessary to reach the target attribute. + /// Irrelevant attributes such as `parent_attribute` and `child.other_attribute` in the example can be omitted. + pub fn json_body_excludes>(mut self, partial: IntoString) -> Self { + update_cell(&self.expectations, |e| { + if e.json_body_excludes.is_none() { + e.json_body_excludes = Some(Vec::new()); + } + let value = Value::from_str(&partial.into()) + .expect("cannot convert JSON string to serde value"); + e.json_body_excludes.as_mut().unwrap().push(value); + }); + self + } + // @docs-group: Body + + /// Adds a key-value pair to the requirements for an `application/x-www-form-urlencoded` request body. + /// + /// This method sets an expectation for a specific key-value pair to be included in the request body + /// of an `application/x-www-form-urlencoded` POST request. Each key and value are URL-encoded as specified + /// by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key of the key-value pair to set as a requirement. + /// - `value`: The value of the key-value pair to set as a requirement. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple("name", "Peter Griffin") + /// .form_urlencoded_tuple("town", "Quahog"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value pair added to the `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple, ValueString: Into>( + mut self, + key: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple.is_none() { + e.form_urlencoded_tuple = Some(Vec::new()); + } + e.form_urlencoded_tuple + .as_mut() + .unwrap() + .push((key.into(), value.into())); + }); + self + } + // @docs-group: Body + + /// Adds a key-value pair to the negative requirements for an `application/x-www-form-urlencoded` request body. + /// + /// This method sets an expectation for a specific key-value pair to be excluded from the request body + /// of an `application/x-www-form-urlencoded` POST request. Each key and value are URL-encoded as specified + /// by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key of the key-value pair to set as a requirement. + /// - `value`: The value of the key-value pair to set as a requirement. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_not("name", "Peter Griffin"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Lois%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value pair added to the negative `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_not, ValueString: Into>( + mut self, + key: KeyString, + value: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_not.is_none() { + e.form_urlencoded_tuple_not = Some(Vec::new()); + } + e.form_urlencoded_tuple_not + .as_mut() + .unwrap() + .push((key.into(), value.into())); + }); + self + } + // @docs-group: Body + + /// Sets a requirement for the existence of a key in an `application/x-www-form-urlencoded` request body. + /// + /// This method sets an expectation that a specific key must be present in the request body of an + /// `application/x-www-form-urlencoded` POST request, regardless of its value. The key is URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key that must exist in the `application/x-www-form-urlencoded` request body. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_exists("name") + /// .form_urlencoded_tuple_exists("town"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key existence requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_exists>( + mut self, + key: IntoString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_exists.is_none() { + e.form_urlencoded_tuple_exists = Some(Vec::new()); + } + e.form_urlencoded_tuple_exists + .as_mut() + .unwrap() + .push(key.into()); + }); + self + } + // @docs-group: Body + + /// Sets a requirement that a key must be absent in an `application/x-www-form-urlencoded` request body. + /// + /// This method sets an expectation that a specific key must not be present in the request body of an + /// `application/x-www-form-urlencoded` POST request. The key is URL-encoded as specified by the + /// [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key that must be absent in the `application/x-www-form-urlencoded` request body. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_missing("name") + /// .form_urlencoded_tuple_missing("town"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("city=Quahog&occupation=Cartoonist") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key absence requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_missing>( + mut self, + key: IntoString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_missing.is_none() { + e.form_urlencoded_tuple_missing = Some(Vec::new()); + } + e.form_urlencoded_tuple_missing + .as_mut() + .unwrap() + .push(key.into()); + }); + self + } + // @docs-group: Body + + /// Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must contain a specific substring. + /// + /// This method sets an expectation that the value associated with a specific key must contain a specified substring + /// in the request body of an `application/x-www-form-urlencoded` POST request. The key and the substring are URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key in the `application/x-www-form-urlencoded` request body. + /// - `substring`: The substring that must be present in the value associated with the key. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_includes("name", "Griffin") + /// .form_urlencoded_tuple_includes("town", "Quahog"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value substring requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_includes, ValueString: Into>( + mut self, + key: KeyString, + substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_includes.is_none() { + e.form_urlencoded_tuple_includes = Some(Vec::new()); + } + e.form_urlencoded_tuple_includes + .as_mut() + .unwrap() + .push((key.into(), substring.into())); + }); + self + } + // @docs-group: Body + + /// Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must not contain a specific substring. + /// + /// This method sets an expectation that the value associated with a specific key must not contain a specified substring + /// in the request body of an `application/x-www-form-urlencoded` POST request. The key and the substring are URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key in the `application/x-www-form-urlencoded` request body. + /// - `substring`: The substring that must not be present in the value associated with the key. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_excludes("name", "Griffin"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Lois%20Smith&city=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value substring exclusion requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_excludes, ValueString: Into>( + mut self, + key: KeyString, + substring: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_excludes.is_none() { + e.form_urlencoded_tuple_excludes = Some(Vec::new()); + } + e.form_urlencoded_tuple_excludes + .as_mut() + .unwrap() + .push((key.into(), substring.into())); + }); + self + } + // @docs-group: Body + + /// Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must start with a specific prefix. + /// + /// This method sets an expectation that the value associated with a specific key must start with a specified prefix + /// in the request body of an `application/x-www-form-urlencoded` POST request. The key and the prefix are URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key in the `application/x-www-form-urlencoded` request body. + /// - `prefix`: The prefix that must appear at the start of the value associated with the key. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_prefix("name", "Pete") + /// .form_urlencoded_tuple_prefix("town", "Qua"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value prefix requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_prefix, ValueString: Into>( + mut self, + key: KeyString, + prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_prefix.is_none() { + e.form_urlencoded_tuple_prefix = Some(Vec::new()); + } + e.form_urlencoded_tuple_prefix + .as_mut() + .unwrap() + .push((key.into(), prefix.into())); + }); + self + } + // @docs-group: Body + + /// Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must not start with a specific prefix. + /// + /// This method sets an expectation that the value associated with a specific key must not start with a specified prefix + /// in the request body of an `application/x-www-form-urlencoded` POST request. The key and the prefix are URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key in the `application/x-www-form-urlencoded` request body. + /// - `prefix`: The prefix that must not appear at the start of the value associated with the key. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_prefix_not("name", "Lois") + /// .form_urlencoded_tuple_prefix_not("town", "Hog"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value prefix exclusion requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_prefix_not, ValueString: Into>( + mut self, + key: KeyString, + prefix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_prefix_not.is_none() { + e.form_urlencoded_tuple_prefix_not = Some(Vec::new()); + } + e.form_urlencoded_tuple_prefix_not + .as_mut() + .unwrap() + .push((key.into(), prefix.into())); + }); + self + } + // @docs-group: Body + + /// Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must end with a specific suffix. + /// + /// This method sets an expectation that the value associated with a specific key must end with a specified suffix + /// in the request body of an `application/x-www-form-urlencoded` POST request. The key and the suffix are URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key in the `application/x-www-form-urlencoded` request body. + /// - `suffix`: The suffix that must appear at the end of the value associated with the key. + /// + /// # Example + /// ```rust + /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_suffix("name", "Griffin") + /// .form_urlencoded_tuple_suffix("town", "hog"); + /// then.status(202); + /// }); + /// + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); + /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value suffix requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_suffix, ValueString: Into>( + mut self, + key: KeyString, + suffix: ValueString, + ) -> Self { + update_cell(&self.expectations, |e| { + if e.form_urlencoded_tuple_suffix.is_none() { + e.form_urlencoded_tuple_suffix = Some(Vec::new()); + } + e.form_urlencoded_tuple_suffix + .as_mut() + .unwrap() + .push((key.into(), suffix.into())); }); self } + // @docs-group: Body - /// Sets the expected HTTP header. - /// * `name` - The HTTP header name (header names are case-insensitive by RFC 2616). - /// * `value` - The header value. + /// Sets a requirement that a key's value in an `application/x-www-form-urlencoded` request body must not end with a specific suffix. + /// + /// This method sets an expectation that the value associated with a specific key must not end with a specified suffix + /// in the request body of an `application/x-www-form-urlencoded` POST request. The key and the suffix are URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key`: The key in the `application/x-www-form-urlencoded` request body. + /// - `suffix`: The suffix that must not appear at the end of the value associated with the key. /// /// # Example - /// ``` + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; + /// use reqwest::blocking::Client; /// + /// // Arrange /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.header("Authorization", "token 1234567890"); - /// then.status(200); + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_suffix_not("name", "Smith") + /// .form_urlencoded_tuple_suffix_not("town", "ville"); + /// then.status(202); /// }); /// - /// Request::post(&format!("http://{}/test", server.address())) - /// .header("Authorization", "token 1234567890") - /// .body(()) - /// .unwrap() - /// .send() - /// .unwrap(); + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); /// - /// mock.assert(); + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); /// ``` - pub fn header, SV: Into>(mut self, name: SK, value: SV) -> Self { + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value suffix exclusion requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_suffix_not, ValueString: Into>( + mut self, + key: KeyString, + suffix: ValueString, + ) -> Self { update_cell(&self.expectations, |e| { - if e.headers.is_none() { - e.headers = Some(Vec::new()); + if e.form_urlencoded_tuple_suffix_not.is_none() { + e.form_urlencoded_tuple_suffix_not = Some(Vec::new()); } - e.headers + e.form_urlencoded_tuple_suffix_not .as_mut() .unwrap() - .push((name.into(), value.into())); + .push((key.into(), suffix.into())); }); self } + // @docs-group: Body - /// Sets the requirement that the HTTP request needs to contain a specific header - /// (value is unchecked, refer to [Mock::expect_header](struct.Mock.html#method.expect_header)). + /// Sets a requirement that a key-value pair in an `application/x-www-form-urlencoded` request body must match specific regular expressions. + /// + /// This method sets an expectation that the key and the value in a key-value pair must match the specified regular expressions + /// in the request body of an `application/x-www-form-urlencoded` POST request. The key and value regular expressions are URL-encoded + /// as specified by the [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). + /// + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. /// - /// * `name` - The HTTP header name (header names are case-insensitive by RFC 2616). + /// # Parameters + /// - `key_regex`: The regular expression that the key must match in the `application/x-www-form-urlencoded` request body. + /// - `value_regex`: The regular expression that the value must match in the `application/x-www-form-urlencoded` request body. /// /// # Example - /// ``` + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; + /// use reqwest::blocking::Client; + /// use regex::Regex; /// + /// // Arrange /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.header_exists("Authorization"); - /// then.status(200); + /// let key_regex = Regex::new(r"^name$").unwrap(); + /// let value_regex = Regex::new(r"^Peter\sGriffin$").unwrap(); + /// + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_matches(key_regex, value_regex); + /// then.status(202); /// }); /// - /// Request::post(&format!("http://{}/test", server.address())) - /// .header("Authorization", "token 1234567890") - /// .body(()) - /// .unwrap() - /// .send() - /// .unwrap(); + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); /// - /// mock.assert(); + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); /// ``` - pub fn header_exists>(mut self, name: S) -> Self { + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value regex matching requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_matches, ValueRegex: Into>( + mut self, + key_regex: KeyRegex, + value_regex: ValueRegex, + ) -> Self { update_cell(&self.expectations, |e| { - if e.header_exists.is_none() { - e.header_exists = Some(Vec::new()); + if e.form_urlencoded_tuple_matches.is_none() { + e.form_urlencoded_tuple_matches = Some(Vec::new()); } - e.header_exists.as_mut().unwrap().push(name.into()); + e.form_urlencoded_tuple_matches + .as_mut() + .unwrap() + .push((key_regex.into(), value_regex.into())); }); self } + // @docs-group: Body - /// Sets the cookie that needs to exist in the HTTP request. - /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). - /// **Attention**: Cookie names are **case-sensitive**. + /// Sets a requirement for the number of times a key-value pair matching specific regular expressions appears in an `application/x-www-form-urlencoded` request body. /// - /// * `name` - The cookie name. - /// * `value` - The expected cookie value. + /// This method sets an expectation that the key-value pair must appear a specific number of times in the request body of an + /// `application/x-www-form-urlencoded` POST request. The key and value regular expressions are URL-encoded as specified by the + /// [URL Standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded). /// - /// > Note: This function is only available when the `cookies` feature is enabled. - /// > It is enabled by default. + /// **Note**: The mock server does not automatically verify that the HTTP method is POST as per spec. + /// If you want to verify that the request method is POST, you must explicitly set it in your mock configuration. + /// + /// # Parameters + /// - `key_regex`: The regular expression that the key must match in the `application/x-www-form-urlencoded` request body. + /// - `value_regex`: The regular expression that the value must match in the `application/x-www-form-urlencoded` request body. + /// - `count`: The number of times the key-value pair matching the regular expressions must appear. /// /// # Example - /// ``` + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; + /// use reqwest::blocking::Client; + /// use regex::Regex; /// + /// // Arrange /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.cookie("SESSIONID", "1234567890"); - /// then.status(200); + /// let m = server.mock(|when, then| { + /// when.method(POST) + /// .path("/example") + /// .header("content-type", "application/x-www-form-urlencoded") + /// .form_urlencoded_tuple_count( + /// Regex::new(r"^name$").unwrap(), + /// Regex::new(r".*Griffin$").unwrap(), + /// 2 + /// ); + /// then.status(202); /// }); /// - /// Request::post(&format!("http://{}/test", server.address())) - /// .header("Cookie", "TRACK=12345; SESSIONID=1234567890; CONSENT=1") - /// .body(()) - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Act + /// let response = Client::new() + /// .post(server.url("/example")) + /// .header("content-type", "application/x-www-form-urlencoded") + /// .body("name=Peter%20Griffin&name=Lois%20Griffin&town=Quahog") + /// .send() + /// .unwrap(); /// - /// mock.assert(); + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 202); /// ``` - pub fn cookie, SV: Into>(mut self, name: SK, value: SV) -> Self { + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new key-value count requirement added to the + /// `application/x-www-form-urlencoded` expectations. + pub fn form_urlencoded_tuple_count, ValueRegex: Into>( + mut self, + key_regex: KeyRegex, + value_regex: ValueRegex, + count: usize, + ) -> Self { update_cell(&self.expectations, |e| { - if e.cookies.is_none() { - e.cookies = Some(Vec::new()); + if e.form_urlencoded_tuple_count.is_none() { + e.form_urlencoded_tuple_count = Some(Vec::new()); } - e.cookies - .as_mut() - .unwrap() - .push((name.into(), value.into())); + e.form_urlencoded_tuple_count.as_mut().unwrap().push(( + key_regex.into(), + value_regex.into(), + count, + )); }); self } + // @docs-group: Body - /// Sets the cookie that needs to exist in the HTTP request. - /// Cookie parsing follows [RFC-6265](https://tools.ietf.org/html/rfc6265.html). - /// **Attention**: Cookie names are **case-sensitive**. + /// Adds a custom matcher for expected HTTP requests. If this function returns true, the request + /// is considered a match, and the mock server will respond to the request + /// (given all other criteria are also met). /// - /// * `name` - The cookie name + /// You can use this function to create custom expectations for your mock server based on any aspect + /// of the `HttpMockRequest` object. /// - /// > Note: This function is only available when the `cookies` feature is enabled. - /// > It is enabled by default. + /// # Parameters + /// - `matcher`: A function that takes a reference to an `HttpMockRequest` and returns a boolean indicating whether the request matches. /// - /// # Example + /// ## Example + /// ```rust + /// use httpmock::prelude::*; + /// + /// // Arrange + /// let server = MockServer::start(); + /// + /// let m = server.mock(|when, then| { + /// when.matches(|req: &HttpMockRequest| { + /// req.uri().path().contains("es") + /// }); + /// then.status(200); + /// }); + /// + /// // Act: Send the HTTP request + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); + /// + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 200); /// ``` + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new custom matcher added to the expectations. + #[deprecated( + since = "0.8.0", + note = "Please use the `is_true` and `is_false` function instead" + )] + pub fn matches( + mut self, + matcher: impl Fn(&HttpMockRequest) -> bool + Sync + Send + 'static, + ) -> Self { + return self.is_true(matcher); + } + // @docs-group: Custom + + /// Adds a custom matcher for expected HTTP requests. If this function returns true, the request + /// is considered a match, and the mock server will respond to the request + /// (given all other criteria are also met). + /// + /// You can use this function to create custom expectations for your mock server based on any aspect + /// of the `HttpMockRequest` object. + /// + /// # Parameters + /// - `matcher`: A function that takes a reference to an `HttpMockRequest` and returns a boolean indicating whether the request matches. + /// + /// ## Example + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, Request}; /// + /// // Arrange /// let server = MockServer::start(); /// - /// let mock = server.mock(|when, then|{ - /// when.cookie_exists("SESSIONID"); - /// then.status(200); + /// let m = server.mock(|when, then| { + /// when.is_true(|req: &HttpMockRequest| { + /// req.uri().path().contains("es") + /// }); + /// then.status(200); /// }); /// - /// Request::post(&format!("http://{}/test", server.address())) - /// .header("Cookie", "TRACK=12345; SESSIONID=1234567890; CONSENT=1") - /// .body(()) - /// .unwrap() - /// .send() - /// .unwrap(); + /// // Act: Send the HTTP request + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); /// - /// mock.assert(); + /// // Assert + /// m.assert(); + /// assert_eq!(response.status(), 200); /// ``` - pub fn cookie_exists>(mut self, name: S) -> Self { + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new custom matcher added to the expectations. + pub fn is_true( + mut self, + matcher: impl Fn(&HttpMockRequest) -> bool + Sync + Send + 'static, + ) -> Self { update_cell(&self.expectations, |e| { - if e.cookie_exists.is_none() { - e.cookie_exists = Some(Vec::new()); + if e.is_true.is_none() { + e.is_true = Some(Vec::new()); } - e.cookie_exists.as_mut().unwrap().push(name.into()); + e.is_true.as_mut().unwrap().push(Arc::new(matcher)); }); self } - /// Sets a custom matcher for expected HTTP request. If this function returns true, the request - /// is considered a match and the mock server will respond to the request + // @docs-group: Custom + + /// Adds a custom matcher for expected HTTP requests. If this function returns false, the request + /// is considered a match, and the mock server will respond to the request /// (given all other criteria are also met). - /// * `matcher` - The matcher function. /// - /// ## Example: - /// ``` + /// You can use this function to create custom expectations for your mock server based on any aspect + /// of the `HttpMockRequest` object. + /// + /// # Parameters + /// - `matcher`: A function that takes a reference to an `HttpMockRequest` and returns a boolean indicating whether the request matches. + /// + /// ## Example + /// ```rust /// use httpmock::prelude::*; /// /// // Arrange /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ - /// when.matches(|req: &HttpMockRequest| { - /// req.path.contains("es") + /// let m = server.mock(|when, then| { + /// when.is_false(|req: &HttpMockRequest| { + /// req.uri().path().contains("es") /// }); - /// then.status(200); + /// then.status(404); /// }); /// /// // Act: Send the HTTP request - /// let response = isahc::get(server.url("/test")).unwrap(); + /// let response = reqwest::blocking::get(server.url("/test")).unwrap(); /// /// // Assert /// m.assert(); - /// assert_eq!(response.status(), 200); + /// assert_eq!(response.status(), 404); /// ``` - pub fn matches(mut self, matcher: MockMatcherFunction) -> Self { + /// + /// # Returns + /// `When`: Returns the modified `When` object with the new custom matcher added to the expectations. + pub fn is_false( + mut self, + matcher: impl Fn(&HttpMockRequest) -> bool + Sync + Send + 'static, + ) -> Self { update_cell(&self.expectations, |e| { - if e.matchers.is_none() { - e.matchers = Some(Vec::new()); + if e.is_false.is_none() { + e.is_false = Some(Vec::new()); } - e.matchers.as_mut().unwrap().push(matcher); + e.is_false.as_mut().unwrap().push(Arc::new(matcher)); }); self } - /// A semantic convenience for applying multiple operations - /// on a given `When` instance via an discrete encapsulating - /// function. + // @docs-group: Custom + + /// Applies a specified function to enhance or modify the `When` instance. This method allows for the + /// encapsulation of multiple matching conditions into a single function, maintaining a clear and fluent + /// interface for setting up HTTP request expectations. /// - /// ## Example: + /// This method is particularly useful for reusing common setup patterns across multiple test scenarios, + /// promoting cleaner and more maintainable test code. /// - /// ``` - /// # use httpmock::When; - /// // Assuming an encapsulating function like: + /// # Parameters + /// - `func`: A function that takes a `When` instance and returns it after applying some conditions. + /// + /// ## Example + /// ```rust + /// use httpmock::{prelude::*, When}; + /// use httpmock::Method::POST; /// + /// // Function to apply a standard authorization and content type setup for JSON POST requests /// fn is_authorized_json_post_request(when: When) -> When { - /// when.method(httpmock::Method::POST) - /// .header("authorization", "SOME API KEY") - /// .header("content-type", "application/json") + /// when.method(POST) + /// .header("Authorization", "SOME API KEY") + /// .header("Content-Type", "application/json") /// } /// - /// // It can be applied without breaking the usual `When` - /// // semantic style. Meaning instead of: - /// # - /// # fn counter_example(when: When) -> When { - /// is_authorized_json_post_request(when.json_body_partial(r#"{"key": "value"}"#)) - /// # } - /// - /// // the `and` method can be used to preserve the - /// // legibility of the method chain: - /// # fn semantic_example(when: When) -> When { - /// when.query_param("some-param", "some-value") - /// .and(is_authorized_json_post_request) - /// .json_body_partial(r#"{"key": "value"}"#) - /// # } - /// - /// // is still intuitively legible as "when some query - /// // parameter equals "some-value", the request is an - /// // authorized POST request, and the request body - /// // is the literal JSON object `{"key": "value"}`. - /// - pub fn and(mut self, func: AndWhenFunction) -> Self { + /// // Usage example demonstrating how to maintain fluent interface style with complex setups. + /// // This approach keeps the chain of conditions clear and readable, enhancing test legibility + /// let server = MockServer::start(); + /// let m = server.mock(|when, then| { + /// when.query_param("user_id", "12345") + /// .and(is_authorized_json_post_request) // apply the function to include common setup + /// .json_body_includes(r#"{"key": "value"}"#); // additional specific condition + /// then.status(200); + /// }); + /// ``` + /// + /// # Returns + /// `When`: The modified `When` instance with additional conditions applied, suitable for further chaining. + pub fn and(mut self, func: impl FnOnce(When) -> When) -> Self { func(self) } + // @docs-group: Miscellaneous } -/// A type that allows the specification of HTTP response values. +/// Represents the configuration of HTTP responses in a mock server environment. +/// +/// The `Then` structure is used to define the details of the HTTP response that will be sent if +/// an incoming request meets the conditions specified by a corresponding `When` structure. It +/// allows for detailed customization of response aspects such as status codes, headers, body +/// content, and delays. This structure is integral to defining how the mock server behaves when +/// it receives a request that matches the defined expectations. pub struct Then { pub(crate) response_template: Rc>, } impl Then { - /// Sets the HTTP response code that will be returned by the mock server. + /// Configures the HTTP response status code that the mock server will return. /// - /// * `status` - The status code. + /// # Parameters + /// - `status`: A `u16` HTTP status code that the mock server should return for the configured request. /// - /// ## Example: - /// ``` + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Mock` object. + /// + /// # Example + /// Demonstrates setting a 200 OK status for a request to the path `/hello`. + /// + /// ```rust /// use httpmock::prelude::*; /// - /// // Arrange + /// // Initialize the mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ + /// // Configure the mock + /// let m = server.mock(|when, then| { /// when.path("/hello"); /// then.status(200); /// }); /// - /// // Act - /// let response = isahc::get(server.url("/hello")).unwrap(); + /// // Send a request and verify the response + /// let response = reqwest::blocking::get(server.url("/hello")).unwrap(); /// - /// // Assert + /// // Check that the mock was called as expected and the response status is as configured /// m.assert(); /// assert_eq!(response.status(), 200); /// ``` - pub fn status(mut self, status: u16) -> Self { + pub fn status>(mut self, status: U16) -> Self + where + >::Error: std::fmt::Debug, + { update_cell(&self.response_template, |r| { - r.status = Some(status); + r.status = Some( + status + .try_into() + .expect("cannot parse status code to usize"), + ); }); self } + // @docs-group: Status - /// Sets the HTTP response body that will be returned by the mock server. + /// Configures the HTTP response body that the mock server will return. /// - /// * `body` - The response body content. + /// # Parameters + /// - `body`: The content of the response body, provided as a type that can be referenced as a byte slice. /// - /// ## Example: - /// ``` + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Mock` object. + /// + /// # Example + /// Demonstrates setting a response body for a request to the path `/hello` with a 200 OK status. + /// + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, ResponseExt}; + /// use reqwest::blocking::Client; /// - /// // Arrange + /// // Initialize the mock server /// let server = MockServer::start(); /// + /// // Configure the mock /// let m = server.mock(|when, then| { /// when.path("/hello"); /// then.status(200) /// .body("ohi!"); /// }); /// - /// // Act - /// let mut response = isahc::get(server.url("/hello")).unwrap(); + /// // Send a request and verify the response + /// let response = Client::new() + /// .get(server.url("/hello")) + /// .send() + /// .unwrap(); /// - /// // Assert + /// // Check that the mock was called as expected and the response body is as configured /// m.assert(); /// assert_eq!(response.status(), 200); /// assert_eq!(response.text().unwrap(), "ohi!"); /// ``` - pub fn body(mut self, body: impl AsRef<[u8]>) -> Self { + pub fn body>(mut self, body: SliceRef) -> Self { update_cell(&self.response_template, |r| { - r.body = Some(body.as_ref().to_vec()); + r.body = Some(HttpMockBytes::from(Bytes::copy_from_slice(body.as_ref()))); }); self } + // @docs-group: Body - /// Sets the HTTP response body that will be returned by the mock server. + /// Configures the HTTP response body with content loaded from a specified file on the mock server. /// - /// * `body` - The response body content. + /// # Parameters + /// - `resource_file_path`: A string representing the path to the file whose contents will be used as the response body. The path can be absolute or relative to the server's running directory. /// - /// ## Example: - /// ``` + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Mock` object. + /// + /// # Panics + /// Panics if the specified file cannot be read, or if the path provided cannot be resolved to an absolute path. + /// + /// # Example + /// Demonstrates setting the response body from a file for a request to the path `/hello` with a 200 OK status. + /// + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, ResponseExt}; + /// use reqwest::blocking::Client; /// - /// // Arrange + /// // Initialize the mock server /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ + /// // Configure the mock + /// let m = server.mock(|when, then| { /// when.path("/hello"); /// then.status(200) /// .body_from_file("tests/resources/simple_body.txt"); /// }); /// - /// // Act - /// let mut response = isahc::get(server.url("/hello")).unwrap(); + /// // Send a request and verify the response + /// let response = Client::new() + /// .get(server.url("/hello")) + /// .send() + /// .unwrap(); /// - /// // Assert + /// // Check that the mock was called as expected and the response body matches the file contents /// m.assert(); /// assert_eq!(response.status(), 200); /// assert_eq!(response.text().unwrap(), "ohi!"); /// ``` - pub fn body_from_file>(mut self, resource_file_path: S) -> Self { + pub fn body_from_file>( + mut self, + resource_file_path: IntoString, + ) -> Self { let resource_file_path = resource_file_path.into(); let path = Path::new(&resource_file_path); let absolute_path = match path.is_absolute() { @@ -940,30 +5068,34 @@ impl Then { )); self.body(content) } + // @docs-group: Body /// Sets the JSON body for the HTTP response that will be returned by the mock server. /// - /// The provided JSON object needs to be both, a deserializable and serializable serde object. + /// This function accepts a JSON object that must be serializable and deserializable by serde. + /// Note that this method does not automatically set the "Content-Type" header to "application/json". + /// You will need to set this header manually if required. /// - /// Note that this method does not set the "content-type" header automatically, so you need - /// to provide one yourself! + /// # Parameters + /// - `body`: The HTTP response body in the form of a `serde_json::Value` object. /// - /// * `body` - The HTTP response body the mock server will return in the form of a - /// serde_json::Value object. + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Mock` object. /// - /// ## Example - /// You can use this method conveniently as follows: - /// ``` + /// # Example + /// Demonstrates how to set a JSON body and a matching "Content-Type" header for a mock response. + /// + /// ```rust /// use httpmock::prelude::*; /// use serde_json::{Value, json}; - /// use isahc::ResponseExt; - /// use isahc::prelude::*; + /// use reqwest::blocking::Client; /// /// // Arrange /// let _ = env_logger::try_init(); /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ + /// // Configure the mock + /// let m = server.mock(|when, then| { /// when.path("/user"); /// then.status(200) /// .header("content-type", "application/json") @@ -971,38 +5103,58 @@ impl Then { /// }); /// /// // Act - /// let mut response = isahc::get(server.url("/user")).unwrap(); + /// let response = Client::new() + /// .get(server.url("/user")) + /// .send() + /// .unwrap(); + /// + /// // Get the status code first + /// let status = response.status(); + /// + /// // Extract the text from the response + /// let response_text = response.text().unwrap(); /// + /// // Deserialize the JSON response /// let user: Value = - /// serde_json::from_str(&response.text().unwrap()).expect("cannot deserialize JSON"); + /// serde_json::from_str(&response_text).expect("cannot deserialize JSON"); /// /// // Assert /// m.assert(); - /// assert_eq!(response.status(), 200); - /// assert_eq!(user.as_object().unwrap().get("name").unwrap(), "Hans"); + /// assert_eq!(status, 200); + /// assert_eq!(user["name"], "Hans"); /// ``` pub fn json_body>(mut self, body: V) -> Self { update_cell(&self.response_template, |r| { - r.body = Some(body.into().to_string().into_bytes()); + r.body = Some(HttpMockBytes::from(Bytes::from(body.into().to_string()))); }); self } + // @docs-group: Body - /// Sets the JSON body that will be returned by the mock server. - /// This method expects a serializable serde object that will be serialized/deserialized - /// to/from a JSON string. + /// Sets the JSON body that will be returned by the mock server using a serializable serde object. /// - /// Note that this method does not set the "content-type" header automatically, so you - /// need to provide one yourself! + /// This method converts the provided object into a JSON string. It does not automatically set + /// the "Content-Type" header to "application/json", so you must set this header manually if it's + /// needed. /// - /// * `body` - The HTTP body object that will be serialized to JSON using serde. + /// # Parameters + /// - `body`: A reference to an object that implements the `serde::Serialize` trait. /// - /// ``` + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Mock` object. + /// + /// # Panics + /// Panics if the object cannot be serialized into a JSON string. + /// + /// # Example + /// Demonstrates setting a JSON body and the corresponding "Content-Type" header for a user object. + /// + /// ```rust /// use httpmock::prelude::*; - /// use isahc::{prelude::*, ResponseExt}; + /// use reqwest::blocking::Client; + /// use serde::{Serialize, Deserialize}; /// - /// // This is a temporary type that we will use for this example - /// #[derive(serde::Serialize, serde::Deserialize)] + /// #[derive(Serialize, Deserialize)] /// struct TestUser { /// name: String, /// } @@ -1011,6 +5163,7 @@ impl Then { /// let _ = env_logger::try_init(); /// let server = MockServer::start(); /// + /// // Configure the mock /// let m = server.mock(|when, then| { /// when.path("/user"); /// then.status(200) @@ -1021,55 +5174,81 @@ impl Then { /// }); /// /// // Act - /// let mut response = isahc::get(server.url("/user")).unwrap(); + /// let response = Client::new() + /// .get(server.url("/user")) + /// .send() + /// .unwrap(); /// + /// // Get the status code first + /// let status = response.status(); + /// + /// // Extract the text from the response + /// let response_text = response.text().unwrap(); + /// + /// // Deserialize the JSON response into a TestUser object /// let user: TestUser = - /// serde_json::from_str(&response.text().unwrap()).unwrap(); + /// serde_json::from_str(&response_text).unwrap(); /// /// // Assert /// m.assert(); - /// assert_eq!(response.status(), 200); + /// assert_eq!(status, 200); /// assert_eq!(user.name, "Hans"); /// ``` - pub fn json_body_obj(self, body: &T) -> Self - where - T: Serialize, - { + pub fn json_body_obj(self, body: &T) -> Self { let json_body = - serde_json::to_value(body).expect("cannot serialize json body to JSON string "); + serde_json::to_value(body).expect("Failed to serialize object to JSON string"); self.json_body(json_body) } + // @docs-group: Body - /// Sets an HTTP header that the mock server will return. + /// Sets an HTTP header that the mock server will return in the response. /// - /// * `name` - The name of the header. - /// * `value` - The value of the header. + /// This method configures a response header to be included when the mock server handles a request. /// - /// ## Example - /// You can use this method conveniently as follows: - /// ``` - /// // Arrange + /// # Parameters + /// - `name`: The name of the header to set. + /// - `value`: The value of the header. + /// + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Mock` object. + /// + /// # Example + /// Demonstrates setting the "Expires" header for a response to a request to the root path. + /// + /// ```rust /// use httpmock::prelude::*; - /// use serde_json::Value; - /// use isahc::ResponseExt; + /// use reqwest::blocking::Client; /// + /// // Arrange /// let _ = env_logger::try_init(); /// let server = MockServer::start(); /// - /// let m = server.mock(|when, then|{ + /// // Configure the mock + /// let m = server.mock(|when, then| { /// when.path("/"); /// then.status(200) /// .header("Expires", "Wed, 21 Oct 2050 07:28:00 GMT"); /// }); /// /// // Act - /// let mut response = isahc::get(server.url("/")).unwrap(); + /// let response = Client::new() + /// .get(server.url("/")) + /// .send() + /// .unwrap(); /// /// // Assert /// m.assert(); /// assert_eq!(response.status(), 200); + /// assert_eq!( + /// response.headers().get("Expires").unwrap().to_str().unwrap(), + /// "Wed, 21 Oct 2050 07:28:00 GMT" + /// ); /// ``` - pub fn header, SV: Into>(mut self, name: SK, value: SV) -> Self { + pub fn header, ValueString: Into>( + mut self, + name: KeyString, + value: ValueString, + ) -> Self { update_cell(&self.response_template, |r| { if r.headers.is_none() { r.headers = Some(Vec::new()); @@ -1081,21 +5260,38 @@ impl Then { }); self } + // @docs-group: Headers - /// Sets a duration that will delay the mock server response. + /// Sets a delay for the mock server response. /// - /// * `duration` - The delay. + /// This method configures the server to wait for a specified duration before sending a response, + /// which can be useful for testing timeout scenarios or asynchronous operations. /// - /// ``` - /// // Arrange + /// # Parameters + /// - `duration`: The length of the delay as a `std::time::Duration`. + /// + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Mock` object. + /// + /// # Panics + /// Panics if the specified duration results in a delay that cannot be represented as a 64-bit + /// unsigned integer of milliseconds (more than approximately 584 million years). + /// + /// # Example + /// Demonstrates setting a 3-second delay for a request to the path `/delay`. + /// + /// ```rust /// use std::time::{SystemTime, Duration}; /// use httpmock::prelude::*; + /// use reqwest::blocking::Client; /// + /// // Arrange /// let _ = env_logger::try_init(); /// let start_time = SystemTime::now(); /// let three_seconds = Duration::from_secs(3); /// let server = MockServer::start(); /// + /// // Configure the mock /// let mock = server.mock(|when, then| { /// when.path("/delay"); /// then.status(200) @@ -1103,55 +5299,72 @@ impl Then { /// }); /// /// // Act - /// let response = isahc::get(server.url("/delay")).unwrap(); + /// let response = Client::new() + /// .get(server.url("/delay")) + /// .send() + /// .unwrap(); /// /// // Assert /// mock.assert(); - /// assert_eq!(start_time.elapsed().unwrap() > three_seconds, true); + /// assert!(start_time.elapsed().unwrap() >= three_seconds); /// ``` pub fn delay>(mut self, duration: D) -> Self { + let duration = duration.into(); + + // Ensure the delay duration does not exceed the maximum u64 milliseconds limit + let millis = duration.as_millis(); + let max = u64::MAX as u128; + if millis >= max { + panic!("A delay higher than {} milliseconds is not supported.", max) + } + update_cell(&self.response_template, |r| { - r.delay = Some(duration.into()); + r.delay = Some(duration.as_millis() as u64); }); self } - /// A semantic convenience for applying multiple operations - /// on a given `Then` instance via an discrete encapsulating - /// function. + // @docs-group: Network + + /// Applies a custom function to modify a `Then` instance, enhancing flexibility and readability + /// in setting up mock server responses. /// - /// ## Example: + /// This method allows you to encapsulate complex configurations into reusable functions, + /// and apply them without breaking the chain of method calls on a `Then` object. /// - /// ``` - /// # use std::time::Duration; - /// use hyper::http;use httpmock::Then; - /// // Assuming an encapsulating function like: + /// # Parameters + /// - `func`: A function that takes a `Then` instance and returns it after applying some modifications. /// + /// # Returns + /// Returns `self` to allow chaining of method calls on the `Then` object. + /// + /// # Example + /// Demonstrates how to use the `and` method to maintain readability while applying multiple + /// modifications from an external function. + /// + /// ```rust + /// use std::time::Duration; + /// use http::{StatusCode, header::HeaderValue}; + /// use httpmock::{Then, MockServer}; + /// + /// // Function that configures a response with JSON content and a delay /// fn ok_json_with_delay(then: Then) -> Then { - /// then.status(http::StatusCode::OK.as_u16()) + /// then.status(StatusCode::OK.as_u16()) /// .header("content-type", "application/json") /// .delay(Duration::from_secs_f32(0.5)) /// } /// - /// // It can be applied without breaking the usual `Then` - /// // semantic style. Meaning instead of: - /// # - /// # fn counter_example(then: Then) -> Then { - /// ok_json_with_delay(then.header("general-vibe", "not great my guy")) - /// # } - /// - /// // the `and` method can be used to preserve the - /// // legibility of the method chain: - /// # fn semantic_example(then: Then) -> Then { - /// then.header("general-vibe", "much better") - /// .and(ok_json_with_delay) - /// # } - /// - /// // is still intuitively legible as "{when some criteria}, - /// // then set the 'general-vibe' header to 'much better' - /// // *and* the status code to 200 (ok), the 'content-type' - /// // header to 'application/json' and return it with a delay - /// // of 0.50 seconds". - pub fn and(mut self, func: AndThenFunction) -> Self { + /// // Usage within a method chain + /// let server = MockServer::start(); + /// let then = server.mock(|when, then| { + /// when.path("/example"); + /// then.header("general-vibe", "much better") + /// .and(ok_json_with_delay); + /// }); + /// + /// // The `and` method keeps the setup intuitively readable as a continuous chain + /// ``` + pub fn and(mut self, func: impl FnOnce(Then) -> Then) -> Self { func(self) } + // @docs-group: Miscellaneous } diff --git a/src/common/data.rs b/src/common/data.rs index 37b7469e..0e34b0ff 100644 --- a/src/common/data.rs +++ b/src/common/data.rs @@ -1,52 +1,379 @@ extern crate serde_regex; -use std::cmp::Ordering; -use std::collections::BTreeMap; -use std::fmt; -use std::fmt::Debug; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering::Relaxed; -use std::sync::{Arc, RwLock}; -use std::time::Duration; - -use regex::Regex; +use crate::{ + common::{ + data::Error::{ + HeaderDeserializationError, RequestConversionError, StaticMockConversionError, + }, + util::HttpMockBytes, + }, + server::matchers::generic::MatchingStrategy, +}; +use bytes::Bytes; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::{ + cmp::Ordering, + collections::HashMap, + convert::{TryFrom, TryInto}, + fmt, + fmt::Debug, + str::FromStr, + sync::Arc, +}; +use url::Url; + +use crate::server::RequestMetadata; +#[cfg(feature = "cookies")] +use headers::{Cookie, HeaderMapExt}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Cannot deserialize header: {0}")] + HeaderDeserializationError(String), + #[error("Cookie parser error: {0}")] + CookieParserError(String), + #[error("cannot convert to/from static mock: {0}")] + StaticMockConversionError(String), + #[error("JSONConversionError: {0}")] + JSONConversionError(#[from] serde_json::Error), + #[error("Invalid request data: {0}")] + InvalidRequestData(String), + #[error("Cannot convert request to/from internal structure: {0}")] + RequestConversionError(String), +} /// A general abstraction of an HTTP request of `httpmock`. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct HttpMockRequest { - pub path: String, - pub method: String, - pub headers: Option>, - pub query_params: Option>, - pub body: Option>, + scheme: String, + uri: String, + method: String, + headers: Vec<(String, String)>, + version: String, + body: HttpMockBytes, } impl HttpMockRequest { - pub fn new(method: String, path: String) -> Self { + pub(crate) fn new( + scheme: String, + uri: String, + method: String, + headers: Vec<(String, String)>, + version: String, + body: HttpMockBytes, + ) -> Self { + // TODO: Many fields from the struct are exposed as structures from http package to the user. + // These values here are also converted to these http crate structures every call. + // ==> Convert these values here into http crate structures and allow returning an error + // here instead of "unwrap" all the time later (see functions below). + // Convert into http crate structures once here and store the converted + // values in the struct instance here rather than only String values everywhere. + // This will require to make the HttpMockRequest serde compatible + // (http types are not serializable by default). Self { - path, + scheme, + uri, method, - headers: None, - query_params: None, - body: None, + headers, + version, + body, + } + } + + /// Parses and returns the URI of the request. + /// + /// # Attention + /// + /// - This method returns the full URI of the request as an `http::Uri` object. + /// - The URI returned by this method does not include the `Host` part. In HTTP/1.1, + /// the request line typically contains only the path and query, not the full URL with the host. + /// - To retrieve the host, you should use the `HttpMockRequest::host` method which extracts the `Host` + /// header (for HTTP/1.1) or the `:authority` pseudo-header (for HTTP/2 and HTTP/3). + /// + /// # Returns + /// + /// An `http::Uri` object representing the full URI of the request. + pub fn uri(&self) -> http::Uri { + self.uri.parse().unwrap() + } + + /// Parses the scheme from the request. + /// + /// This function extracts the scheme (protocol) used in the request. If the request contains a relative path, + /// the scheme will be inferred based on how the server received the request. For instance, if the request was + /// sent to the server using HTTPS, the scheme will be set to "https"; otherwise, it will be set to "http". + /// + /// # Returns + /// + /// A `String` representing the scheme of the request, either "https" or "http". + pub fn scheme(&self) -> String { + let uri = self.uri(); + if let Some(scheme) = uri.scheme() { + return scheme.to_string(); + } + + self.scheme.clone() + } + + /// Returns the URI of the request as a string slice. + /// + /// # Attention + /// + /// - This method returns the full URI as a string slice. + /// - The URI string returned by this method does not include the `Host` part. In HTTP/1.1, + /// the request line typically contains only the path and query, not the full URL with the host. + /// - To retrieve the host, you should use the `host` method which extracts the `Host` + /// header (for HTTP/1.1) or the `:authority` pseudo-header (for HTTP/2 and HTTP/3). + /// + /// # Returns + /// + /// A string slice representing the full URI of the request. + pub fn uri_str(&self) -> &str { + self.uri.as_ref() + } + + /// Returns the host that the request was sent to, based on the `Host` header or `:authority` pseudo-header. + /// + /// # Attention + /// + /// - This method retrieves the host from the `Host` header of the HTTP request for HTTP/1.1 requests. + /// For HTTP/2 and HTTP/3 requests, it retrieves the host from the `:authority` pseudo-header. + /// - If you use the `HttpMockRequest::uri` method to get the full URI, note that + /// the URI might not include the host part. In HTTP/1.1, the request line + /// typically contains only the path and query, not the full URL. + /// + /// # Returns + /// + /// An `Option` containing the host if the `Host` header or `:authority` pseudo-header is present, or + /// `None` if neither is found. + pub fn host(&self) -> Option { + // Check the Host header first (HTTP 1.1) + if let Some((_, host)) = self + .headers + .iter() + .find(|&&(ref k, _)| k.eq_ignore_ascii_case("host")) + { + return Some(host.split(':').next().unwrap().to_string()); + } + + // If Host header is not found, check the URI authority (HTTP/2 and HTTP/3) + let uri = self.uri(); + if let Some(authority) = uri.authority() { + return Some(authority.as_str().split(':').next().unwrap().to_string()); + } + + None + } + + /// Returns the port that the request was sent to, based on the `Host` header or `:authority` pseudo-header. + /// + /// # Attention + /// + /// 1. This method retrieves the port from the `Host` header of the HTTP request for HTTP/1.1 requests. + /// For HTTP/2 and HTTP/3 requests, it retrieves the port from the `:authority` pseudo-header. + /// This method attempts to parse the port as a `u16`. If the port cannot be parsed as a `u16`, this method will continue as if the port was not specified (see point 2). + /// 2. If the port is not specified in the `Host` header or `:authority` pseudo-header, this method will return 443 (https) or 80 (http) based on the used scheme. + /// + /// # Returns + /// + /// An `u16` containing the port if the `Host` header or `:authority` pseudo-header is present and includes a valid port, + /// or 443 (https) or 80 (http) based on the used scheme otherwise. + pub fn port(&self) -> u16 { + // Check the Host header first (HTTP 1.1) + if let Some((_, host)) = self + .headers + .iter() + .find(|&&(ref k, _)| k.eq_ignore_ascii_case("host")) + { + if let Some(port_str) = host.split(':').nth(1) { + if let Ok(port) = port_str.parse::() { + return port; + } + } + } + + // If Host header is not found, check the URI authority (HTTP/2 and HTTP/3) + let uri = self.uri(); + if let Some(authority) = uri.authority() { + if let Some(port_str) = authority.as_str().split(':').nth(1) { + if let Ok(port) = port_str.parse::() { + return port; + } + } + } + + if self.scheme().eq("https") { + return 443; + } + + return 80; + } + + pub fn method(&self) -> http::Method { + http::Method::from_bytes(self.method.as_bytes()).unwrap() + } + + pub fn method_str(&self) -> &str { + self.method.as_ref() + } + + pub fn headers(&self) -> http::HeaderMap { + let mut header_map: http::HeaderMap = http::HeaderMap::new(); + for (key, value) in &self.headers { + let header_name = http::HeaderName::from_bytes(key.as_bytes()).unwrap(); + let header_value = http::HeaderValue::from_str(&value).unwrap(); + + header_map.insert(header_name, header_value); + } + + header_map + } + + pub fn headers_vec(&self) -> &Vec<(String, String)> { + self.headers.as_ref() + } + + pub fn query_params(&self) -> HashMap { + self.query_params_vec().into_iter().collect() + } + + pub fn query_params_vec(&self) -> Vec<(String, String)> { + // There doesn't seem to be a way to just parse Query string with `url` crate, so we're + // prefixing a dummy URL for parsing. + let url = format!("http://dummy?{}", self.uri().query().unwrap_or("")); + let url = Url::parse(&url).unwrap(); + + url.query_pairs() + .map(|(k, v)| (k.into_owned(), v.into_owned())) + .collect() + } + + pub fn body(&self) -> &HttpMockBytes { + &self.body + } + + pub fn body_string(&self) -> String { + self.body.to_string() + } + + pub fn body_ref<'a>(&'a self) -> &'a [u8] { + self.body.as_ref() + } + + // Move all body functions to HttpMockBytes + pub fn body_vec(&self) -> Vec { + self.body.to_vec() + } + + pub fn body_bytes(&self) -> bytes::Bytes { + self.body.to_bytes() + } + + pub fn version(&self) -> http::Version { + match self.version.as_ref() { + "HTTP/0.9" => http::Version::HTTP_09, + "HTTP/1.0" => http::Version::HTTP_10, + "HTTP/1.1" => http::Version::HTTP_11, + "HTTP/2.0" => http::Version::HTTP_2, + "HTTP/3.0" => http::Version::HTTP_3, + // Attention: This scenario is highly unlikely, so we panic here for the users + // convenience (user does not need to deal with errors for this reason alone). + _ => panic!("unknown HTTP version: {:?}", self.version), } } - pub fn with_headers(mut self, arg: Vec<(String, String)>) -> Self { - self.headers = Some(arg); - self + pub fn version_ref(&self) -> &str { + self.version.as_ref() } - pub fn with_query_params(mut self, arg: Vec<(String, String)>) -> Self { - self.query_params = Some(arg); - self + #[cfg(feature = "cookies")] + pub(crate) fn cookies(&self) -> Result, Error> { + let mut result = Vec::new(); + + if let Some(cookie) = self.headers().typed_get::() { + for (key, value) in cookie.iter() { + result.push((key.to_string(), value.to_string())); + } + } + + Ok(result) + } + + pub fn to_http_request(&self) -> http::Request { + self.try_into().unwrap() + } +} + +fn headers_to_vec(parts: &http::request::Parts) -> Vec<(String, String)> { + parts + .headers + .iter() + .map(|(name, value)| { + ( + name.as_str().to_string(), + value.to_str().unwrap().to_string(), + ) + }) + .collect() +} + +fn http_headers_to_vec(req: &http::Request) -> Result, Error> { + req.headers() + .iter() + .map(|(name, value)| { + // Attempt to convert the HeaderValue to a &str, returning an error if it fails. + let value_str = value + .to_str() + .map_err(|e| RequestConversionError(e.to_string()))?; + Ok((name.as_str().to_string(), value_str.to_string())) + }) + .collect() +} + +impl TryInto> for &HttpMockRequest { + type Error = Error; + + fn try_into(self) -> Result, Self::Error> { + let mut builder = http::Request::builder() + .method(self.method()) + .uri(self.uri()) + .version(self.version()); + + for (k, v) in self.headers() { + builder = builder.header(k.map_or(String::new(), |v| v.to_string()), v) + } + + let req = builder + .body(self.body().to_bytes()) + .map_err(|err| RequestConversionError(err.to_string()))?; + + Ok(req) } +} + +impl TryFrom<&http::Request> for HttpMockRequest { + type Error = Error; - pub fn with_body(mut self, arg: Vec) -> Self { - self.body = Some(arg); - self + fn try_from(value: &http::Request) -> Result { + let metadata = value + .extensions() + .get::() + .unwrap_or_else(|| panic!("request metadata was not added to the request")); + + let headers = http_headers_to_vec(&value)?; + + // Since Bytes shares data, clone does not copy the body. + let body = HttpMockBytes::from(value.body().clone()); + + Ok(HttpMockRequest::new( + metadata.scheme.to_string(), + value.uri().to_string(), + value.method().to_string(), + headers, + format!("{:?}", value.version()), + body, + )) } } @@ -56,8 +383,8 @@ pub struct MockServerHttpResponse { pub status: Option, pub headers: Option>, #[serde(default, with = "opt_vector_serde_base64")] - pub body: Option>, - pub delay: Option, + pub body: Option, + pub delay: Option, } impl MockServerHttpResponse { @@ -77,8 +404,41 @@ impl Default for MockServerHttpResponse { } } +impl TryFrom<&http::Response> for MockServerHttpResponse { + type Error = Error; + + fn try_from(value: &http::Response) -> Result { + let mut headers = Vec::with_capacity(value.headers().len()); + + for (key, value) in value.headers() { + let value = value + .to_str() + .map_err(|err| HeaderDeserializationError(err.to_string()))?; + + headers.push((key.as_str().to_string(), value.to_string())) + } + + Ok(Self { + status: Some(value.status().as_u16()), + headers: if !headers.is_empty() { + Some(headers) + } else { + None + }, + body: if !value.body().is_empty() { + Some(HttpMockBytes::from(value.body().clone())) + } else { + None + }, + delay: None, + }) + } +} + /// Serializes and deserializes the response body to/from a Base64 string. mod opt_vector_serde_base64 { + use crate::common::util::HttpMockBytes; + use bytes::Bytes; use serde::{Deserialize, Deserializer, Serializer}; // See the following references: @@ -97,23 +457,24 @@ mod opt_vector_serde_base64 { // See the following references: // https://github.com/serde-rs/serde/issues/1444 - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] - struct Wrapper(#[serde(deserialize_with = "from_base64")] Vec); + struct Wrapper(#[serde(deserialize_with = "from_base64")] HttpMockBytes); let v = Option::deserialize(deserializer)?; Ok(v.map(|Wrapper(a)| a)) } - fn from_base64<'de, D>(deserializer: D) -> Result, D::Error> + fn from_base64<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { - let vec = Vec::deserialize(deserializer)?; - base64::decode(vec).map_err(serde::de::Error::custom) + let value = Vec::deserialize(deserializer)?; + let decoded = base64::decode(value).map_err(serde::de::Error::custom)?; + Ok(HttpMockBytes::from(Bytes::from(decoded))) } } @@ -137,62 +498,145 @@ impl fmt::Debug for MockServerHttpResponse { /// A general abstraction of an HTTP request for all handlers. #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Pattern { - #[serde(with = "serde_regex")] - pub regex: Regex, -} - -impl Pattern { - pub fn from_regex(regex: Regex) -> Pattern { - Pattern { regex } - } -} +pub struct HttpMockRegex(#[serde(with = "serde_regex")] pub regex::Regex); -impl Ord for Pattern { +impl Ord for HttpMockRegex { fn cmp(&self, other: &Self) -> Ordering { - self.regex.as_str().cmp(other.regex.as_str()) + self.0.as_str().cmp(other.0.as_str()) } } -impl PartialOrd for Pattern { +impl PartialOrd for HttpMockRegex { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl PartialEq for Pattern { +impl PartialEq for HttpMockRegex { fn eq(&self, other: &Self) -> bool { - self.regex.as_str() == other.regex.as_str() + self.0.as_str() == other.0.as_str() + } +} + +impl Eq for HttpMockRegex {} + +impl From for HttpMockRegex { + fn from(value: regex::Regex) -> Self { + HttpMockRegex(value) + } +} + +impl From<&str> for HttpMockRegex { + fn from(value: &str) -> Self { + let re = regex::Regex::from_str(value).expect("cannot parse value as regex"); + HttpMockRegex::from(re) } } -impl Eq for Pattern {} +impl From for HttpMockRegex { + fn from(value: String) -> Self { + HttpMockRegex::from(value.as_str()) + } +} -pub type MockMatcherFunction = fn(&HttpMockRequest) -> bool; +impl fmt::Display for HttpMockRegex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} /// A general abstraction of an HTTP request for all handlers. #[derive(Serialize, Deserialize, Clone)] pub struct RequestRequirements { - pub path: Option, - pub path_contains: Option>, - pub path_matches: Option>, + pub scheme: Option, + pub scheme_not: Option, // NEW + pub host: Option, + pub host_not: Option>, // NEW + pub host_contains: Option>, // NEW + pub host_excludes: Option>, // NEW + pub host_prefix: Option>, // NEW + pub host_suffix: Option>, // NEW + pub host_prefix_not: Option>, // NEW + pub host_suffix_not: Option>, // NEW + pub host_matches: Option>, + pub port: Option, + pub port_not: Option>, // NEW pub method: Option, - pub headers: Option>, + pub method_not: Option>, // NEW + pub path: Option, + pub path_not: Option>, // NEW + pub path_includes: Option>, // NEW + pub path_excludes: Option>, // NEW + pub path_prefix: Option>, // NEW + pub path_suffix: Option>, // NEW + pub path_prefix_not: Option>, // NEW + pub path_suffix_not: Option>, // NEW + pub path_matches: Option>, + pub query_param: Option>, + pub query_param_not: Option>, // NEW + pub query_param_exists: Option>, + pub query_param_missing: Option>, // NEW + pub query_param_includes: Option>, // NEW + pub query_param_excludes: Option>, // NEW + pub query_param_prefix: Option>, // NEW + pub query_param_suffix: Option>, // NEW + pub query_param_prefix_not: Option>, // NEW + pub query_param_suffix_not: Option>, // NEW + pub query_param_matches: Option>, // NEW + pub query_param_count: Option>, // NEW + pub header: Option>, // CHANGED from headers to header + pub header_not: Option>, // NEW pub header_exists: Option>, - pub cookies: Option>, + pub header_missing: Option>, // NEW + pub header_includes: Option>, // NEW + pub header_excludes: Option>, // NEW + pub header_prefix: Option>, // NEW + pub header_suffix: Option>, // NEW + pub header_prefix_not: Option>, // NEW + pub header_suffix_not: Option>, // NEW + pub header_matches: Option>, // NEW + pub header_count: Option>, // NEW + pub cookie: Option>, // CHANGED from cookies to cookie + pub cookie_not: Option>, // NEW pub cookie_exists: Option>, - pub body: Option, + pub cookie_missing: Option>, // NEW + pub cookie_includes: Option>, // NEW + pub cookie_excludes: Option>, // NEW + pub cookie_prefix: Option>, // NEW + pub cookie_suffix: Option>, // NEW + pub cookie_prefix_not: Option>, // NEW + pub cookie_suffix_not: Option>, // NEW + pub cookie_matches: Option>, // NEW + pub cookie_count: Option>, // NEW // NEW + pub body: Option, + pub body_not: Option>, // NEW + pub body_includes: Option>, // CHANG + pub body_excludes: Option>, // NEW + pub body_prefix: Option>, // NEW + pub body_suffix: Option>, // NEW + pub body_prefix_not: Option>, // + pub body_suffix_not: Option>, // + pub body_matches: Option>, // NEW pub json_body: Option, + pub json_body_not: Option, // NEW pub json_body_includes: Option>, - pub body_contains: Option>, - pub body_matches: Option>, - pub query_param_exists: Option>, - pub query_param: Option>, - pub x_www_form_urlencoded_key_exists: Option>, - pub x_www_form_urlencoded: Option>, - - #[serde(skip_serializing, skip_deserializing)] - pub matchers: Option>, + pub json_body_excludes: Option>, // NEW + pub form_urlencoded_tuple: Option>, + pub form_urlencoded_tuple_not: Option>, // NEW + pub form_urlencoded_tuple_exists: Option>, + pub form_urlencoded_tuple_missing: Option>, // NEW + pub form_urlencoded_tuple_includes: Option>, // NEW + pub form_urlencoded_tuple_excludes: Option>, // NEW + pub form_urlencoded_tuple_prefix: Option>, // NEW + pub form_urlencoded_tuple_suffix: Option>, // NEW + pub form_urlencoded_tuple_prefix_not: Option>, // NEW + pub form_urlencoded_tuple_suffix_not: Option>, // NEW + pub form_urlencoded_tuple_matches: Option>, // NEW + pub form_urlencoded_tuple_count: Option>, // NEW + #[serde(skip)] + pub is_true: Option bool + Sync + Send>>>, // NEW + DEPRECATE matches() -> point to using "is_true" instead + #[serde(skip)] + pub is_false: Option bool + Sync + Send>>>, // NEW } impl Default for RequestRequirements { @@ -204,101 +648,95 @@ impl Default for RequestRequirements { impl RequestRequirements { pub fn new() -> Self { Self { + scheme: None, + scheme_not: None, + host: None, + host_not: None, + host_contains: None, + host_excludes: None, + host_prefix: None, + host_suffix: None, + host_prefix_not: None, + host_suffix_not: None, + host_matches: None, + port: None, path: None, - path_contains: None, + path_not: None, + path_includes: None, + path_excludes: None, + path_prefix: None, + path_suffix: None, + path_prefix_not: None, + path_suffix_not: None, path_matches: None, method: None, - headers: None, + header: None, + header_not: None, header_exists: None, - cookies: None, + header_missing: None, + header_includes: None, + header_excludes: None, + header_prefix: None, + header_suffix: None, + header_prefix_not: None, + header_suffix_not: None, + header_matches: None, + header_count: None, + cookie: None, + cookie_not: None, cookie_exists: None, + cookie_missing: None, + cookie_includes: None, + cookie_excludes: None, + cookie_prefix: None, + cookie_suffix: None, + cookie_prefix_not: None, + cookie_suffix_not: None, + cookie_matches: None, + cookie_count: None, body: None, json_body: None, + json_body_not: None, json_body_includes: None, - body_contains: None, + body_includes: None, + body_excludes: None, + body_prefix: None, + body_suffix: None, + body_prefix_not: None, + body_suffix_not: None, body_matches: None, query_param_exists: None, + query_param_missing: None, + query_param_includes: None, + query_param_excludes: None, + query_param_prefix: None, + query_param_suffix: None, + query_param_prefix_not: None, + query_param_suffix_not: None, + query_param_matches: None, + query_param_count: None, query_param: None, - x_www_form_urlencoded: None, - x_www_form_urlencoded_key_exists: None, - matchers: None, + form_urlencoded_tuple: None, + form_urlencoded_tuple_not: None, + form_urlencoded_tuple_exists: None, + form_urlencoded_tuple_missing: None, + form_urlencoded_tuple_includes: None, + form_urlencoded_tuple_excludes: None, + form_urlencoded_tuple_prefix: None, + form_urlencoded_tuple_suffix: None, + form_urlencoded_tuple_prefix_not: None, + form_urlencoded_tuple_suffix_not: None, + form_urlencoded_tuple_matches: None, + form_urlencoded_tuple_count: None, + is_true: None, + port_not: None, + method_not: None, + query_param_not: None, + body_not: None, + json_body_excludes: None, + is_false: None, } } - - pub fn with_path(mut self, arg: String) -> Self { - self.path = Some(arg); - self - } - - pub fn with_method(mut self, arg: String) -> Self { - self.method = Some(arg); - self - } - - pub fn with_body(mut self, arg: String) -> Self { - self.body = Some(arg); - self - } - - pub fn with_json_body(mut self, arg: Value) -> Self { - self.json_body = Some(arg); - self - } - - pub fn with_path_contains(mut self, arg: Vec) -> Self { - self.path_contains = Some(arg); - self - } - - pub fn with_path_matches(mut self, arg: Vec) -> Self { - self.path_matches = Some(arg); - self - } - - pub fn with_headers(mut self, arg: Vec<(String, String)>) -> Self { - self.headers = Some(arg); - self - } - - pub fn with_header_exists(mut self, arg: Vec) -> Self { - self.header_exists = Some(arg); - self - } - - pub fn with_cookies(mut self, arg: Vec<(String, String)>) -> Self { - self.cookies = Some(arg); - self - } - - pub fn with_cookie_exists(mut self, arg: Vec) -> Self { - self.cookie_exists = Some(arg); - self - } - - pub fn with_json_body_includes(mut self, arg: Vec) -> Self { - self.json_body_includes = Some(arg); - self - } - - pub fn with_body_contains(mut self, arg: Vec) -> Self { - self.body_contains = Some(arg); - self - } - - pub fn with_body_matches(mut self, arg: Vec) -> Self { - self.body_matches = Some(arg); - self - } - - pub fn with_query_param_exists(mut self, arg: Vec) -> Self { - self.query_param_exists = Some(arg); - self - } - - pub fn with_query_param(mut self, arg: Vec<(String, String)>) -> Self { - self.query_param = Some(arg); - self - } } /// A Request that is made to set a new mock. @@ -317,17 +755,6 @@ impl MockDefinition { } } -#[derive(Serialize, Deserialize)] -pub struct MockRef { - pub mock_id: usize, -} - -impl MockRef { - pub fn new(mock_id: usize) -> Self { - Self { mock_id } - } -} - #[derive(Serialize, Deserialize, Clone)] pub struct ActiveMock { pub id: usize, @@ -337,16 +764,62 @@ pub struct ActiveMock { } impl ActiveMock { - pub fn new(id: usize, mock_definition: MockDefinition, is_static: bool) -> Self { + pub fn new( + id: usize, + definition: MockDefinition, + call_counter: usize, + is_static: bool, + ) -> Self { ActiveMock { id, - definition: mock_definition, - call_counter: 0, + definition, + call_counter, is_static, } } } +#[derive(Serialize, Deserialize, Clone)] +pub struct ActiveForwardingRule { + pub id: usize, + pub config: ForwardingRuleConfig, +} + +impl ActiveForwardingRule { + pub fn new(id: usize, config: ForwardingRuleConfig) -> Self { + ActiveForwardingRule { id, config } + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ActiveProxyRule { + pub id: usize, + pub config: ProxyRuleConfig, +} + +impl ActiveProxyRule { + pub fn new(id: usize, config: ProxyRuleConfig) -> Self { + ActiveProxyRule { id, config } + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ActiveRecording { + pub id: usize, + pub config: RecordingRuleConfig, + pub mocks: Vec, +} + +impl ActiveRecording { + pub fn new(id: usize, config: RecordingRuleConfig) -> Self { + ActiveRecording { + id, + config, + mocks: vec![], + } + } +} + #[derive(Serialize, Deserialize)] pub struct ClosestMatch { pub request: HttpMockRequest, @@ -396,102 +869,971 @@ pub enum Tokenizer { } #[derive(Debug, Serialize, Deserialize)] -pub struct Reason { +pub struct KeyValueComparisonKeyValuePair { + pub key: String, + pub value: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyValueComparisonAttribute { + pub operator: String, + pub expected: String, + pub actual: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct KeyValueComparison { + pub key: Option, + pub value: Option, + pub expected_count: Option, + pub actual_count: Option, + pub all: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct FunctionComparison { + pub index: usize, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SingleValueComparison { + pub operator: String, pub expected: String, pub actual: String, - pub comparison: String, - pub best_match: bool, } #[derive(Debug, Serialize, Deserialize)] pub struct Mismatch { - pub title: String, - pub reason: Option, + pub entity: String, + pub matcher_method: String, + pub comparison: Option, + pub key_value_comparison: Option, + pub function_comparison: Option, + pub matching_strategy: Option, + pub best_match: bool, pub diff: Option, } -#[cfg(test)] -mod test { - use std::collections::BTreeMap; - - use regex::Regex; - use serde_json::json; - - use crate::common::data::{Pattern, RequestRequirements}; - - /// This test makes sure that adding the matching rules to a mock fills the struct as expected. - #[test] - fn fill_mock_requirements() { - // Arrange - let with_path = "with_path"; - let with_path_contains = vec!["with_path_contains".into()]; - let with_path_matches = vec![Pattern::from_regex( - Regex::new(r#"with_path_matches"#).unwrap(), - )]; - let mut with_headers = Vec::new(); - with_headers.push(("test".into(), "value".into())); - let with_method = "GET"; - let with_body = "with_body"; - let with_body_contains = vec!["body_contains".into()]; - let with_body_matches = vec![Pattern::from_regex( - Regex::new(r#"with_body_matches"#).unwrap(), - )]; - let with_json_body = json!(12.5); - let with_json_body_includes = vec![json!(12.5)]; - let with_query_param_exists = vec!["with_query_param_exists".into()]; - let mut with_query_param = Vec::new(); - with_query_param.push(("with_query_param".into(), "value".into())); - let with_header_exists = vec!["with_header_exists".into()]; - - // Act - let rr = RequestRequirements::new() - .with_path(with_path.clone().into()) - .with_path_contains(with_path_contains.clone()) - .with_path_matches(with_path_matches.clone()) - .with_headers(with_headers.clone()) - .with_method(with_method.clone().into()) - .with_body(with_body.clone().into()) - .with_body_contains(with_body_contains.clone()) - .with_body_matches(with_body_matches.clone()) - .with_json_body(with_json_body.clone()) - .with_json_body_includes(with_json_body_includes.clone()) - .with_query_param_exists(with_query_param_exists.clone()) - .with_query_param(with_query_param.clone()) - .with_header_exists(with_header_exists.clone()); - - // Assert - assert_eq!(rr.path.as_ref().unwrap(), with_path.clone()); - assert_eq!( - rr.path_contains.as_ref().unwrap(), - &with_path_contains.clone() - ); - assert_eq!( - rr.path_matches.as_ref().unwrap(), - &with_path_matches.clone() - ); - assert_eq!(rr.headers.as_ref().unwrap(), &with_headers.clone()); - assert_eq!(rr.body.as_ref().unwrap(), with_body.clone()); - assert_eq!( - rr.body_contains.as_ref().unwrap(), - &with_body_contains.clone() - ); - assert_eq!( - rr.body_matches.as_ref().unwrap(), - &with_body_matches.clone() - ); - assert_eq!(rr.json_body.as_ref().unwrap(), &with_json_body.clone()); - assert_eq!( - rr.json_body_includes.as_ref().unwrap(), - &with_json_body_includes.clone() - ); - assert_eq!( - rr.query_param_exists.as_ref().unwrap(), - &with_query_param_exists.clone() - ); - assert_eq!(rr.query_param.as_ref().unwrap(), &with_query_param.clone()); - assert_eq!( - rr.header_exists.as_ref().unwrap(), - &with_header_exists.clone() +// ************************************************************************************************* +// Configs and Builders +// ************************************************************************************************* + +#[cfg(feature = "record")] +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct RecordingRuleConfig { + pub request_requirements: RequestRequirements, + pub record_headers: Vec, + pub record_response_delays: bool, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct ProxyRuleConfig { + pub request_requirements: RequestRequirements, + pub request_header: Vec<(String, String)>, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct ForwardingRuleConfig { + pub target_base_url: String, + pub request_requirements: RequestRequirements, + pub request_header: Vec<(String, String)>, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct NameValueStringPair { + name: String, + value: String, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct NameValuePatternPair { + name: HttpMockRegex, + value: HttpMockRegex, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct KeyPatternCountPair { + key: HttpMockRegex, + count: usize, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct ValuePatternCountPair { + value: HttpMockRegex, + count: usize, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct KeyValuePatternCountTriple { + name: HttpMockRegex, + value: HttpMockRegex, + count: usize, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StaticRequestRequirements { + // Scheme-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub scheme: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub scheme_not: Option, + + // Host-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_contains: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_excludes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_prefix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_suffix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_prefix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_suffix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_matches: Option>, + + // Port-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub port: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub port_not: Option>, + + // Path-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_contains: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_excludes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_prefix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_suffix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_prefix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_suffix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub path_matches: Option>, + + // Method-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub method: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub method_not: Option>, + + // Query Parameter-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_exists: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_missing: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_contains: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_excludes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_prefix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_suffix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_prefix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_suffix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_matches: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_param_count: Option>, + + // Header-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub header: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_exists: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_missing: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_contains: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_excludes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_prefix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_suffix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_prefix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_suffix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_matches: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub header_count: Option>, + + // Cookie-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_exists: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_missing: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_contains: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_excludes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_prefix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_suffix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_prefix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_suffix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_matches: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub cookie_count: Option>, + + // Body-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_base64: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_not_base64: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_contains: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_contains_base64: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_excludes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_excludes_base64: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_prefix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_prefix_base64: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_suffix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_suffix_base64: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_prefix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_prefix_not_base64: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_suffix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_suffix_not_base64: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_matches: Option>, + + // JSON Body-related fields + #[serde(skip_serializing_if = "Option::is_none")] + pub json_body: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub json_body_not: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub json_body_includes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub json_body_excludes: Option>, + + // x-www-form-urlencoded fields + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_tuple: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_tuple_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_key_exists: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_key_missing: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_contains: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_excludes: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_prefix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_suffix: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_prefix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_suffix_not: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_matches: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub form_urlencoded_count: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StaticHTTPResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub header: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub body_base64: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub delay: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StaticMockDefinition { + when: StaticRequestRequirements, + then: StaticHTTPResponse, +} + +impl TryInto for StaticMockDefinition { + type Error = Error; + + fn try_into(self) -> Result { + Ok(MockDefinition { + request: RequestRequirements { + // Scheme-related fields + scheme: self.when.scheme, + scheme_not: self.when.scheme_not, + + // Host-related fields + host: self.when.host, + host_not: self.when.host_not, + host_contains: self.when.host_contains, + host_excludes: self.when.host_excludes, + host_prefix: self.when.host_prefix, + host_suffix: self.when.host_suffix, + host_prefix_not: self.when.host_prefix_not, + host_suffix_not: self.when.host_suffix_not, + host_matches: self.when.host_matches, + + // Port-related fields + port: self.when.port, + port_not: self.when.port_not, + + // Path-related fields + path: self.when.path, + path_not: self.when.path_not, + path_includes: self.when.path_contains, + path_excludes: self.when.path_excludes, + path_prefix: self.when.path_prefix, + path_suffix: self.when.path_suffix, + path_prefix_not: self.when.path_prefix_not, + path_suffix_not: self.when.path_suffix_not, + path_matches: self.when.path_matches, + + // Method-related fields + method: self.when.method.map(|m| m.to_string()), + method_not: from_method_vec(self.when.method_not), + // Query Parameter-related fields + query_param: from_name_value_string_pair_vec(self.when.query_param), + query_param_not: from_name_value_string_pair_vec(self.when.query_param_not), + query_param_exists: self.when.query_param_exists, + query_param_missing: self.when.query_param_missing, + query_param_includes: from_name_value_string_pair_vec( + self.when.query_param_contains, + ), + query_param_excludes: from_name_value_string_pair_vec( + self.when.query_param_excludes, + ), + query_param_prefix: from_name_value_string_pair_vec(self.when.query_param_prefix), + query_param_suffix: from_name_value_string_pair_vec(self.when.query_param_suffix), + query_param_prefix_not: from_name_value_string_pair_vec( + self.when.query_param_prefix_not, + ), + query_param_suffix_not: from_name_value_string_pair_vec( + self.when.query_param_suffix_not, + ), + query_param_matches: from_name_value_pattern_pair_vec( + self.when.query_param_matches, + ), + query_param_count: from_key_value_pattern_count_triple_vec( + self.when.query_param_count, + ), + + // Header-related fields + header: from_name_value_string_pair_vec(self.when.header), + header_not: from_name_value_string_pair_vec(self.when.header_not), + header_exists: self.when.header_exists, + header_missing: self.when.header_missing, + header_includes: from_name_value_string_pair_vec(self.when.header_contains), + header_excludes: from_name_value_string_pair_vec(self.when.header_excludes), + header_prefix: from_name_value_string_pair_vec(self.when.header_prefix), + header_suffix: from_name_value_string_pair_vec(self.when.header_suffix), + header_prefix_not: from_name_value_string_pair_vec(self.when.header_prefix_not), + header_suffix_not: from_name_value_string_pair_vec(self.when.header_suffix_not), + header_matches: from_name_value_pattern_pair_vec(self.when.header_matches), + header_count: from_key_value_pattern_count_triple_vec(self.when.header_count), + // Cookie-related fields + cookie: from_name_value_string_pair_vec(self.when.cookie), + cookie_not: from_name_value_string_pair_vec(self.when.cookie_not), + cookie_exists: self.when.cookie_exists, + cookie_missing: self.when.cookie_missing, + cookie_includes: from_name_value_string_pair_vec(self.when.cookie_contains), + cookie_excludes: from_name_value_string_pair_vec(self.when.cookie_excludes), + cookie_prefix: from_name_value_string_pair_vec(self.when.cookie_prefix), + cookie_suffix: from_name_value_string_pair_vec(self.when.cookie_suffix), + cookie_prefix_not: from_name_value_string_pair_vec(self.when.cookie_prefix_not), + cookie_suffix_not: from_name_value_string_pair_vec(self.when.cookie_suffix_not), + cookie_matches: from_name_value_pattern_pair_vec(self.when.cookie_matches), + cookie_count: from_key_value_pattern_count_triple_vec(self.when.cookie_count), + + // Body-related fields + body: from_string_to_bytes_choose(self.when.body, self.when.body_base64), + body_not: to_bytes_vec(self.when.body_not, self.when.body_not_base64), + body_includes: to_bytes_vec( + self.when.body_contains, + self.when.body_contains_base64, + ), + body_excludes: to_bytes_vec( + self.when.body_excludes, + self.when.body_excludes_base64, + ), + body_prefix: to_bytes_vec(self.when.body_prefix, self.when.body_prefix_base64), + body_suffix: to_bytes_vec(self.when.body_suffix, self.when.body_suffix_base64), + body_prefix_not: to_bytes_vec( + self.when.body_prefix_not, + self.when.body_prefix_not_base64, + ), + body_suffix_not: to_bytes_vec( + self.when.body_suffix_not, + self.when.body_suffix_not_base64, + ), + body_matches: from_pattern_vec(self.when.body_matches), + + // JSON Body-related fields + json_body: self.when.json_body, + json_body_not: self.when.json_body_not, + json_body_includes: self.when.json_body_includes, + json_body_excludes: self.when.json_body_excludes, + + // x-www-form-urlencoded fields + form_urlencoded_tuple: from_name_value_string_pair_vec( + self.when.form_urlencoded_tuple, + ), + form_urlencoded_tuple_not: from_name_value_string_pair_vec( + self.when.form_urlencoded_tuple_not, + ), + form_urlencoded_tuple_exists: self.when.form_urlencoded_key_exists, + form_urlencoded_tuple_missing: self.when.form_urlencoded_key_missing, + form_urlencoded_tuple_includes: from_name_value_string_pair_vec( + self.when.form_urlencoded_contains, + ), + form_urlencoded_tuple_excludes: from_name_value_string_pair_vec( + self.when.form_urlencoded_excludes, + ), + form_urlencoded_tuple_prefix: from_name_value_string_pair_vec( + self.when.form_urlencoded_prefix, + ), + form_urlencoded_tuple_suffix: from_name_value_string_pair_vec( + self.when.form_urlencoded_suffix, + ), + form_urlencoded_tuple_prefix_not: from_name_value_string_pair_vec( + self.when.form_urlencoded_prefix_not, + ), + form_urlencoded_tuple_suffix_not: from_name_value_string_pair_vec( + self.when.form_urlencoded_suffix_not, + ), + form_urlencoded_tuple_matches: from_name_value_pattern_pair_vec( + self.when.form_urlencoded_matches, + ), + + form_urlencoded_tuple_count: from_key_value_pattern_count_triple_vec( + self.when.form_urlencoded_count, + ), + + // Boolean dynamic checks + is_true: None, + is_false: None, + }, + response: MockServerHttpResponse { + status: self.then.status, + headers: from_name_value_string_pair_vec(self.then.header), + body: from_string_to_bytes_choose(self.then.body, self.then.body_base64), + delay: self.then.delay, + }, + }) + } +} + +fn to_method_vec(vec: Option>) -> Option> { + vec.map(|vec| vec.iter().map(|val| Method::from(val.as_str())).collect()) +} + +fn from_method_vec(value: Option>) -> Option> { + value.map(|vec| vec.iter().map(|m| m.to_string()).collect()) +} + +fn to_pattern_vec(vec: Option>) -> Option> { + vec.map(|vec| { + vec.iter() + .map(|val| HttpMockRegex(regex::Regex::from_str(val).expect("cannot parse regex"))) + .collect() + }) +} + +fn from_pattern_vec(patterns: Option>) -> Option> { + patterns.map(|vec| vec.iter().cloned().collect()) +} + +fn from_name_value_string_pair_vec( + kvp: Option>, +) -> Option> { + kvp.map(|vec| vec.into_iter().map(|nvp| (nvp.name, nvp.value)).collect()) +} + +fn from_name_value_pattern_pair_vec( + kvp: Option>, +) -> Option> { + kvp.map(|vec| { + vec.into_iter() + .map(|pair| (pair.name, pair.value)) + .collect() + }) +} + +fn from_string_pair_vec(vec: Option>) -> Option> { + vec.map(|vec| { + vec.into_iter() + .map(|(name, value)| NameValueStringPair { name, value }) + .collect() + }) +} + +fn from_key_pattern_count_pair_vec( + input: Option>, +) -> Option> { + input.map(|vec| vec.into_iter().map(|pair| (pair.key, pair.count)).collect()) +} + +fn from_value_pattern_count_pair_vec( + input: Option>, +) -> Option> { + input.map(|vec| { + vec.into_iter() + .map(|pair| (pair.value, pair.count)) + .collect() + }) +} + +fn from_key_value_pattern_count_triple_vec( + input: Option>, +) -> Option> { + input.map(|vec| { + vec.into_iter() + .map(|triple| (triple.name, triple.value, triple.count)) + .collect() + }) +} + +fn to_name_value_string_pair_vec( + vec: Option>, +) -> Option> { + vec.map(|vec| { + vec.into_iter() + .map(|(name, value)| NameValueStringPair { name, value }) + .collect() + }) +} + +fn to_name_value_pattern_pair_vec( + vec: Option>, +) -> Option> { + vec.map(|vec| { + vec.into_iter() + .map(|(name, value)| NameValuePatternPair { name, value }) + .collect() + }) +} + +fn to_key_pattern_count_pair_vec( + vec: Option>, +) -> Option> { + vec.map(|vec| { + vec.into_iter() + .map(|(key, count)| KeyPatternCountPair { key, count }) + .collect() + }) +} + +fn to_value_pattern_count_pair_vec( + vec: Option>, +) -> Option> { + vec.map(|vec| { + vec.into_iter() + .map(|(value, count)| ValuePatternCountPair { value, count }) + .collect() + }) +} + +fn to_key_value_pattern_count_triple_vec( + vec: Option>, +) -> Option> { + vec.map(|vec| { + vec.into_iter() + .map(|(name, value, count)| KeyValuePatternCountTriple { name, value, count }) + .collect() + }) +} + +fn from_bytes_to_string(data: Option) -> (Option, Option) { + let mut text_representation = None; + let mut base64_representation = None; + + if let Some(bytes_container) = data { + if let Ok(text_str) = std::str::from_utf8(&bytes_container.to_bytes()) { + text_representation = Some(text_str.to_string()); + } else { + base64_representation = Some(base64::encode(&bytes_container.to_bytes())); + } + } + + (text_representation, base64_representation) +} + +fn bytes_to_string_vec( + data: Option>, +) -> (Option>, Option>) { + let mut text_representations = Vec::new(); + let mut base64_representations = Vec::new(); + + if let Some(bytes_vec) = data { + for bytes_container in bytes_vec { + let bytes = bytes_container.to_bytes(); + if let Ok(text) = std::str::from_utf8(&bytes) { + text_representations.push(text.to_owned()); + } else { + base64_representations.push(base64::encode(&bytes)); + } + } + } + + let text_opt_vec = if !text_representations.is_empty() { + Some(text_representations) + } else { + None + }; + + let base64_opt_vec = if !base64_representations.is_empty() { + Some(base64_representations) + } else { + None + }; + + (text_opt_vec, base64_opt_vec) +} + +fn to_bytes_vec( + option_string: Option>, + option_base64: Option>, +) -> Option> { + let mut result = Vec::new(); + + if let Some(strings) = option_string { + result.extend( + strings + .into_iter() + .map(|s| HttpMockBytes::from(Bytes::from(s))), ); } + + if let Some(base64_strings) = option_base64 { + result.extend(base64_strings.into_iter().filter_map(|s| { + base64::decode(&s) + .ok() + .map(|decoded_bytes| HttpMockBytes::from(Bytes::from(decoded_bytes))) + })); + } + + if result.is_empty() { + None + } else { + Some(result) + } +} + +fn to_bytes(option_string: Option, option_base64: Option) -> Option { + if option_string.is_some() { + return option_string; + } + + return option_base64; +} + +fn from_string_to_bytes_choose( + option_string: Option, + option_base64: Option, +) -> Option { + let request_body = match (option_string, option_base64) { + (Some(body), None) => Some(body.into_bytes()), + (None, Some(base64_body)) => base64::decode(base64_body).ok(), + _ => None, // Handle unexpected combinations or both None + }; + + return request_body.map(|s| HttpMockBytes::from(Bytes::from(s))); +} + +impl TryFrom<&MockDefinition> for StaticMockDefinition { + type Error = Error; + + fn try_from(value: &MockDefinition) -> Result { + let value = value.clone(); + + let (response_body, response_body_base64) = from_bytes_to_string(value.response.body); + + let (request_body, request_body_base64) = from_bytes_to_string(value.request.body); + let (request_body_not, request_body_not_base64) = + bytes_to_string_vec(value.request.body_not); + let (request_body_includes, request_body_includes_base64) = + bytes_to_string_vec(value.request.body_includes); + let (request_body_excludes, request_body_excludes_base64) = + bytes_to_string_vec(value.request.body_excludes); + let (request_body_prefix, request_body_prefix_base64) = + bytes_to_string_vec(value.request.body_prefix); + let (request_body_suffix, request_body_suffix_base64) = + bytes_to_string_vec(value.request.body_suffix); + let (request_body_prefix_not, request_body_prefix_not_base64) = + bytes_to_string_vec(value.request.body_prefix_not); + let (request_body_suffix_not, request_body_suffix_not_base64) = + bytes_to_string_vec(value.request.body_suffix_not); + + let mut method = None; + if let Some(method_str) = value.request.method { + method = Some( + Method::from_str(&method_str) + .map_err(|err| StaticMockConversionError(err.to_string()))?, + ); + } + + Ok(StaticMockDefinition { + when: StaticRequestRequirements { + // Scheme-related fields + scheme: value.request.scheme, + scheme_not: value.request.scheme_not, + + // Method-related fields + method, + method_not: to_method_vec(value.request.method_not), + // Host-related fields + host: value.request.host, + host_not: value.request.host_not, + host_contains: value.request.host_contains, + host_excludes: value.request.host_excludes, + host_prefix: value.request.host_prefix, + host_suffix: value.request.host_suffix, + host_prefix_not: value.request.host_prefix_not, + host_suffix_not: value.request.host_suffix_not, + host_matches: value.request.host_matches, + + // Port-related fields + port: value.request.port, + port_not: value.request.port_not, + + // Path-related fields + path: value.request.path, + path_not: value.request.path_not, + path_contains: value.request.path_includes, + path_excludes: value.request.path_excludes, + path_prefix: value.request.path_prefix, + path_suffix: value.request.path_suffix, + path_prefix_not: value.request.path_prefix_not, + path_suffix_not: value.request.path_suffix_not, + path_matches: from_pattern_vec(value.request.path_matches), + + // Header-related fields + header: from_string_pair_vec(value.request.header), + header_not: from_string_pair_vec(value.request.header_not), + header_exists: value.request.header_exists, + header_missing: value.request.header_missing, + header_contains: to_name_value_string_pair_vec(value.request.header_includes), + header_excludes: to_name_value_string_pair_vec(value.request.header_excludes), + header_prefix: to_name_value_string_pair_vec(value.request.header_prefix), + header_suffix: to_name_value_string_pair_vec(value.request.header_suffix), + header_prefix_not: to_name_value_string_pair_vec(value.request.header_prefix_not), + header_suffix_not: to_name_value_string_pair_vec(value.request.header_suffix_not), + header_matches: to_name_value_pattern_pair_vec(value.request.header_matches), + header_count: to_key_value_pattern_count_triple_vec(value.request.header_count), + + // Cookie-related fields + cookie: from_string_pair_vec(value.request.cookie), + cookie_not: from_string_pair_vec(value.request.cookie_not), + cookie_exists: value.request.cookie_exists, + cookie_missing: value.request.cookie_missing, + cookie_contains: to_name_value_string_pair_vec(value.request.cookie_includes), + cookie_excludes: to_name_value_string_pair_vec(value.request.cookie_excludes), + cookie_prefix: to_name_value_string_pair_vec(value.request.cookie_prefix), + cookie_suffix: to_name_value_string_pair_vec(value.request.cookie_suffix), + cookie_prefix_not: to_name_value_string_pair_vec(value.request.cookie_prefix_not), + cookie_suffix_not: to_name_value_string_pair_vec(value.request.cookie_suffix_not), + cookie_matches: to_name_value_pattern_pair_vec(value.request.cookie_matches), + + cookie_count: to_key_value_pattern_count_triple_vec(value.request.cookie_count), + + // Query Parameter-related fields + query_param: from_string_pair_vec(value.request.query_param), + query_param_not: from_string_pair_vec(value.request.query_param_not), + query_param_exists: value.request.query_param_exists, + query_param_missing: value.request.query_param_missing, + query_param_contains: to_name_value_string_pair_vec( + value.request.query_param_includes, + ), + query_param_excludes: to_name_value_string_pair_vec( + value.request.query_param_excludes, + ), + query_param_prefix: to_name_value_string_pair_vec(value.request.query_param_prefix), + query_param_suffix: to_name_value_string_pair_vec(value.request.query_param_suffix), + query_param_prefix_not: to_name_value_string_pair_vec( + value.request.query_param_prefix_not, + ), + query_param_suffix_not: to_name_value_string_pair_vec( + value.request.query_param_suffix_not, + ), + query_param_matches: to_name_value_pattern_pair_vec( + value.request.query_param_matches, + ), + query_param_count: to_key_value_pattern_count_triple_vec( + value.request.query_param_count, + ), + + // Body-related fields + body: request_body, + body_base64: request_body_base64, + body_not: request_body_not, + body_not_base64: request_body_not_base64, + body_contains: request_body_includes, + body_contains_base64: request_body_includes_base64, + body_excludes: request_body_excludes, + body_excludes_base64: request_body_excludes_base64, + body_prefix: request_body_prefix, + body_prefix_base64: request_body_prefix_base64, + body_suffix: request_body_suffix, + body_suffix_base64: request_body_suffix_base64, + body_prefix_not: request_body_prefix_not, + body_prefix_not_base64: request_body_prefix_not_base64, + body_suffix_not: request_body_suffix_not, + body_suffix_not_base64: request_body_suffix_not_base64, + body_matches: from_pattern_vec(value.request.body_matches), + + // JSON Body-related fields + json_body: value.request.json_body, + json_body_not: value.request.json_body_not, + json_body_includes: value.request.json_body_includes, + json_body_excludes: value.request.json_body_excludes, + + // Form URL-encoded fields + form_urlencoded_tuple: from_string_pair_vec(value.request.form_urlencoded_tuple), + form_urlencoded_tuple_not: from_string_pair_vec( + value.request.form_urlencoded_tuple_not, + ), + form_urlencoded_key_exists: value.request.form_urlencoded_tuple_exists, + form_urlencoded_key_missing: value.request.form_urlencoded_tuple_missing, + form_urlencoded_contains: to_name_value_string_pair_vec( + value.request.form_urlencoded_tuple_includes, + ), + form_urlencoded_excludes: to_name_value_string_pair_vec( + value.request.form_urlencoded_tuple_excludes, + ), + form_urlencoded_prefix: to_name_value_string_pair_vec( + value.request.form_urlencoded_tuple_prefix, + ), + form_urlencoded_suffix: to_name_value_string_pair_vec( + value.request.form_urlencoded_tuple_suffix, + ), + form_urlencoded_prefix_not: to_name_value_string_pair_vec( + value.request.form_urlencoded_tuple_prefix_not, + ), + form_urlencoded_suffix_not: to_name_value_string_pair_vec( + value.request.form_urlencoded_tuple_suffix_not, + ), + form_urlencoded_matches: to_name_value_pattern_pair_vec( + value.request.form_urlencoded_tuple_matches, + ), + + form_urlencoded_count: to_key_value_pattern_count_triple_vec( + value.request.form_urlencoded_tuple_count, + ), + }, + then: StaticHTTPResponse { + status: value.response.status, + header: from_string_pair_vec(value.response.headers), + body: response_body, + body_base64: response_body_base64, + // Reason for the cast to u64: The Duration::as_millis method returns the total + // number of milliseconds contained within the Duration as a u128. This is + // because Duration::as_millis needs to handle larger values that + // can result from multiplying the seconds (stored internally as a u64) + // by 1000 and adding the milliseconds (also a u64), potentially + // exceeding the u64 limit. + delay: value.response.delay, + }, + }) + } +} + +/// Represents an HTTP method. +#[derive(Serialize, Deserialize, Debug)] +pub enum Method { + GET, + HEAD, + POST, + PUT, + DELETE, + CONNECT, + OPTIONS, + TRACE, + PATCH, +} + +impl PartialEq for http::method::Method { + fn eq(&self, other: &Method) -> bool { + self.to_string().to_uppercase() == other.to_string().to_uppercase() + } +} + +impl FromStr for Method { + type Err = String; + + fn from_str(input: &str) -> Result { + match input.to_uppercase().as_str() { + "GET" => Ok(Method::GET), + "HEAD" => Ok(Method::HEAD), + "POST" => Ok(Method::POST), + "PUT" => Ok(Method::PUT), + "DELETE" => Ok(Method::DELETE), + "CONNECT" => Ok(Method::CONNECT), + "OPTIONS" => Ok(Method::OPTIONS), + "TRACE" => Ok(Method::TRACE), + "PATCH" => Ok(Method::PATCH), + _ => Err(format!("Invalid HTTP method {}", input)), + } + } +} + +impl From<&str> for Method { + fn from(value: &str) -> Self { + value + .parse() + .expect(&format!("Cannot parse HTTP method from string {:?}", value)) + } +} + +impl std::fmt::Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } } diff --git a/src/common/http.rs b/src/common/http.rs new file mode 100644 index 00000000..d3a4ac44 --- /dev/null +++ b/src/common/http.rs @@ -0,0 +1,90 @@ +use async_trait::async_trait; +use bytes::Bytes; +use http::{Request, Response}; +use http_body_util::{BodyExt, Full}; +use hyper_rustls::HttpsConnector; +use hyper_util::{ + client::legacy::{connect::HttpConnector, Client}, + rt::TokioExecutor, +}; +use std::{convert::TryInto, sync::Arc}; +use thiserror::Error; +use tokio::runtime::Runtime; + +#[derive(Error, Debug)] +pub enum Error { + #[error("cannot send request: {0}")] + HyperError(#[from] hyper::Error), + #[error("cannot send request: {0}")] + HyperUtilError(#[from] hyper_util::client::legacy::Error), + #[error("runtime error: {0}")] + RuntimeError(#[from] tokio::task::JoinError), + #[error("unknown error")] + Unknown, +} + +#[async_trait] +pub trait HttpClient { + async fn send(&self, req: Request) -> Result, Error>; +} + +pub struct HttpMockHttpClient { + runtime: Option>, + #[cfg(any(feature = "remote-https", feature = "https"))] + client: Arc, Full>>, + #[cfg(not(any(feature = "remote-https", feature = "https")))] + client: Arc>>, +} + +impl<'a> HttpMockHttpClient { + #[cfg(any(feature = "remote-https", feature = "https"))] + pub fn new(runtime: Option>) -> Self { + // see https://github.com/rustls/rustls/issues/1938 + if rustls::crypto::CryptoProvider::get_default().is_none() { + rustls::crypto::ring::default_provider() + .install_default() + .expect("cannot install rustls crypto provider"); + } + + let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_native_roots() + .expect("cannot set up using native root certificates") + .https_or_http() + .enable_all_versions() + .build(); + + Self { + runtime, + client: Arc::new(Client::builder(TokioExecutor::new()).build(https_connector)), + } + } + + #[cfg(not(any(feature = "remote-https", feature = "https")))] + pub fn new(runtime: Option>) -> Self { + Self { + runtime, + client: Arc::new(Client::builder(TokioExecutor::new()).build(HttpConnector::new())), + } + } +} + +#[async_trait] +impl HttpClient for HttpMockHttpClient { + async fn send(&self, req: Request) -> Result, Error> { + let (req_parts, req_body) = req.into_parts(); + let hyper_req = Request::from_parts(req_parts, Full::new(req_body)); + + let res = if let Some(rt) = self.runtime.clone() { + let client = self.client.clone(); + rt.spawn(async move { client.request(hyper_req).await }) + .await?? + } else { + self.client.request(hyper_req).await? + }; + + let (res_parts, res_body) = res.into_parts(); + let body = res_body.collect().await?.to_bytes(); + + return Ok(Response::from_parts(res_parts, body)); + } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 5b853857..7870dd87 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,2 +1,6 @@ -pub mod data; +pub(crate) mod data; +pub(crate) mod runtime; pub mod util; + +#[cfg(any(feature = "remote", feature = "proxy"))] +pub mod http; diff --git a/src/common/runtime.rs b/src/common/runtime.rs new file mode 100644 index 00000000..7254561c --- /dev/null +++ b/src/common/runtime.rs @@ -0,0 +1,35 @@ +use std::{future::Future, time::Duration}; +use tokio::{runtime::Runtime, task::LocalSet}; + +pub(crate) async fn sleep(duration: Duration) { + tokio::time::sleep(duration).await +} + +pub(crate) fn block_on_current_thread(f: F) -> O +where + F: Future, +{ + let mut runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Cannot build local tokio runtime"); + + LocalSet::new().block_on(&mut runtime, f) +} + +pub(crate) fn new(worker_threads: usize, blocking_threads: usize) -> std::io::Result { + assert!( + worker_threads > 0, + "Parameter worker_threads must be larger than 0" + ); + assert!( + blocking_threads > 0, + "Parameter blocking_threads must be larger than 0" + ); + + return tokio::runtime::Builder::new_multi_thread() + .worker_threads(worker_threads) + .max_blocking_threads(blocking_threads) // This is a maximum + .enable_all() + .build(); +} diff --git a/src/common/util.rs b/src/common/util.rs index ebeede94..da6cacb6 100644 --- a/src/common/util.rs +++ b/src/common/util.rs @@ -1,17 +1,23 @@ -use std::fs::File; -use std::io::Read; -use std::path::{Path, PathBuf}; -use std::sync::Arc; +use async_std::fs::{create_dir_all as create_dir_all_async, File as AsyncFile}; use std::{ + borrow::Cow, env, + fs::File, future::Future, + io::Read, + path::{Path, PathBuf}, + sync::Arc, task::{Context, Poll}, }; +use async_std::io::{ReadExt, WriteExt}; +use bytes::Bytes; /// Extension trait for efficiently blocking on a future. use crossbeam_utils::sync::{Parker, Unparker}; +use futures_timer::Delay; use futures_util::{pin_mut, task::ArcWake}; -use std::cell::Cell; +use serde::{Deserialize, Serialize, Serializer}; +use std::{cell::Cell, time::Duration}; // =============================================================================================== // Misc @@ -32,9 +38,11 @@ where Fut: Future>, { let mut result = (f)().await; - for _ in 1..=retries { + for i in 1..=retries { if result.is_ok() { return result; + } else { + Delay::new(Duration::from_secs(1 * i as u64)).await; } result = (f)().await; } @@ -89,14 +97,14 @@ impl Join for F { // =============================================================================================== // Files // =============================================================================================== -pub(crate) fn get_test_resource_file_path(relative_resource_path: &str) -> Result { +pub fn get_test_resource_file_path(relative_resource_path: &str) -> Result { match env::var("CARGO_MANIFEST_DIR") { Ok(manifest_path) => Ok(Path::new(&manifest_path).join(relative_resource_path)), Err(e) => Err(e.to_string()), } } -pub(crate) fn read_file>(absolute_resource_path: P) -> Result, String> { +pub fn read_file>(absolute_resource_path: P) -> Result, String> { let mut f = match File::open(&absolute_resource_path) { Ok(mut opened_file) => opened_file, Err(e) => return Err(e.to_string()), @@ -118,6 +126,48 @@ pub(crate) fn read_file>(absolute_resource_path: P) -> Result>( + resource_path: P, + content: &Bytes, + create_dir: bool, +) -> Result> { + let mut path = resource_path.as_ref().to_path_buf(); + + if path.is_relative() { + let current_dir = env::current_dir()?; + path = current_dir.join(path); + } + + if create_dir { + if let Some(parent) = path.parent() { + create_dir_all_async(parent).await?; + } + } + + let mut file = AsyncFile::create(&path).await?; + file.write_all(content).await?; + file.flush().await?; + + Ok(path) +} + +pub async fn read_file_async>( + path: IntoPathBuf, +) -> std::io::Result> { + let mut file = async_std::fs::File::open(path.into()).await?; + let mut content = Vec::new(); + file.read_to_end(&mut content).await?; + Ok(content) +} + +// Checks if the executing thread is running in a Tokio runtime. +fn is_tokio_runtime_running() -> bool { + match tokio::runtime::Handle::try_current() { + Ok(_) => true, + Err(_) => false, + } +} + #[cfg(test)] mod test { use crate::common::util::{with_retry, Join}; @@ -133,3 +183,161 @@ mod test { assert_eq!(result.err().unwrap(), "test error") } } + +/// A wrapper around `bytes::Bytes` providing utility methods for common operations. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct HttpMockBytes(pub Bytes); + +impl HttpMockBytes { + /// Converts the bytes to a `Vec`. + /// + /// # Returns + /// A `Vec` containing the bytes. + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } + + /// Cheaply clones the bytes into a new `Bytes` instance. + /// See + /// + /// # Returns + /// A `Bytes` instance containing the same data. + pub fn to_bytes(&self) -> Bytes { + self.0.clone() + } + + /// Checks if the byte slice is empty. + /// + /// # Returns + /// `true` if the byte slice is empty, otherwise `false`. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Checks if the byte slice is blank (empty or only contains ASCII whitespace). + /// + /// # Returns + /// `true` if the byte slice is blank, otherwise `false`. + pub fn is_blank(&self) -> bool { + self.is_empty() || self.0.iter().all(|&b| b.is_ascii_whitespace()) + } + + /// Checks if the byte slice contains the specified substring. + /// + /// # Arguments + /// * `substring` - The substring to search for. + /// + /// # Returns + /// `true` if the substring is found, otherwise `false`. + pub fn contains_str(&self, substring: &str) -> bool { + if substring.is_empty() { + return true; + } + + self.0 + .as_ref() + .windows(substring.as_bytes().len()) + .any(|window| window == substring.as_bytes()) + } + + /// Checks if the byte slice contains the specified byte slice. + /// + /// # Arguments + /// * `slice` - The byte slice to search for. + /// + /// # Returns + /// `true` if the byte slice is found, otherwise `false`. + pub fn contains_slice(&self, slice: &[u8]) -> bool { + self.0 + .as_ref() + .windows(slice.len()) + .any(|window| window == slice) + } + + /// Checks if the byte slice contains the specified `Vec`. + /// + /// # Arguments + /// * `vec` - The vector to search for. + /// + /// # Returns + /// `true` if the vector is found, otherwise `false`. + pub fn contains_vec(&self, vec: &Vec) -> bool { + self.0 + .as_ref() + .windows(vec.len()) + .any(|window| window == vec.as_slice()) + } + + /// Converts the bytes to a UTF-8 string, potentially lossy. + /// Tries to parse input as a UTF-8 string first to avoid copying and creating an owned instance. + /// If the bytes are not valid UTF-8, it creates a lossy string by replacing invalid characters + /// with the Unicode replacement character. + /// + /// # Returns + /// A `Cow` which is either borrowed if the bytes are valid UTF-8 or owned if conversion was required. + pub fn to_maybe_lossy_str(&self) -> Cow { + return match std::str::from_utf8(&self.0) { + Ok(valid_str) => Cow::Borrowed(valid_str), + Err(_) => Cow::Owned(String::from_utf8_lossy(&self.0).to_string()), + }; + } +} + +impl Into for HttpMockBytes { + fn into(self) -> Bytes { + self.0.clone() + } +} + +impl From for HttpMockBytes { + fn from(value: Bytes) -> Self { + HttpMockBytes(value) + } +} + +impl PartialEq for HttpMockBytes { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl AsRef<[u8]> for HttpMockBytes { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl std::fmt::Display for HttpMockBytes { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match std::str::from_utf8(&self.0) { + Ok(result) => write!(f, "{}", result), + Err(_) => write!(f, "{}", base64::encode(&self.0)), + } + } +} + +pub fn title_case(s: &str) -> String { + let mut result = String::new(); + let mut capitalize_next = true; + + for c in s.chars() { + if c.is_whitespace() { + capitalize_next = true; + result.push(c); + } else if capitalize_next { + result.push(c.to_uppercase().next().unwrap()); + capitalize_next = false; + } else { + result.push(c.to_lowercase().next().unwrap()); + } + } + + result +} + +pub fn is_none_or_empty(option: &Option>) -> bool { + match option { + None => true, + Some(vec) => vec.is_empty(), + } +} diff --git a/src/lib.rs b/src/lib.rs index c239b67a..a12a58ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,27 +3,32 @@ //! //! # Features //! * Simple, expressive, fluent API. -//! * Many built-in helpers for easy request matching. +//! * Many built-in helpers for easy request matching ([Regex](https://docs.rs/regex/), JSON, [serde](https://crates.io/crates/serde), cookies, and more). //! * Parallel test execution. -//! * Extensible request matching. +//! * Custom request matchers. +//! * Record and Playback +//! * Forward and Proxy Mode +//! * HTTPS support +//! * Fault and network delay simulation. +//! * Standalone mode with an accompanying [Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock). +//! * Helpful error messages +//! * [Advanced verification and debugging support](https://alexliesenfeld.github.io/posts/mocking-http--services-in-rust/#creating-mocks) (including diff generation between actual and expected HTTP request values) //! * Fully asynchronous core with synchronous and asynchronous APIs. -//! * [Advanced verification and debugging support](https://web.archive.org/web/20201202160613/https://dev.to/alexliesenfeld/rust-http-testing-with-httpmock-2mi0#verification) -//! * [Network delay simulation](https://github.com/alexliesenfeld/httpmock/blob/master/tests/examples/delay_tests.rs). //! * Support for [Regex](https://docs.rs/regex/) matching, JSON, [serde](https://crates.io/crates/serde), cookies, and more. -//! * Standalone mode with an accompanying [Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock). -//! * Support for [mock specification based on YAML files](https://github.com/alexliesenfeld/httpmock/blob/master/src/lib.rs#L185-L201). +//! * Support for [mock configuration using YAML files](https://github.com/alexliesenfeld/httpmock/tree/master#file-based-mock-specification). //! //! # Getting Started //! Add `httpmock` to `Cargo.toml`: //! //! ```toml //! [dev-dependencies] -//! httpmock = "0.7.0" +//! httpmock = "0.8.0-alpha.1" //! ``` //! //! You can then use `httpmock` as follows: //! ``` //! use httpmock::prelude::*; +//! use reqwest::blocking::get; //! //! // Start a lightweight mock server. //! let server = MockServer::start(); @@ -39,7 +44,7 @@ //! }); //! //! // Send an HTTP request to the mock server. This simulates your code. -//! let response = isahc::get(server.url("/translate?word=hello")).unwrap(); +//! let response = get(&server.url("/translate?word=hello")).unwrap(); //! //! // Ensure the specified mock was called exactly one time (or fail with a detailed error description). //! hello_mock.assert(); @@ -52,160 +57,8 @@ //! //! ![colored-diff.png](https://raw.githubusercontent.com/alexliesenfeld/httpmock/master/docs/diff.png) //! -//! # Usage -//! To be able to configure mocks, you first need to start a mock server by calling -//! [MockServer::start](struct.MockServer.html#method.start). -//! This will spin up a lightweight HTTP -//! mock server in the background and wait until the server is ready to accept requests. -//! -//! You can then create a [Mock](struct.Mock.html) object on the server by using the -//! [MockServer::mock](struct.MockServer.html#method.mock) method. This method expects a closure -//! with two parameters, that we will refer to as the `when` and `then` parameter: -//! - The `when` parameter is of type [When](struct.When.html) and holds all request characteristics. -//! The mock server will only respond to HTTP requests that meet all the criteria. Otherwise it -//! will respond with HTTP status code `404` and an error message. -//! - The `then` parameter is of type [Then](struct.Then.html) and holds all values that the mock -//! server will respond with. -//! -//! # Sync / Async -//! The internal implementation of `httpmock` is completely asynchronous. It provides you a -//! synchronous and an asynchronous API though. If you want to schedule awaiting operations manually, then -//! you can use the `async` variants that exist for every potentially blocking operation. For -//! example, there is [MockServer::start_async](struct.MockServer.html#method.start_async) as an -//! asynchronous counterpart to [MockServer::start](struct.MockServer.html#method.start). You can -//! find similar methods throughout the entire library. -//! -//! # Parallelism -//! To balance execution speed and resource consumption, [MockServer](struct.MockServer.html)s -//! are kept in a server pool internally. This allows to run tests in parallel without overwhelming -//! the executing machine by creating too many HTTP servers. A test will be blocked if it tries to -//! use a [MockServer](struct.MockServer.html) (e.g. by calling -//! [MockServer::start](struct.MockServer.html#method.start)) while the server pool is empty -//! (i.e. all servers are occupied by other tests). -//! -//! [MockServer](struct.MockServer.html)s are never recreated but recycled/reset. -//! The pool is filled on demand up to a maximum number of 25 servers. -//! You can override this number by using the environment variable `HTTPMOCK_MAX_SERVERS`. -//! -//! # Debugging -//! `httpmock` logs against the [log](https://crates.io/crates/log) crate. This allows you to -//! see detailed log output that contains information about `httpmock`s behaviour. -//! You can use this log output to investigate -//! issues, such as to find out why a request does not match a mock definition. -//! -//! The most useful log level is `debug`, but you can also go down to `trace` to see even more -//! information. -//! -//! **Attention**: To be able to see the log output, you need to add the `--nocapture` argument -//! when starting test execution! -//! -//! *Hint*: If you use the `env_logger` backend, you need to set the `RUST_LOG` environment variable to -//! `httpmock=debug`. -//! -//! # API Alternatives -//! This library provides two functionally interchangeable DSL APIs that allow you to create -//! mocks on the server. You can choose the one you like best or use both side-by-side. For a -//! consistent look, it is recommended to stick to one of them, though. -//! -//! ## When/Then API -//! This is the default API of `httpmock`. It is concise and easy to read. The main goal -//! is to reduce overhead emposed by this library to a bare minimum. It works well with -//! formatting tools, such as [rustfmt](https://crates.io/crates/rustfmt) (i.e. `cargo fmt`), -//! and can fully benefit from IDE support. -//! -//! ### Example -//! ``` -//! let server = httpmock::MockServer::start(); -//! -//! let greeting_mock = server.mock(|when, then| { -//! when.path("/hi"); -//! then.status(200); -//! }); -//! -//! let response = isahc::get(server.url("/hi")).unwrap(); -//! -//! greeting_mock.assert(); -//! ``` -//! Note that `when` and `then` are variables. This allows you to rename them to something you -//! like better (such as `expect`/`respond_with`). -//! -//! Relevant elements for this API are [MockServer::mock](struct.MockServer.html#method.mock), [When](struct.When.html) and [Then](struct.Then.html). -//! -//! # Examples -//! You can find examples in the test directory in this crates Git repository: -//! [this crates test directory](https://github.com/alexliesenfeld/httpmock/blob/master/tests ). -//! -//! # Standalone Mode -//! You can use `httpmock` to run a standalone mock server that runs in a separate process. -//! This allows it to be available to multiple applications, not only inside your unit and integration -//! tests. This is useful if you want to use `httpmock` in system (or even end-to-end) tests, that -//! require mocked services. With this feature, `httpmock` is a universal HTTP mocking tool that is -//! useful in all stages of the development lifecycle. -//! -//! ## Using a Standalone Mock Server -//! Although you can build the mock server in standalone mode yourself, it is easiest to use the -//! accompanying [Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock). -//! -//! To be able to use the standalone server from within your tests, you need to change how an -//! instance of the [MockServer](struct.MockServer.html) instance is created. -//! Instead of using [MockServer::start](struct.MockServer.html#method.start), -//! you need to connect to a remote server by using one of the `connect` methods (such as -//! [MockServer::connect](struct.MockServer.html#method.connect) or -//! [MockServer::connect_from_env](struct.MockServer.html#method.connect_from_env)). **Note**: -//! These are only available with the `remote` feature **enabled**. -//! -//! ``` -//! use httpmock::prelude::*; -//! use isahc::get; -//! -//! #[test] -//! fn simple_test() { -//! // Arrange -//! let server = MockServer::connect("some-host:5000"); -//! -//! let hello_mock = server.mock(|when, then|{ -//! when.path("/hello/standalone"); -//! then.status(200); -//! }); -//! -//! // Act -//! let response = get(server.url("/hello/standalone")).unwrap(); -//! -//! // Assert -//! hello_mock.assert(); -//! assert_eq!(response.status(), 200); -//! } -//! ``` -//! -//! ## Standalone Parallelism -//! To prevent interference with other tests, test functions are forced to use the standalone -//! mock server sequentially. -//! This means that test functions may be blocked when connecting to the remote server until -//! it becomes free again. -//! This is in contrast to tests that use a local mock server. -//! -//! ## Limitations of the Standalone Mode -//! At this time, it is not possible to use custom request matchers in combination with standalone -//! mock servers (see [When::matches](struct.When.html#method.matches) or -//! [Mock::expect_match](struct.Mock.html#method.expect_match)). -//! -//! ## Standalone Mode with YAML Mock Definition Files -//! The standalone server can also be used to read mock definitions from YAML files on startup once -//! and serve the mocked endpoints until the server is shut down again. These `static` mocks -//! cannot be deleted at runtime (even by Rust-based tests that use the mock server) and exist -//! for the entire uptime of the mock server. -//! -//! The definition files follow the standard `httpmock` API that you would also use in regular -//! Rust tests. Please find an example mock definition file in the `httpmock` Github repository -//! [here in this crates test directory](https://github.com/alexliesenfeld/httpmock/blob/master/tests/resources/static_yaml_mock.yaml). -//! -//! You can start the mock server with static mock support as follows: -//! * If you use the [Docker image from this creates repository](https://github.com/alexliesenfeld/httpmock/blob/master/Dockerfile) -//! or from [Dockerhub](https://hub.docker.com/r/alexliesenfeld/httpmock), you just need to mount a -//! volume with all your mock specification files to the `/mocks` directory within the container. -//! * If you build `httpmock` from source and use the binary, then you can pass the path to -//! the directory containing all your mock specification files using the `--static-mock-dir` -//! parameter. Example: `httpmock --expose --static-mock-dir=/mocks`. +//! # Online Documentation +//! Please find the official `httpmock` documentation and website at: http://alexliesenfeld.github.io/httpmock //! //! # License //! `httpmock` is free software: you can redistribute it and/or modify it under the terms @@ -215,11 +68,9 @@ //! without even the implied //! warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MIT Public //! License for more details. -#[macro_use] extern crate lazy_static; -use std::borrow::BorrowMut; -use std::net::ToSocketAddrs; +use std::{borrow::BorrowMut, net::ToSocketAddrs}; use std::str::FromStr; @@ -231,14 +82,19 @@ use common::util::Join; pub use api::{Method, Mock, MockExt, MockServer, Regex, Then, When}; mod api; -mod common; -mod server; -pub mod standalone; +pub mod common; +pub mod server; + +#[cfg(feature = "record")] +pub use common::data::RecordingRuleConfig; + +#[cfg(feature = "proxy")] +pub use common::data::{ForwardingRuleConfig, ProxyRuleConfig}; pub mod prelude { #[doc(no_inline)] pub use crate::{ - api::MockServer, common::data::HttpMockRequest, Method::DELETE, Method::GET, - Method::OPTIONS, Method::POST, Method::PUT, Regex, + api::MockServer, common::data::HttpMockRequest, Method, Method::DELETE, Method::GET, + Method::OPTIONS, Method::PATCH, Method::POST, Method::PUT, Regex, }; } diff --git a/src/main.rs b/src/main.rs index 69fbfbc4..b1d49e0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ +use std::{env, path::PathBuf}; + use clap::Parser; -use httpmock::standalone::start_standalone_server; -use std::env; -use std::path::PathBuf; + +use httpmock::server::HttpMockServerBuilder; /// Holds command line parameters provided by the user. #[derive(Parser, Debug)] @@ -10,7 +11,7 @@ use std::path::PathBuf; author = "Alexander Liesenfeld " )] struct ExecutionParameters { - #[clap(short, long, env = "HTTPMOCK_PORT", default_value = "5000")] + #[clap(short, long, env = "HTTPMOCK_PORT", default_value = "5050")] pub port: u16, #[clap(short, long, env = "HTTPMOCK_EXPOSE")] pub expose: bool, @@ -48,16 +49,19 @@ async fn main() { log::info!("{:?}", params); - start_standalone_server( - params.port, - params.expose, - params.mock_files_dir, - !params.disable_access_log, - params.request_history_limit, - shutdown_signal(), - ) - .await - .expect("an error occurred during mock server execution"); + let server = HttpMockServerBuilder::new() + .port(params.port) + .expose(params.expose) + .print_access_log(!params.disable_access_log) + .history_limit(params.request_history_limit) + .static_mock_dir_option(params.mock_files_dir) + .build() + .unwrap(); + + server + .start_with_signals(None, shutdown_signal()) + .await + .expect("an error occurred during mock server execution"); } #[cfg(not(target_os = "windows"))] diff --git a/src/server/builder.rs b/src/server/builder.rs new file mode 100644 index 00000000..a095970d --- /dev/null +++ b/src/server/builder.rs @@ -0,0 +1,507 @@ +#[cfg(any(feature = "proxy"))] +use crate::common::http::{HttpClient, HttpMockHttpClient}; +#[cfg(feature = "https")] +use crate::server::server::MockServerHttpsConfig; +#[cfg(feature = "https")] +use crate::server::tls::{CertificateResolverFactory, GeneratingCertificateResolverFactory}; + +use crate::server::{ + handler::HttpMockHandler, + persistence::read_static_mock_definitions, + server::{MockServer, MockServerConfig}, + state::{HttpMockStateManager, StateManager}, + HttpMockServer, +}; +use std::{error::Error, path::PathBuf, sync::Arc}; + +const DEFAULT_CA_PRIVATE_KEY: &'static str = include_str!("../../certs/ca.key"); +const DEFAULT_CA_CERTIFICATE: &'static str = include_str!("../../certs/ca.pem"); + +/// The Builder streamlines the configuration process, automatically setting up defaults and +/// handling dependency injection for the mock server. It consolidates configuration parameters, +/// fallback mechanisms, and default settings into a single point of management. +#[cfg(feature = "https")] +pub struct HttpsConfigBuilder { + ca_cert: Option, + ca_key: Option, + ca_cert_path: Option, + ca_key_path: Option, + enable_https: Option, + cert_resolver_factory: Option>, +} + +#[cfg(feature = "https")] +impl HttpsConfigBuilder { + fn new() -> Self { + Self { + ca_cert: None, + ca_key: None, + ca_cert_path: None, + ca_key_path: None, + cert_resolver_factory: None, + enable_https: None, + } + } + + /// Validates the HTTPS configuration to ensure no conflicting settings are present. + fn validate(&self) -> Result<(), Box> { + if self.enable_https.unwrap_or(true) { + let has_ca_cert = self.ca_cert.is_some() || self.ca_key.is_some(); + let has_ca_cert_path = self.ca_cert_path.is_some() || self.ca_key_path.is_some(); + let has_cert_generator = self.cert_resolver_factory.is_some(); + + if has_ca_cert && has_ca_cert_path { + return Err("A CA certificate and a CA certificate path have both been configured. Please choose only one method.".into()); + } + + if (has_ca_cert || has_ca_cert_path) && has_cert_generator { + return Err("Both a CA certificate and a certificate generator were configured. Please use only one of them.".into()); + } + } + + Ok(()) + } + + /// Sets the CA certificate for HTTPS. + /// + /// # Parameters + /// - `ca_cert`: An optional CA certificate as a string in PEM format. + /// + /// # Returns + /// A modified `HttpsConfigBuilder` instance for method chaining. + pub fn ca_cert(mut self, ca_cert: Option) -> Self + where + IntoString: Into, + { + self.ca_cert = ca_cert.map(|b| b.into()); + self + } + + /// Sets the CA private key for HTTPS. + /// + /// # Parameters + /// - `ca_key`: An optional CA private key as a string in PEM format. + /// + /// # Returns + /// A modified `HttpsConfigBuilder` instance for method chaining. + pub fn ca_key(mut self, ca_key: Option) -> Self + where + IntoString: Into, + { + self.ca_key = ca_key.map(|b| b.into()); + self + } + + /// Sets the path to the CA certificate for HTTPS. + /// + /// # Parameters + /// - `ca_cert_path`: An optional path to the CA certificate in PEM format. + /// + /// # Returns + /// A modified `HttpsConfigBuilder` instance for method chaining. + pub fn ca_cert_path(mut self, ca_cert_path: Option) -> Self { + self.ca_cert_path = ca_cert_path; + self + } + + /// Sets the path to the CA private key for HTTPS. + /// + /// # Parameters + /// - `ca_key_path`: An optional path to the CA private key. + /// + /// # Returns + /// A modified `HttpsConfigBuilder` instance for method chaining. + pub fn ca_key_path(mut self, ca_key_path: Option) -> Self { + self.ca_key_path = ca_key_path; + self + } + + /// Sets the certificate resolver factory for generating certificates. + /// + /// # Parameters + /// - `generator`: An optional certificate resolver factory. + /// + /// # Returns + /// A modified `HttpsConfigBuilder` instance for method chaining. + pub(crate) fn cert_resolver( + mut self, + generator: Option>, + ) -> Self { + self.cert_resolver_factory = generator; + self + } + + /// Enables or disables HTTPS. + /// + /// # Parameters + /// - `enable`: An optional boolean to enable or disable HTTPS. + /// + /// # Returns + /// A modified `HttpsConfigBuilder` instance for method chaining. + pub fn enable_https(mut self, enable: Option) -> Self { + self.enable_https = enable; + self + } + + /// Builds the `MockServerHttpsConfig` with the current settings. + /// + /// # Returns + /// A `MockServerHttpsConfig` instance or an error if validation fails. + pub fn build(mut self) -> Result> { + self.validate()?; + + let cert_resolver_factory = match ( + self.cert_resolver_factory, + self.ca_cert_path, + self.ca_key_path, + self.ca_cert, + self.ca_key, + ) { + // If a direct resolver was provided, use it. + (Some(cert_resolver), _, _, _, _) => cert_resolver, + // If paths are provided, read the certificates and create a default resolver + // with these certs. + (_, Some(ca_cert_path), Some(ca_key_path), _, _) => { + let ca_cert = std::fs::read_to_string(ca_cert_path)?; + let ca_key = std::fs::read_to_string(ca_key_path)?; + Arc::new(GeneratingCertificateResolverFactory::new(ca_cert, ca_key)?) + } + // If certificate data is directly provided, use it to create the resolver. + (_, _, _, Some(ca_cert), Some(ca_key)) => Arc::new( + GeneratingCertificateResolverFactory::new(ca_cert.clone(), ca_key.clone())?, + ), + // If no CA certificate information was configured, use the default. + _ => Arc::new(GeneratingCertificateResolverFactory::new( + DEFAULT_CA_CERTIFICATE, + DEFAULT_CA_PRIVATE_KEY, + )?), + }; + + Ok(MockServerHttpsConfig { + cert_resolver_factory, + }) + } +} + +/// The `HttpMockServerBuilder` struct is used to configure the HTTP mock server. +/// It provides methods to set various configuration options such as port, logging, history limit, and HTTPS settings. +pub struct HttpMockServerBuilder { + port: Option, + expose: Option, + print_access_log: Option, + history_limit: Option, + #[cfg(feature = "static-mock")] + static_mock_dir: Option, + #[cfg(feature = "https")] + https_config_builder: HttpsConfigBuilder, + #[cfg(feature = "proxy")] + http_client: Option>, +} + +impl HttpMockServerBuilder { + /// Creates a new instance of `HttpMockServerBuilder` with default settings. + /// + /// # Returns + /// A new `HttpMockServerBuilder` instance. + pub fn new() -> Self { + HttpMockServerBuilder { + print_access_log: None, + port: None, + expose: None, + history_limit: None, + #[cfg(feature = "static-mock")] + static_mock_dir: None, + #[cfg(feature = "proxy")] + http_client: None, + #[cfg(feature = "https")] + https_config_builder: HttpsConfigBuilder::new(), + } + } + + /// Sets the port for the HTTP mock server. + /// + /// # Parameters + /// - `port`: The port number. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn port(mut self, port: u16) -> Self { + self.port = Some(port); + self + } + + /// Sets the port for the HTTP mock server as an optional value. + /// + /// # Parameters + /// - `port`: An optional port number. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn port_option(mut self, port: Option) -> Self { + self.port = port; + self + } + + /// Sets whether the server should be exposed to external access. + /// + /// # Parameters + /// - `expose`: A boolean indicating whether to expose the server. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn expose(mut self, expose: bool) -> Self { + self.expose = Some(expose); + self + } + + /// Sets whether the server should be exposed to external access as an optional value. + /// + /// # Parameters + /// - `expose`: An optional boolean indicating whether to expose the server. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn expose_option(mut self, expose: Option) -> Self { + self.expose = expose; + self + } + + /// Sets whether to print access logs. + /// + /// # Parameters + /// - `enabled`: A boolean indicating whether to print access logs. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn print_access_log(mut self, enabled: bool) -> Self { + self.print_access_log = Some(enabled); + self + } + + /// Sets whether to print access logs as an optional value. + /// + /// # Parameters + /// - `enabled`: An optional boolean indicating whether to print access logs. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn print_access_log_option(mut self, enabled: Option) -> Self { + self.print_access_log = enabled; + self + } + + /// Sets the history limit for the server. + /// + /// # Parameters + /// - `limit`: The maximum number of history entries to keep. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn history_limit(mut self, limit: usize) -> Self { + self.history_limit = Some(limit); + self + } + + /// Sets the history limit for the server as an optional value. + /// + /// # Parameters + /// - `limit`: An optional maximum number of history entries to keep. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + pub fn history_limit_option(mut self, limit: Option) -> Self { + self.history_limit = limit; + self + } + + /// Sets the directory for static mock files. + /// + /// # Parameters + /// - `path`: The path to the static mock directory. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "static-mock")] + pub fn static_mock_dir(mut self, path: PathBuf) -> Self { + self.static_mock_dir = Some(path); + self + } + + /// Sets the directory for static mock files as an optional value. + /// + /// # Parameters + /// - `path`: An optional path to the static mock directory. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "static-mock")] + pub fn static_mock_dir_option(mut self, path: Option) -> Self { + self.static_mock_dir = path; + self + } + + /// Sets the certificate resolver factory for generating certificates. + /// + /// # Parameters + /// - `factory`: A certificate resolver factory. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "https")] + pub fn server_config_factory( + mut self, + factory: Arc, + ) -> Self { + self.https_config_builder = self.https_config_builder.cert_resolver(Some(factory)); + self + } + + /// Sets the certificate resolver factory as an optional value. + /// + /// # Parameters + /// - `factory`: An optional certificate resolver factory. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "https")] + pub fn cert_resolver_option( + mut self, + factory: Option>, + ) -> Self { + self.https_config_builder = self.https_config_builder.cert_resolver(factory); + self + } + + /// Sets the CA certificate and private key for HTTPS. + /// + /// # Parameters + /// - `cert`: The CA certificate. + /// - `private_key`: The CA private key. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "https")] + pub fn https_ca_key_pair>( + mut self, + cert: IntoString, + private_key: IntoString, + ) -> Self { + self.https_config_builder = self.https_config_builder.ca_cert(Some(cert)); + self.https_config_builder = self.https_config_builder.ca_key(Some(private_key)); + self + } + + /// Sets the CA certificate and private key for HTTPS as optional values. + /// + /// # Parameters + /// - `cert`: An optional CA certificate. + /// - `private_key`: An optional CA private key. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "https")] + pub fn https_ca_key_pair_option>( + mut self, + cert: Option, + private_key: Option, + ) -> Self { + self.https_config_builder = self.https_config_builder.ca_cert(cert); + self.https_config_builder = self.https_config_builder.ca_key(private_key); + self + } + + /// Sets the paths to the CA certificate and private key files for HTTPS. + /// + /// # Parameters + /// - `cert_path`: The path to the CA certificate file. + /// - `private_key_path`: The path to the CA private key file. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "https")] + pub fn https_ca_key_pair_files>( + mut self, + cert_path: Path, + private_key_path: Path, + ) -> Self { + self.https_config_builder = self + .https_config_builder + .ca_cert_path(Some(cert_path.into())); + self.https_config_builder = self + .https_config_builder + .ca_key_path(Some(private_key_path.into())); + self + } + + /// Sets the paths to the CA certificate and private key files for HTTPS as optional values. + /// + /// # Parameters + /// - `cert_path`: An optional path to the CA certificate file. + /// - `private_key_path`: An optional path to the CA private key file. + /// + /// # Returns + /// A modified `HttpMockServerBuilder` instance for method chaining. + #[cfg(feature = "https")] + pub fn https_ca_key_pair_files_option>( + mut self, + cert_path: Option, + private_key_path: Option, + ) -> Self { + let cert_path = cert_path.map(|b| b.into()); + let private_key_path = private_key_path.map(|b| b.into()); + + self.https_config_builder = self.https_config_builder.ca_cert_path(cert_path); + self.https_config_builder = self.https_config_builder.ca_key_path(private_key_path); + self + } + + /// Builds the `HttpMockServer` with the current settings. + /// + /// # Returns + /// A `HttpMockServer` instance or an error if the build process fails. + pub fn build(self) -> Result> { + self.build_with_state(Arc::new(HttpMockStateManager::default())) + } + + /// Builds the `MockServer` with the current settings and provided state manager. + /// + /// # Parameters + /// - `state`: The state manager to use. + /// + /// # Returns + /// A `MockServer` instance or an error if the build process fails. + pub(crate) fn build_with_state( + mut self, + state: Arc, + ) -> Result>, Box> + where + S: StateManager + Send + Sync + 'static, + { + #[cfg(feature = "proxy")] + let http_client = self + .http_client + .unwrap_or_else(|| Arc::new(HttpMockHttpClient::new(None))); + + #[cfg(feature = "static-mock")] + if let Some(dir) = self.static_mock_dir { + read_static_mock_definitions(dir, state.as_ref())?; + } + + let handler = HttpMockHandler::new( + state, + #[cfg(feature = "proxy")] + http_client, + ); + + Ok(MockServer::new( + Box::new(handler), + MockServerConfig { + static_port: self.port, + expose: self.expose.unwrap_or(false), + print_access_log: self.print_access_log.unwrap_or(false), + #[cfg(feature = "https")] + https: self.https_config_builder.build()?, + }, + )?) + } +} diff --git a/src/server/handler.rs b/src/server/handler.rs new file mode 100644 index 00000000..66696a6a --- /dev/null +++ b/src/server/handler.rs @@ -0,0 +1,553 @@ +use crate::common::data::{ + ActiveForwardingRule, ActiveProxyRule, Error as DataError, ErrorResponse, MockDefinition, + RequestRequirements, +}; + +use crate::{ + common::runtime, + server::{ + handler::Error::{ + InvalidHeader, ParamError, ParamFormatError, RequestBodyDeserializeError, + RequestConversionError, ResponseBodyConversionError, ResponseBodySerializeError, + }, + state, + state::StateManager, + }, +}; +use std::convert::TryInto; + +#[cfg(any(feature = "remote", feature = "proxy"))] +use crate::common::http::{Error as HttpClientError, HttpClient}; + +use crate::common::data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig}; + +use crate::prelude::HttpMockRequest; +use async_std::{sync::Mutex, task}; +use async_trait::async_trait; +use http::{HeaderMap, HeaderName, HeaderValue, StatusCode, Uri}; +use http_body_util::BodyExt; +use hyper::{body::Bytes, Method, Request, Response}; +use path_tree::{Path, PathTree}; +use serde::{de::DeserializeOwned, Serialize}; +use std::{ + fmt::{Debug, Display}, + str::FromStr, + sync::Arc, + thread, + time::Duration, +}; +use thiserror::Error; +use tokio::time::Instant; + +#[derive(Error, Debug)] +pub enum Error { + #[error("cannot parse regex: {0}")] + RegexError(#[from] regex::Error), + #[error("cannot deserialize request body: {0}")] + RequestBodyDeserializeError(serde_json::Error), + #[error("cannot process request body: {0}")] + RequestBodyError(String), + #[error("cannot serialize response body: {0}")] + ResponseBodySerializeError(serde_json::Error), + #[error("cannot convert response body: {0}")] + ResponseBodyConversionError(http::Error), + #[error("expected URL parameters not found")] + ParamError, + #[error("URL parameter format is invalid: {0}")] + ParamFormatError(String), + #[error("cannot modify state: {0}")] + StateManagerError(#[from] state::Error), + #[error("invalid status code: {0}")] + InvalidStatusCode(#[from] http::status::InvalidStatusCode), + #[error("cannot convert request to internal data structure: {0}")] + RequestConversionError(String), + #[cfg(any(feature = "remote", feature = "proxy"))] + #[error("failed to send HTTP request: {0}")] + HttpClientError(#[from] HttpClientError), + #[error("invalid header: {0}")] + InvalidHeader(String), + #[error("unknown error")] + Unknown, +} + +enum RoutePath { + Ping, + Reset, + MockCollection, + SingleMock, + History, + Verify, + SingleForwardingRule, + ForwardingRuleCollection, + ProxyRuleCollection, + SingleProxyRule, + RecordingCollection, + SingleRecording, +} + +#[async_trait] +pub(crate) trait Handler { + async fn handle(&self, req: Request) -> Result, Error>; +} + +pub struct HttpMockHandler +where + S: StateManager + Send + Sync + 'static, +{ + path_tree: PathTree, + state: Arc, + #[cfg(feature = "proxy")] + http_client: Arc, +} + +#[async_trait] +impl Handler for HttpMockHandler +where + H: StateManager + Send + Sync + 'static, +{ + async fn handle(&self, req: Request) -> Result, Error> { + log::trace!("Routing incoming request: {:?}", req); + + let method = req.method().clone(); + let path = req.uri().path().to_string(); + + if let Some((matched_path, params)) = self.path_tree.find(&path) { + match matched_path { + RoutePath::Ping => match method { + Method::GET => return self.handle_ping(), + _ => {} + }, + RoutePath::Reset => match method { + Method::DELETE => return self.handle_reset(), + _ => {} + }, + RoutePath::SingleMock => match method { + Method::GET => return self.handle_read_mock(params), + Method::DELETE => return self.handle_delete_mock(params), + _ => {} + }, + RoutePath::MockCollection => match method { + Method::POST => return self.handle_add_mock(req), + Method::DELETE => return self.handle_delete_all_mocks(), + _ => {} + }, + RoutePath::History => match method { + Method::DELETE => return self.handle_delete_history(), + _ => {} + }, + RoutePath::Verify => match method { + Method::POST => return self.handle_verify(req), + _ => {} + }, + RoutePath::ForwardingRuleCollection => match method { + Method::POST => return self.handle_add_forwarding_rule(req), + Method::DELETE => return self.handle_delete_all_forwarding_rules(), + _ => {} + }, + RoutePath::SingleForwardingRule => match method { + Method::DELETE => return self.handle_delete_forwarding_rule(params), + _ => {} + }, + RoutePath::ProxyRuleCollection => match method { + Method::POST => return self.handle_add_proxy_rule(req), + Method::DELETE => return self.handle_delete_all_proxy_rules(), + _ => {} + }, + RoutePath::SingleProxyRule => match method { + Method::DELETE => return self.handle_delete_proxy_rule(params), + _ => {} + }, + RoutePath::RecordingCollection => match method { + Method::POST => return self.handle_add_recording_matcher(req), + Method::DELETE => return self.handle_delete_all_recording_matchers(), + _ => {} + }, + RoutePath::SingleRecording => match method { + Method::GET => return self.handle_read_recording(params), + Method::DELETE => return self.handle_delete_recording(params), + Method::POST => return self.handle_load_recording(req), + _ => {} + }, + } + } + + return self.catch_all(req).await; + } +} + +impl HttpMockHandler +where + H: StateManager + Send + Sync + 'static, +{ + pub fn new( + state: Arc, + #[cfg(feature = "proxy")] http_client: Arc, + ) -> Self { + let mut path_tree: PathTree = PathTree::new(); + #[allow(unused_must_use)] + { + path_tree.insert("/__httpmock__/ping", RoutePath::Ping); + path_tree.insert("/__httpmock__/state", RoutePath::Reset); + path_tree.insert("/__httpmock__/mocks", RoutePath::MockCollection); + path_tree.insert("/__httpmock__/mocks/:id", RoutePath::SingleMock); + path_tree.insert("/__httpmock__/verify", RoutePath::Verify); + path_tree.insert("/__httpmock__/history", RoutePath::History); + path_tree.insert( + "/__httpmock__/forwarding_rules", + RoutePath::ForwardingRuleCollection, + ); + path_tree.insert("/__httpmock__/proxy_rules", RoutePath::ProxyRuleCollection); + path_tree.insert("/__httpmock__/recordings", RoutePath::RecordingCollection); + } + + Self { + path_tree, + state, + #[cfg(feature = "proxy")] + http_client, + } + } + + fn ping(&self) -> Result<(), Error> { + Ok(()) + } + + fn handle_ping(&self) -> Result, Error> { + return response::<()>(StatusCode::OK, None); + } + + fn handle_reset(&self) -> Result, Error> { + self.state.reset(); + return response::<()>(StatusCode::NO_CONTENT, None); + } + + fn handle_add_mock(&self, req: Request) -> Result, Error> { + let definition: MockDefinition = parse_json_body(req)?; + let active_mock = self.state.add_mock(definition, false)?; + return response(StatusCode::CREATED, Some(active_mock)); + } + + fn handle_read_mock(&self, params: Path) -> Result, Error> { + let active_mock = self.state.read_mock(param("id", params)?)?; + let status_code = active_mock + .as_ref() + .map_or(StatusCode::NOT_FOUND, |_| StatusCode::OK); + return response(status_code, active_mock); + } + + fn handle_delete_mock(&self, params: Path) -> Result, Error> { + let deleted = self.state.delete_mock(param("id", params)?)?; + let status_code = if deleted { + StatusCode::NO_CONTENT + } else { + StatusCode::NOT_FOUND + }; + return response::<()>(status_code, None); + } + + fn handle_delete_all_mocks(&self) -> Result, Error> { + self.state.delete_all_mocks(); + return response::<()>(StatusCode::NO_CONTENT, None); + } + + fn handle_delete_history(&self) -> Result, Error> { + self.state.delete_history(); + return response::<()>(StatusCode::NO_CONTENT, None); + } + + fn handle_verify(&self, req: Request) -> Result, Error> { + let requirements: RequestRequirements = parse_json_body(req)?; + let closest_match = self.state.verify(&requirements)?; + let status_code = closest_match + .as_ref() + .map_or(StatusCode::NOT_FOUND, |_| StatusCode::OK); + return response(status_code, closest_match); + } + + fn handle_add_forwarding_rule(&self, req: Request) -> Result, Error> { + let config: ForwardingRuleConfig = parse_json_body(req)?; + let active_forwarding_rule = self.state.create_forwarding_rule(config); + return response(StatusCode::CREATED, Some(active_forwarding_rule)); + } + + fn handle_delete_forwarding_rule(&self, params: Path) -> Result, Error> { + let deleted = self.state.delete_forwarding_rule(param("id", params)?); + let status_code = if deleted.is_some() { + StatusCode::NO_CONTENT + } else { + StatusCode::NOT_FOUND + }; + return response::<()>(status_code, None); + } + + fn handle_delete_all_forwarding_rules(&self) -> Result, Error> { + self.state.delete_all_forwarding_rules(); + return response::<()>(StatusCode::NO_CONTENT, None); + } + + fn handle_add_proxy_rule(&self, req: Request) -> Result, Error> { + let config: ProxyRuleConfig = parse_json_body(req)?; + let active_proxy_rule = self.state.create_proxy_rule(config); + return response(StatusCode::CREATED, Some(active_proxy_rule)); + } + + fn handle_delete_proxy_rule(&self, params: Path) -> Result, Error> { + let deleted = self.state.delete_proxy_rule(param("id", params)?); + let status_code = if deleted.is_some() { + StatusCode::NO_CONTENT + } else { + StatusCode::NOT_FOUND + }; + return response::<()>(status_code, None); + } + + fn handle_delete_all_proxy_rules(&self) -> Result, Error> { + self.state.delete_all_proxy_rules(); + return response::<()>(StatusCode::NO_CONTENT, None); + } + + fn handle_add_recording_matcher(&self, req: Request) -> Result, Error> { + let req_req: RecordingRuleConfig = parse_json_body(req)?; + let active_recording = self.state.create_recording(req_req); + return response(StatusCode::CREATED, Some(active_recording)); + } + + fn handle_delete_recording(&self, params: Path) -> Result, Error> { + let deleted = self.state.delete_proxy_rule(param("id", params)?); + let status_code = if deleted.is_some() { + StatusCode::NO_CONTENT + } else { + StatusCode::NOT_FOUND + }; + return response::<()>(status_code, None); + } + + fn handle_delete_all_recording_matchers(&self) -> Result, Error> { + self.state.delete_all_recordings(); + return response::<()>(StatusCode::NO_CONTENT, None); + } + + fn handle_read_recording(&self, params: Path) -> Result, Error> { + let rec = self.state.export_recording(param("id", params)?)?; + let status_code = rec + .as_ref() + .map_or(StatusCode::NOT_FOUND, |_| StatusCode::OK); + return response(status_code, rec); + } + + fn handle_load_recording(&self, req: Request) -> Result, Error> { + let recording_file_content = std::str::from_utf8(&req.body()) + .map_err(|err| RequestConversionError(err.to_string()))?; + + let rec = self + .state + .load_mocks_from_recording(recording_file_content)?; + return response(StatusCode::OK, Some(rec)); + } + + async fn catch_all(&self, req: Request) -> Result, Error> { + let internal_request: HttpMockRequest = (&req) + .try_into() + .map_err(|err: DataError| RequestConversionError(err.to_string()))?; + + let mut is_proxied = false; + + let start = Instant::now(); + + #[cfg(feature = "proxy")] + let res = if let Some(rule) = self.state.find_forward_rule(&internal_request)? { + self.forward(rule, req).await? + } else if let Some(rule) = self.state.find_proxy_rule(&internal_request)? { + is_proxied = true; + self.proxy(rule, req).await? + } else { + self.serve_mock(&internal_request).await? + }; + + #[cfg(not(feature = "proxy"))] + let res = self.serve_mock(&internal_request).await?; + + #[cfg(feature = "record")] + self.state + .record(is_proxied, start.elapsed(), internal_request, &res)?; + + Ok(res) + } + + #[cfg(feature = "proxy")] + async fn forward( + &self, + rule: ActiveForwardingRule, + req: Request, + ) -> Result, Error> { + let to_base_uri: Uri = rule.config.target_base_url.parse().unwrap(); + + let (mut req_parts, body) = req.into_parts(); + + // We need to remove the host header, because it contains the host of this mock server. + req_parts.headers.remove(http::header::HOST); + + let mut uri_parts = req_parts.uri.into_parts(); + uri_parts.authority = Some(to_base_uri.authority().unwrap().clone()); + uri_parts.scheme = to_base_uri.scheme().map(|s| s.clone()).or(uri_parts.scheme); + req_parts.uri = Uri::from_parts(uri_parts).unwrap(); + + if !rule.config.request_header.is_empty() { + for (key, value) in &rule.config.request_header { + let key = HeaderName::from_str(key).map_err(|err| { + InvalidHeader(format!("invalid header key: {}", err.to_string())) + })?; + + let value = HeaderValue::from_str(value).map_err(|err| { + InvalidHeader(format!("invalid header value: {}", err.to_string())) + })?; + + req_parts.headers.insert(key, value); + } + } + + let req = Request::from_parts(req_parts, body); + Ok(self.http_client.send(req).await?) + } + + #[cfg(feature = "proxy")] + async fn proxy( + &self, + rule: ActiveProxyRule, + mut req: Request, + ) -> Result, Error> { + if !rule.config.request_header.is_empty() { + let headers = req.headers_mut(); + + for (key, value) in &rule.config.request_header { + let key = HeaderName::from_str(key).map_err(|err| { + InvalidHeader(format!("invalid header key: {}", err.to_string())) + })?; + + let value = HeaderValue::from_str(value).map_err(|err| { + InvalidHeader(format!("invalid header value: {}", err.to_string())) + })?; + + headers.insert(key, value); + } + } + + Ok(self.http_client.send(req).await?) + } + + async fn serve_mock(&self, req: &HttpMockRequest) -> Result, Error> { + let mock_response = self.state.serve_mock(req)?; + + if let Some(mock_response) = mock_response { + let status_code = match mock_response.status.as_ref() { + None => StatusCode::OK, + Some(c) => StatusCode::from_u16(c.clone())?, + }; + + let mut builder = Response::builder().status(status_code); + + if let Some(headers) = mock_response.headers { + for (name, value) in headers { + builder = builder.header(name, value); + } + } + + let response = builder + .body( + mock_response + .body + .map_or(Bytes::new(), |bytes| bytes.to_bytes()), + ) + .map_err(|e| ResponseBodyConversionError(e))?; + + if let Some(duration) = mock_response.delay { + runtime::sleep(Duration::from_millis(duration)).await; + } + + return Ok(response); + } + + let status_code = mock_response.map_or(StatusCode::NOT_FOUND, |_| StatusCode::OK); + + return response( + status_code, + Some(ErrorResponse::new( + &"Request did not match any route or mock", + )), + ); + } +} + +fn param(name: &str, tree_path: Path) -> Result +where + T: FromStr, + T::Err: Debug + Display, +{ + for (n, v) in tree_path.params() { + if n.eq(name) { + let parse_result: Result = v.parse::(); + let parsed_value = parse_result.map_err(|e| ParamFormatError(format!("{:?}", e)))?; + return Ok(parsed_value); + } + } + + Err(ParamError) +} + +fn response(status: StatusCode, body: Option) -> Result, Error> +where + T: Serialize, +{ + let mut builder = Response::builder().status(status); + + if let Some(body_obj) = body { + builder = builder.header("content-type", "application/json"); + + let body_bytes = + serde_json::to_vec(&body_obj).map_err(|e| ResponseBodySerializeError(e))?; + + return Ok(builder + .body(Bytes::from(body_bytes)) + .map_err(|e| ResponseBodyConversionError(e))?); + } + + return Ok(builder + .body(Bytes::new()) + .map_err(|e| ResponseBodyConversionError(e))?); +} + +fn parse_json_body(req: Request) -> Result +where + T: DeserializeOwned, +{ + let body: T = + serde_json::from_slice(req.body().as_ref()).map_err(|e| RequestBodyDeserializeError(e))?; + Ok(body) +} + +fn extract_query_params(req: &Request) -> Result, Error> { + // There doesn't seem to be a way to just parse Query string with the `url` crate, so we're + // prefixing a dummy URL for parsing. + let url = format!("http://dummy?{}", req.uri().query().unwrap_or("")); + let url = url::Url::parse(&url).map_err(|e| RequestConversionError(e.to_string()))?; + + let query_params = url + .query_pairs() + .map(|(k, v)| (k.into(), v.into())) + .collect(); + + Ok(query_params) +} + +fn headers_to_vec(req: &Request) -> Result, Error> { + req.headers() + .iter() + .map(|(name, value)| { + // Attempt to convert the HeaderValue to a &str, returning an error if it fails. + let value_str = value + .to_str() + .map_err(|e| RequestConversionError(e.to_string()))?; + Ok((name.as_str().to_string(), value_str.to_string())) + }) + .collect() +} diff --git a/src/server/matchers/comparators.rs b/src/server/matchers/comparators.rs index 8b783492..89e0a79e 100644 --- a/src/server/matchers/comparators.rs +++ b/src/server/matchers/comparators.rs @@ -1,12 +1,24 @@ use assert_json_diff::{assert_json_matches_no_panic, CompareMode, Config}; +use bytes::Bytes; use serde_json::Value; - -use crate::common::data::{HttpMockRequest, MockMatcherFunction}; -use crate::server::matchers::distance_for; -use crate::Regex; - -pub trait ValueComparator { - fn matches(&self, mock_value: &S, req_value: &T) -> bool; +use std::{borrow::Cow, convert::TryInto, ops::Deref, sync::Arc}; + +use crate::{ + common::{ + data::{HttpMockRegex, HttpMockRequest}, + util::HttpMockBytes, + }, + server::matchers::comparison::{ + distance_for, distance_for_prefix, distance_for_substring, distance_for_suffix, + equal_weight_distance_for, hostname_equals, regex_unmatched_length, string_contains, + string_distance, string_equals, string_has_prefix, string_has_suffix, + }, +}; + +use crate::server::matchers::comparison; + +pub trait ValueComparator { + fn matches(&self, mock_value: &Option<&S>, req_value: &Option<&T>) -> bool; fn name(&self) -> &str; fn distance(&self, mock_value: &Option<&S>, req_value: &Option<&T>) -> usize; } @@ -23,9 +35,15 @@ impl JSONExactMatchComparator { } impl ValueComparator for JSONExactMatchComparator { - fn matches(&self, mock_value: &Value, req_value: &Value) -> bool { - let config = Config::new(CompareMode::Strict); - assert_json_matches_no_panic(req_value, mock_value, config).is_ok() + fn matches(&self, mock_value: &Option<&Value>, req_value: &Option<&Value>) -> bool { + match (mock_value, req_value) { + (None, _) => true, + (Some(_), None) => false, + (Some(mv), Some(rv)) => { + let config = Config::new(CompareMode::Strict); + assert_json_matches_no_panic(rv, mv, config).is_ok() + } + } } fn name(&self) -> &str { @@ -33,91 +51,368 @@ impl ValueComparator for JSONExactMatchComparator { } fn distance(&self, mock_value: &Option<&Value>, req_value: &Option<&Value>) -> usize { - distance_for(mock_value, req_value) + let mv_bytes = mock_value.map_or(Vec::new(), |v| v.to_string().into_bytes()); + let rv_bytes = req_value.map_or(Vec::new(), |v| v.to_string().into_bytes()); + distance_for(&mv_bytes, &rv_bytes) } } // ************************************************************************************************ -// JSONExactMatchComparator +// JSONContainsMatchComparator // ************************************************************************************************ -pub struct JSONContainsMatchComparator {} +pub struct JSONContainsMatchComparator { + pub negated: bool, +} impl JSONContainsMatchComparator { - pub fn new() -> Self { - Self {} + pub fn new(negated: bool) -> Self { + Self { negated } } } impl ValueComparator for JSONContainsMatchComparator { - fn matches(&self, mock_value: &Value, req_value: &Value) -> bool { - let config = Config::new(CompareMode::Inclusive); - assert_json_matches_no_panic(req_value, mock_value, config).is_ok() + fn matches(&self, mock_value: &Option<&Value>, req_value: &Option<&Value>) -> bool { + match (mock_value, req_value) { + (None, _) => true, + (Some(_), None) => self.negated, + (Some(mv), Some(rv)) => { + let config = Config::new(CompareMode::Inclusive); + let matches = assert_json_matches_no_panic(rv, mv, config).is_ok(); + if self.negated { + !matches + } else { + matches + } + } + } } fn name(&self) -> &str { - "contains" + if self.negated { + return "excludes"; + } + + return "includes"; } fn distance(&self, mock_value: &Option<&Value>, req_value: &Option<&Value>) -> usize { - distance_for(mock_value, req_value) + let mv_bytes = mock_value.map_or(Vec::new(), |v| v.to_string().into_bytes()); + let rv_bytes = req_value.map_or(Vec::new(), |v| v.to_string().into_bytes()); + let distance = equal_weight_distance_for(&mv_bytes, &rv_bytes); + + if self.negated { + std::cmp::max(mv_bytes.len(), rv_bytes.len()) - distance + } else { + distance + } + } +} + +// ************************************************************************************************ +// StringExactMatchComparator +// ************************************************************************************************ +pub struct HostEqualsComparator { + negated: bool, +} + +impl HostEqualsComparator { + pub fn new(negated: bool) -> Self { + Self { negated } + } +} + +impl ValueComparator for HostEqualsComparator { + fn matches(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> bool { + hostname_equals(self.negated, &mock_value, &req_value) + } + + fn name(&self) -> &str { + if self.negated { + return "not equal to"; + } + + return "equals"; + } + + fn distance(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> usize { + // negation is taken care of in matches! + if self.matches(mock_value, req_value) { + return 0; + } + + string_distance(false, self.negated, mock_value, req_value) } } // ************************************************************************************************ // StringExactMatchComparator // ************************************************************************************************ -pub struct StringExactMatchComparator { +pub struct StringEqualsComparator { case_sensitive: bool, + negated: bool, +} + +impl StringEqualsComparator { + pub fn new(case_sensitive: bool, negated: bool) -> Self { + Self { + case_sensitive, + negated, + } + } } -impl StringExactMatchComparator { - pub fn new(case_sensitive: bool) -> Self { - Self { case_sensitive } +impl ValueComparator for StringEqualsComparator { + fn matches(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> bool { + string_equals(self.case_sensitive, self.negated, &mock_value, &req_value) } + + fn name(&self) -> &str { + if self.negated { + return "not equal to"; + } + + return "equals"; + } + + fn distance(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> usize { + string_distance(self.case_sensitive, self.negated, mock_value, req_value) + } +} + +// ************************************************************************************************ +// StringIncludesMatchComparator +// ************************************************************************************************ +pub struct StringContainsComparator { + case_sensitive: bool, + negated: bool, } -impl ValueComparator for StringExactMatchComparator { - fn matches(&self, mock_value: &String, req_value: &String) -> bool { - match self.case_sensitive { - true => mock_value.eq(req_value), - false => mock_value.to_lowercase().eq(&req_value.to_lowercase()), +impl StringContainsComparator { + pub fn new(case_sensitive: bool, negated: bool) -> Self { + Self { + case_sensitive, + negated, } } +} + +impl ValueComparator for StringContainsComparator { + fn matches(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> bool { + string_contains(self.case_sensitive, self.negated, &mock_value, &req_value) + } + fn name(&self) -> &str { - "equals" + if self.negated { + return "excludes"; + } + + return "includes"; } + fn distance(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> usize { - distance_for(mock_value, req_value) + let mock_slice = mock_value.as_ref().map(|s| s.as_str()); + let req_slice = req_value.as_ref().map(|s| s.as_str()); + + distance_for_substring(self.case_sensitive, self.negated, &mock_slice, &req_slice) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_distance_mock_shorter_than_req() { + let binding1 = "hello".to_string(); + let mock_value = Some(&binding1); + let binding2 = "hello world".to_string(); + let req_value = Some(&binding2); + + let comparator = StringContainsComparator { + case_sensitive: true, + negated: false, + }; + assert_eq!(comparator.distance(&mock_value, &req_value), 6); // Assuming "hello" vs "hello world" + } + + #[test] + fn test_distance_mock_equal_size_different() { + let binding1 = "hello".to_string(); + let mock_value = Some(&binding1); + let binding2 = "world".to_string(); + let req_value = Some(&binding2); + + let comparator = StringContainsComparator { + case_sensitive: true, + negated: false, + }; + assert_eq!(comparator.distance(&mock_value, &req_value), 4); // Assuming "hello" vs "world" + } + + #[test] + fn test_distance_exact_match() { + let binding1 = "hello".to_string(); + let mock_value = Some(&binding1); + let binding2 = "hello".to_string(); + let req_value = Some(&binding2); + + let comparator = StringContainsComparator { + case_sensitive: true, + negated: false, + }; + assert_eq!(comparator.distance(&mock_value, &req_value), 0); // Exact match } } // ************************************************************************************************ -// StringExactMatchComparator +// StringContainsMatchComparator // ************************************************************************************************ -pub struct StringContainsMatchComparator { +pub struct StringPrefixMatchComparator { case_sensitive: bool, + negated: bool, } -impl StringContainsMatchComparator { - pub fn new(case_sensitive: bool) -> Self { - Self { case_sensitive } +impl StringPrefixMatchComparator { + pub fn new(case_sensitive: bool, negated: bool) -> Self { + Self { + case_sensitive, + negated, + } + } +} + +impl ValueComparator for StringPrefixMatchComparator { + fn matches(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> bool { + string_has_prefix(self.case_sensitive, self.negated, &mock_value, &req_value) + } + + fn name(&self) -> &str { + if self.negated { + return "prefix not"; + } + + return "has prefix"; + } + + fn distance(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> usize { + distance_for_prefix(self.case_sensitive, self.negated, mock_value, req_value) } } -impl ValueComparator for StringContainsMatchComparator { - fn matches(&self, mock_value: &String, req_value: &String) -> bool { - match self.case_sensitive { - true => req_value.contains(mock_value), - false => req_value - .to_lowercase() - .contains(&mock_value.to_lowercase()), +// ************************************************************************************************ +// StringContainsMatchComparator +// ************************************************************************************************ +pub struct StringSuffixMatchComparator { + case_sensitive: bool, + negated: bool, +} + +impl StringSuffixMatchComparator { + pub fn new(case_sensitive: bool, negated: bool) -> Self { + Self { + case_sensitive, + negated, } } +} + +impl ValueComparator for StringSuffixMatchComparator { + fn matches(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> bool { + string_has_suffix(self.case_sensitive, self.negated, &mock_value, &req_value) + } + fn name(&self) -> &str { - "contains" + if self.negated { + return "suffix not"; + } + + return "has suffix"; } + fn distance(&self, mock_value: &Option<&String>, req_value: &Option<&String>) -> usize { - distance_for(mock_value, req_value) + distance_for_suffix(self.case_sensitive, self.negated, mock_value, req_value) + } +} + +// ************************************************************************************************ +// StringPatternMatchComparator +// ************************************************************************************************ +pub struct StringPatternMatchComparator { + case_sensitive: bool, + negated: bool, +} + +impl StringPatternMatchComparator { + pub fn new(negated: bool, case_sensitive: bool) -> Self { + Self { + negated, + case_sensitive, + } + } +} + +impl ValueComparator for StringPatternMatchComparator { + fn matches(&self, mock_value: &Option<&HttpMockRegex>, req_value: &Option<&String>) -> bool { + comparison::string_matches_regex(self.negated, self.case_sensitive, &mock_value, &req_value) + } + + fn name(&self) -> &str { + if self.negated { + return "does not match regex"; + } + + return "matches regex"; + } + + fn distance(&self, mock_value: &Option<&HttpMockRegex>, req_value: &Option<&String>) -> usize { + comparison::regex_string_distance(self.negated, self.case_sensitive, mock_value, req_value) + } +} + +// ************************************************************************************************ +// StringExactMatchComparator +// ************************************************************************************************ +pub struct HttpMockBytesPatternComparator {} + +impl HttpMockBytesPatternComparator { + pub fn new() -> Self { + Self {} + } +} + +impl ValueComparator for HttpMockBytesPatternComparator { + fn matches( + &self, + mock_value: &Option<&HttpMockRegex>, + req_value: &Option<&HttpMockBytes>, + ) -> bool { + match (mock_value, req_value) { + (None, Some(_)) => true, + (Some(_), None) => false, + (Some(mv), Some(rv)) => mv.0.is_match(&rv.to_maybe_lossy_str()), + (None, None) => true, + } + } + + fn name(&self) -> &str { + "matches regex" + } + + fn distance( + &self, + mock_value: &Option<&HttpMockRegex>, + req_value: &Option<&HttpMockBytes>, + ) -> usize { + let rv = match req_value { + Some(s) => s.to_maybe_lossy_str(), + None => Cow::Borrowed(""), + }; + + let default_pattern = HttpMockRegex(regex::Regex::new(".*").unwrap()); + + let mv = mock_value.unwrap_or(&default_pattern); + + regex_unmatched_length(&rv, &mv) } } @@ -132,17 +427,302 @@ impl StringRegexMatchComparator { } } -impl ValueComparator for StringRegexMatchComparator { - fn matches(&self, mock_value: &Regex, req_value: &String) -> bool { - mock_value.is_match(req_value) +impl ValueComparator for StringRegexMatchComparator { + fn matches(&self, mock_value: &Option<&HttpMockRegex>, req_value: &Option<&String>) -> bool { + return match (mock_value, req_value) { + (None, Some(_)) => true, + (Some(_), None) => false, + (Some(mv), Some(rv)) => mv.0.is_match(&rv), + (None, None) => true, + }; } fn name(&self) -> &str { "matches regex" } - fn distance(&self, mock_value: &Option<&Regex>, req_value: &Option<&String>) -> usize { - distance_for(mock_value, req_value) + fn distance(&self, mock_value: &Option<&HttpMockRegex>, req_value: &Option<&String>) -> usize { + let rv = req_value.map_or("", |s| s.as_str()); + let mut mv = &HttpMockRegex(regex::Regex::new(".*").unwrap()); + if mock_value.is_some() { + mv = mock_value.unwrap() + }; + regex_unmatched_length(rv, &mv) + } +} + +// ************************************************************************************************ +// IntegerExactMatchComparator +// ************************************************************************************************ +pub struct U16ExactMatchComparator { + negated: bool, +} + +impl U16ExactMatchComparator { + pub fn new(negated: bool) -> Self { + Self { negated } + } +} + +impl ValueComparator for U16ExactMatchComparator { + fn matches(&self, mock_value: &Option<&u16>, req_value: &Option<&u16>) -> bool { + comparison::integer_equals(self.negated, &mock_value, &req_value) + } + + fn name(&self) -> &str { + if self.negated { + return "not equal to"; + } + + return "equals"; + } + + fn distance(&self, mock_value: &Option<&u16>, req_value: &Option<&u16>) -> usize { + comparison::distance_for_usize(mock_value, req_value) + } +} + +// ************************************************************************************************ +// BytesExactMatchComparator +// ************************************************************************************************ +pub struct BytesExactMatchComparator { + negated: bool, +} + +impl BytesExactMatchComparator { + pub fn new(negated: bool) -> Self { + Self { negated } + } +} + +impl ValueComparator for BytesExactMatchComparator { + fn matches( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> bool { + return comparison::bytes_equal(self.negated, &mock_value, &req_value); + } + + fn name(&self) -> &str { + if self.negated { + return "not equal to"; + } + + return "equals"; + } + + fn distance( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> usize { + let mock_slice = mock_value + .as_ref() + .map(|mv| mv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + let req_slice = req_value + .as_ref() + .map(|rv| rv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + distance_for(mock_slice.as_ref(), req_slice.as_ref()) + } +} + +// ************************************************************************************************ +// BytesExactMatchComparator +// ************************************************************************************************ +pub struct BytesIncludesComparator { + negated: bool, +} + +impl BytesIncludesComparator { + pub fn new(negated: bool) -> Self { + Self { negated } + } +} + +impl ValueComparator for BytesIncludesComparator { + fn matches( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> bool { + comparison::bytes_includes(self.negated, &mock_value, &req_value) + } + + fn name(&self) -> &str { + if self.negated { + return "excludes"; + } + + return "includes"; + } + + fn distance( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> usize { + let mock_slice = mock_value + .as_ref() + .map(|mv| mv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + let req_slice = req_value + .as_ref() + .map(|rv| rv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + distance_for(mock_slice.as_ref(), req_slice.as_ref()) + } +} + +// ************************************************************************************************ +// BytesPrefixComparator +// ************************************************************************************************ +pub struct BytesPrefixComparator { + negated: bool, +} + +impl BytesPrefixComparator { + pub fn new(negated: bool) -> Self { + Self { negated } + } +} + +impl ValueComparator for BytesPrefixComparator { + fn matches( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> bool { + comparison::bytes_prefix(self.negated, &mock_value, &req_value) + } + + fn name(&self) -> &str { + if self.negated { + return "prefix not"; + } + + "has prefix" + } + + fn distance( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> usize { + let mock_slice = mock_value + .as_ref() + .map(|mv| mv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + let req_slice = req_value + .as_ref() + .map(|rv| rv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + // If mock has no requirement, distance is always 0 + if mock_value.is_none() || mock_slice.is_empty() { + return 0; + } + + // If request does not contain any data + if req_value.is_none() || req_slice.is_empty() { + return mock_slice.len(); + } + + // Compare only up to the length of the mock_slice + let compared_window = std::cmp::min(mock_slice.len(), req_slice.len()); + let distance = equal_weight_distance_for( + &mock_slice[..compared_window], + &req_slice[..compared_window], + ); + + // if negated, we want to find out how many + if self.negated { + // This is why we need the equal_weight_distance_for function: + // to calculate the distance as the number of differing characters. + return compared_window - distance; + } + + return distance; + } +} + +// ************************************************************************************************ +// BytesSuffixComparator +// ************************************************************************************************ +pub struct BytesSuffixComparator { + negated: bool, +} + +impl BytesSuffixComparator { + pub fn new(negated: bool) -> Self { + Self { negated } + } +} + +impl ValueComparator for BytesSuffixComparator { + fn matches( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> bool { + comparison::bytes_suffix(self.negated, &mock_value, &req_value) + } + + fn name(&self) -> &str { + if self.negated { + return "suffix not"; + } + + return "has suffix"; + } + + fn distance( + &self, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, + ) -> usize { + let mock_slice = mock_value + .as_ref() + .map(|mv| mv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + let req_slice = req_value + .as_ref() + .map(|rv| rv.to_bytes().clone()) + .unwrap_or_else(|| Bytes::new()); + + // If mock has no requirement, distance is always 0 + if mock_value.is_none() || mock_slice.is_empty() { + return 0; + } + + // If request does not contain any data + if req_value.is_none() || req_slice.is_empty() { + return mock_slice.len(); + } + + // Compare only up to the length of the mock_slice + let compared_window = std::cmp::min(mock_slice.len(), req_slice.len()); + let distance = equal_weight_distance_for( + &mock_slice[..compared_window], + &req_slice[req_slice.len() - compared_window..], + ); + + // if negated, we want to find out how many + if self.negated { + // This is why we need the equal_weight_distance_for function: + // to calculate the distance as the number of differing characters. + return compared_window - distance; + } + + return distance; } } @@ -158,12 +738,13 @@ impl AnyValueComparator { } impl ValueComparator for AnyValueComparator { - fn matches(&self, _: &T, _: &U) -> bool { + fn matches(&self, _: &Option<&T>, _: &Option<&U>) -> bool { true } fn name(&self) -> &str { "any" } + fn distance(&self, _: &Option<&T>, _: &Option<&U>) -> usize { 0 } @@ -172,17 +753,35 @@ impl ValueComparator for AnyValueComparator { // ************************************************************************************************ // FunctionMatchComparator // ************************************************************************************************ -pub struct FunctionMatchesRequestComparator {} +pub struct FunctionMatchesRequestComparator { + negated: bool, +} impl FunctionMatchesRequestComparator { - pub fn new() -> Self { - Self {} + pub fn new(negated: bool) -> Self { + Self { negated } } } -impl ValueComparator for FunctionMatchesRequestComparator { - fn matches(&self, mock_value: &MockMatcherFunction, req_value: &HttpMockRequest) -> bool { - (*mock_value)(req_value) +impl ValueComparator bool + 'static + Sync + Send>, HttpMockRequest> + for FunctionMatchesRequestComparator +{ + fn matches( + &self, + mock_value: &Option<&Arc bool + 'static + Sync + Send>>, + req_value: &Option<&HttpMockRequest>, + ) -> bool { + let result = match (mock_value, req_value) { + (None, _) => true, + (Some(_), None) => self.negated, + (Some(mv), Some(rv)) => mv(rv), + }; + + if self.negated { + !result + } else { + result + } } fn name(&self) -> &str { @@ -191,34 +790,37 @@ impl ValueComparator for FunctionMatchesRe fn distance( &self, - mock_value: &Option<&MockMatcherFunction>, + mock_value: &Option<&Arc bool + 'static + Sync + Send>>, req_value: &Option<&HttpMockRequest>, ) -> usize { - let mock_value = match mock_value { - None => return 0, - Some(v) => v, - }; - let req_value = match req_value { - None => return 1, - Some(v) => v, - }; - match self.matches(mock_value, req_value) { + let result = match self.matches(mock_value, req_value) { true => 0, false => 1, + }; + + if self.negated { + return match result { + 0 => 1, + _ => 0, + }; } + + return result; } } #[cfg(test)] mod test { - use serde_json::json; - - use crate::server::matchers::comparators::{ - AnyValueComparator, JSONContainsMatchComparator, JSONExactMatchComparator, - StringContainsMatchComparator, StringExactMatchComparator, StringRegexMatchComparator, - ValueComparator, + use crate::{ + common::data::HttpMockRegex, + server::matchers::comparators::{ + AnyValueComparator, JSONContainsMatchComparator, JSONExactMatchComparator, + StringContainsComparator, StringEqualsComparator, StringRegexMatchComparator, + ValueComparator, + }, }; - use crate::Regex; + use regex::Regex; + use serde_json::json; fn run_test( comparator: &dyn ValueComparator, @@ -229,7 +831,7 @@ mod test { expected_name: &str, ) { // Act - let match_result = comparator.matches(&v1, &v2); + let match_result = comparator.matches(&Some(&v1), &Some(&v2)); let distance_result = comparator.distance(&Some(&v1), &Some(&v2)); let name_result = comparator.name(); @@ -266,31 +868,31 @@ mod test { #[test] fn json_contains_comparator_match() { run_test( - &JSONContainsMatchComparator::new(), + &JSONContainsMatchComparator::new(false), &json!({ "other" : { "human" : { "surname" : "Griffin" }}}), &json!({ "name" : "Peter", "other" : { "human" : { "surname" : "Griffin" }}}), true, 15, // compute distance even if values match! - "contains", + "includes", ); } #[test] fn json_contains_comparator_no_match() { run_test( - &JSONContainsMatchComparator::new(), + &JSONContainsMatchComparator::new(false), &json!({ "surname" : "Griffin" }), &json!({ "name" : "Peter", "other" : { "human" : { "surname" : "Griffin" }}}), false, 35, // compute distance even if values match! - "contains", + "includes", ); } #[test] fn string_exact_comparator_match() { run_test( - &StringExactMatchComparator::new(true), + &StringEqualsComparator::new(true, false), &"test string".to_string(), &"test string".to_string(), true, @@ -302,7 +904,7 @@ mod test { #[test] fn string_exact_comparator_no_match() { run_test( - &StringExactMatchComparator::new(true), + &StringEqualsComparator::new(true, false), &"test string".to_string(), &"not a test string".to_string(), false, @@ -314,11 +916,11 @@ mod test { #[test] fn string_exact_comparator_case_sensitive_match() { run_test( - &StringExactMatchComparator::new(false), + &StringEqualsComparator::new(false, false), &"TEST string".to_string(), &"test STRING".to_string(), true, - 10, // compute distance even if values match! + 0, "equals", ); } @@ -326,36 +928,36 @@ mod test { #[test] fn string_contains_comparator_match() { run_test( - &StringContainsMatchComparator::new(true), + &StringContainsComparator::new(true, false), &"st st".to_string(), &"test string".to_string(), true, 6, // compute distance even if values match! - "contains", + "includes", ); } #[test] fn string_contains_comparator_no_match() { run_test( - &StringContainsMatchComparator::new(true), + &StringContainsComparator::new(true, false), &"xxx".to_string(), &"yyy".to_string(), false, 3, // compute distance even if values match! - "contains", + "includes", ); } #[test] fn string_contains_comparator_case_sensitive_match() { run_test( - &StringContainsMatchComparator::new(false), + &StringContainsComparator::new(false, false), &"ST st".to_string(), &"test STRING".to_string(), true, - 9, // compute distance even if values match! - "contains", + 6, + "includes", ); } @@ -363,10 +965,10 @@ mod test { fn regex_comparator_match() { run_test( &StringRegexMatchComparator::new(), - &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(), + &HttpMockRegex(Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()), &"2014-01-01".to_string(), true, - 16, // compute distance even if values match! + 0, // compute distance even if values match! "matches regex", ); } @@ -375,10 +977,10 @@ mod test { fn regex_comparator_no_match() { run_test( &StringRegexMatchComparator::new(), - &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap(), + &HttpMockRegex(Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()), &"xxx".to_string(), false, - 19, // compute distance even if values match! + 3, "matches regex", ); } diff --git a/src/server/matchers/comparison.rs b/src/server/matchers/comparison.rs new file mode 100644 index 00000000..c0e77a00 --- /dev/null +++ b/src/server/matchers/comparison.rs @@ -0,0 +1,1773 @@ +use crate::common::{data::HttpMockRegex, util::HttpMockBytes}; +use regex::Regex; +use std::{convert::TryInto, ops::Deref}; +use stringmetrics::LevWeights; + +pub fn string_has_prefix( + case_sensitive: bool, + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => { + if rv.len() < mv.len() { + return false; + } + + match case_sensitive { + true => rv.starts_with(mv.as_str()), + false => rv.to_lowercase().starts_with(&mv.to_lowercase()), + } + } + }; + + if negated { + !result + } else { + result + } +} + +#[cfg(test)] +mod string_has_prefix_tests { + use super::*; + + #[test] + fn test_case_sensitive_prefix_match() { + let mock_value = "Hello".to_string(); + let req_value = "Hello World".to_string(); + assert!(string_has_prefix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + + let mock_value = "hello".to_string(); + let req_value = "Hello World".to_string(); + assert!(!string_has_prefix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_case_insensitive_prefix_match() { + let mock_value = "hello".to_string(); + let req_value = "Hello World".to_string(); + assert!(string_has_prefix( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + + let mock_value = "HELLO".to_string(); + let req_value = "Hello World".to_string(); + assert!(string_has_prefix( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_negated_prefix_match() { + let mock_value = "Hello".to_string(); + let req_value = "Hello World".to_string(); + assert!(!string_has_prefix( + true, + true, + &Some(&mock_value), + &Some(&req_value), + )); + + let mock_value = "hello".to_string(); + let req_value = "Hello World".to_string(); + assert!(string_has_prefix( + true, + true, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_prefix_too_short() { + let mock_value = "Hello World".to_string(); + let req_value = "Hello".to_string(); + assert!(!string_has_prefix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_exact_match() { + let mock_value = "Hello".to_string(); + let req_value = "Hello".to_string(); + assert!(string_has_prefix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_empty_mock_value() { + let mock_value = "".to_string(); + let req_value = "Hello World".to_string(); + assert!(string_has_prefix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_empty_req_value() { + let mock_value = "Hello".to_string(); + let req_value = "".to_string(); + assert!(!string_has_prefix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_both_empty() { + let mock_value = "".to_string(); + let req_value = "".to_string(); + assert!(string_has_prefix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } +} + +pub fn distance_for_prefix( + case_sensitive: bool, + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> usize { + if mock_value.map_or(0, |v| v.len()) == 0 { + return 0; + } + + let mock_slice = mock_value.as_deref(); + let mock_slice_len = mock_slice.map_or(0, |v| v.len()); + + let req_slice = req_value + .as_deref() + .map(|s| &s[..mock_slice_len.min(s.len())]); + + return distance_for_substring( + case_sensitive, + negated, + &mock_slice.map(|v| v.as_str()), + &req_slice, + ); +} + +#[cfg(test)] +mod distance_for_prefix_tests { + use super::*; + + #[test] + fn test_distance_for_prefix_case_sensitive() { + let mock_value_str = "hello".to_string(); + let req_value_str = "hello world".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 0); + } + + #[test] + fn test_distance_for_prefix_case_insensitive() { + let mock_value_str = "Hello".to_string(); + let req_value_str = "hello world".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!( + distance_for_prefix(false, false, &mock_value, &req_value), + 0 + ); + } + + #[test] + fn test_distance_for_prefix_negated() { + let mock_value_str = "hello".to_string(); + let req_value_str = "hello world".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(distance_for_prefix(true, true, &mock_value, &req_value), 5); + } + + #[test] + fn test_distance_for_prefix_no_match() { + let mock_value_str = "world".to_string(); + let req_value_str = "hello world".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert!(distance_for_prefix(true, false, &mock_value, &req_value) > 0); + } + + #[test] + fn test_distance_for_prefix_partial_match() { + let mock_value_str = "hell".to_string(); + let req_value_str = "hello world".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 0); + } + + #[test] + fn test_distance_for_prefix_empty_mock_value() { + let mock_value_str = "".to_string(); + let req_value_str = "hello world".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 0); + } + + #[test] + fn test_distance_for_prefix_empty_req_value() { + let mock_value_str = "hello".to_string(); + let req_value_str = "".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 5); + } + + #[test] + fn test_distance_for_prefix_both_empty() { + let mock_value_str = "".to_string(); + let req_value_str = "".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 0); + } + + #[test] + fn test_distance_for_prefix_none_mock_value() { + let req_value_str = "hello world".to_string(); + let mock_value: Option<&String> = None; + let req_value = Some(&req_value_str); + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 0); + } + + #[test] + fn test_distance_for_prefix_none_req_value() { + let mock_value_str = "hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value: Option<&String> = None; + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 5); + } + + #[test] + fn test_distance_for_prefix_none_both() { + let mock_value: Option<&String> = None; + let req_value: Option<&String> = None; + assert_eq!(distance_for_prefix(true, false, &mock_value, &req_value), 0); + } +} + +pub fn string_has_suffix( + case_sensitive: bool, + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => { + if rv.len() < mv.len() { + return false; + } + + match case_sensitive { + true => rv.ends_with(mv.as_str()), + false => rv.to_lowercase().ends_with(&mv.to_lowercase()), + } + } + }; + + if negated { + !result + } else { + result + } +} + +#[cfg(test)] +mod string_has_suffix_tests { + use super::*; + + #[test] + fn test_case_sensitive_suffix_match() { + let mock_value = "world".to_string(); + let req_value = "hello world".to_string(); + assert!(string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + let mock_value = "World".to_string(); + assert!(!string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_case_insensitive_suffix_match() { + let mock_value = "world".to_string(); + let req_value = "hello world".to_string(); + assert!(string_has_suffix( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + let mock_value = "World".to_string(); + assert!(string_has_suffix( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_negated_suffix_match() { + let mock_value = "world".to_string(); + let req_value = "hello world".to_string(); + assert!(!string_has_suffix( + true, + true, + &Some(&mock_value), + &Some(&req_value), + )); + let mock_value = "World".to_string(); + assert!(string_has_suffix( + true, + true, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_suffix_too_short() { + let mock_value = "hello world".to_string(); + let req_value = "world".to_string(); + assert!(!string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_exact_match_case_sensitive() { + let mock_value = "hello".to_string(); + let req_value = "hello".to_string(); + assert!(string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_exact_match_case_insensitive() { + let mock_value = "Hello".to_string(); + let req_value = "hello".to_string(); + assert!(string_has_suffix( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_no_match_case_sensitive() { + let mock_value = "world".to_string(); + let req_value = "hello".to_string(); + assert!(!string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_no_match_case_insensitive() { + let mock_value = "World".to_string(); + let req_value = "hello".to_string(); + assert!(!string_has_suffix( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_empty_mock_value() { + let mock_value = "".to_string(); + let req_value = "hello world".to_string(); + assert!(string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_empty_req_value() { + let mock_value = "hello".to_string(); + let req_value = "".to_string(); + assert!(!string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_both_empty() { + let mock_value = "".to_string(); + let req_value = "".to_string(); + assert!(string_has_suffix( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } +} + +/// Calculates the distance between the suffix of a given string (`req_value`) and the expected suffix (`mock_value`). +/// +/// # Arguments +/// +/// * `case_sensitive` - A boolean indicating if the comparison should be case-sensitive. +/// * `negated` - A boolean indicating if the result should be negated. +/// * `mock_value` - An optional reference to the expected suffix. +/// * `req_value` - An optional reference to the string to be checked. +/// +/// # Returns +/// +/// A usize representing the distance between the suffix of `req_value` and `mock_value`. +/// If `negated` is true, the result will be the length of `mock_value` minus the calculated distance. +pub fn distance_for_suffix( + case_sensitive: bool, + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> usize { + if mock_value.map_or(0, |v| v.len()) == 0 { + return 0; + } + + let mock_slice = mock_value.as_deref(); + let mock_slice_len = mock_slice.map_or(0, |v| v.len()); + + let req_slice = req_value + .as_deref() + .map(|s| &s[..mock_slice_len.min(s.len())]); + + return distance_for_substring( + case_sensitive, + negated, + &mock_slice.map(|v| v.as_str()), + &req_slice, + ); +} + +pub fn string_contains( + case_sensitive: bool, + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => match case_sensitive { + true => rv.contains(mv.as_str()), + false => rv.to_lowercase().contains(&mv.to_lowercase()), + }, + }; + + if negated { + !result + } else { + result + } +} + +#[cfg(test)] +mod string_contains_tests { + use super::*; + + #[test] + fn test_case_sensitive_contains() { + let mock_value = "world".to_string(); + let req_value = "hello world".to_string(); + assert!(string_contains( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + + let mock_value = "World".to_string(); + assert!(!string_contains( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_case_insensitive_contains() { + let mock_value = "world".to_string(); + let req_value = "hello world".to_string(); + assert!(string_contains( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + + let mock_value = "World".to_string(); + assert!(string_contains( + false, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_negated_contains() { + let mock_value = "world".to_string(); + let req_value = "hello world".to_string(); + assert!(!string_contains( + true, + true, + &Some(&mock_value), + &Some(&req_value), + )); + + let mock_value = "World".to_string(); + assert!(string_contains( + true, + true, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_contains_substring() { + let mock_value = "lo wo".to_string(); + let req_value = "hello world".to_string(); + assert!(string_contains( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_no_match_contains() { + let mock_value = "test".to_string(); + let req_value = "hello world".to_string(); + assert!(!string_contains( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_empty_mock_value() { + let mock_value = "".to_string(); + let req_value = "hello world".to_string(); + assert!(string_contains( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_empty_req_value() { + let mock_value = "hello".to_string(); + let req_value = "".to_string(); + assert!(!string_contains( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } + + #[test] + fn test_both_empty() { + let mock_value = "".to_string(); + let req_value = "".to_string(); + assert!(string_contains( + true, + false, + &Some(&mock_value), + &Some(&req_value), + )); + } +} + +pub fn distance_for_substring( + case_sensitive: bool, + negated: bool, + mock_value: &Option, + req_value: &Option, +) -> usize +where + T: Deref + AsRef, +{ + if mock_value.is_none() { + return 0; + } + + let mock_slice = mock_value.as_deref().unwrap_or(""); + let req_slice = req_value.as_deref().unwrap_or(""); + + let lcs_length = longest_common_substring(case_sensitive, mock_slice, req_slice); + + if negated { + lcs_length + } else { + std::cmp::max(mock_slice.len(), req_slice.len()) - lcs_length + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_distance_for_substring_case_sensitive_match() { + let mock_value = Some("Hello"); + let req_value = Some("Hello World"); + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 6 + ); + } + + #[test] + fn test_distance_for_substring_case_sensitive_no_match() { + let mock_value = Some("Hello"); + let req_value = Some("World"); + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 4 + ); + } + + #[test] + fn test_distance_for_substring_case_insensitive_match() { + let mock_value = Some("hello"); + let req_value = Some("Hello World"); + assert_eq!( + distance_for_substring(false, false, &mock_value, &req_value), + 6 + ); + } + + #[test] + fn test_distance_for_substring_case_insensitive_no_match() { + let mock_value = Some("hello"); + let req_value = Some("WORLD"); + assert_eq!( + distance_for_substring(false, false, &mock_value, &req_value), + 4 + ); + } + + #[test] + fn test_distance_for_substring_negated_match() { + let mock_value = Some("Hello"); + let req_value = Some("Hello World"); + assert_eq!( + distance_for_substring(true, true, &mock_value, &req_value), + 5 + ); + } + + #[test] + fn test_distance_for_substring_negated_no_match() { + let mock_value = Some("Hello"); + let req_value = Some("World"); + assert_eq!( + distance_for_substring(true, true, &mock_value, &req_value), + 1 + ); + } + + #[test] + fn test_distance_for_substring_empty_mock_value() { + let mock_value = Some(""); + let req_value = Some("Hello"); + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 5 + ); + } + + #[test] + fn test_distance_for_substring_empty_req_value() { + let mock_value = Some("Hello"); + let req_value = Some(""); + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 5 + ); + } + + #[test] + fn test_distance_for_substring_both_empty() { + let mock_value = Some(""); + let req_value = Some(""); + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 0 + ); + } + + #[test] + fn test_distance_for_substring_none_mock_value() { + let req_value = Some("hello"); + let mock_value: Option<&str> = None; + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 0 + ); + } + + #[test] + fn test_distance_for_substring_none_req_value() { + let mock_value = Some("hello"); + let req_value: Option<&str> = None; + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 5 + ); + } + + #[test] + fn test_distance_for_substring_none_both() { + let mock_value: Option<&str> = None; + let req_value: Option<&str> = None; + assert_eq!( + distance_for_substring(true, false, &mock_value, &req_value), + 0 + ); + } +} + +pub fn longest_common_substring(case_sensitive: bool, s1: &str, s2: &str) -> usize { + let (long_s, short_s) = if s1.len() < s2.len() { + (s2, s1) + } else { + (s1, s2) + }; + + let mut previous = vec![0; short_s.chars().count() + 1]; + let mut current = vec![0; short_s.chars().count() + 1]; + let mut longest = 0; + + for (i, long_char) in long_s.chars().enumerate() { + for (j, short_char) in short_s.chars().enumerate() { + let long_char = if case_sensitive { + long_char + } else { + long_char.to_lowercase().next().unwrap() + }; + + let short_char = if case_sensitive { + short_char + } else { + short_char.to_lowercase().next().unwrap() + }; + + if long_char == short_char { + current[j + 1] = previous[j] + 1; + if current[j + 1] > longest { + longest = current[j + 1]; + } + } else { + current[j + 1] = 0; + } + } + std::mem::swap(&mut previous, &mut current); + } + longest +} + +#[cfg(test)] +mod longest_common_substring_tests { + use super::*; + + #[test] + fn test_case_sensitive_match() { + let s1 = "abcdef"; + let s2 = "zabcy"; + assert_eq!(longest_common_substring(true, s1, s2), 3); + + let s1 = "abcdef"; + let s2 = "zabCY"; + assert_eq!(longest_common_substring(true, s1, s2), 2); + + let s1 = "abcDEF"; + let s2 = "zabcy"; + assert_eq!(longest_common_substring(true, s1, s2), 3); + } + + #[test] + fn test_case_insensitive_match() { + let s1 = "abcdef"; + let s2 = "zabcy"; + assert_eq!(longest_common_substring(false, s1, s2), 3); + + let s1 = "abcDEF"; + let s2 = "zabcY"; + assert_eq!(longest_common_substring(false, s1, s2), 3); + + let s1 = "ABCdef"; + let s2 = "ZABcy"; + assert_eq!(longest_common_substring(false, s1, s2), 3); + } + + #[test] + fn test_no_common_substring() { + let s1 = "abc"; + let s2 = "xyz"; + assert_eq!(longest_common_substring(true, s1, s2), 0); + assert_eq!(longest_common_substring(false, s1, s2), 0); + } + + #[test] + fn test_empty_strings() { + let s1 = ""; + let s2 = "abcdef"; + assert_eq!(longest_common_substring(true, s1, s2), 0); + assert_eq!(longest_common_substring(false, s1, s2), 0); + + let s1 = "abcdef"; + let s2 = ""; + assert_eq!(longest_common_substring(true, s1, s2), 0); + assert_eq!(longest_common_substring(false, s1, s2), 0); + + let s1 = ""; + let s2 = ""; + assert_eq!(longest_common_substring(true, s1, s2), 0); + assert_eq!(longest_common_substring(false, s1, s2), 0); + } + + #[test] + fn test_full_string_match() { + let s1 = "abcdef"; + let s2 = "abcdef"; + assert_eq!(longest_common_substring(true, s1, s2), 6); + assert_eq!(longest_common_substring(false, s1, s2), 6); + } + + #[test] + fn test_utf8_support() { + let s1 = "你好世界"; + let s2 = "世界你好"; + assert_eq!(longest_common_substring(true, s1, s2), 2); + + let s1 = "你好世界"; + let s2 = "世界HELLO"; + assert_eq!(longest_common_substring(false, s1, s2), 2); + } +} + +pub fn string_equals( + case_sensitive: bool, + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => match case_sensitive { + true => mv.eq(rv), + false => mv.to_lowercase().eq(&rv.to_lowercase()), + }, + }; + + if negated { + !result + } else { + result + } +} + +pub fn hostname_equals( + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> bool { + if let (Some(mv), Some(rv)) = (mock_value, req_value) { + let mv_is = mv.eq_ignore_ascii_case("localhost") || mv.eq_ignore_ascii_case("127.0.0.1"); + let rv_is = rv.eq_ignore_ascii_case("localhost") || rv.eq_ignore_ascii_case("127.0.0.1"); + + if mv_is == rv_is { + return !negated; + } + } + + return string_equals(false, negated, mock_value, req_value); +} + +/// Computes the distance between two optional strings (`mock_value` and `req_value`), +/// with optional case sensitivity and negation. +/// +/// # Arguments +/// +/// * `case_sensitive` - A boolean indicating if the comparison should be case-sensitive. +/// * `negated` - A boolean indicating if the result should be negated. +/// * `mock_value` - An optional reference to the first string to compare. +/// * `req_value` - An optional reference to the second string to compare. +/// +/// # Returns +/// +/// A `usize` representing the distance between `mock_value` and `req_value`, +/// taking into account case sensitivity and negation. +pub fn string_distance( + case_sensitive: bool, + negated: bool, + mock_value: &Option<&String>, + req_value: &Option<&String>, +) -> usize { + if mock_value.is_none() { + return 0; + } + + let mock_slice = mock_value.as_deref().map_or("", |s| s.as_str()); + let req_slice = req_value.as_deref().map_or("", |s| s.as_str()); + + let (mock_slice, req_slice) = if case_sensitive { + (mock_slice.to_string(), req_slice.to_string()) + } else { + (mock_slice.to_lowercase(), req_slice.to_lowercase()) + }; + + let distance = equal_weight_distance_for(mock_slice.as_bytes(), req_slice.as_bytes()); + + if negated { + std::cmp::max(mock_slice.len(), req_slice.len()) - distance + } else { + distance + } +} + +#[cfg(test)] +mod string_distance_tests { + use super::*; + + #[test] + fn test_string_distance_case_sensitive() { + let mock_value_str = "Hello".to_string(); + let req_value_str = "Hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, false, &mock_value, &req_value), 0); + + let mock_value_str = "Hello".to_string(); + let req_value_str = "hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, false, &mock_value, &req_value), 1); + } + + #[test] + fn test_string_distance_case_insensitive() { + let mock_value_str = "Hello".to_string(); + let req_value_str = "hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(false, false, &mock_value, &req_value), 0); + + let mock_value_str = "HELLO".to_string(); + let req_value_str = "hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(false, false, &mock_value, &req_value), 0); + } + + #[test] + fn test_string_distance_negated() { + let mock_value_str = "Hello".to_string(); + let req_value_str = "Hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, true, &mock_value, &req_value), 5); + + let mock_value_str = "Hello".to_string(); + let req_value_str = "hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, true, &mock_value, &req_value), 4); + } + + #[test] + fn test_string_distance_no_match() { + let mock_value_str = "Hello".to_string(); + let req_value_str = "World".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, false, &mock_value, &req_value), 4); + } + + #[test] + fn test_string_distance_empty_mock_value() { + let mock_value_str = "".to_string(); + let req_value_str = "hello".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, false, &mock_value, &req_value), 5); + } + + #[test] + fn test_string_distance_empty_req_value() { + let mock_value_str = "hello".to_string(); + let req_value_str = "".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, false, &mock_value, &req_value), 5); + } + + #[test] + fn test_string_distance_both_empty() { + let mock_value_str = "".to_string(); + let req_value_str = "".to_string(); + let mock_value = Some(&mock_value_str); + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, false, &mock_value, &req_value), 0); + } + + #[test] + fn test_string_distance_none_mock_value() { + let req_value_str = "hello".to_string(); + let mock_value: Option<&String> = None; + let req_value = Some(&req_value_str); + assert_eq!(string_distance(true, false, &mock_value, &req_value), 0); + } +} + +pub fn is_lowercase(s: &str) -> bool { + s.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()) +} + +// ************************************************************************************************* +// Helper functions +// ************************************************************************************************* +pub fn distance_for(expected: &[T], actual: &[T]) -> usize +where + T: PartialEq + Sized, +{ + stringmetrics::levenshtein_limit_iter(expected.iter(), actual.iter(), u32::MAX) as usize +} + +pub fn equal_weight_distance_for(expected: &[T], actual: &[T]) -> usize +where + T: PartialEq + Sized, +{ + stringmetrics::try_levenshtein_weight_iter( + expected.iter(), + actual.iter(), + u32::MAX, + &LevWeights { + insertion: 1, + deletion: 1, + substitution: 1, + }, + ) + // Option=None is only returned in case limit is maxed out here it realistically can't + .expect("character limit exceeded") as usize +} + +pub fn regex_unmatched_length(text: &str, re: &HttpMockRegex) -> usize { + let mut last_end = 0; + let mut total_unmatched_length = 0; + + // Iterate through all matches and sum the lengths of unmatched parts + for mat in re.0.find_iter(text) { + if last_end != mat.start() { + total_unmatched_length += mat.start() - last_end; + } + last_end = mat.end(); + } + + // Add any characters after the last match to the total + if last_end < text.len() { + total_unmatched_length += text.len() - last_end; + } + + total_unmatched_length +} + +pub fn integer_equals( + negated: bool, + mock_value: &Option<&T>, + req_value: &Option<&T>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => mv == rv, + }; + + if negated { + !result + } else { + result + } +} +#[cfg(test)] +mod usize_equals_tests { + use super::*; + + #[test] + fn test_usize_equals_equal_values_not_negated() { + assert_eq!(true, integer_equals(false, &Some(&10), &Some(&10))); + } + + #[test] + fn test_usize_equals_unequal_values_not_negated() { + assert_eq!(false, integer_equals(false, &Some(&10), &Some(&20))); + } + + #[test] + fn test_usize_equals_equal_values_negated() { + assert_eq!(false, integer_equals(true, &Some(&10), &Some(&10))); + } + + #[test] + fn test_usize_equals_unequal_values_negated() { + assert_eq!(true, integer_equals(true, &Some(&10), &Some(&20))); + } + + #[test] + fn test_usize_equals_mock_value_none_not_negated() { + assert_eq!(true, integer_equals(false, &None, &Some(&10))); + } + + #[test] + fn test_usize_equals_req_value_none_not_negated() { + assert_eq!(false, integer_equals(false, &Some(&10), &None)); + } + + #[test] + fn test_usize_equals_both_none_not_negated() { + assert_eq!(true, integer_equals::(false, &None, &None)); + } + + #[test] + fn test_usize_equals_mock_value_none_negated() { + assert_eq!(true, integer_equals(true, &None, &Some(&10))); + } + + #[test] + fn test_usize_equals_req_value_none_negated() { + assert_eq!(true, integer_equals(true, &Some(&10), &None)); + } + + #[test] + fn test_usize_equals_both_none_negated() { + assert_eq!(true, integer_equals::(true, &None, &None)); + } +} + +pub fn bytes_equal( + negated: bool, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => mv == rv, + }; + + if negated { + !result + } else { + result + } +} + +pub fn bytes_includes( + negated: bool, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => { + // Convert both Bytes into slices for comparison + let mock_slice = mv.to_bytes(); + let req_slice = rv.to_bytes(); + + if mock_slice.is_empty() { + return true; + } + + // Check if the request slice contains the mock slice + req_slice + .windows(mock_slice.len()) + .any(|window| window == mock_slice) + } + }; + + if negated { + !result + } else { + result + } +} + +#[cfg(test)] +mod bytes_includes_test { + use crate::{common::util::HttpMockBytes, server::matchers::comparison::bytes_includes}; + + #[test] + fn test_bytes_includes() { + assert_eq!( + bytes_includes( + false, + &Some(&HttpMockBytes::from(bytes::Bytes::from(" b\n c"))), + &Some(&HttpMockBytes::from(bytes::Bytes::from( + "a b\n c \ncd ef" + ))), + ), + true + ); + } +} + +pub fn bytes_prefix( + negated: bool, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => { + // Convert both Bytes into slices for comparison + let mock_slice = mv.to_bytes(); + let req_slice = rv.to_bytes(); + + // Check if the request slice starts with the mock slice + req_slice.starts_with(&mock_slice) + } + }; + + if negated { + !result + } else { + result + } +} + +pub fn bytes_suffix( + negated: bool, + mock_value: &Option<&HttpMockBytes>, + req_value: &Option<&HttpMockBytes>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => { + // Convert both Bytes into slices for comparison + let mock_slice = mv.to_bytes(); + let req_slice = rv.to_bytes(); + + // Check if the request slice ends with the mock slice + req_slice.ends_with(&mock_slice) + } + }; + + if negated { + !result + } else { + result + } +} + +/// Calculates the "distance" between two optional numeric values. +/// +/// The distance is defined as the absolute difference between the two values +/// if both are present. If one is present and the other is not, the distance +/// is the present value, unless it is zero, in which case the distance is 1. +/// If both values are `None`, the distance is 0. +/// +/// # Arguments +/// +/// * `mock_value` - An optional reference to a u16 that may or may not be present. +/// * `req_value` - An optional reference to a u16 that may or may not be present. +/// +/// # Returns +/// +/// Returns a usize representing the distance as defined above. +/// +pub fn distance_for_usize(expected: &Option<&T>, actual: &Option<&T>) -> usize +where + T: TryInto + Copy, +{ + let mock_size = expected.map_or(0, |&v| v.try_into().unwrap_or(0)); + let req_size = actual.map_or(0, |&v| v.try_into().unwrap_or(0)); + + match (expected, actual) { + (Some(&mv), Some(&rv)) => { + let diff = if mock_size > req_size { + mock_size - req_size + } else { + req_size - mock_size + }; + diff + } + (Some(&mv), None) | (None, Some(&mv)) => { + if mock_size == 0 { + 1 + } else { + mock_size + } + } + (None, None) => 0, + // Redundant pattern, logically unnecessary but included for completeness + } +} + +#[cfg(test)] +mod distance_for_usize_test { + use crate::server::matchers::comparison::distance_for_usize; + + #[test] + fn tree_map_fully_contains_other() { + assert_eq!(distance_for_usize::(&Some(&4), &None), 4); + assert_eq!(distance_for_usize::(&Some(&0), &None), 1); + assert_eq!(distance_for_usize::(&Some(&5), &Some(&3)), 2); + assert_eq!(distance_for_usize::(&None, &None), 0); + } +} + +pub fn string_matches_regex( + negated: bool, + case_sensitive: bool, + mock_value: &Option<&HttpMockRegex>, + req_value: &Option<&String>, +) -> bool { + let result = match (mock_value, req_value) { + (None, _) => return true, + (Some(_), None) => return negated, + (Some(mv), Some(rv)) => { + if case_sensitive { + mv.0.is_match(rv) + } else { + let case_insensitive_str = mv.0.as_str().to_lowercase(); + let case_insensitive_regex = Regex::new(&case_insensitive_str).unwrap(); + case_insensitive_regex.is_match(&rv.to_lowercase()) + } + } + }; + + match negated { + true => !result, + false => result, + } +} + +#[cfg(test)] +mod string_matches_regex_tests { + use super::*; + use crate::common::data::HttpMockRegex; + + #[test] + fn test_string_matches_regex() { + let pattern = HttpMockRegex(Regex::new(r"^Hello.*").unwrap()); + + let req_value = "Hello, world!".to_string(); + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &Some(&req_value)), + true + ); + + let req_value = "Goodbye, world!".to_string(); + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &Some(&req_value)), + false + ); + } + + #[test] + fn test_string_matches_regex_negated() { + let pattern = HttpMockRegex(Regex::new(r"^Hello.*").unwrap()); + + let req_value = "Hello, world!".to_string(); + assert_eq!( + string_matches_regex(true, true, &Some(&pattern), &Some(&req_value)), + false + ); + + let req_value = "Goodbye, world!".to_string(); + assert_eq!( + string_matches_regex(true, true, &Some(&pattern), &Some(&req_value)), + true + ); + } + + #[test] + fn test_string_matches_regex_empty_pattern() { + let pattern = HttpMockRegex(Regex::new(r"").unwrap()); + + let req_value = "Anything".to_string(); + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &Some(&req_value)), + true + ); + } + + #[test] + fn test_string_matches_regex_empty_request_value() { + let pattern = HttpMockRegex(Regex::new(r"^Hello.*").unwrap()); + + let req_value = "".to_string(); + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &Some(&req_value)), + false + ); + } + + #[test] + fn test_string_matches_regex_empty_pattern_and_request_value() { + let pattern = HttpMockRegex(Regex::new(r"").unwrap()); + + let req_value = "".to_string(); + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &Some(&req_value)), + true + ); + } + + #[test] + fn test_string_matches_regex_none_pattern() { + let req_value = "Hello, world!".to_string(); + assert_eq!( + string_matches_regex(false, true, &None, &Some(&req_value)), + true + ); + } + + #[test] + fn test_string_matches_regex_none_request_value() { + let pattern = HttpMockRegex(Regex::new(r"^Hello.*").unwrap()); + + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &None), + false + ); + } + + #[test] + fn test_string_matches_regex_none_pattern_and_request_value() { + assert_eq!(string_matches_regex(false, true, &None, &None), true); + } + + #[test] + fn test_string_matches_regex_none_pattern_negated() { + let req_value = "Hello, world!".to_string(); + assert_eq!( + string_matches_regex(true, true, &None, &Some(&req_value)), + true + ); + } + + #[test] + fn test_string_matches_regex_none_request_value_negated() { + let pattern = HttpMockRegex(Regex::new(r"^Hello.*").unwrap()); + + assert_eq!( + string_matches_regex(true, true, &Some(&pattern), &None), + true + ); + } + + #[test] + fn test_string_matches_regex_none_pattern_and_request_value_negated() { + assert_eq!(string_matches_regex(true, true, &None, &None), true); + } + + #[test] + fn test_string_matches_regex_pattern_with_special_chars() { + let pattern = HttpMockRegex(Regex::new(r"^\d{3}-\d{2}-\d{4}$").unwrap()); + + let req_value = "123-45-6789".to_string(); + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &Some(&req_value)), + true + ); + + let req_value = "123-45-678".to_string(); + assert_eq!( + string_matches_regex(false, true, &Some(&pattern), &Some(&req_value)), + false + ); + } + + #[test] + fn test_string_matches_regex_case_insensitive() { + let pattern = HttpMockRegex(Regex::new(r"(?i)^hello.*").unwrap()); + + let req_value = "Hello, world!".to_string(); + assert_eq!( + string_matches_regex(false, false, &Some(&pattern), &Some(&req_value)), + true + ); + + let req_value = "hello, world!".to_string(); + assert_eq!( + string_matches_regex(false, false, &Some(&pattern), &Some(&req_value)), + true + ); + + let req_value = "HELLO, WORLD!".to_string(); + assert_eq!( + string_matches_regex(false, false, &Some(&pattern), &Some(&req_value)), + true + ); + } +} +/// Computes the distance between a given string and a regex pattern based on whether the match is negated or not. +/// +/// # Arguments +/// * `negated` - A boolean indicating whether the match should be negated. +/// * `mock_value` - An optional reference to a `Pattern` containing the regex to match against. +/// * `req_value` - An optional reference to a `String` representing the input string to be matched. +/// +/// # Returns +/// * `usize` - The computed distance. In the negated case, it returns the number of characters that did match the regex. +/// In the non-negated case, it returns the number of characters that did not match the regex. +pub fn regex_string_distance( + negated: bool, + case_sensitive: bool, + mock_value: &Option<&HttpMockRegex>, + req_value: &Option<&String>, +) -> usize { + if mock_value.is_none() { + return 0; + } + + if req_value.is_none() || req_value.unwrap().is_empty() { + let matches = string_matches_regex(negated, case_sensitive, mock_value, req_value); + return match matches { + true => 0, + false => mock_value.unwrap().0.as_str().len(), + }; + } + + let rv = req_value.map_or("", |s| s.as_str()); + let unmatched_len = regex_unmatched_length(rv, &mock_value.unwrap()); + + return match negated { + true => rv.len() - unmatched_len, + false => unmatched_len, + }; +} + +#[cfg(test)] +mod regex_string_distance_tests { + use super::*; + use regex::Regex; + + #[test] + fn test_non_negated_full_match() { + let pattern = HttpMockRegex(Regex::new("a+").unwrap()); + let mock_value = Some(&pattern); + let req_value_str = String::from("aaaa"); + let req_value = Some(&req_value_str); + assert_eq!( + regex_string_distance(false, true, &mock_value, &req_value), + 0 + ); + } + + #[test] + fn test_negated_full_match() { + let pattern = HttpMockRegex(Regex::new("a+").unwrap()); + let mock_value = Some(&pattern); + let req_value_str = String::from("aaaa"); + let req_value = Some(&req_value_str); + assert_eq!( + regex_string_distance(true, true, &mock_value, &req_value), + 4 + ); + } + + #[test] + fn test_non_negated_partial_match() { + let pattern = HttpMockRegex(Regex::new("a+").unwrap()); + let mock_value = Some(&pattern); + let req_value_str = String::from("aaabbb"); + let req_value = Some(&req_value_str); + assert_eq!( + regex_string_distance(false, true, &mock_value, &req_value), + 3 + ); + } + + #[test] + fn test_negated_partial_match() { + let pattern = HttpMockRegex(Regex::new("a+").unwrap()); + let mock_value = Some(&pattern); + let req_value_str = String::from("aaaabbb"); + let req_value = Some(&req_value_str); + assert_eq!( + regex_string_distance(true, true, &mock_value, &req_value), + 4 + ); + } + + #[test] + fn test_non_negated_no_match() { + let pattern = HttpMockRegex(Regex::new("c+").unwrap()); + let mock_value = Some(&pattern); + let req_value_str = String::from("aaabbb"); + let req_value = Some(&req_value_str); + assert_eq!( + regex_string_distance(false, true, &mock_value, &req_value), + 6 + ); + } + + #[test] + fn test_negated_no_match() { + let pattern = HttpMockRegex(Regex::new("c+").unwrap()); + let mock_value = Some(&pattern); + let req_value_str = String::from("aaabbb"); + let req_value = Some(&req_value_str); + assert_eq!( + regex_string_distance(true, true, &mock_value, &req_value), + 0 + ); + } + + #[test] + fn test_no_req_value() { + let pattern = HttpMockRegex(Regex::new("a+").unwrap()); + let mock_value = Some(&pattern); + let req_value: Option<&String> = None; + assert_eq!( + regex_string_distance(false, true, &mock_value, &req_value), + 2 + ); + } + + #[test] + fn test_no_mock_value() { + let mock_value: Option<&HttpMockRegex> = None; + let req_value_str = String::from("aaabbb"); + let req_value = Some(&req_value_str); + assert_eq!( + regex_string_distance(false, true, &mock_value, &req_value), + 0 + ); + } + + #[test] + fn test_empty_rv_non_negated_match() { + let pattern = HttpMockRegex(Regex::new(".*").unwrap()); + let mock_value = Some(&pattern); + let req_value: Option<&String> = None; + assert_eq!( + regex_string_distance(false, true, &mock_value, &req_value), + 2 + ); + } + + #[test] + fn test_empty_rv_non_negated_no_match() { + let pattern = HttpMockRegex(Regex::new(".+").unwrap()); + let mock_value = Some(&pattern); + let req_value: Option<&String> = None; // This will make rv empty + assert_eq!( + regex_string_distance(false, true, &mock_value, &req_value), + pattern.0.as_str().len() + ); + } + + #[test] + fn test_empty_rv_negated_match() { + let pattern = HttpMockRegex(Regex::new(".*").unwrap()); + let mock_value = Some(&pattern); + let req_value: Option<&String> = None; + + assert_eq!( + // When request does not have any value, it is never a match. Since we have "negate=true", + // it is a match. Hence, distance is 0. + regex_string_distance(true, true, &mock_value, &req_value), + 0 + ); + } + + #[test] + fn test_empty_rv_negated_no_match() { + let pattern = HttpMockRegex(Regex::new(".+").unwrap()); + let mock_value = Some(&pattern); + let req_value: Option<&String> = None; + // Body does not match, but negated = true, so its a match, hence distance is a 0. + assert_eq!( + regex_string_distance(true, true, &mock_value, &req_value), + 0 + ); + } +} diff --git a/src/server/matchers/generic.rs b/src/server/matchers/generic.rs index c6bded0b..b3c5bc8b 100644 --- a/src/server/matchers/generic.rs +++ b/src/server/matchers/generic.rs @@ -1,15 +1,18 @@ -use std::collections::BTreeMap; -use std::fmt::Display; -use std::net::ToSocketAddrs; +use serde::{Deserialize, Serialize}; +use similar::{ChangeTag, TextDiff}; +use std::{collections::HashSet, fmt::Display}; -use serde_json::Value; - -use crate::common::data::{HttpMockRequest, Mismatch, Reason, RequestRequirements, Tokenizer}; -use crate::server::matchers::comparators::ValueComparator; -use crate::server::matchers::sources::{MultiValueSource, ValueRefSource}; -use crate::server::matchers::targets::{MultiValueTarget, ValueRefTarget, ValueTarget}; -use crate::server::matchers::transformers::Transformer; -use crate::server::matchers::{diff_str, Matcher}; +use crate::{ + common::{ + data::{ + Diff, DiffResult, FunctionComparison, HttpMockRequest, KeyValueComparison, + KeyValueComparisonAttribute, KeyValueComparisonKeyValuePair, Mismatch, + RequestRequirements, SingleValueComparison, Tokenizer, + }, + util::is_none_or_empty, + }, + server::matchers::{comparators::ValueComparator, Matcher}, +}; // ************************************************************************************************ // SingleValueMatcher @@ -20,10 +23,11 @@ where T: Display, { pub entity_name: &'static str, - pub source: Box + Send + Sync>, - pub target: Box + Send + Sync>, + pub matcher_method: &'static str, + pub matching_strategy: MatchingStrategy, + pub expectation: for<'a> fn(&'a RequestRequirements) -> Option>, + pub request_value: fn(&HttpMockRequest) -> Option, pub comparator: Box + Send + Sync>, - pub transformer: Option + Send + Sync>>, pub with_reason: bool, pub diff_with: Option, pub weight: usize, @@ -43,13 +47,10 @@ where None => return Vec::new(), Some(mv) => mv.to_vec(), }; - let req_value = match req_value { - None => return mock_values, - Some(rv) => rv, - }; + mock_values .into_iter() - .filter(|e| !self.comparator.matches(e, req_value)) + .filter(|e| !self.comparator.matches(&Some(e), &req_value.as_ref())) .collect() } } @@ -60,14 +61,22 @@ where T: Display, { fn matches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> bool { - let req_value = self.target.parse_from_request(req); - let mock_value = self.source.parse_from_mock(mock); + let mock_value = (self.expectation)(mock); + if is_none_or_empty(&mock_value) { + return true; + } + + let req_value = (self.request_value)(req); self.find_unmatched(&req_value, &mock_value).is_empty() } fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize { - let req_value = self.target.parse_from_request(req); - let mock_values = self.source.parse_from_mock(mock); + let mock_values = (self.expectation)(mock); + if is_none_or_empty(&mock_values) { + return 0; + } + + let req_value = (self.request_value)(req); self.find_unmatched(&req_value, &mock_values) .into_iter() .map(|s| self.comparator.distance(&Some(s), &req_value.as_ref())) @@ -76,133 +85,184 @@ where } fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec { - let req_value = self.target.parse_from_request(req); - let mock_value = self.source.parse_from_mock(mock); + let mock_value = (self.expectation)(mock); + if is_none_or_empty(&mock_value) { + return Vec::new(); + } + + let req_value = (self.request_value)(req); self.find_unmatched(&req_value, &mock_value) .into_iter() .map(|mock_value| { let mock_value = mock_value.to_string(); - let req_value = req_value.as_ref().unwrap().to_string(); + let req_value = req_value.as_ref().map_or(String::new(), |v| v.to_string()); Mismatch { - title: format!("The {} does not match", self.entity_name), - reason: match self.with_reason { - true => Some(Reason { - expected: mock_value.to_owned(), - actual: req_value.to_owned(), - comparison: self.comparator.name().into(), - best_match: false, - }), - false => None, - }, + matcher_method: self.matcher_method.to_string(), + comparison: Some(SingleValueComparison { + operator: self.comparator.name().to_string(), + expected: mock_value.to_owned(), + actual: req_value.to_owned(), + }), + key_value_comparison: None, + function_comparison: None, + entity: self.entity_name.to_string(), diff: self.diff_with.map(|t| diff_str(&mock_value, &req_value, t)), + best_match: false, + matching_strategy: Some(self.matching_strategy.clone()), } }) .collect() } } +pub enum KeyValueOperator { + AND, + NAND, + NOR, + OR, + IMPLICATION, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum MatchingStrategy { + Presence, + Absence, +} + // ************************************************************************************************ // MultiValueMatcher // ************************************************************************************************ -pub(crate) struct MultiValueMatcher +pub(crate) struct MultiValueMatcher where - SK: Display, - SV: Display, - TK: Display, - TV: Display, + EK: Display, + EV: Display, + RK: Display, + RV: Display, { pub entity_name: &'static str, - pub source: Box + Send + Sync>, - pub target: Box + Send + Sync>, - pub key_comparator: Box + Send + Sync>, - pub value_comparator: Box + Send + Sync>, - pub key_transformer: Option + Send + Sync>>, - pub value_transformer: Option + Send + Sync>>, + pub matcher_method: &'static str, + pub operator: KeyValueOperator, + pub expectation: for<'a> fn(&'a RequestRequirements) -> Option)>>, + pub request_value: fn(&HttpMockRequest) -> Option)>>, + pub matching_strategy: MatchingStrategy, + pub key_required: bool, + pub key_comparator: Box + Send + Sync>, + pub value_comparator: Box + Send + Sync>, pub with_reason: bool, pub diff_with: Option, pub weight: usize, } -impl MultiValueMatcher +impl MultiValueMatcher where - SK: Display, - SV: Display, - TK: Display, - TV: Display, + EK: Display, + EV: Display, + RK: Display, + RV: Display, { fn find_unmatched<'a>( &self, - req_values: &Vec<(TK, Option)>, - mock_values: &'a Vec<(&'a SK, Option<&'a SV>)>, - ) -> Vec<&'a (&'a SK, Option<&'a SV>)> { - mock_values - .into_iter() - .filter(|(sk, sv)| { - req_values - .iter() - .find(|(tk, tv)| { - let key_matches = self.key_comparator.matches(sk, &tk); - let value_matches = match (sv, tv) { - (Some(_), None) => false, // Mock required a value but none was present - (Some(sv), Some(tv)) => self.value_comparator.matches(sv, &tv), - _ => true, - }; - key_matches && value_matches - }) - .is_none() + req_values: &Vec<(RK, Option)>, + mock_values: &'a Vec<(&'a EK, Option<&'a EV>)>, + ) -> Vec<&'a (&'a EK, Option<&'a EV>)> { + return mock_values + .iter() + .filter(|(ek, ev)| { + if self.key_required { + let key_present = req_values.iter().any(|(rk, _): &(RK, Option)| { + self.key_comparator.matches(&Some(ek), &Some(rk)) + }); + + if !key_present { + // We negate here, since we are filtering for "unmatched" expectations -> true = unmatched + return true; + } + } + + let request_value_matches = |(rk, rv): &(RK, Option)| { + let key_matches = self.key_comparator.matches(&Some(ek), &Some(rk)); + let value_matches = match (ev, rv) { + (Some(_), None) => false, // Mock required a value but none was present + (Some(ev), Some(rv)) => self.value_comparator.matches(&Some(ev), &Some(rv)), + _ => true, + }; + + return match self.operator { + KeyValueOperator::NAND => !(key_matches && value_matches), + KeyValueOperator::AND => key_matches && value_matches, + KeyValueOperator::NOR => !(key_matches || value_matches), + KeyValueOperator::OR => key_matches || value_matches, + KeyValueOperator::IMPLICATION => !key_matches || value_matches, + }; + }; + + let is_match = match self.matching_strategy { + MatchingStrategy::Absence => req_values.iter().all(request_value_matches), + MatchingStrategy::Presence => req_values.iter().any(request_value_matches), + }; + + // We negate here, since we are filtering for "unmatched" expectations -> true = unmatched + return !is_match; }) - .collect() + .collect(); } fn find_best_match<'a>( &self, - sk: &SK, - sv: &Option<&SV>, - req_values: &'a Vec<(TK, Option)>, - ) -> Option<(&'a TK, &'a Option)> { + sk: &EK, + sv: &Option<&EV>, + req_values: &'a [(RK, Option)], + ) -> Option<(&'a RK, &'a Option)> { if req_values.is_empty() { return None; } - let found = req_values - .into_iter() - .find(|(k, v)| k.to_string().eq(&sk.to_string())); - if let Some((fk, fv)) = found { + if let Some((fk, fv)) = req_values + .iter() + .find(|(k, _)| k.to_string() == sk.to_string()) + { return Some((fk, fv)); } req_values - .into_iter() + .iter() .map(|(tk, tv)| { let key_distance = self.key_comparator.distance(&Some(sk), &Some(tk)); - let value_distance = self.value_comparator.distance(&sv, &tv.as_ref()); + let value_distance = self.value_comparator.distance(sv, &tv.as_ref()); (tk, tv, key_distance + value_distance) }) .min_by(|(_, _, d1), (_, _, d2)| d1.cmp(d2)) - .map(|(k, v, _)| (k.to_owned(), v.to_owned())) + .map(|(k, v, _)| (k, v)) } } -impl Matcher for MultiValueMatcher +impl Matcher for MultiValueMatcher where - SK: Display, - SV: Display, - TK: Display, - TV: Display, + EK: Display, + EV: Display, + RK: Display, + RV: Display, { fn matches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> bool { - let req_values = self.target.parse_from_request(req).unwrap_or(Vec::new()); - let mock_values = self.source.parse_from_mock(mock).unwrap_or(Vec::new()); + let mock_values = (self.expectation)(mock).unwrap_or(Vec::new()); + if mock_values.is_empty() { + return true; + } + + let req_values = (self.request_value)(req).unwrap_or(Vec::new()); self.find_unmatched(&req_values, &mock_values).is_empty() } fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize { - let req_values = self.target.parse_from_request(req).unwrap_or(Vec::new()); - let mock_values = self.source.parse_from_mock(mock).unwrap_or(Vec::new()); + let mock_values = (self.expectation)(mock).unwrap_or_default(); + if mock_values.is_empty() { + return 0; + } + + let req_values = (self.request_value)(req).unwrap_or_default(); self.find_unmatched(&req_values, &mock_values) - .into_iter() - .map(|(k, v)| (k, v, self.find_best_match(&k, v, &req_values))) - .map(|(k, v, best_match)| match best_match { + .iter() + .map(|(k, v)| match self.find_best_match(k, v, &req_values) { None => { self.key_comparator.distance(&Some(k), &None) + self.value_comparator.distance(v, &None) @@ -217,31 +277,222 @@ where } fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec { - let req_values = self.target.parse_from_request(req).unwrap_or(Vec::new()); - let mock_values = self.source.parse_from_mock(mock).unwrap_or(Vec::new()); + let mock_values = (self.expectation)(mock); + if is_none_or_empty(&mock_values) { + return Vec::new(); + } + + let mock_values = mock_values.unwrap_or_default(); + let req_values = (self.request_value)(req).unwrap_or_default(); self.find_unmatched(&req_values, &mock_values) - .into_iter() - .map(|(k, v)| (k, v, self.find_best_match(&k, v, &req_values))) - .map(|(k, v, best_match)| Mismatch { - title: match v { - None => format!("Expected {} with name '{}' to be present in the request but it wasn't.", self.entity_name, &k), - Some(v) => format!("Expected {} with name '{}' and value '{}' to be present in the request but it wasn't.", self.entity_name, &k, v), - }, - reason: best_match.as_ref().map(|(bmk, bmv)| { - Reason { - expected: match v { - None => format!("{}", k), - Some(v) => format!("{}={}", k, v), - }, - actual: match bmv { - None => format!("{}", bmk), - Some(bmv) => format!("{}={}", bmk, bmv), - }, - comparison: format!("key={}, value={}", self.key_comparator.name(), self.value_comparator.name()), - best_match: true, - } - }), - diff: None, + .iter() // Use iter to avoid ownership issues and unnecessary data moving. + .map(|(k, v)| { + let best_match = self.find_best_match(k, v, &req_values); + Mismatch { + entity: self.entity_name.to_string(), + matcher_method: self.matcher_method.to_string(), + comparison: None, + function_comparison: None, + key_value_comparison: Some(KeyValueComparison { + key: Some(KeyValueComparisonAttribute { + operator: self.key_comparator.name().to_string(), + expected: k.to_string(), + actual: best_match.map(|(bmk, _)| bmk.to_string()), + }), + value: v.map(|v| KeyValueComparisonAttribute { + operator: self.value_comparator.name().to_string(), + expected: v.to_string(), + actual: best_match + .and_then(|(_, bmv)| bmv.as_ref().map(|bmv| bmv.to_string())), + }), + expected_count: None, + actual_count: None, + all: (&req_values) + .into_iter() + .map(|(key, value)| KeyValueComparisonKeyValuePair { + key: key.to_string(), + value: value.as_ref().map(|v| v.to_string()), + }) + .collect(), + }), + matching_strategy: Some(self.matching_strategy.clone()), + diff: None, + best_match: best_match.is_some(), + } + }) + .collect() + } +} + +// ************************************************************************************************ +// MultiValueCountMatcher +// ************************************************************************************************ +pub(crate) struct MultiValueCountMatcher +where + EK: Display, + EV: Display, + RK: Display, + RV: Display, +{ + pub entity_name: &'static str, + pub matcher_method: &'static str, + pub expectation: + for<'a> fn(&'a RequestRequirements) -> Option, Option<&'a EV>, usize)>>, + pub request_value: fn(&HttpMockRequest) -> Option)>>, + pub key_comparator: Box + Send + Sync>, + pub value_comparator: Box + Send + Sync>, + pub with_reason: bool, + pub diff_with: Option, + pub weight: usize, +} + +impl MultiValueCountMatcher +where + EK: Display, + EV: Display, + RK: Display, + RV: Display, +{ + fn find_unmatched<'a>( + &self, + req_values: &[(RK, Option)], + mock_values: &'a [(Option<&'a EK>, Option<&'a EV>, usize)], + ) -> Vec<&'a (Option<&'a EK>, Option<&'a EV>, usize)> { + let matched_idx = self.find_matched_indices(req_values, mock_values); + self.filter_unmatched_indices(mock_values, &matched_idx) + } + + fn find_matched_indices<'a>( + &self, + req_values: &[(RK, Option)], + mock_values: &'a [(Option<&'a EK>, Option<&'a EV>, usize)], + ) -> HashSet { + let mut matched_idx = HashSet::new(); + + for (idx, (ek, ev, count)) in mock_values.iter().enumerate() { + let matches = self.count_matching_req_values(req_values, ek, ev); + if matches == *count { + matched_idx.insert(idx); + } + } + + matched_idx + } + + fn count_matching_req_values( + &self, + req_values: &[(RK, Option)], + ek: &Option<&EK>, + ev: &Option<&EV>, + ) -> usize { + req_values + .iter() + .filter(|(rk, rv)| { + let key_matches = match ek { + Some(ek) => self.key_comparator.matches(&Some(ek), &Some(rk)), + None => true, // No expectation => true + }; + + let value_matches = match (ev, rv) { + (Some(ev), Some(rv)) => self.value_comparator.matches(&Some(ev), &Some(rv)), + (Some(_), None) => false, // Expectation but no request value => false + (None, _) => true, // No expectation => true + }; + + key_matches && value_matches + }) + .count() + } + + fn filter_unmatched_indices<'a>( + &self, + mock_values: &'a [(Option<&'a EK>, Option<&'a EV>, usize)], + matched_idx: &HashSet, + ) -> Vec<&'a (Option<&'a EK>, Option<&'a EV>, usize)> { + mock_values + .iter() + .enumerate() + .filter(|(i, _)| !matched_idx.contains(i)) + .map(|(_, item)| item) + .collect() + } +} + +impl Matcher for MultiValueCountMatcher +where + EK: Display, + EV: Display, + RK: Display, + RV: Display, +{ + fn matches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> bool { + let mock_values = (self.expectation)(mock).unwrap_or_default(); + if mock_values.is_empty() { + return true; + } + + let req_values = (self.request_value)(req).unwrap_or(Vec::new()); + self.find_unmatched(&req_values, &mock_values).is_empty() + } + + fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize { + let mock_values = (self.expectation)(mock).unwrap_or_default(); + if mock_values.is_empty() { + return 0; + } + + let req_values = (self.request_value)(req).unwrap_or_default(); + self.find_unmatched(&req_values, &mock_values) + .iter() + .map(|(k, v, c)| { + let num_matching = self.count_matching_req_values(&req_values, k, v); + num_matching.abs_diff(*c) + }) + .map(|d| d * self.weight) + .sum() + } + + fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec { + let mock_values = (self.expectation)(mock).unwrap_or_default(); + if mock_values.is_empty() { + return Vec::new(); + } + + let req_values = (self.request_value)(req).unwrap_or_default(); + self.find_unmatched(&req_values, &mock_values) + .iter() // Use iter to avoid ownership issues and unnecessary data moving. + .map(|(k, v, expected_count)| { + let actual_count = self.count_matching_req_values(&req_values, k, v); + Mismatch { + entity: self.entity_name.to_string(), + matcher_method: self.matcher_method.to_string(), + comparison: None, + key_value_comparison: Some(KeyValueComparison { + key: k.map(|k| KeyValueComparisonAttribute { + operator: self.key_comparator.name().to_string(), + expected: k.to_string(), + actual: None, + }), + value: v.map(|v| KeyValueComparisonAttribute { + operator: self.value_comparator.name().to_string(), + expected: v.to_string(), + actual: None, + }), + expected_count: Some(*expected_count), + actual_count: Some(actual_count), + all: (&req_values) + .into_iter() + .map(|(key, value)| KeyValueComparisonKeyValuePair { + key: key.to_string(), + value: value.as_ref().map(|v| v.to_string()), + }) + .collect(), + }), + matching_strategy: None, + function_comparison: None, + diff: None, + best_match: false, + } }) .collect() } @@ -252,10 +503,10 @@ where // ************************************************************************************************ pub(crate) struct FunctionValueMatcher { pub entity_name: &'static str, - pub source: Box + Send + Sync>, - pub target: Box + Send + Sync>, + pub matcher_function: &'static str, + pub expectation: for<'a> fn(&'a RequestRequirements) -> Option>, + pub request_value: for<'a> fn(&'a HttpMockRequest) -> Option<&'a T>, pub comparator: Box + Send + Sync>, - pub transformer: Option + Send + Sync>>, pub weight: usize, } @@ -282,7 +533,7 @@ impl FunctionValueMatcher { mock_values .into_iter() .enumerate() - .filter(|(idx, e)| !self.comparator.matches(e, req_value)) + .filter(|(idx, e)| !self.comparator.matches(&Some(e), &Some(req_value))) .map(|(idx, e)| (idx)) .collect() } @@ -290,37 +541,84 @@ impl FunctionValueMatcher { impl Matcher for FunctionValueMatcher { fn matches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> bool { - let req_value = self.target.parse_from_request(req); - let mock_values = self.source.parse_from_mock(mock); + let mock_values = (self.expectation)(mock); + if is_none_or_empty(&mock_values) { + return true; + } + + let req_value = (self.request_value)(req); self.get_unmatched(&req_value, &mock_values).is_empty() } fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize { - let req_value = self.target.parse_from_request(req); - let mock_values = self.source.parse_from_mock(mock); + let mock_values = (self.expectation)(mock); + if is_none_or_empty(&mock_values) { + return 0; + } + + let req_value = (self.request_value)(req); self.get_unmatched(&req_value, &mock_values).len() * self.weight } fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec { - let req_value = self.target.parse_from_request(req); - let mock_value = self.source.parse_from_mock(mock); - self.get_unmatched(&req_value, &mock_value) + let mock_values = (self.expectation)(mock); + if is_none_or_empty(&mock_values) { + return Vec::new(); + } + + let req_value = (self.request_value)(req); + self.get_unmatched(&req_value, &mock_values) .into_iter() .map(|idx| Mismatch { - title: format!( - "The {} at position {} does not match", - self.entity_name, - idx + 1 - ), - reason: None, + entity: self.entity_name.to_string(), + matcher_method: self.matcher_function.to_string(), + function_comparison: Some(FunctionComparison { index: idx }), + comparison: None, + key_value_comparison: None, diff: None, + best_match: false, + matching_strategy: None, }) .collect() } } -#[cfg(test)] -mod test { - #[test] - fn todo() {} +#[inline] +fn times_str<'a>(v: usize) -> &'a str { + if v == 1 { + return "time"; + } + + return "times"; +} + +#[inline] +fn get_plural<'a>(v: usize, singular: &'a str, plural: &'a str) -> &'a str { + if v == 1 { + return singular; + } + + return plural; +} + +#[inline] +pub fn diff_str(base: &str, edit: &str, tokenizer: Tokenizer) -> DiffResult { + let changes = match tokenizer { + Tokenizer::Line => TextDiff::from_lines(base, edit), + Tokenizer::Word => TextDiff::from_words(base, edit), + Tokenizer::Character => TextDiff::from_chars(base, edit), + }; + + DiffResult { + tokenizer, + distance: changes.ratio(), + differences: changes + .iter_all_changes() + .map(|change| match change.tag() { + ChangeTag::Equal => Diff::Same(change.to_string_lossy().to_string()), + ChangeTag::Insert => Diff::Add(change.to_string_lossy().to_string()), + ChangeTag::Delete => Diff::Rem(change.to_string_lossy().to_string()), + }) + .collect(), + } } diff --git a/src/server/matchers/mod.rs b/src/server/matchers/mod.rs index 4bf05f44..b735bc88 100644 --- a/src/server/matchers/mod.rs +++ b/src/server/matchers/mod.rs @@ -1,40 +1,1168 @@ -use std::collections::BTreeMap; -use std::fmt::Display; +use std::{convert::TryInto, fmt::Display, ops::Deref}; -#[cfg(feature = "cookies")] -use basic_cookies::Cookie; use serde::{Deserialize, Serialize}; -use similar::{ChangeTag, TextDiff}; -use crate::common::data::{ - Diff, DiffResult, HttpMockRequest, Mismatch, RequestRequirements, Tokenizer, +use crate::common::data::{HttpMockRequest, Mismatch, RequestRequirements, Tokenizer}; + +use crate::server::matchers::comparators::{ + AnyValueComparator, BytesExactMatchComparator, BytesIncludesComparator, BytesPrefixComparator, + BytesSuffixComparator, FunctionMatchesRequestComparator, HostEqualsComparator, + HttpMockBytesPatternComparator, JSONContainsMatchComparator, JSONExactMatchComparator, + StringContainsComparator, StringEqualsComparator, StringPatternMatchComparator, + StringPrefixMatchComparator, StringRegexMatchComparator, StringSuffixMatchComparator, + U16ExactMatchComparator, }; -pub(crate) mod comparators; -pub(crate) mod generic; -pub(crate) mod sources; -pub(crate) mod targets; -pub(crate) mod transformers; +use crate::server::matchers::generic::{ + FunctionValueMatcher, KeyValueOperator, MatchingStrategy, MultiValueCountMatcher, + MultiValueMatcher, SingleValueMatcher, +}; -pub(crate) fn diff_str(base: &str, edit: &str, tokenizer: Tokenizer) -> DiffResult { - let changes = match tokenizer { - Tokenizer::Line => TextDiff::from_lines(base, edit), - Tokenizer::Word => TextDiff::from_words(base, edit), - Tokenizer::Character => TextDiff::from_chars(base, edit), - }; +pub mod comparators; +mod comparison; +pub mod generic; +pub mod readers; - DiffResult { - tokenizer, - distance: changes.ratio(), - differences: changes - .iter_all_changes() - .map(|change| match change.tag() { - ChangeTag::Equal => Diff::Same(change.to_string_lossy().to_string()), - ChangeTag::Insert => Diff::Add(change.to_string_lossy().to_string()), - ChangeTag::Delete => Diff::Rem(change.to_string_lossy().to_string()), - }) - .collect(), - } +pub fn all() -> Vec> { + vec![ + //************************************************************************************* + // Scheme matchers + //************************************************************************************* + Box::new(SingleValueMatcher { + entity_name: "scheme", + matcher_method: "scheme", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringEqualsComparator::new(false, false)), + expectation: readers::expectations::scheme_equal_to, + request_value: readers::request_value::scheme, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "scheme", + matcher_method: "scheme_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringEqualsComparator::new(false, true)), + expectation: readers::expectations::scheme_not_equal_to, + request_value: readers::request_value::scheme, + with_reason: true, + diff_with: None, + weight: 3, + }), + //************************************************************************************* + // Method matchers + //************************************************************************************* + Box::new(SingleValueMatcher { + entity_name: "method", + matcher_method: "method", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringEqualsComparator::new(false, false)), + expectation: readers::expectations::method_equal_to, + request_value: readers::request_value::method, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "method", + matcher_method: "method_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringEqualsComparator::new(false, true)), + expectation: readers::expectations::method_not_equal_to, + request_value: readers::request_value::method, + with_reason: true, + diff_with: None, + weight: 3, + }), + //************************************************************************************* + // Host matchers + //************************************************************************************* + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(HostEqualsComparator::new(false)), + expectation: readers::expectations::host_equal_to, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(HostEqualsComparator::new(true)), + expectation: readers::expectations::host_not_equal_to, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_includes", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringContainsComparator::new(false, false)), + expectation: readers::expectations::host_includes, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_excludes", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringContainsComparator::new(false, true)), + expectation: readers::expectations::host_excludes, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_prefix", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringPrefixMatchComparator::new(false, false)), + expectation: readers::expectations::host_prefix, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_prefix_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringPrefixMatchComparator::new(false, true)), + expectation: readers::expectations::host_prefix_not, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_suffix", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringSuffixMatchComparator::new(false, false)), + expectation: readers::expectations::host_has_suffix, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_suffix_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringSuffixMatchComparator::new(false, true)), + expectation: readers::expectations::host_has_no_suffix, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + Box::new(SingleValueMatcher { + entity_name: "host", + matcher_method: "host_matches", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringPatternMatchComparator::new(false, true)), + expectation: readers::expectations::host_matches_regex, + request_value: readers::request_value::host, + with_reason: true, + diff_with: None, + weight: 3, + }), + //************************************************************************************* + // Port matchers + //************************************************************************************* + Box::new(SingleValueMatcher { + entity_name: "port", + matcher_method: "port", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(U16ExactMatchComparator::new(false)), + expectation: readers::expectations::port_equal_to, + request_value: readers::request_value::port, + with_reason: true, + diff_with: None, + weight: 2, + }), + Box::new(SingleValueMatcher { + entity_name: "port", + matcher_method: "port_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(U16ExactMatchComparator::new(true)), + expectation: readers::expectations::port_not_equal_to, + request_value: readers::request_value::port, + with_reason: true, + diff_with: None, + weight: 2, + }), + //************************************************************************************* + // Path matchers + //************************************************************************************* + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringEqualsComparator::new(true, false)), + expectation: readers::expectations::path_equal_to, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringEqualsComparator::new(true, true)), + expectation: readers::expectations::path_not_equal_to, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_includes", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringContainsComparator::new(true, false)), + expectation: readers::expectations::path_includes, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + // path excludes + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_excludes", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringContainsComparator::new(true, true)), + expectation: readers::expectations::path_excludes, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_prefix", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringPrefixMatchComparator::new(true, false)), + expectation: readers::expectations::path_prefix, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_prefix_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringPrefixMatchComparator::new(true, true)), + expectation: readers::expectations::path_prefix_not, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_suffix", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringSuffixMatchComparator::new(true, false)), + expectation: readers::expectations::path_suffix, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_suffix_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(StringSuffixMatchComparator::new(true, true)), + expectation: readers::expectations::path_suffix_not, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + Box::new(SingleValueMatcher { + entity_name: "path", + matcher_method: "path_matches", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(StringRegexMatchComparator::new()), + expectation: readers::expectations::path_matches, + request_value: readers::request_value::path, + with_reason: true, + diff_with: None, + weight: 10, + }), + //************************************************************************************* + // Query param matchers + //************************************************************************************* + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param", + matching_strategy: MatchingStrategy::Presence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::query_param, + request_value: readers::request_value::query_params, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_not", + matching_strategy: MatchingStrategy::Absence, + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::query_param_not, + request_value: readers::request_value::query_params, + key_required: true, + // Key is not negated, since we expect a query parameter to be present with the expected key. + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_exists", + matching_strategy: MatchingStrategy::Presence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::query_param_exists, + request_value: readers::request_value::query_params, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_missing", + matching_strategy: MatchingStrategy::Absence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::query_param_missing, + request_value: readers::request_value::query_params, + key_required: false, + key_comparator: Box::new(StringEqualsComparator::new(true, true)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_includes", + matching_strategy: MatchingStrategy::Presence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::query_param_includes, + request_value: readers::request_value::query_params, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringContainsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_excludes", + matching_strategy: MatchingStrategy::Absence, + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::query_param_excludes, + request_value: readers::request_value::query_params, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringContainsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_prefix", + matching_strategy: MatchingStrategy::Presence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::query_param_prefix, + request_value: readers::request_value::query_params, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_prefix_not", + matching_strategy: MatchingStrategy::Absence, + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::query_param_prefix_not, + request_value: readers::request_value::query_params, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_suffix", + matching_strategy: MatchingStrategy::Presence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::query_param_suffix, + request_value: readers::request_value::query_params, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_suffix_not", + matching_strategy: MatchingStrategy::Absence, + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::query_param_suffix_not, + request_value: readers::request_value::query_params, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "query parameter", + matcher_method: "query_param_matches", + matching_strategy: MatchingStrategy::Presence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::query_param_matches, + request_value: readers::request_value::query_params, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueCountMatcher { + entity_name: "query parameter", + matcher_method: "query_param_count", + expectation: readers::expectations::query_param_count, + request_value: readers::request_value::query_params, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + //************************************************************************************ + // Header matchers + //************************************************************************************ + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header", + matching_strategy: MatchingStrategy::Presence, + operator: KeyValueOperator::AND, + expectation: readers::expectations::header, + request_value: readers::request_value::headers, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_not", + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::header_not, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_exists", + operator: KeyValueOperator::AND, + expectation: readers::expectations::header_exists, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_missing", + operator: KeyValueOperator::AND, + expectation: readers::expectations::header_missing, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Absence, + key_required: false, + key_comparator: Box::new(StringEqualsComparator::new(false, true)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_includes", + operator: KeyValueOperator::AND, + expectation: readers::expectations::header_includes, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringContainsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_excludes", + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::header_excludes, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringContainsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_prefix", + operator: KeyValueOperator::AND, + expectation: readers::expectations::header_prefix, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_prefix_not", + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::header_prefix_not, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_suffix", + operator: KeyValueOperator::AND, + expectation: readers::expectations::header_suffix, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_suffix_not", + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::header_suffix_not, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "header", + matcher_method: "header_matches", + operator: KeyValueOperator::AND, + expectation: readers::expectations::header_matches, + request_value: readers::request_value::headers, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, false)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueCountMatcher { + entity_name: "header", + matcher_method: "header_count", + expectation: readers::expectations::header_count, + request_value: readers::request_value::headers, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, false)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + // *********************************************************************************** + // Cookie matchers + // *********************************************************************************** + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie", + operator: KeyValueOperator::AND, + expectation: readers::expectations::cookie, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_not", + matching_strategy: MatchingStrategy::Absence, + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::cookie_not, + request_value: readers::request_value::cookies, + key_required: true, + // Key is not negated, since we expect a query parameter to be present with the expected key. + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_exists", + operator: KeyValueOperator::AND, + expectation: readers::expectations::cookie_exists, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_missing", + operator: KeyValueOperator::AND, + expectation: readers::expectations::cookie_missing, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Absence, + key_required: false, + key_comparator: Box::new(StringEqualsComparator::new(true, true)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_includes", + operator: KeyValueOperator::AND, + expectation: readers::expectations::cookie_includes, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringContainsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_excludes", + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::cookie_excludes, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringContainsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_prefix", + operator: KeyValueOperator::AND, + expectation: readers::expectations::cookie_prefix, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_prefix_not", + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::cookie_prefix_not, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_suffix", + operator: KeyValueOperator::AND, + expectation: readers::expectations::cookie_suffix, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_suffix_not", + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::cookie_suffix_not, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueMatcher { + entity_name: "cookie", + matcher_method: "cookie_matches", + operator: KeyValueOperator::AND, + expectation: readers::expectations::cookie_matches, + request_value: readers::request_value::cookies, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + #[cfg(feature = "cookies")] + Box::new(MultiValueCountMatcher { + entity_name: "cookie", + matcher_method: "cookie_count", + expectation: readers::expectations::cookie_count, + request_value: readers::request_value::cookies, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + // ************************************************************************************ + // Body matchers + // ************************************************************************************ + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(BytesExactMatchComparator::new(false)), + expectation: readers::expectations::body, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(BytesExactMatchComparator::new(true)), + expectation: readers::expectations::body_not, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_includes", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(BytesIncludesComparator::new(false)), + expectation: readers::expectations::body_includes, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_excludes", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(BytesIncludesComparator::new(true)), + expectation: readers::expectations::body_excludes, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_prefix", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(BytesPrefixComparator::new(false)), + expectation: readers::expectations::body_prefix, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_prefix_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(BytesPrefixComparator::new(true)), + expectation: readers::expectations::body_prefix_not, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_suffix", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(BytesSuffixComparator::new(false)), + expectation: readers::expectations::body_suffix, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_suffix_not", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(BytesSuffixComparator::new(true)), + expectation: readers::expectations::body_suffix_not, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "body_matches", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(HttpMockBytesPatternComparator::new()), + expectation: readers::expectations::body_matches, + request_value: readers::request_value::body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + //************************************************************************************ + // JSON body matchers + //************************************************************************************ + Box::new(SingleValueMatcher { + entity_name: "body", + matcher_method: "json_body", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(JSONExactMatchComparator::new()), + expectation: readers::expectations::json_body, + request_value: readers::request_value::json_body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "JSON body", + matcher_method: "json_body_includes", + matching_strategy: MatchingStrategy::Presence, + comparator: Box::new(JSONContainsMatchComparator::new(false)), + expectation: readers::expectations::json_body_includes, + request_value: readers::request_value::json_body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(SingleValueMatcher { + entity_name: "JSON body", + matcher_method: "json_body_excludes", + matching_strategy: MatchingStrategy::Absence, + comparator: Box::new(JSONContainsMatchComparator::new(true)), + expectation: readers::expectations::json_body_excludes, + request_value: readers::request_value::json_body, + with_reason: true, + diff_with: Some(Tokenizer::Line), + weight: 1, + }), + Box::new(FunctionValueMatcher { + entity_name: "custom matcher function", + matcher_function: "is_true", + comparator: Box::new(FunctionMatchesRequestComparator::new(false)), + expectation: readers::expectations::is_true, + request_value: readers::request_value::full_request, + weight: 1, + }), + Box::new(FunctionValueMatcher { + entity_name: "custom matcher function", + matcher_function: "is_false", + comparator: Box::new(FunctionMatchesRequestComparator::new(false)), + expectation: readers::expectations::is_true, + request_value: readers::request_value::full_request, + weight: 1, + }), + //************************************************************************************* + // x-www-form-urlencoded body + //************************************************************************************* + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple", + expectation: readers::expectations::form_urlencoded_tuple, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::AND, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_not", + matching_strategy: MatchingStrategy::Absence, + operator: KeyValueOperator::IMPLICATION, + expectation: readers::expectations::form_urlencoded_tuple_not, + request_value: readers::request_value::form_urlencoded_body, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringEqualsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_exists", + expectation: readers::expectations::form_urlencoded_key_exists, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::AND, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_missing", + expectation: readers::expectations::form_urlencoded_key_missing, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::AND, + matching_strategy: MatchingStrategy::Absence, + key_required: false, + key_comparator: Box::new(StringEqualsComparator::new(true, true)), + value_comparator: Box::new(AnyValueComparator::new()), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_includes", + expectation: readers::expectations::form_urlencoded_includes, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::AND, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringContainsComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + // TODO: Make text more understandable for the user (what excludes ? value? key?) + matcher_method: "form_urlencoded_tuple_excludes", + expectation: readers::expectations::form_urlencoded_excludes, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::IMPLICATION, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringContainsComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_prefix", + expectation: readers::expectations::form_urlencoded_prefix, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::AND, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_prefix_not", + expectation: readers::expectations::form_urlencoded_prefix_not, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::IMPLICATION, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(false, false)), + value_comparator: Box::new(StringPrefixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_suffix", + operator: KeyValueOperator::AND, + expectation: readers::expectations::form_urlencoded_suffix, + request_value: readers::request_value::form_urlencoded_body, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, false)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_suffix_not", + expectation: readers::expectations::form_urlencoded_suffix_not, + request_value: readers::request_value::form_urlencoded_body, + operator: KeyValueOperator::IMPLICATION, + matching_strategy: MatchingStrategy::Absence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringEqualsComparator::new(true, false)), + value_comparator: Box::new(StringSuffixMatchComparator::new(true, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_matches", + operator: KeyValueOperator::AND, + expectation: readers::expectations::form_urlencoded_matches, + request_value: readers::request_value::form_urlencoded_body, + matching_strategy: MatchingStrategy::Presence, + key_required: true, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + Box::new(MultiValueCountMatcher { + entity_name: "form-urlencoded body", + matcher_method: "form_urlencoded_tuple_count", + expectation: readers::expectations::form_urlencoded_key_value_count, + request_value: readers::request_value::form_urlencoded_body, + // TODO: ATTENTION: still false, because it is expected that the key appears in the request! + key_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + value_comparator: Box::new(StringPatternMatchComparator::new(false, true)), + with_reason: true, + diff_with: None, + weight: 1, + }), + ] } pub trait Matcher { @@ -42,37 +1170,3 @@ pub trait Matcher { fn distance(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> usize; fn mismatches(&self, req: &HttpMockRequest, mock: &RequestRequirements) -> Vec; } - -// ************************************************************************************************* -// Helper functions -// ************************************************************************************************* -#[cfg(feature = "cookies")] -pub(crate) fn parse_cookies(req: &HttpMockRequest) -> Result, String> { - let parsing_result = req.headers.as_ref().map_or(None, |request_headers| { - request_headers - .iter() - .find(|(k, _)| k.to_lowercase().eq("cookie")) - .map(|(k, v)| Cookie::parse(v)) - }); - - match parsing_result { - None => Ok(Vec::new()), - Some(res) => match res { - Err(err) => Err(err.to_string()), - Ok(vec) => Ok(vec - .into_iter() - .map(|c| (c.get_name().to_owned(), c.get_value().to_owned())) - .collect()), - }, - } -} - -pub(crate) fn distance_for(expected: &Option<&T>, actual: &Option<&U>) -> usize -where - T: Display, - U: Display, -{ - let expected = expected.map_or(String::new(), |x| x.to_string()); - let actual = actual.map_or(String::new(), |x| x.to_string()); - levenshtein::levenshtein(&expected, &actual) -} diff --git a/src/server/matchers/readers.rs b/src/server/matchers/readers.rs new file mode 100644 index 00000000..faec118e --- /dev/null +++ b/src/server/matchers/readers.rs @@ -0,0 +1,710 @@ +pub mod expectations { + use crate::{ + common::{ + data::{HttpMockRegex, RequestRequirements}, + util::HttpMockBytes, + }, + prelude::HttpMockRequest, + }; + use serde_json::Value; + use std::sync::Arc; + + #[inline] + pub fn scheme_equal_to(mock: &RequestRequirements) -> Option> { + mock.scheme.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn scheme_not_equal_to(mock: &RequestRequirements) -> Option> { + mock.scheme_not.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn method_equal_to(mock: &RequestRequirements) -> Option> { + mock.method.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn method_not_equal_to(mock: &RequestRequirements) -> Option> { + mock.method_not + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn host_equal_to(mock: &RequestRequirements) -> Option> { + mock.host.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn host_not_equal_to(mock: &RequestRequirements) -> Option> { + mock.host_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn host_includes(mock: &RequestRequirements) -> Option> { + mock.host_contains + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn host_excludes(mock: &RequestRequirements) -> Option> { + mock.host_excludes + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn host_prefix(mock: &RequestRequirements) -> Option> { + mock.host_prefix + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn host_prefix_not(mock: &RequestRequirements) -> Option> { + mock.host_prefix_not + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn host_has_suffix(mock: &RequestRequirements) -> Option> { + mock.host_suffix + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn host_has_no_suffix(mock: &RequestRequirements) -> Option> { + mock.host_suffix_not + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn host_matches_regex(mock: &RequestRequirements) -> Option> { + mock.host_matches + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn port_equal_to(mock: &RequestRequirements) -> Option> { + mock.port.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn port_not_equal_to(mock: &RequestRequirements) -> Option> { + mock.port_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_equal_to(mock: &RequestRequirements) -> Option> { + mock.path.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn path_not_equal_to(mock: &RequestRequirements) -> Option> { + mock.path_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_includes(mock: &RequestRequirements) -> Option> { + mock.path_includes.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_excludes(mock: &RequestRequirements) -> Option> { + mock.path_excludes.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_prefix(mock: &RequestRequirements) -> Option> { + mock.path_prefix.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_prefix_not(mock: &RequestRequirements) -> Option> { + mock.path_prefix_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_suffix(mock: &RequestRequirements) -> Option> { + mock.path_suffix.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_suffix_not(mock: &RequestRequirements) -> Option> { + mock.path_suffix_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn path_matches(mock: &RequestRequirements) -> Option> { + mock.path_matches + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn query_param(mock: &RequestRequirements) -> Option)>> { + mock.query_param + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_not(mock: &RequestRequirements) -> Option)>> { + mock.query_param_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_exists( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_exists + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + #[inline] + pub fn query_param_missing( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_missing + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + #[inline] + pub fn query_param_includes( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_includes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_excludes( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_excludes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_prefix( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_prefix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_prefix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_prefix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_suffix( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_suffix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_suffix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_suffix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_matches( + mock: &RequestRequirements, + ) -> Option)>> { + mock.query_param_matches + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn query_param_count( + mock: &RequestRequirements, + ) -> Option, Option<&HttpMockRegex>, usize)>> { + mock.query_param_count + .as_ref() + .map(|v| v.iter().map(|(k, v, c)| (Some(k), Some(v), *c)).collect()) + } + + #[inline] + pub fn header(mock: &RequestRequirements) -> Option)>> { + mock.header + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_not(mock: &RequestRequirements) -> Option)>> { + mock.header_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_exists(mock: &RequestRequirements) -> Option)>> { + mock.header_exists + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + #[inline] + pub fn header_missing(mock: &RequestRequirements) -> Option)>> { + mock.header_missing + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + #[inline] + pub fn header_includes(mock: &RequestRequirements) -> Option)>> { + mock.header_includes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_excludes(mock: &RequestRequirements) -> Option)>> { + mock.header_excludes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_prefix(mock: &RequestRequirements) -> Option)>> { + mock.header_prefix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_prefix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.header_prefix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_suffix(mock: &RequestRequirements) -> Option)>> { + mock.header_suffix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_suffix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.header_suffix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_matches( + mock: &RequestRequirements, + ) -> Option)>> { + mock.header_matches + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn header_count( + mock: &RequestRequirements, + ) -> Option, Option<&HttpMockRegex>, usize)>> { + mock.header_count + .as_ref() + .map(|v| v.iter().map(|(k, v, c)| (Some(k), Some(v), *c)).collect()) + } + + #[inline] + pub fn cookie(mock: &RequestRequirements) -> Option)>> { + mock.cookie + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_not(mock: &RequestRequirements) -> Option)>> { + mock.cookie_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_exists(mock: &RequestRequirements) -> Option)>> { + mock.cookie_exists + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + #[inline] + pub fn cookie_missing(mock: &RequestRequirements) -> Option)>> { + mock.cookie_missing + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + #[inline] + pub fn cookie_includes(mock: &RequestRequirements) -> Option)>> { + mock.cookie_includes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_excludes(mock: &RequestRequirements) -> Option)>> { + mock.cookie_excludes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_prefix(mock: &RequestRequirements) -> Option)>> { + mock.cookie_prefix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_prefix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.cookie_prefix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_suffix(mock: &RequestRequirements) -> Option)>> { + mock.cookie_suffix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_suffix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.cookie_suffix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_matches( + mock: &RequestRequirements, + ) -> Option)>> { + mock.cookie_matches + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn cookie_count( + mock: &RequestRequirements, + ) -> Option, Option<&HttpMockRegex>, usize)>> { + mock.cookie_count + .as_ref() + .map(|v| v.iter().map(|(k, v, c)| (Some(k), Some(v), *c)).collect()) + } + + #[inline] + pub fn body(mock: &RequestRequirements) -> Option> { + mock.body.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn body_not(mock: &RequestRequirements) -> Option> { + mock.body_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn body_includes(mock: &RequestRequirements) -> Option> { + mock.body_includes.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn body_excludes(mock: &RequestRequirements) -> Option> { + mock.body_excludes.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn body_prefix(mock: &RequestRequirements) -> Option> { + mock.body_prefix.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn body_prefix_not(mock: &RequestRequirements) -> Option> { + mock.body_prefix_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn body_suffix(mock: &RequestRequirements) -> Option> { + mock.body_suffix.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn body_suffix_not(mock: &RequestRequirements) -> Option> { + mock.body_suffix_not.as_ref().map(|v| v.iter().collect()) + } + + #[inline] + pub fn body_matches(mock: &RequestRequirements) -> Option> { + mock.body_matches + .as_ref() + .map(|b| b.into_iter().map(|v| v).collect()) + } + + #[inline] + pub fn json_body(mock: &RequestRequirements) -> Option> { + mock.json_body.as_ref().map(|b| vec![b]) + } + + #[inline] + pub fn json_body_includes(mock: &RequestRequirements) -> Option> { + mock.json_body_includes + .as_ref() + .map(|b| b.into_iter().collect()) + } + + #[inline] + pub fn json_body_excludes(mock: &RequestRequirements) -> Option> { + mock.json_body_excludes + .as_ref() + .map(|b| b.into_iter().collect()) + } + + #[inline] + pub fn is_true( + mock: &RequestRequirements, + ) -> Option bool + 'static + Sync + Send>>> { + mock.is_true.as_ref().map(|b| b.iter().map(|f| f).collect()) + } + + pub fn form_urlencoded_tuple( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + pub fn form_urlencoded_tuple_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + pub fn form_urlencoded_key_exists( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_exists + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + pub fn form_urlencoded_key_missing( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_missing + .as_ref() + .map(|v| v.into_iter().map(|v| (v, None)).collect()) + } + + #[inline] + pub fn form_urlencoded_includes( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_includes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn form_urlencoded_excludes( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_excludes + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn form_urlencoded_prefix( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_prefix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn form_urlencoded_prefix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_prefix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn form_urlencoded_suffix( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_suffix + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn form_urlencoded_suffix_not( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_suffix_not + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn form_urlencoded_matches( + mock: &RequestRequirements, + ) -> Option)>> { + mock.form_urlencoded_tuple_matches + .as_ref() + .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) + } + + #[inline] + pub fn form_urlencoded_key_value_count( + mock: &RequestRequirements, + ) -> Option, Option<&HttpMockRegex>, usize)>> { + mock.form_urlencoded_tuple_count + .as_ref() + .map(|v| v.iter().map(|(k, v, c)| (Some(k), Some(v), *c)).collect()) + } +} + +pub mod request_value { + use crate::{common::util::HttpMockBytes, prelude::HttpMockRequest}; + use serde_json::Value; + + #[inline] + pub fn scheme(req: &HttpMockRequest) -> Option { + Some(req.scheme()) + } + + #[inline] + pub fn method(req: &HttpMockRequest) -> Option { + Some(req.method().to_string()) + } + + #[inline] + pub fn host(req: &HttpMockRequest) -> Option { + req.host().map(|h| h.to_string()) + } + + #[inline] + pub fn port(req: &HttpMockRequest) -> Option { + Some(req.port()) + } + + #[inline] + pub fn path(req: &HttpMockRequest) -> Option { + Some(req.uri().path().to_string()) + } + + #[inline] + pub fn query_params(req: &HttpMockRequest) -> Option)>> { + Some( + req.query_params_vec() + .iter() + .map(|(k, v)| (k.into(), Some(v.into()))) + .collect(), + ) + } + + #[inline] + pub fn headers(req: &HttpMockRequest) -> Option)>> { + Some( + req.headers_vec() + .iter() + .map(|(k, v)| (k.into(), Some(v.into()))) + .collect(), + ) + } + + #[inline] + pub fn cookies(req: &HttpMockRequest) -> Option)>> { + Some( + req.cookies() + .expect("cannot parse cookies") + .iter() + .map(|(k, v)| (k.into(), Some(v.into()))) + .collect(), + ) + } + + #[inline] + pub fn body(req: &HttpMockRequest) -> Option { + Some(req.body().clone()) + } + + #[inline] + pub fn json_body(req: &HttpMockRequest) -> Option { + let body = req.body_ref(); + if body.len() == 0 { + () + } + + match serde_json::from_slice(body) { + Err(e) => { + log::trace!("Cannot parse json value: {}", e); + None + } + Ok(v) => Some(v), + } + } + + pub fn form_urlencoded_body(req: &HttpMockRequest) -> Option)>> { + Some( + form_urlencoded::parse(req.body_ref()) + .into_owned() + .map(|(k, v)| (k, Some(v))) + .collect(), + ) + } + + #[inline] + pub fn full_request(req: &HttpMockRequest) -> Option<&HttpMockRequest> { + Some(req) + } +} diff --git a/src/server/matchers/sources.rs b/src/server/matchers/sources.rs deleted file mode 100644 index 23d7f11f..00000000 --- a/src/server/matchers/sources.rs +++ /dev/null @@ -1,378 +0,0 @@ -use std::collections::BTreeMap; - -use serde_json::Value; - -use crate::common::data::{MockMatcherFunction, RequestRequirements}; -use crate::Regex; - -pub(crate) trait ValueRefSource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option>; -} - -pub(crate) trait MultiValueSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>>; -} - -// ************************************************************************************************ -// StringBodySource -// ************************************************************************************************ -pub(crate) struct StringBodySource {} - -impl StringBodySource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for StringBodySource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.body.as_ref().map(|b| vec![b]) - } -} - -// ************************************************************************************************ -// StringBodySource -// ************************************************************************************************ -pub(crate) struct StringBodyContainsSource {} - -impl StringBodyContainsSource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for StringBodyContainsSource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.body_contains - .as_ref() - .map(|v| v.into_iter().map(|bc| bc).collect()) - } -} - -// ************************************************************************************************ -// BodyRegexSource -// ************************************************************************************************ -pub(crate) struct JSONBodySource {} - -impl JSONBodySource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for JSONBodySource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.json_body.as_ref().map(|b| vec![b]) - } -} - -// ************************************************************************************************ -// PartialJSONBodySource -// ************************************************************************************************ -pub(crate) struct PartialJSONBodySource {} - -impl PartialJSONBodySource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for PartialJSONBodySource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.json_body_includes - .as_ref() - .map(|b| b.into_iter().collect()) - } -} - -// ************************************************************************************************ -// BodyRegexSource -// ************************************************************************************************ -pub(crate) struct BodyRegexSource {} - -impl BodyRegexSource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for BodyRegexSource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.body_matches - .as_ref() - .map(|b| b.iter().map(|p| &p.regex).collect()) - } -} - -// ************************************************************************************************ -// MethodSource -// ************************************************************************************************ -pub(crate) struct MethodSource {} - -impl MethodSource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for MethodSource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.method.as_ref().map(|b| vec![b]) - } -} - -// ************************************************************************************************ -// StringPathSource -// ************************************************************************************************ -pub(crate) struct StringPathSource {} - -impl StringPathSource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for StringPathSource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.path.as_ref().map(|b| vec![b]) - } -} - -// ************************************************************************************************ -// StringPathContainsSource -// ************************************************************************************************ -pub(crate) struct PathContainsSubstringSource {} - -impl PathContainsSubstringSource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for PathContainsSubstringSource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.path_contains - .as_ref() - .map(|b| b.into_iter().map(|v| v).collect()) - } -} - -// ************************************************************************************************ -// PathRegexSource -// ************************************************************************************************ -pub(crate) struct PathRegexSource {} - -impl PathRegexSource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for PathRegexSource { - fn parse_from_mock<'a>(&self, mock: &'a RequestRequirements) -> Option> { - mock.path_matches - .as_ref() - .map(|b| b.into_iter().map(|v| &v.regex).collect()) - } -} - -// ************************************************************************************************ -// CookieSource -// ************************************************************************************************ -pub(crate) struct CookieSource {} - -impl CookieSource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for CookieSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.cookies - .as_ref() - .map(|c| c.iter().map(|(k, v)| (k, Some(v))).collect()) - } -} - -// ************************************************************************************************ -// ContainsCookieSource -// ************************************************************************************************ -pub(crate) struct ContainsCookieSource {} - -impl ContainsCookieSource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for ContainsCookieSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.cookie_exists - .as_ref() - .map(|c| c.iter().map(|v| (v, None)).collect()) - } -} - -// ************************************************************************************************ -// HeaderSource -// ************************************************************************************************ -pub(crate) struct HeaderSource {} - -impl HeaderSource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for HeaderSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.headers - .as_ref() - .map(|c| c.iter().map(|(k, v)| (k, Some(v))).collect()) - } -} - -// ************************************************************************************************ -// ContainsCookieSource -// ************************************************************************************************ -pub(crate) struct ContainsHeaderSource {} - -impl ContainsHeaderSource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for ContainsHeaderSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.header_exists - .as_ref() - .map(|c| c.iter().map(|v| (v, None)).collect()) - } -} - -// ************************************************************************************************ -// QueryParameterSource -// ************************************************************************************************ -pub(crate) struct QueryParameterSource {} - -impl QueryParameterSource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for QueryParameterSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.query_param - .as_ref() - .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) - } -} - -// ************************************************************************************************ -// ContainsQueryParameterSource -// ************************************************************************************************ -pub(crate) struct ContainsQueryParameterSource {} - -impl ContainsQueryParameterSource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for ContainsQueryParameterSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.query_param_exists - .as_ref() - .map(|v| v.into_iter().map(|v| (v, None)).collect()) - } -} - -// ************************************************************************************************ -// QueryParameterSource -// ************************************************************************************************ -pub(crate) struct XWWWFormUrlencodedSource {} - -impl XWWWFormUrlencodedSource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for XWWWFormUrlencodedSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.x_www_form_urlencoded - .as_ref() - .map(|v| v.into_iter().map(|(k, v)| (k, Some(v))).collect()) - } -} - -// ************************************************************************************************ -// ContainsQueryParameterSource -// ************************************************************************************************ -pub(crate) struct ContainsXWWWFormUrlencodedKeySource {} - -impl ContainsXWWWFormUrlencodedKeySource { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueSource for ContainsXWWWFormUrlencodedKeySource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option)>> { - mock.x_www_form_urlencoded_key_exists - .as_ref() - .map(|v| v.into_iter().map(|v| (v, None)).collect()) - } -} - -// ************************************************************************************************ -// FunctionSource -// ************************************************************************************************ -pub(crate) struct FunctionSource {} - -impl FunctionSource { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefSource for FunctionSource { - fn parse_from_mock<'a>( - &self, - mock: &'a RequestRequirements, - ) -> Option> { - mock.matchers - .as_ref() - .map(|b| b.iter().map(|f| f).collect()) - } -} diff --git a/src/server/matchers/targets.rs b/src/server/matchers/targets.rs deleted file mode 100644 index 1fafc6fc..00000000 --- a/src/server/matchers/targets.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::cell::RefCell; -use std::collections::BTreeMap; - -use serde_json::Value; - -use crate::common::data::HttpMockRequest; -use crate::server::matchers; - -pub(crate) trait ValueTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option; -} - -pub(crate) trait ValueRefTarget { - fn parse_from_request<'a>(&self, req: &'a HttpMockRequest) -> Option<&'a T>; -} - -pub(crate) trait MultiValueTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option)>>; -} - -// ************************************************************************************* -// StringBodyTarget -// ************************************************************************************* -pub(crate) struct StringBodyTarget {} - -impl StringBodyTarget { - pub fn new() -> Self { - Self {} - } -} - -impl ValueTarget for StringBodyTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option { - req.body - .as_ref() - .map(|b| String::from_utf8_lossy(b).to_string()) // FIXME: Avoid copying here. Create a "ValueRefTarget". - } -} - -// ************************************************************************************* -// JSONBodyTarget -// ************************************************************************************* -pub(crate) struct JSONBodyTarget {} - -impl JSONBodyTarget { - pub fn new() -> Self { - Self {} - } -} - -impl ValueTarget for JSONBodyTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option { - let body = req.body.as_ref(); - if body.is_none() { - return None; - } - - match serde_json::from_slice(body.unwrap()) { - Err(e) => { - log::trace!("Cannot parse json value: {}", e); - None - } - Ok(v) => Some(v), - } - } -} - -// ************************************************************************************* -// CookieTarget -// ************************************************************************************* -#[cfg(feature = "cookies")] -pub(crate) struct CookieTarget {} - -#[cfg(feature = "cookies")] -impl CookieTarget { - pub fn new() -> Self { - Self {} - } -} - -#[cfg(feature = "cookies")] -impl MultiValueTarget for CookieTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option)>> { - let req_cookies = match matchers::parse_cookies(req) { - Ok(v) => v, - Err(err) => { - log::info!( - "Cannot parse cookies. Cookie matching will not work for this request. Error: {}", - err - ); - return None; - } - }; - - Some(req_cookies.into_iter().map(|(k, v)| (k, Some(v))).collect()) - } -} - -// ************************************************************************************* -// HeaderTarget -// ************************************************************************************* -pub(crate) struct HeaderTarget {} - -impl HeaderTarget { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueTarget for HeaderTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option)>> { - req.headers.as_ref().map(|headers| { - headers - .into_iter() - .map(|(k, v)| (k.to_string(), Some(v.to_string()))) - .collect() - }) - } -} - -// ************************************************************************************* -// HeaderTarget -// ************************************************************************************* -pub(crate) struct QueryParameterTarget {} - -impl QueryParameterTarget { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueTarget for QueryParameterTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option)>> { - req.query_params.as_ref().map(|headers| { - headers - .into_iter() - .map(|(k, v)| (k.to_string(), Some(v.to_string()))) - .collect() - }) - } -} - -// ************************************************************************************* -// PathTarget -// ************************************************************************************* -pub(crate) struct PathTarget {} - -impl PathTarget { - pub fn new() -> Self { - Self {} - } -} - -impl ValueTarget for PathTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option { - Some(req.path.to_string()) // FIXME: Avoid copying here. Create a "ValueRefTarget". - } -} - -// ************************************************************************************* -// MethodTarget -// ************************************************************************************* -pub(crate) struct MethodTarget {} - -impl MethodTarget { - pub fn new() -> Self { - Self {} - } -} - -impl ValueTarget for MethodTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option { - Some(req.method.to_string()) // FIXME: Avoid copying here. Create a "ValueRefTarget". - } -} - -// ************************************************************************************* -// FullRequestTarget -// ************************************************************************************* -pub(crate) struct FullRequestTarget {} - -impl FullRequestTarget { - pub fn new() -> Self { - Self {} - } -} - -impl ValueRefTarget for FullRequestTarget { - fn parse_from_request<'a>(&self, req: &'a HttpMockRequest) -> Option<&'a HttpMockRequest> { - Some(req) - } -} - -// ************************************************************************************* -// XWWWFormUrlEncodedBodyTarget -// ************************************************************************************* -pub(crate) struct XWWWFormUrlEncodedBodyTarget {} - -impl XWWWFormUrlEncodedBodyTarget { - pub fn new() -> Self { - Self {} - } -} - -impl MultiValueTarget for XWWWFormUrlEncodedBodyTarget { - fn parse_from_request(&self, req: &HttpMockRequest) -> Option)>> { - req.body.as_ref().map(|body| { - form_urlencoded::parse(body) - .into_owned() - .map(|(k, v)| (k, Some(v))) - .collect() - }) - } -} diff --git a/src/server/matchers/transformers.rs b/src/server/matchers/transformers.rs deleted file mode 100644 index a835b701..00000000 --- a/src/server/matchers/transformers.rs +++ /dev/null @@ -1,84 +0,0 @@ -pub(crate) trait Transformer { - fn transform(&self, v: &I) -> Result; -} - -// ************************************************************************************************ -// Base64ValueTransformer -// ************************************************************************************************ -pub(crate) struct DecodeBase64ValueTransformer {} - -impl DecodeBase64ValueTransformer { - pub fn new() -> Self { - Self {} - } -} - -impl Transformer for DecodeBase64ValueTransformer { - fn transform(&self, v: &String) -> Result { - base64::decode(v) - .map(|t| String::from_utf8_lossy(&t.as_slice()).into()) - .map_err(|err| err.to_string()) - } -} - -// ************************************************************************************************ -// Base64ValueTransformer -// ************************************************************************************************ -pub(crate) struct ToLowercaseTransformer {} - -impl ToLowercaseTransformer { - pub fn new() -> Self { - Self {} - } -} - -impl Transformer for ToLowercaseTransformer { - fn transform(&self, v: &String) -> Result { - Ok(v.to_lowercase()) - } -} - -#[cfg(test)] -mod test { - use crate::server::matchers::transformers::{ - DecodeBase64ValueTransformer, ToLowercaseTransformer, Transformer, - }; - - #[test] - fn base64_decode_transformer() { - // Arrange - let transformer = DecodeBase64ValueTransformer::new(); - - // Act - let result = transformer.transform(&"dGVzdA==".to_string()); - - // Assert - assert_eq!(result.is_ok(), true); - assert_eq!(result.unwrap(), "test".to_string()); - } - - #[test] - fn base64_decode_transformer_error() { - // Arrange - let transformer = DecodeBase64ValueTransformer::new(); - - // Act - let result = transformer.transform(&"xÿ".to_string()); - - // Assert - assert_eq!(result.is_err(), true); - } - - #[test] - fn to_lowercase_transformer() { - // Arrange - let transformer = ToLowercaseTransformer::new(); - - // Act - let result = transformer.transform(&"HeLlO".to_string()); - - // Assert - assert_eq!(result.is_ok(), true); - assert_eq!(result.unwrap(), "hello".to_string()); - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index ef8ef149..7e29efdc 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,776 +1,43 @@ #![allow(clippy::trivial_regex)] +use std::{borrow::Borrow, str::FromStr}; -use std::borrow::Borrow; -use std::collections::BTreeMap; -use std::net::SocketAddr; -use std::str::FromStr; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering::Relaxed; -use std::sync::{Arc, Mutex}; - -use hyper::body::Buf; -use hyper::header::HeaderValue; -use hyper::http::header::HeaderName; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{ - Body, HeaderMap, Request as HyperRequest, Response as HyperResponse, Result as HyperResult, - Server, StatusCode, -}; -use regex::Regex; - -use matchers::generic::SingleValueMatcher; -use matchers::targets::{JSONBodyTarget, StringBodyTarget}; - -use crate::common::data::{ActiveMock, HttpMockRequest, Tokenizer}; -use crate::server::matchers::comparators::{ - AnyValueComparator, FunctionMatchesRequestComparator, JSONContainsMatchComparator, - JSONExactMatchComparator, StringContainsMatchComparator, StringExactMatchComparator, - StringRegexMatchComparator, -}; -use crate::server::matchers::generic::{FunctionValueMatcher, MultiValueMatcher}; -use crate::server::matchers::sources::{ - BodyRegexSource, ContainsCookieSource, ContainsHeaderSource, ContainsQueryParameterSource, - ContainsXWWWFormUrlencodedKeySource, CookieSource, FunctionSource, HeaderSource, - JSONBodySource, MethodSource, PartialJSONBodySource, PathContainsSubstringSource, - PathRegexSource, QueryParameterSource, StringBodyContainsSource, StringBodySource, - StringPathSource, XWWWFormUrlencodedSource, -}; -#[cfg(feature = "cookies")] -use crate::server::matchers::targets::CookieTarget; -use crate::server::matchers::targets::{ - FullRequestTarget, HeaderTarget, MethodTarget, PathTarget, QueryParameterTarget, - XWWWFormUrlEncodedBodyTarget, -}; use crate::server::matchers::Matcher; -use crate::server::web::routes; +use bytes::Bytes; use futures_util::task::Spawn; -use std::future::Future; -use std::iter::Map; -use std::time::Instant; +use hyper::body::{Body, Buf}; +use std::{future::Future, net::SocketAddr}; -mod matchers; +use futures_util::{FutureExt, TryStreamExt}; +use http_body_util::BodyExt; +mod builder; +mod handler; +pub mod matchers; +mod server; +pub mod state; mod util; -pub(crate) mod web; - -/// The shared state accessible to all handlers -pub struct MockServerState { - id_counter: AtomicUsize, - history_limit: usize, - pub mocks: Mutex>, - pub history: Mutex>>, - pub matchers: Vec>, -} - -impl MockServerState { - pub fn create_new_id(&self) -> usize { - self.id_counter.fetch_add(1, Relaxed) - } - - pub fn new(history_limit: usize) -> Self { - MockServerState { - mocks: Mutex::new(BTreeMap::new()), - history_limit, - history: Mutex::new(Vec::new()), - id_counter: AtomicUsize::new(0), - matchers: vec![ - // path exact - Box::new(SingleValueMatcher { - entity_name: "path", - comparator: Box::new(StringExactMatchComparator::new(false)), - source: Box::new(StringPathSource::new()), - target: Box::new(PathTarget::new()), - transformer: None, - with_reason: true, - diff_with: None, - weight: 10, - }), - // path contains - Box::new(SingleValueMatcher { - entity_name: "path", - comparator: Box::new(StringContainsMatchComparator::new(true)), - source: Box::new(PathContainsSubstringSource::new()), - target: Box::new(PathTarget::new()), - transformer: None, - with_reason: true, - diff_with: None, - weight: 10, - }), - // path matches regex - Box::new(SingleValueMatcher { - entity_name: "path", - comparator: Box::new(StringRegexMatchComparator::new()), - source: Box::new(PathRegexSource::new()), - target: Box::new(PathTarget::new()), - transformer: None, - with_reason: true, - diff_with: None, - weight: 10, - }), - // method exact - Box::new(SingleValueMatcher { - entity_name: "method", - comparator: Box::new(StringExactMatchComparator::new(false)), - source: Box::new(MethodSource::new()), - target: Box::new(MethodTarget::new()), - transformer: None, - with_reason: true, - diff_with: None, - weight: 3, - }), - // Query Param exact - Box::new(MultiValueMatcher { - entity_name: "query parameter", - key_comparator: Box::new(StringExactMatchComparator::new(true)), - value_comparator: Box::new(StringExactMatchComparator::new(true)), - key_transformer: None, - value_transformer: None, - source: Box::new(QueryParameterSource::new()), - target: Box::new(QueryParameterTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // Query Param exists - Box::new(MultiValueMatcher { - entity_name: "query parameter", - key_comparator: Box::new(StringExactMatchComparator::new(true)), - value_comparator: Box::new(AnyValueComparator::new()), - key_transformer: None, - value_transformer: None, - source: Box::new(ContainsQueryParameterSource::new()), - target: Box::new(QueryParameterTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // Cookie exact - #[cfg(feature = "cookies")] - Box::new(MultiValueMatcher { - entity_name: "cookie", - key_comparator: Box::new(StringExactMatchComparator::new(true)), - value_comparator: Box::new(StringExactMatchComparator::new(true)), - key_transformer: None, - value_transformer: None, - source: Box::new(CookieSource::new()), - target: Box::new(CookieTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // Cookie exists - #[cfg(feature = "cookies")] - Box::new(MultiValueMatcher { - entity_name: "cookie", - key_comparator: Box::new(StringExactMatchComparator::new(true)), - value_comparator: Box::new(AnyValueComparator::new()), - key_transformer: None, - value_transformer: None, - source: Box::new(ContainsCookieSource::new()), - target: Box::new(CookieTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // Header exact - Box::new(MultiValueMatcher { - entity_name: "header", - key_comparator: Box::new(StringExactMatchComparator::new(false)), - value_comparator: Box::new(StringExactMatchComparator::new(true)), - key_transformer: None, - value_transformer: None, - source: Box::new(HeaderSource::new()), - target: Box::new(HeaderTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // Header exists - Box::new(MultiValueMatcher { - entity_name: "header", - key_comparator: Box::new(StringExactMatchComparator::new(false)), - value_comparator: Box::new(AnyValueComparator::new()), - key_transformer: None, - value_transformer: None, - source: Box::new(ContainsHeaderSource::new()), - target: Box::new(HeaderTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // Box::new(CustomFunctionMatcher::new(1.0)), - // string body exact - Box::new(SingleValueMatcher { - entity_name: "body", - comparator: Box::new(StringExactMatchComparator::new(false)), - source: Box::new(StringBodySource::new()), - target: Box::new(StringBodyTarget::new()), - transformer: None, - with_reason: false, - diff_with: Some(Tokenizer::Line), - weight: 1, - }), - // string body contains - Box::new(SingleValueMatcher { - entity_name: "body", - comparator: Box::new(StringContainsMatchComparator::new(true)), - source: Box::new(StringBodyContainsSource::new()), - target: Box::new(StringBodyTarget::new()), - transformer: None, - with_reason: false, - diff_with: Some(Tokenizer::Line), - weight: 1, - }), - // string body regex - Box::new(SingleValueMatcher { - entity_name: "body", - comparator: Box::new(StringRegexMatchComparator::new()), - source: Box::new(BodyRegexSource::new()), - target: Box::new(StringBodyTarget::new()), - transformer: None, - with_reason: false, - diff_with: Some(Tokenizer::Line), - weight: 1, - }), - // JSON body contains - Box::new(SingleValueMatcher { - entity_name: "body", - comparator: Box::new(JSONContainsMatchComparator::new()), - source: Box::new(PartialJSONBodySource::new()), - target: Box::new(JSONBodyTarget::new()), - transformer: None, - with_reason: false, - diff_with: Some(Tokenizer::Line), - weight: 1, - }), - // JSON body exact - Box::new(SingleValueMatcher { - entity_name: "body", - comparator: Box::new(JSONExactMatchComparator::new()), - source: Box::new(JSONBodySource::new()), - target: Box::new(JSONBodyTarget::new()), - transformer: None, - with_reason: true, - diff_with: Some(Tokenizer::Line), - weight: 1, - }), - // Query Param exact - Box::new(MultiValueMatcher { - entity_name: "x-www-form-urlencoded body tuple", - key_comparator: Box::new(StringExactMatchComparator::new(true)), - value_comparator: Box::new(StringExactMatchComparator::new(true)), - key_transformer: None, - value_transformer: None, - source: Box::new(XWWWFormUrlencodedSource::new()), - target: Box::new(XWWWFormUrlEncodedBodyTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // Query Param exists - Box::new(MultiValueMatcher { - entity_name: "x-www-form-urlencoded body tuple", - key_comparator: Box::new(StringExactMatchComparator::new(true)), - value_comparator: Box::new(AnyValueComparator::new()), - key_transformer: None, - value_transformer: None, - source: Box::new(ContainsXWWWFormUrlencodedKeySource::new()), - target: Box::new(XWWWFormUrlEncodedBodyTarget::new()), - with_reason: true, - diff_with: None, - weight: 1, - }), - // User provided matcher function - Box::new(FunctionValueMatcher { - entity_name: "user provided matcher function", - comparator: Box::new(FunctionMatchesRequestComparator::new()), - source: Box::new(FunctionSource::new()), - target: Box::new(FullRequestTarget::new()), - transformer: None, - weight: 1, - }), - ], - } - } -} - -impl Default for MockServerState { - fn default() -> Self { - MockServerState::new(usize::MAX) - } -} - -type GenericError = Box; - -#[derive(Default, Debug)] -pub(crate) struct ServerRequestHeader { - pub method: String, - pub path: String, - pub query: String, - pub headers: Vec<(String, String)>, -} - -impl ServerRequestHeader { - pub fn from(req: &HyperRequest) -> Result { - let headers = extract_headers(req.headers()); - if let Err(e) = headers { - return Err(format!("error parsing headers: {}", e)); - } - - let method = req.method().as_str().to_string(); - let path = req.uri().path().to_string(); - let query = req.uri().query().unwrap_or("").to_string(); - let headers = headers.unwrap(); - - let server_request = ServerRequestHeader::new(method, path, query, headers); - - Ok(server_request) - } - - pub fn new( - method: String, - path: String, - query: String, - headers: Vec<(String, String)>, - ) -> Self { - Self { - method, - path, - query, - headers, - } - } -} - -#[derive(Default, Debug)] -pub(crate) struct ServerResponse { - pub status: u16, - pub headers: Vec<(String, String)>, - pub body: Vec, -} - -impl ServerResponse { - pub fn new(status: u16, headers: Vec<(String, String)>, body: Vec) -> Self { - Self { - status, - headers, - body, - } - } -} - -/// Extracts all headers from the URI of the given request. -fn extract_headers(header_map: &HeaderMap) -> Result, String> { - let mut headers = Vec::new(); - for (hn, hv) in header_map { - let hn = hn.as_str().to_string(); - let hv = hv.to_str(); - if let Err(e) = hv { - return Err(format!("error parsing headers: {}", e)); - } - headers.push((hn, hv.unwrap().to_string())); - } - Ok(headers) -} - -async fn access_log_middleware( - req: HyperRequest, - state: Arc, - print_access_log: bool, - next: fn(req: HyperRequest, state: Arc) -> T, -) -> HyperResult> -where - T: Future>>, -{ - let time_request_received = Instant::now(); - - let request_method = req.method().to_string(); - let request_uri = req.uri().to_string(); - let request_http_version = format!("{:?}", &req.version()); - - let result = next(req, state).await; - - if print_access_log && !request_uri.starts_with(&format!("{}/", BASE_PATH)) { - if let Ok(response) = &result { - log::info!( - "\"{} {} {:?}\" {} {}", - request_method, - request_uri, - request_http_version, - response.status().as_u16(), - time_request_received.elapsed().as_millis() - ); - } - }; - - return result; -} - -async fn handle_server_request( - req: HyperRequest, - state: Arc, -) -> HyperResult> { - let request_header = ServerRequestHeader::from(&req); - - if let Err(e) = request_header { - return Ok(error_response(format!("Cannot parse request: {}", e))); - } - - let body = hyper::body::to_bytes(req.into_body()).await; - if let Err(e) = body { - return Ok(error_response(format!("Cannot read request body: {}", e))); - } - - let routing_result = route_request( - state.borrow(), - &request_header.unwrap(), - body.unwrap().to_vec(), - ) - .await; - if let Err(e) = routing_result { - return Ok(error_response(format!("Request handler error: {}", e))); - } - - let response = map_response(routing_result.unwrap()); - if let Err(e) = response { - return Ok(error_response(format!("Cannot build response: {}", e))); - } - - Ok(response.unwrap()) -} - -/// Starts a new instance of an HTTP mock server. You should never need to use this function -/// directly. Use it if you absolutely need to manage the low-level details of how the mock -/// server operates. -pub(crate) async fn start_server( - port: u16, - expose: bool, - state: &Arc, - socket_addr_sender: Option>, - print_access_log: bool, - shutdown: F, -) -> Result<(), String> -where - F: Future, -{ - let host = if expose { "0.0.0.0" } else { "127.0.0.1" }; - - let state = state.clone(); - let new_service = make_service_fn(move |_| { - let state = state.clone(); - async move { - Ok::<_, GenericError>(service_fn(move |req: HyperRequest| { - let state = state.clone(); - access_log_middleware(req, state, print_access_log, handle_server_request) - })) - } - }); - - let server = Server::bind(&format!("{}:{}", host, port).parse().unwrap()).serve(new_service); - let addr = server.local_addr(); - - if let Some(socket_addr_sender) = socket_addr_sender { - if let Err(e) = socket_addr_sender.send(addr) { - return Err(format!( - "Cannot send socket information to the test thread: {:?}", - e - )); - } - } - - // And now add a graceful shutdown signal... - let graceful = server.with_graceful_shutdown(shutdown); - - log::info!("Listening on {}", addr); - if let Err(e) = graceful.await { - return Err(format!("Err: {}", e)); - } - - Ok(()) -} -/// Maps a server response to a hyper response. -fn map_response(route_response: ServerResponse) -> Result, String> { - let mut builder = HyperResponse::builder(); - builder = builder.status(route_response.status); +#[cfg(feature = "static-mock")] +mod persistence; - for (key, value) in route_response.headers { - let name = HeaderName::from_str(&key); - if let Err(e) = name { - return Err(format!("Cannot create header from name: {}", e)); - } +#[cfg(feature = "https")] +mod tls; - let value = HeaderValue::from_str(&value); - if let Err(e) = value { - return Err(format!("Cannot create header from value: {}", e)); - } +use crate::server::{handler::HttpMockHandler, server::MockServer, state::HttpMockStateManager}; - let value = value.unwrap(); - let value = value.to_str(); - if let Err(e) = value { - return Err(format!("Cannot create header from value string: {}", e)); - } +pub use builder::HttpMockServerBuilder; +pub use server::Error; - builder = builder.header(name.unwrap(), value.unwrap()); - } - - let result = builder.body(Body::from(route_response.body)); - if let Err(e) = result { - return Err(format!("Cannot create HTTP response: {}", e)); - } - - Ok(result.unwrap()) -} - -/// Routes a request to the appropriate route handler. -async fn route_request( - state: &MockServerState, - request_header: &ServerRequestHeader, - body: Vec, -) -> Result { - log::trace!("Routing incoming request: {:?}", request_header); - - if PING_PATH.is_match(&request_header.path) { - if let "GET" = request_header.method.as_str() { - return routes::ping(); - } - } - - if MOCKS_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "POST" => return routes::add(state, body), - "DELETE" => return routes::delete_all_mocks(state), - _ => {} - } - } - - if MOCK_PATH.is_match(&request_header.path) { - let id = get_path_param(&MOCK_PATH, 1, &request_header.path); - if let Err(e) = id { - return Err(format!("Cannot parse id from path: {}", e)); - } - let id = id.unwrap(); - - match request_header.method.as_str() { - "GET" => return routes::read_one(state, id), - "DELETE" => return routes::delete_one(state, id), - _ => {} - } - } - - if VERIFY_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "POST" => return routes::verify(state, body), - _ => {} - } - } - - if HISTORY_PATH.is_match(&request_header.path) { - match request_header.method.as_str() { - "DELETE" => return routes::delete_history(state), - _ => {} - } - } - - routes::serve(state, request_header, body).await -} - -/// Get request path parameters. -fn get_path_param(regex: &Regex, idx: usize, path: &str) -> Result { - let cap = regex.captures(path); - if cap.is_none() { - return Err(format!( - "Error capturing parameter from request path: {}", - path - )); - } - let cap = cap.unwrap(); - - let id = cap.get(idx); - if id.is_none() { - return Err(format!( - "Error capturing resource id in request path: {}", - path - )); - } - let id = id.unwrap().as_str(); - - let id = id.parse::(); - if let Err(e) = id { - return Err(format!("Error parsing id as a number: {}", e)); - } - let id = id.unwrap(); - - Ok(id) -} - -/// Creates a default error response. -fn error_response(body: String) -> HyperResponse { - HyperResponse::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::from(body)) - .expect("Cannot build route error response") -} - -static BASE_PATH: &'static str = "/__httpmock__"; +// We want to expose this error to the user +pub type HttpMockServer = MockServer>; -lazy_static! { - static ref PING_PATH: Regex = Regex::new(&format!(r"^{}/ping$", BASE_PATH)).unwrap(); - static ref MOCKS_PATH: Regex = Regex::new(&format!(r"^{}/mocks$", BASE_PATH)).unwrap(); - static ref MOCK_PATH: Regex = Regex::new(&format!(r"^{}/mocks/([0-9]+)$", BASE_PATH)).unwrap(); - static ref HISTORY_PATH: Regex = Regex::new(&format!(r"^{}/history$", BASE_PATH)).unwrap(); - static ref VERIFY_PATH: Regex = Regex::new(&format!(r"^{}/verify$", BASE_PATH)).unwrap(); +#[derive(Clone)] +pub struct RequestMetadata { + pub scheme: &'static str, } -#[cfg(test)] -mod test { - use std::collections::BTreeMap; - - use futures_util::TryStreamExt; - - use crate::server::{ - error_response, get_path_param, map_response, ServerResponse, HISTORY_PATH, MOCKS_PATH, - MOCK_PATH, PING_PATH, VERIFY_PATH, - }; - use crate::Regex; - use hyper::body::Bytes; - use hyper::Error; - - #[test] - fn route_regex_test() { - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/1"), true); - assert_eq!( - MOCK_PATH.is_match("/__httpmock__/mocks/1295473892374"), - true - ); - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/abc"), false); - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks"), false); - assert_eq!(MOCK_PATH.is_match("/__httpmock__/mocks/345345/test"), false); - assert_eq!( - MOCK_PATH.is_match("test/__httpmock__/mocks/345345/test"), - false - ); - - assert_eq!(PING_PATH.is_match("/__httpmock__/ping"), true); - assert_eq!( - PING_PATH.is_match("/__httpmock__/ping/1295473892374"), - false - ); - assert_eq!(PING_PATH.is_match("test/ping/1295473892374"), false); - - assert_eq!(VERIFY_PATH.is_match("/__httpmock__/verify"), true); - assert_eq!( - VERIFY_PATH.is_match("/__httpmock__/verify/1295473892374"), - false - ); - assert_eq!(VERIFY_PATH.is_match("test/verify/1295473892374"), false); - - assert_eq!(HISTORY_PATH.is_match("/__httpmock__/history"), true); - println!("{:?}", HISTORY_PATH.as_str()); - - assert_eq!( - HISTORY_PATH.is_match("/__httpmock__/history/1295473892374"), - false - ); - assert_eq!(HISTORY_PATH.is_match("test/history/1295473892374"), false); - - assert_eq!(MOCKS_PATH.is_match("/__httpmock__/mocks"), true); - assert_eq!(MOCKS_PATH.is_match("/__httpmock__/mocks/5"), false); - assert_eq!(MOCKS_PATH.is_match("test/__httpmock__/mocks/5"), false); - assert_eq!(MOCKS_PATH.is_match("test/__httpmock__/mocks/567"), false); - } - - /// Make sure passing an empty string to the error response does not result in an error. - #[test] - fn error_response_test() { - let res = error_response("test".into()); - let (parts, body) = res.into_parts(); - - let body = async_std::task::block_on(async { - return match hyper::body::to_bytes(body).await { - Ok(bytes) => bytes.to_vec(), - Err(e) => panic!(e), - }; - }); - - assert_eq!(String::from_utf8(body).unwrap(), "test".to_string()) - } - - /// Makes sure an error is return if there is a header parsing error - #[test] - fn response_header_key_parsing_error_test() { - // Arrange - let mut headers = Vec::new(); - headers.push((";;;".to_string(), ";;;".to_string())); - - let res = ServerResponse { - body: Vec::new(), - status: 500, - headers, - }; - - // Act - let result = map_response(res); - - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - result - .err() - .unwrap() - .contains("Cannot create header from name"), - true - ); - } - - #[test] - fn get_path_param_regex_error_test() { - // Arrange - let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); - - // Act - let result = get_path_param(&re, 0, ""); - - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - result - .err() - .unwrap() - .contains("Error capturing parameter from request path"), - true - ); - } - - #[test] - fn get_path_param_index_error_test() { - // Arrange - let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); - - // Act - let result = get_path_param(&re, 5, "/__httpmock__/mocks/5"); - - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - "Error capturing resource id in request path: /__httpmock__/mocks/5", - result.err().unwrap() - ); - } - - #[test] - fn get_path_param_number_error_test() { - // Arrange - let re = Regex::new(r"^/__httpmock__/mocks/([0-9]+)$").unwrap(); - - // Act - let result = get_path_param(&re, 0, "/__httpmock__/mocks/9999999999999999999999999"); - - // Assert - assert_eq!(result.is_err(), true); - assert_eq!( - "Error parsing id as a number: invalid digit found in string", - result.err().unwrap() - ); +impl RequestMetadata { + pub fn new(scheme: &'static str) -> Self { + Self { scheme } } } diff --git a/src/server/persistence.rs b/src/server/persistence.rs new file mode 100644 index 00000000..03e13d68 --- /dev/null +++ b/src/server/persistence.rs @@ -0,0 +1,112 @@ +use bytes::{BufMut, Bytes, BytesMut}; +use std::{ + convert::{TryFrom, TryInto}, + fs::read_dir, + path::PathBuf, + str::FromStr, +}; + +use serde::Deserialize; + +use crate::common::data; +use serde_yaml::{Deserializer, Value as YamlValue}; +use thiserror::Error; + +use crate::{ + common::{ + data::{MockDefinition, StaticMockDefinition}, + util::read_file, + }, + server::{ + persistence::Error::{DeserializationError, FileReadError}, + state, + state::{Error::DataConversionError, StateManager}, + }, +}; + +#[derive(Error, Debug)] +pub enum Error { + #[error("cannot read from mock file: {0}")] + FileReadError(String), + #[error("cannot modify state: {0}")] + StateError(#[from] state::Error), + #[error("cannot deserialize YAML: {0}")] + DeserializationError(String), + #[error("cannot convert data structures: {0}")] + DataConversionError(#[from] data::Error), + #[error("unknown data store error")] + Unknown, +} + +pub fn read_static_mock_definitions(path_opt: PathBuf, state: &S) -> Result<(), Error> +where + S: StateManager + Send + Sync + 'static, +{ + for def in read_static_mocks(path_opt)? { + state.add_mock(def.try_into()?, true)?; + } + + Ok(()) +} + +fn read_static_mocks(path: PathBuf) -> Result, Error> { + let mut definitions: Vec = Vec::new(); + + let paths = read_dir(path).expect("cannot list files in directory"); + for file_path in paths { + let file_path = file_path.unwrap().path(); + if let Some(ext) = file_path.extension() { + if !"yaml".eq(ext) && !"yml".eq(ext) { + continue; + } + } + + log::info!( + "Loading static mock file from '{}'", + file_path.to_string_lossy() + ); + + let content = read_file(file_path).map_err(|err| FileReadError(err.to_string()))?; + let content = String::from_utf8(content).map_err(|err| FileReadError(err.to_string()))?; + + definitions.extend(deserialize_mock_defs_from_yaml(&content)?); + } + + return Ok(definitions); +} + +pub fn deserialize_mock_defs_from_yaml( + yaml_content: &str, +) -> Result, Error> { + let mut definitions = Vec::new(); + + for document in Deserializer::from_str(&yaml_content) { + let value = YamlValue::deserialize(document) + .map_err(|err| DeserializationError(err.to_string()))?; + + let definition: StaticMockDefinition = + serde_yaml::from_value(value).map_err(|err| DeserializationError(err.to_string()))?; + + definitions.push(definition); + } + + Ok(definitions) +} + +pub fn serialize_mock_defs_to_yaml(mocks: &Vec) -> Result { + let mut buffer = BytesMut::new(); + + for (idx, mock) in mocks.iter().enumerate() { + if idx > 0 { + buffer.put_slice(b"---\n"); + } + + let static_mock: StaticMockDefinition = StaticMockDefinition::try_from(mock) + .map_err(|err| DataConversionError(err.to_string()))?; + let yaml = serde_yaml::to_string(&static_mock) + .map_err(|err| DataConversionError(err.to_string()))?; + buffer.put_slice(yaml.as_bytes()); + } + + Ok(buffer.freeze()) +} diff --git a/src/server/server.rs b/src/server/server.rs new file mode 100644 index 00000000..268d5461 --- /dev/null +++ b/src/server/server.rs @@ -0,0 +1,466 @@ +use futures_util::{stream::StreamExt, FutureExt}; +use http::{Request, StatusCode}; +use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; +use hyper::body::{Bytes, Incoming}; +use std::{ + future::{pending, Future}, + net::SocketAddr, + path::PathBuf, + sync::Arc, +}; + +use hyper_util::server::conn::auto::Builder as ServerBuilder; + +use crate::server; +use hyper::{http, service::service_fn, upgrade::on as upgrade_on, Method, Response}; +use hyper_util::rt::tokio::TokioIo; +use thiserror::Error; +use tokio::{ + net::{TcpListener, TcpStream}, + sync::oneshot::Sender, + task::spawn, +}; + +use crate::server::{ + handler::Handler, + server::Error::{ + BufferError, LocalSocketAddrError, PublishSocketAddrError, RouterError, SocketBindError, + }, +}; + +use std::io; + +#[cfg(feature = "https")] +use rustls::ServerConfig; +#[cfg(feature = "https")] +use tokio_rustls::TlsAcceptor; + +#[derive(Error, Debug)] +pub enum Error { + #[error("cannot bind to socket addr {0}: {1}")] + SocketBindError(SocketAddr, std::io::Error), + #[error("cannot parse socket address: {0}")] + SocketAddrParseError(#[from] std::net::AddrParseError), + #[error("cannot obtain local error: {0}")] + LocalSocketAddrError(std::io::Error), + #[error("cannot send reserved TCP address to test thread {0}")] + PublishSocketAddrError(SocketAddr), + #[error("cannot create response: {0}")] + ResponseConstructionError(http::Error), + #[error("buffering error: {0}")] + BufferError(hyper::Error), + #[error("HTTP error: {0}")] + HTTPError(#[from] http::Error), + #[error("cannot process request: {0}")] + RouterError(#[from] server::handler::Error), + #[error("HTTPS error: {0}")] + TlsError(String), + #[error("Server configuration error: {0}")] + ConfigurationError(String), + #[error("Server I/O error: {0}")] + IOError(io::Error), + #[error("Server error: {0}")] + ServerError(#[from] hyper::Error), + #[error("Server error: {0}")] + ServerConnectionError(Box), + #[error("unknown data store error")] + Unknown, +} + +#[cfg(feature = "https")] +pub struct MockServerHttpsConfig { + pub cert_resolver_factory: Arc, +} + +pub struct MockServerConfig { + pub static_port: Option, + pub expose: bool, + pub print_access_log: bool, + #[cfg(feature = "https")] + pub https: MockServerHttpsConfig, +} + +/// The `MockServer` struct represents a mock server that can handle incoming HTTP requests. +pub struct MockServer +where + H: Handler + Send + Sync + 'static, +{ + handler: Box, + config: MockServerConfig, +} + +impl MockServer +where + H: Handler + Send + Sync + 'static, +{ + /// Creates a new `MockServer` instance with the given handler and configuration. + /// + /// # Parameters + /// - `handler`: A boxed handler that implements the `Handler` trait. + /// - `config`: The configuration settings for the mock server. + /// + /// # Returns + /// A `Result` containing the new `MockServer` instance or an `Error` if creation fails. + pub fn new(handler: Box, config: MockServerConfig) -> Result { + Ok(MockServer { handler, config }) + } + + /// Starts the mock server asynchronously. + pub async fn start(self) -> Result<(), Error> { + self.start_with_signals(None, pending()).await + } + + /// Starts the mock server asynchronously with support for handling external shutdown signals. + /// + /// # Parameters + /// - `socket_addr_sender`: An optional `Sender` to send the server's socket address once it's bound. + /// - `shutdown`: A future that resolves when the server should shut down. + /// + pub async fn start_with_signals( + self, + socket_addr_sender: Option>, + shutdown: F, + ) -> Result<(), Error> + where + F: Future, + { + let host = if self.config.expose { + "0.0.0.0" + } else { + "127.0.0.1" + }; + let addr: SocketAddr = + format!("{}:{}", host, self.config.static_port.unwrap_or(0)).parse()?; + let listener = TcpListener::bind(addr) + .await + .map_err(|e| SocketBindError(addr, e))?; + + if let Some(sender) = socket_addr_sender { + let addr = listener.local_addr().map_err(|e| LocalSocketAddrError(e))?; + sender + .send(addr) + .map_err(|addr| PublishSocketAddrError(addr))?; + } + + // **************************************************************************************** + // SERVER START + log::info!("Listening on {}", addr); + self.run_accept_loop(listener, shutdown).await + } + + pub async fn run_accept_loop(self, listener: TcpListener, shutdown: F) -> Result<(), Error> + where + F: Future, + { + let shutdown = shutdown.shared(); + let server = Arc::new(self); + + loop { + tokio::select! { + accepted = listener.accept() => { + match accepted { + Ok((tcp_stream, remote_address)) => { + let server = server.clone(); + spawn(async move { + if let Err(err) = server.handle_tcp_stream(tcp_stream, remote_address).await { + log::error!("{:?}", err); + } + }); + }, + Err(err) => { + log::error!("TCP error: {:?}", err); + }, + }; + } + _ = shutdown.clone() => { + break; + } + } + } + + Ok(()) + } + + async fn service( + self: Arc, + req: Request, + ) -> Result>, Error> { + log::trace!("New HTTP request received: {}", req.uri()); + + if req.method() == Method::CONNECT { + return Ok(Response::new(empty())); + // return handle_connect(req).await; + } + + let req = match buffer_request(req).await { + Ok(req) => req, + Err(err) => { + return error_response(StatusCode::INTERNAL_SERVER_ERROR, BufferError(err)); + } + }; + + match self.handler.handle(req).await { + Ok(response) => to_service_response(response), + Err(err) => error_response(StatusCode::INTERNAL_SERVER_ERROR, RouterError(err)), + } + } + + async fn handle_tcp_stream( + self: Arc, + tcp_stream: TcpStream, + remote_address: SocketAddr, + ) -> Result<(), Error> { + log::trace!("new TCP connection incoming"); + + #[cfg(feature = "https")] + { + let mut peek_buffer = TcpStreamPeekBuffer::new(&tcp_stream); + if is_encrypted(&mut peek_buffer, 0).await { + log::trace!("TCP connection seems to be TLS encrypted"); + + let tcp_address = tcp_stream.local_addr().map_err(|err| IOError(err))?; + + let cert_resolver = self.config.https.cert_resolver_factory.build(tcp_address); + let mut server_config = ServerConfig::builder() + .with_no_client_auth() + .with_cert_resolver(cert_resolver); + + #[cfg(feature = "http2")] + { + server_config.alpn_protocols = + vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; + } + #[cfg(not(feature = "http2"))] + { + server_config.alpn_protocols = vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()]; + } + + let tls_acceptor = TlsAcceptor::from(Arc::new(server_config)); + let tls_stream = tls_acceptor.accept(tcp_stream).await.map_err(|e| { + TlsError(format!("Could not accept TLS from TCP stream: {:?}", e)) + })?; + + return serve_connection(self.clone(), tls_stream, "https").await; + } + + if log::max_level() >= log::LevelFilter::Trace { + let peeked_str = + String::from_utf8_lossy(&peek_buffer.buffer().to_vec()).to_string(); + log::trace!( + "TCP connection seems NOT to be TLS encrypted (based on peeked data: {}", + peeked_str + ); + } + } + + log::trace!("TCP connection is not TLS encrypted"); + + return serve_connection(self.clone(), tcp_stream, "http").await; + } +} + +async fn serve_connection( + server: Arc>, + stream: S, + scheme: &'static str, +) -> Result<(), Error> +where + H: Handler + Send + Sync + 'static, + S: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let mut server_builder = ServerBuilder::new(TokioExecutor::new()); + + server_builder.http1().preserve_header_case(true); + + server_builder.http2(); + //.enable_connect_protocol(); + + server_builder + .serve_connection_with_upgrades( + TokioIo::new(stream), + service_fn(|mut req| { + req.extensions_mut().insert(RequestMetadata::new(scheme)); + server.clone().service(req) + }), + ) + .await + .map_err(|err| ServerConnectionError(err)) +} + +async fn handle_connect( + req: Request, +) -> Result>, Error> { + if let Some(addr) = host_addr(req.uri()) { + spawn(async move { + match upgrade_on(req).await { + Ok(upgraded) => { + if let Err(e) = tunnel(upgraded, addr).await { + log::warn!("Proxy I/O error: {}", e); + } else { + log::info!("Proxied request"); + }; + } + Err(e) => { + log::warn!("Proxy upgrade error: {}", e) + } + } + }); + + Ok(Response::new(empty())) + } else { + log::warn!("CONNECT host is not socket addr: {:?}", req.uri()); + let mut resp = Response::new(full("CONNECT must be sent to a socket address")); + *resp.status_mut() = StatusCode::BAD_REQUEST; + + Ok(resp) + } +} + +async fn buffer_request(req: Request) -> Result, hyper::Error> { + let (parts, body) = req.into_parts(); + let body = body.collect().await?.to_bytes(); + return Ok(Request::from_parts(parts, body)); +} + +fn host_addr(uri: &http::Uri) -> Option { + uri.authority().and_then(|auth| Some(auth.to_string())) +} + +fn full>(chunk: T) -> BoxBody { + Full::new(chunk.into()) + .map_err(|never| match never {}) + .boxed() +} + +fn empty() -> BoxBody { + Empty::::new() + .map_err(|never| match never {}) + .boxed() +} + +async fn tunnel(upgraded: hyper::upgrade::Upgraded, addr: String) -> std::io::Result<()> { + let mut server = tokio::net::TcpStream::connect(addr).await?; + let mut upgraded = RecordingStream::new(TokioIo::new(upgraded)); + + let (from_client, from_server) = + tokio::io::copy_bidirectional(&mut server, &mut upgraded).await?; + + log::info!( + "client wrote {} bytes and received {} bytes. \n\nread:\n{}\n\n wrote: {}\n\n", + from_client, + from_server, + String::from_utf8_lossy(&upgraded.read_bytes), + String::from_utf8_lossy(&upgraded.written_bytes) + ); + + Ok(()) +} + +fn error_response( + code: StatusCode, + err: Error, +) -> Result>, Error> { + log::error!("failed to process request: {}", err.to_string()); + Ok(Response::builder() + .status(code) + .body(full(err.to_string()))?) +} + +fn to_service_response( + response: Response, +) -> Result>, Error> { + let (parts, body) = response.into_parts(); + Ok(Response::from_parts(parts, full(body))) +} + +use crate::server::Error::{IOError, ServerConnectionError, ServerError, TlsError, Unknown}; +use async_trait::async_trait; +use bytes::BytesMut; +use hyper_util::rt::TokioExecutor; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +#[cfg(feature = "https")] +use crate::server::tls::{CertificateResolverFactory, TcpStreamPeekBuffer}; + +use crate::server::RequestMetadata; +#[cfg(feature = "https")] +use tls_detect::is_encrypted; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +struct RecordingStream { + stream: S, + read_bytes: BytesMut, // Buffer to store bytes read from the stream + written_bytes: BytesMut, // Buffer to store bytes written to the stream +} + +impl RecordingStream { + pub fn new(stream: S) -> Self { + RecordingStream { + stream, + read_bytes: BytesMut::new(), + written_bytes: BytesMut::new(), + } + } + + // Method to access the collected read bytes + pub fn get_read_bytes(&self) -> &[u8] { + &self.read_bytes + } + + // Method to access the collected written bytes + pub fn get_written_bytes(&self) -> &[u8] { + &self.written_bytes + } +} + +impl AsyncRead for RecordingStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let this = self.get_mut(); + let stream = Pin::new(&mut this.stream); + + let before = buf.filled().len(); + match stream.poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + let after = buf.filled().len(); + let new_bytes = &buf.filled()[before..after]; + this.read_bytes.extend_from_slice(new_bytes); + Poll::Ready(Ok(())) + } + other => other, + } + } +} + +impl AsyncWrite for RecordingStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let this = self.get_mut(); + let stream = Pin::new(&mut this.stream); + + match stream.poll_write(cx, buf) { + Poll::Ready(Ok(size)) => { + this.written_bytes.extend_from_slice(&buf[..size]); + Poll::Ready(Ok(size)) + } + other => other, + } + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().stream).poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.get_mut().stream).poll_shutdown(cx) + } +} diff --git a/src/server/state.rs b/src/server/state.rs new file mode 100644 index 00000000..900fa785 --- /dev/null +++ b/src/server/state.rs @@ -0,0 +1,744 @@ +use crate::{ + common::{ + data, + data::{ + ActiveForwardingRule, ActiveMock, ActiveProxyRule, ActiveRecording, ClosestMatch, + Mismatch, MockDefinition, MockServerHttpResponse, RequestRequirements, + }, + }, + prelude::HttpMockRequest, + server::{ + matchers, + matchers::{all, Matcher}, + persistence::{deserialize_mock_defs_from_yaml, serialize_mock_defs_to_yaml}, + state::Error::{BodyMethodInvalid, DataConversionError, StaticMockError, ValidationError}, + }, +}; + +use crate::common::data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig}; +use bytes::Bytes; +use std::{ + collections::BTreeMap, + convert::{TryFrom, TryInto}, + sync::{Arc, Mutex}, + time::Duration, +}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("The mock is static and cannot be deleted")] + StaticMockError, + #[error("Validation error: request HTTP method GET or HEAD cannot have a body")] + BodyMethodInvalid, + #[error("cannot convert: {0}")] + DataConversionError(String), + #[error("validation error: {0}")] + ValidationError(String), + #[error("unknown error")] + Unknown, +} + +pub struct MockServerState { + history_limit: usize, + next_mock_id: usize, + next_forwarding_rule_id: usize, + next_proxy_rule_id: usize, + next_recording_id: usize, + pub mocks: BTreeMap, + pub history: Vec>, + pub matchers: Vec>, + pub forwarding_rules: BTreeMap, + pub proxy_rules: BTreeMap, + pub recordings: BTreeMap, +} + +impl MockServerState { + pub fn new(history_limit: usize) -> Self { + MockServerState { + mocks: BTreeMap::new(), + forwarding_rules: BTreeMap::new(), + proxy_rules: BTreeMap::new(), + recordings: BTreeMap::new(), + history_limit, + history: Vec::new(), + next_mock_id: 0, + next_forwarding_rule_id: 0, + next_proxy_rule_id: 0, + next_recording_id: 0, + matchers: matchers::all(), + } + } +} + +pub(crate) trait StateManager { + fn reset(&self); + fn add_mock(&self, definition: MockDefinition, is_static: bool) -> Result; + fn read_mock(&self, id: usize) -> Result, Error>; + fn delete_mock(&self, id: usize) -> Result; + fn delete_all_mocks(&self); + + fn delete_history(&self); + + fn verify(&self, requirements: &RequestRequirements) -> Result, Error>; + + fn serve_mock(&self, req: &HttpMockRequest) -> Result, Error>; + + fn create_forwarding_rule(&self, config: ForwardingRuleConfig) -> ActiveForwardingRule; + fn delete_forwarding_rule(&self, id: usize) -> Option; + fn delete_all_forwarding_rules(&self); + + fn create_proxy_rule(&self, constraints: ProxyRuleConfig) -> ActiveProxyRule; + fn delete_proxy_rule(&self, id: usize) -> Option; + fn delete_all_proxy_rules(&self); + + fn create_recording(&self, config: RecordingRuleConfig) -> ActiveRecording; + fn delete_recording(&self, recording_id: usize) -> Option; + fn delete_all_recordings(&self); + fn export_recording(&self, id: usize) -> Result, Error>; + fn load_mocks_from_recording(&self, recording_file_content: &str) -> Result, Error>; + + fn find_forward_rule<'a>( + &'a self, + req: &'a HttpMockRequest, + ) -> Result, Error>; + fn find_proxy_rule<'a>( + &'a self, + req: &'a HttpMockRequest, + ) -> Result, Error>; + fn record< + IntoResponse: TryInto, + >( + &self, + is_proxied: bool, + time_taken: Duration, + req: HttpMockRequest, + res: IntoResponse, + ) -> Result<(), Error>; +} + +pub struct HttpMockStateManager { + state: Mutex, +} + +impl HttpMockStateManager { + pub fn new(history_limit: usize) -> Self { + Self { + state: Mutex::new(MockServerState::new(history_limit)), + } + } +} + +impl Default for HttpMockStateManager { + fn default() -> Self { + HttpMockStateManager::new(usize::MAX) + } +} + +impl StateManager for HttpMockStateManager { + fn reset(&self) { + self.delete_all_mocks(); + self.delete_history(); + self.delete_all_forwarding_rules(); + self.delete_all_proxy_rules(); + self.delete_all_recordings(); + } + + fn add_mock(&self, definition: MockDefinition, is_static: bool) -> Result { + validate_request_requirements(&definition.request)?; + + let mut state = self.state.lock().unwrap(); + + let id = state.next_mock_id; + let active_mock = ActiveMock::new(id, definition, 0, is_static); + + log::debug!("Adding new mock with ID={}", id); + + state.mocks.insert(id, active_mock.clone()); + + state.next_mock_id += 1; + + Ok(active_mock) + } + + fn read_mock(&self, id: usize) -> Result, Error> { + let mut state = self.state.lock().unwrap(); + + let result = state.mocks.get(&id); + match result { + Some(found) => Ok(Some(found.clone())), + None => Ok(None), + } + } + + fn delete_mock(&self, id: usize) -> Result { + let mut state = self.state.lock().unwrap(); + + if let Some(m) = state.mocks.get(&id) { + if m.is_static { + return Err(StaticMockError); + } + } + + log::debug!("Deleting mock with id={}", id); + + Ok(state.mocks.remove(&id).is_some()) + } + + fn delete_all_mocks(&self) { + let mut state = self.state.lock().unwrap(); + + let ids: Vec = state + .mocks + .iter() + .filter(|(k, v)| !v.is_static) + .map(|(k, v)| *k) + .collect(); + + ids.iter().for_each(|k| { + state.mocks.remove(k); + }); + + log::trace!("Deleted all mocks"); + } + + fn delete_history(&self) { + let mut state = self.state.lock().unwrap(); + state.history.clear(); + log::trace!("Deleted request history"); + } + + fn verify(&self, requirements: &RequestRequirements) -> Result, Error> { + let mut state = self.state.lock().unwrap(); + + let non_matching_requests: Vec<&Arc> = state + .history + .iter() + .filter(|req| !request_matches(&state.matchers, req, requirements)) + .collect(); + + let request_distances = + get_distances(&non_matching_requests, &state.matchers, requirements); + let best_matches = get_min_distance_requests(&request_distances); + + let closes_match_request_idx = match best_matches.get(0) { + None => return Ok(None), + Some(idx) => *idx, + }; + + let req = non_matching_requests.get(closes_match_request_idx).unwrap(); + let mismatches = get_request_mismatches(req, &requirements, &state.matchers); + + Ok(Some(ClosestMatch { + request: HttpMockRequest::clone(&req), + request_index: closes_match_request_idx, + mismatches, + })) + } + + fn serve_mock(&self, req: &HttpMockRequest) -> Result, Error> { + let mut state = self.state.lock().unwrap(); + + let req = Arc::new(req.clone()); + + if state.history.len() > 100 { + // TODO: Make max history configurable + state.history.remove(0); + } + state.history.push(req.clone()); + + let result = state + .mocks + .values() + .find(|&mock| request_matches(&state.matchers, &req, &mock.definition.request)); + + let found_mock_id = match result { + Some(mock) => Some(mock.id), + None => None, + }; + + if let Some(found_id) = found_mock_id { + log::debug!( + "Matched mock with id={} to the following request: {:#?}", + found_id, + req + ); + + let mock = state.mocks.get_mut(&found_id).unwrap(); + mock.call_counter += 1; + + return Ok(Some(mock.definition.response.clone())); + } + + log::debug!( + "Could not match any mock to the following request: {:#?}", + req + ); + + Ok(None) + } + + fn create_forwarding_rule(&self, config: ForwardingRuleConfig) -> ActiveForwardingRule { + let mut state = self.state.lock().unwrap(); + + let rule = ActiveForwardingRule { + id: state.next_forwarding_rule_id, + config, + }; + + state.forwarding_rules.insert(rule.id, rule.clone()); + + state.next_forwarding_rule_id += 1; + + rule + } + + fn delete_forwarding_rule(&self, id: usize) -> Option { + let mut state = self.state.lock().unwrap(); + + let result = state.forwarding_rules.remove(&id); + + if result.is_some() { + log::debug!("Deleting proxy rule with id={}", id); + } else { + log::warn!( + "Could not delete proxy rule with id={} (no proxy rule with that id found)", + id + ); + } + + result + } + + fn delete_all_forwarding_rules(&self) { + let mut state = self.state.lock().unwrap(); + state.forwarding_rules.clear(); + + log::debug!("Deleted all forwarding rules"); + } + + fn create_proxy_rule(&self, config: ProxyRuleConfig) -> ActiveProxyRule { + let mut state = self.state.lock().unwrap(); + + let rule = ActiveProxyRule { + id: state.next_proxy_rule_id, + config, + }; + + state.proxy_rules.insert(rule.id, rule.clone()); + + state.next_proxy_rule_id += 1; + + rule + } + + fn delete_proxy_rule(&self, id: usize) -> Option { + let mut state = self.state.lock().unwrap(); + + let result = state.proxy_rules.remove(&id); + + if result.is_some() { + log::debug!("Deleting proxy rule with id={}", id); + } else { + log::warn!( + "Could not delete proxy rule with id={} (no proxy rule with that id found)", + id + ); + } + + result + } + + fn delete_all_proxy_rules(&self) { + let mut state = self.state.lock().unwrap(); + state.proxy_rules.clear(); + + log::debug!("Deleted all proxy rules"); + } + + fn create_recording(&self, config: RecordingRuleConfig) -> ActiveRecording { + let mut state = self.state.lock().unwrap(); + + let rec = ActiveRecording { + id: state.next_recording_id, + config, + mocks: Vec::new(), + }; + + state.recordings.insert(rec.id, rec.clone()); + + state.next_recording_id += 1; + + rec + } + + fn delete_recording(&self, id: usize) -> Option { + let mut state = self.state.lock().unwrap(); + + let result = state.recordings.remove(&id); + + if result.is_some() { + log::debug!("Deleting proxy rule with id={}", id); + } else { + log::warn!( + "Could not delete proxy rule with id={} (no proxy rule with that id found)", + id + ); + } + + result + } + + fn delete_all_recordings(&self) { + let mut state = self.state.lock().unwrap(); + state.recordings.clear(); + + log::debug!("Deleted all recorders"); + } + + fn export_recording(&self, id: usize) -> Result, Error> { + let mut state = self.state.lock().unwrap(); + + if let Some(rec) = state.recordings.get(&id) { + return Ok(Some( + serialize_mock_defs_to_yaml(&rec.mocks) + .map_err(|err| DataConversionError(err.to_string()))?, + )); + } + + Ok(None) + } + + fn load_mocks_from_recording(&self, recording_file_content: &str) -> Result, Error> { + let all_static_mock_defs = deserialize_mock_defs_from_yaml(recording_file_content) + .map_err(|err| DataConversionError(err.to_string()))?; + + if all_static_mock_defs.is_empty() { + return Err(ValidationError( + "no mock definitions could be found in the provided recording content".to_string(), + )); + } + + let mut mock_ids = Vec::with_capacity(all_static_mock_defs.len()); + + for static_mock_def in all_static_mock_defs { + let mock_def: MockDefinition = static_mock_def + .try_into() + .map_err(|err: data::Error| DataConversionError(err.to_string()))?; + + let active_mock = self.add_mock(mock_def, false)?; + mock_ids.push(active_mock.id); + } + + Ok(mock_ids) + } + + fn find_forward_rule<'a>( + &'a self, + req: &'a HttpMockRequest, + ) -> Result<(Option), Error> { + let mut state = self.state.lock().unwrap(); + + let result = state + .forwarding_rules + .values() + .find(|&rule| request_matches(&state.matchers, req, &rule.config.request_requirements)) + .cloned(); + + Ok(result) + } + + fn find_proxy_rule<'a>( + &'a self, + req: &'a HttpMockRequest, + ) -> Result, Error> { + let mut state = self.state.lock().unwrap(); + + let result = state + .proxy_rules + .values() + .find(|&rule| request_matches(&state.matchers, req, &rule.config.request_requirements)) + .cloned(); + + Ok(result) + } + + fn record< + IntoResponse: TryInto, + >( + &self, + is_proxied: bool, + time_taken: Duration, + req: HttpMockRequest, + res: IntoResponse, + ) -> Result<(), Error> { + let mut state = self.state.lock().unwrap(); + + let recording_ids: Vec = state + .recordings + .values() + .filter(|rec| request_matches(&state.matchers, &req, &rec.config.request_requirements)) + .map(|r| r.id) + .collect(); + + if recording_ids.is_empty() { + return Ok(()); + } + + let res = res + .try_into() + .map_err(|err| DataConversionError(err.to_string()))?; + + for id in recording_ids { + let rec = state.recordings.get_mut(&id).unwrap(); + let definition = + build_mock_definition(is_proxied, time_taken, &req, &res, &rec.config)?; + rec.mocks.push(definition); + } + + Ok(()) + } +} + +fn build_mock_definition( + is_proxied: bool, + time_taken: Duration, + request: &HttpMockRequest, + response: &MockServerHttpResponse, + config: &RecordingRuleConfig, +) -> Result { + // ************************************************************************************ + // Request + let mut headers = Vec::with_capacity(config.record_headers.len()); + for header_name in &config.record_headers { + let header_name_lowercase = header_name.to_lowercase(); + for (key, value) in request.headers() { + if let Some(key) = key { + if header_name_lowercase == key.to_string().to_lowercase() { + let value = value + .to_str() + .map_err(|err| DataConversionError(err.to_string()))?; + headers.push((header_name.to_string(), value.to_string())) + } + } + } + } + + let request = RequestRequirements { + /* Authority and scheme are assumed to always exist for proxies requests for the + following reasons: + + RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing + Section 5.3.2 (absolute-form): + The section clearly states that an absolute URI (absolute-form) must be used when the + request is made to a proxy. This inclusion of the full URI (including the scheme, + host, and optional port) ensures that the proxy can correctly interpret the destination + of the request without additional context. + Exact Text from RFC 7230: + The RFC says under Section 5.3.2: + + "absolute-form = absolute-URI" + "When making a request to a proxy, other than a CONNECT or server-wide + OPTIONS request (as detailed in Section 5.3.4), a client MUST send + the target URI in absolute-form as the request-target." + "An example absolute-form of request-line would be: + GET http://www.example.org/pub/WWW/TheProject.html HTTP/1.1" + */ + host: if is_proxied { + request.uri().host().map(|h| h.to_string()) + } else { + None + }, + host_not: None, + host_contains: None, + host_excludes: None, + host_prefix: None, + host_suffix: None, + host_prefix_not: None, + host_suffix_not: None, + host_matches: None, + port: if is_proxied { + request.uri().port().map(|h| h.as_u16()) + } else { + None + }, + scheme: if is_proxied { + request.uri().scheme().map(|h| h.to_string()) + } else { + None + }, + path: Some(request.uri().path().to_string()), + path_not: None, + path_includes: None, + path_excludes: None, + path_prefix: None, + path_suffix: None, + path_prefix_not: None, + path_suffix_not: None, + path_matches: None, + method: Some(request.method().to_string()), + header: if !headers.is_empty() { + Some(headers) + } else { + None + }, + header_not: None, + header_exists: None, + header_missing: None, + header_includes: None, + header_excludes: None, + header_prefix: None, + header_suffix: None, + header_prefix_not: None, + header_suffix_not: None, + header_matches: None, + header_count: None, + cookie: None, + cookie_not: None, + cookie_exists: None, + cookie_missing: None, + cookie_includes: None, + cookie_excludes: None, + cookie_prefix: None, + cookie_suffix: None, + cookie_prefix_not: None, + cookie_suffix_not: None, + cookie_matches: None, + cookie_count: None, + body: if request.body().is_empty() { + None + } else { + Some(request.body().clone()) + }, + json_body: None, + json_body_not: None, + json_body_includes: None, + body_includes: None, + body_excludes: None, + body_prefix: None, + body_suffix: None, + body_prefix_not: None, + body_suffix_not: None, + body_matches: None, + query_param_exists: None, + query_param_missing: None, + query_param_includes: None, + query_param_excludes: None, + query_param_prefix: None, + query_param_suffix: None, + query_param_prefix_not: None, + query_param_suffix_not: None, + query_param_matches: None, + query_param_count: None, + query_param: None, + form_urlencoded_tuple_exists: None, + form_urlencoded_tuple_missing: None, + form_urlencoded_tuple_includes: None, + form_urlencoded_tuple_excludes: None, + form_urlencoded_tuple_prefix: None, + form_urlencoded_tuple_suffix: None, + form_urlencoded_tuple_prefix_not: None, + form_urlencoded_tuple_suffix_not: None, + form_urlencoded_tuple_matches: None, + form_urlencoded_tuple_count: None, + form_urlencoded_tuple: None, + is_true: None, + scheme_not: None, + port_not: None, + method_not: None, + query_param_not: None, + body_not: None, + json_body_excludes: None, + form_urlencoded_tuple_not: None, + is_false: None, + }; + + // ************************************************************************************ + // Response + let mut response = response.clone(); + + if config.record_response_delays { + response.delay = Some(time_taken.as_millis() as u64) + } + + Ok(MockDefinition { request, response }) +} + +fn validate_request_requirements(req: &RequestRequirements) -> Result<(), Error> { + const NON_BODY_METHODS: &[&str] = &["GET", "HEAD"]; + + if let Some(_body) = &req.body { + if let Some(method) = &req.method { + if NON_BODY_METHODS.contains(&method.as_str()) { + return Err(BodyMethodInvalid); + } + } + } + Ok(()) +} + +fn request_matches( + matchers: &Vec>, + req: &HttpMockRequest, + request_requirements: &RequestRequirements, +) -> bool { + log::trace!("Matching incoming HTTP request"); + matchers + .iter() + .enumerate() + .all(|(i, x)| x.matches(req, request_requirements)) +} + +fn get_distances( + history: &Vec<&Arc>, + matchers: &Vec>, + mock_rr: &RequestRequirements, +) -> BTreeMap { + history + .iter() + .enumerate() + .map(|(idx, req)| (idx, get_request_distance(req, mock_rr, matchers))) + .collect() +} + +fn get_request_distance( + req: &Arc, + mock_request_requirements: &RequestRequirements, + matchers: &Vec>, +) -> usize { + matchers + .iter() + .map(|matcher| matcher.distance(req, mock_request_requirements)) + .sum() +} + +fn get_min_distance_requests(request_distances: &BTreeMap) -> Vec { + // Find the element with the maximum matches + let min_elem = request_distances + .iter() + .min_by(|(idx1, d1), (idx2, d2)| (**d1).cmp(d2)); + + let max = match min_elem { + None => return Vec::new(), + Some((_, n)) => *n, + }; + + request_distances + .into_iter() + .filter(|(idx, distance)| **distance == max) + .map(|(idx, _)| *idx) + .collect() +} + +fn get_request_mismatches( + req: &Arc, + mock_rr: &RequestRequirements, + matchers: &Vec>, +) -> Vec { + matchers + .iter() + .map(|mat| mat.mismatches(req, mock_rr)) + .flatten() + .into_iter() + .collect() +} diff --git a/src/server/tls.rs b/src/server/tls.rs new file mode 100644 index 00000000..1a2a9193 --- /dev/null +++ b/src/server/tls.rs @@ -0,0 +1,311 @@ +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; + +use crate::server::tls::Error::{CaCertificateError, GenerateCertificateError}; +use async_trait::async_trait; +use rcgen::{Certificate, CertificateParams, KeyPair}; +use rustls::{ + crypto::ring::sign::any_supported_type, + server::{ClientHello, ResolvesServerCert}, + sign::CertifiedKey, +}; +use std::{ + collections::HashMap, + fmt::Debug, + io::Cursor, + net::SocketAddr, + sync::{Arc, Mutex, RwLock}, +}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("CA certificate error: {0}")] + CaCertificateError(String), + #[error("cannot generate certificate: {0}")] + GenerateCertificateError(String), +} + +pub trait CertificateResolverFactory { + fn build(&self, tcp_address: SocketAddr) -> Arc; +} + +struct SharedState { + certificates: RwLock>>, + locks: RwLock>>>, + ca_cert_str: String, + ca_key_str: String, +} + +impl std::fmt::Debug for SharedState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SharedState") + .field("certificates", &self.certificates.read().unwrap().keys()) + .field("locks", &self.locks.read().unwrap().keys()) + .field("ca_cert_str", &self.ca_cert_str) + .field("ca_key_str", &self.ca_key_str) + .finish() + } +} + +#[derive(Debug)] +pub struct GeneratingCertificateResolverFactory { + state: Arc, +} + +impl<'a> GeneratingCertificateResolverFactory { + pub fn new>( + ca_cert: IntoString, + ca_key: IntoString, + ) -> Result { + Ok(Self { + state: Arc::new(SharedState { + certificates: RwLock::new(HashMap::new()), + locks: RwLock::new(HashMap::new()), + ca_cert_str: ca_cert.into(), + ca_key_str: ca_key.into(), + }), + }) + } +} + +impl CertificateResolverFactory for GeneratingCertificateResolverFactory { + fn build(&self, tcp_address: SocketAddr) -> Arc { + Arc::new(GeneratingCertificateResolver { + state: self.state.clone(), + tcp_address, + }) + } +} + +#[derive(Debug)] +pub struct GeneratingCertificateResolver { + state: Arc, + tcp_address: SocketAddr, +} + +impl<'a> GeneratingCertificateResolver { + fn load_certificates(cert_pem: String) -> Result>, Error> { + let mut cert_pem_reader = Cursor::new(cert_pem.into_bytes()); + let mut certificates = Vec::new(); + let certs_iterator = rustls_pemfile::certs(&mut cert_pem_reader); + for cert_result in certs_iterator { + let cert = cert_result.map_err(|err| { + GenerateCertificateError(format!("cannot use generated certificate: {:?}", err)) + })?; // Propagate error if any + certificates.push(cert); + } + + Ok(certificates) + } + + fn load_private_key(key_pem: String) -> Result, Error> { + let mut cert_pem_reader = Cursor::new(key_pem.into_bytes()); + let private_key = rustls_pemfile::private_key(&mut cert_pem_reader) + .map_err(|err| { + GenerateCertificateError(format!("cannot use generated private key: {:?}", err)) + })? + .ok_or(GenerateCertificateError(format!( + "invalid generated private key" + )))?; + Ok(private_key) + } + + pub fn generate_host_certificate(&'a self, hostname: &str) -> Result, Error> { + // Create a key pair for the CA from the provided PEM + let ca_key = KeyPair::from_pem(&self.state.ca_key_str).map_err(|err| { + CaCertificateError(format!("Expected CA key to be provided in PEM format but failed to parse it (host: {}: error: {:?})", hostname, err)) + })?; + + // Set up certificate parameters for the new certificate + let mut params = CertificateParams::new(vec![hostname.to_owned()]); + + let key_pair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).map_err(|err| { + GenerateCertificateError(format!( + "Cannot generate new key pair (host: {}: error: {:?})", + hostname, err + )) + })?; + + let serialized_key_pair = key_pair.serialize_pem(); + params.key_pair = Some(key_pair); + + // Create the new certificate + let cert = Certificate::from_params(params).map_err(|err| { + GenerateCertificateError(format!( + "Cannot generate new certificate (host: {}: error: {:?})", + hostname, err + )) + })?; + + // Serialize the new certificate, signing it with the CA's private key + let new_host_cert_params = CertificateParams::from_ca_cert_pem(&self.state.ca_cert_str, ca_key).map_err(|err| { + GenerateCertificateError(format!("Cannot create new host certificate parameters from CA certificate (host: {}: error: {:?})", hostname, err)) + })?; + + let new_host_cert = Certificate::from_params(new_host_cert_params).map_err(|err| { + GenerateCertificateError(format!( + "Cannot generate new host certificate (host: {}: error: {:?})", + hostname, err + )) + })?; + + let cert_pem = cert.serialize_pem_with_signer(&new_host_cert).map_err(|err| { + GenerateCertificateError(format!("Cannot sign generated host certificate with CA certificate (host: {}: error: {:?})", hostname, err)) + })?; + + // Convert the generated key and certificate into rustls-compatible formats + let private_key = Self::load_private_key(serialized_key_pair).map_err(|err| { + GenerateCertificateError(format!( + "Cannot convert generated key pair to private key for host (host: {}: error: {:?})", + hostname, err + )) + })?; + + let certificates = Self::load_certificates(cert_pem).map_err(|err| { + GenerateCertificateError(format!("Cannot convert generated generated cert PEN to list of certificates (host: {}: error: {:?})", hostname, err)) + })?; + + let signing_key = any_supported_type(&private_key).map_err(|err| { + GenerateCertificateError(format!( + "Cannot convert generated private key to signing key (host: {}: error: {:?})", + hostname, err + )) + })?; + + Ok(Arc::new(CertifiedKey::new(certificates, signing_key))) + } + + fn get_lock_for_hostname(&self, hostname: &str) -> Arc> { + let mut locks = self.state.locks.write().unwrap(); + locks + .entry(hostname.to_string()) + .or_insert_with(|| Arc::new(Mutex::new(()))) + .clone() + } + + fn generate(&self, hostname: &str) -> Result, Error> { + { + let configs = self.state.certificates.read().unwrap(); + if let Some(config) = configs.get(hostname) { + return Ok(config.clone()); + } + } + + let lock = self.get_lock_for_hostname(hostname); + let _guard = lock.lock(); + { + let certs = self.state.certificates.read().unwrap(); + if let Some(bundle) = certs.get(hostname) { + return Ok(bundle.clone()); + } + } + + let key = self.generate_host_certificate(hostname).unwrap(); + { + let mut certs = self.state.certificates.write().unwrap(); + certs.insert(hostname.to_string(), key.clone()); + } + + Ok(key) + } +} + +// TODO: Change ResolvesServerCert to acceptor so that async operations are supported +impl ResolvesServerCert for GeneratingCertificateResolver { + // TODO: This implementation is synchronous, which will cause synchronous locking to + // enable certificate caching (lock protected hash map, sync implementation). + // If you look at ResolvesServerCert, it suggests that for async IO, the Acceptor interface + // is recommended for usage. However, it seems to require a significantly larger implementation + // overhead than a ResolvesServerCert. For now, this is an accepted performance loss, but should + // definitely be looked into and improved later! + fn resolve(&self, client_hello: ClientHello) -> Option> { + if let Some(hostname) = client_hello.server_name() { + log::info!("have hostname: {}", hostname); + + return Some(self.generate(hostname).expect(&format!( + "Cannot generate certificate for host {}", + hostname + ))); + } + + // According to https://datatracker.ietf.org/doc/html/rfc6066#section-3 + // clients may choose to not include a server name (SNI extension) in TLS ClientHello + // messages. If there is no SNI extension, we assume the client used an IP address instead + // of a hostname. + let hostname = self.tcp_address.ip().to_string(); + log::info!("no hostname using: {}", hostname); + return Some( + self.generate(&hostname) + .expect(&format!("Cannot generate wildcard certificate")), + ); + } +} + +pub struct TcpStreamPeekBuffer<'a> { + stream: &'a tokio::net::TcpStream, + buffer: Vec, +} + +impl<'a> TcpStreamPeekBuffer<'a> { + pub fn new(stream: &'a tokio::net::TcpStream) -> Self { + TcpStreamPeekBuffer { + stream, + buffer: Vec::new(), + } + } + + pub fn buffer(&self) -> &[u8] { + return &self.buffer; + } + + pub async fn advance(&mut self, offset: usize) -> std::io::Result<()> { + if self.buffer.len() > offset { + return Ok(()); + } + + let required_size = offset + 1; + if required_size > self.buffer.len() { + self.buffer.resize(required_size, 0); + } + + let mut total_peeked = 0; + while total_peeked < required_size { + let peeked_now = self.stream.peek(&mut self.buffer[total_peeked..]).await?; + if peeked_now == 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "EOF reached before offset", + )); + } + total_peeked += peeked_now; + } + + Ok(()) + } +} + +#[async_trait] +impl<'a> tls_detect::Read<'a> for TcpStreamPeekBuffer<'a> { + async fn read_byte(&mut self, from_offset: usize) -> std::io::Result { + self.advance(from_offset).await?; + Ok(self.buffer[from_offset]) + } + + async fn read_bytes( + &mut self, + from_offset: usize, + to_offset: usize, + ) -> std::io::Result> { + self.advance(to_offset).await?; + Ok(self.buffer[from_offset..to_offset].to_vec()) + } + + async fn read_u16_from_be(&mut self, offset: usize) -> std::io::Result { + let u16_bytes = self.read_bytes(offset, offset + 2).await?; + Ok(u16::from_be_bytes([u16_bytes[0], u16_bytes[1]])) + } + + async fn buffer_to(&mut self, limit: usize) -> std::io::Result<()> { + self.advance(limit).await + } +} diff --git a/src/server/util.rs b/src/server/util.rs index f38b1e95..6735441f 100644 --- a/src/server/util.rs +++ b/src/server/util.rs @@ -1,11 +1,10 @@ -use std::cmp::Ordering; -use std::collections::BTreeMap; +use std::{cmp::Ordering, collections::BTreeMap}; /// Extends a tree map to provide additional operations. pub(crate) trait TreeMapExtension where - K: std::cmp::Ord, - V: std::cmp::Ord, + K: Ord, + V: Ord, { /// Checks if a tree map contains another tree map. fn contains(&self, other: &BTreeMap) -> bool; @@ -17,8 +16,8 @@ where /// Implements [`TreeMapExtension`]. impl TreeMapExtension for BTreeMap where - K: std::cmp::Ord, - V: std::cmp::Ord, + K: Ord, + V: Ord, { fn contains(&self, other: &BTreeMap) -> bool { other.iter().all(|(k, v)| self.contains_entry(k, v)) @@ -75,9 +74,8 @@ impl StringTreeMapExtension for BTreeMap { #[cfg(test)] mod test { - use std::collections::BTreeMap; - use crate::server::util::{StringTreeMapExtension, TreeMapExtension}; + use std::collections::BTreeMap; #[test] fn tree_map_fully_contains_other() { diff --git a/src/server/web/handlers.rs b/src/server/web/handlers.rs deleted file mode 100644 index 9208f38a..00000000 --- a/src/server/web/handlers.rs +++ /dev/null @@ -1,733 +0,0 @@ -use std::cmp::Ordering; -use std::collections::BTreeMap; -use std::str::FromStr; -use std::sync::Arc; - -#[cfg(feature = "cookies")] -use basic_cookies::Cookie; -use serde_json::Value; - -use crate::common::data::{ - ActiveMock, ClosestMatch, HttpMockRequest, Mismatch, MockDefinition, MockServerHttpResponse, - RequestRequirements, -}; -use crate::server::matchers::Matcher; -use crate::server::util::{StringTreeMapExtension, TreeMapExtension}; -use crate::server::MockServerState; - -/// Contains HTTP methods which cannot have a body. -const NON_BODY_METHODS: &[&str] = &["GET", "HEAD"]; - -/// Adds a new mock to the internal state. -pub(crate) fn add_new_mock( - state: &MockServerState, - mock_def: MockDefinition, - is_static: bool, -) -> Result { - let result = validate_mock_definition(&mock_def); - - if let Err(error_msg) = result { - let error_msg = format!("Validation error: {}", error_msg); - return Err(error_msg); - } - - let mock_id = state.create_new_id(); - log::debug!("Adding new mock with ID={}", mock_id); - - let mut mocks = state.mocks.lock().unwrap(); - mocks.insert(mock_id, ActiveMock::new(mock_id, mock_def, is_static)); - - Result::Ok(mock_id) -} - -/// Reads exactly one mock object. -pub(crate) fn read_one_mock( - state: &MockServerState, - id: usize, -) -> Result, String> { - let mocks = state.mocks.lock().unwrap(); - let result = mocks.get(&id); - match result { - Some(found) => Ok(Some(found.clone())), - None => Ok(None), - } -} - -/// Deletes one mock by id. Returns the number of deleted elements. -pub(crate) fn delete_one_mock(state: &MockServerState, id: usize) -> Result { - let mut mocks = state.mocks.lock().unwrap(); - if let Some(m) = mocks.get(&id) { - if m.is_static { - return Err(format!("Cannot delete static mock with ID {}", id)); - } - } - let result = mocks.remove(&id); - - log::debug!("Deleted mock with id={}", id); - Result::Ok(result.is_some()) -} - -/// Deletes all mocks. -pub(crate) fn delete_all_mocks(state: &MockServerState) { - let mut mocks = state.mocks.lock().unwrap(); - let ids: Vec = mocks - .iter() - .filter(|(k, v)| !v.is_static) - .map(|(k, v)| *k) - .collect(); - - ids.iter().for_each(|k| { - mocks.remove(k); - }); - - log::trace!("Deleted all mocks"); -} - -/// Deletes the request history. -pub(crate) fn delete_history(state: &MockServerState) { - let mut mocks = state.history.lock().unwrap(); - mocks.clear(); - log::trace!("Deleted request history"); -} - -/// Finds a mock that matches the current request and serve a response according to the mock -/// specification. If no mock is found, an empty result is being returned. -pub(crate) fn find_mock( - state: &MockServerState, - req: HttpMockRequest, -) -> Result, String> { - let req = Arc::new(req); - { - let mut history = state.history.lock().unwrap(); - if history.len() > 100 { - history.remove(0); - } - history.push(req.clone()); - } - - let mut mocks = state.mocks.lock().unwrap(); - - let result = mocks - .values() - .find(|&mock| request_matches(&state, req.clone(), &mock.definition.request)); - - let found_mock_id = match result { - Some(mock) => Some(mock.id), - None => None, - }; - - if let Some(found_id) = found_mock_id { - log::debug!( - "Matched mock with id={} to the following request: {:#?}", - found_id, - req - ); - - let mock = mocks.get_mut(&found_id).unwrap(); - mock.call_counter += 1; - - return Ok(Some(mock.definition.response.clone())); - } - - log::debug!( - "Could not match any mock to the following request: {:#?}", - req - ); - - Result::Ok(None) -} - -/// Checks if a request matches a mock. -fn request_matches( - state: &MockServerState, - req: Arc, - mock: &RequestRequirements, -) -> bool { - log::trace!("Matching incoming HTTP request"); - state - .matchers - .iter() - .enumerate() - .all(|(i, x)| x.matches(&req, mock)) -} - -/// Deletes the request history. -pub(crate) fn verify( - state: &MockServerState, - mock_rr: &RequestRequirements, -) -> Result, String> { - let mut history = state.history.lock().unwrap(); - - let non_matching_requests: Vec<&Arc> = history - .iter() - .filter(|a| !request_matches(state, (*a).clone(), mock_rr)) - .collect(); - - let request_distances = get_distances(&non_matching_requests, &state.matchers, mock_rr); - let best_matches = get_min_distance_requests(&request_distances); - - let closes_match_request_idx = match best_matches.get(0) { - None => return Ok(None), - Some(idx) => *idx, - }; - - let req = non_matching_requests.get(closes_match_request_idx).unwrap(); - let mismatches = get_request_mismatches(req, &mock_rr, &state.matchers); - - Ok(Some(ClosestMatch { - request: HttpMockRequest::clone(&req), - request_index: closes_match_request_idx, - mismatches, - })) -} - -/// Validates a mock request. -fn validate_mock_definition(req: &MockDefinition) -> Result<(), String> { - if let Some(_body) = &req.request.body { - if let Some(method) = &req.request.method { - if NON_BODY_METHODS.contains(&method.as_str()) { - return Err(String::from( - "A body cannot be sent along with the specified method", - )); - } - } - } - Ok(()) -} - -// Remember the maximum number of matchers that successfully matched -fn get_distances( - history: &Vec<&Arc>, - matchers: &Vec>, - mock_rr: &RequestRequirements, -) -> BTreeMap { - history - .iter() - .enumerate() - .map(|(idx, req)| (idx, get_request_distance(req, mock_rr, matchers))) - .collect() -} - -fn get_request_mismatches( - req: &Arc, - mock_rr: &RequestRequirements, - matchers: &Vec>, -) -> Vec { - matchers - .iter() - .map(|mat| mat.mismatches(req, mock_rr)) - .flatten() - .into_iter() - .collect() -} - -fn get_request_distance( - req: &Arc, - mock_rr: &RequestRequirements, - matchers: &Vec>, -) -> usize { - matchers - .iter() - .map(|matcher| matcher.distance(req, mock_rr)) - .sum() -} - -// Remember the maximum number of matchers that successfully matched -fn get_min_distance_requests(request_distances: &BTreeMap) -> Vec { - // Find the element with the maximum matches - let min_elem = request_distances - .iter() - .min_by(|(idx1, d1), (idx2, d2)| (**d1).cmp(d2)); - - let max = match min_elem { - None => return Vec::new(), - Some((_, n)) => *n, - }; - - request_distances - .into_iter() - .filter(|(idx, distance)| **distance == max) - .map(|(idx, _)| *idx) - .collect() -} - -#[cfg(test)] -mod test { - use std::collections::BTreeMap; - use std::rc::Rc; - use std::sync::Arc; - - use regex::Regex; - - use crate::common::data::{ - HttpMockRequest, MockDefinition, MockServerHttpResponse, Pattern, RequestRequirements, - }; - use crate::server::web::handlers::{ - add_new_mock, read_one_mock, request_matches, validate_mock_definition, verify, - }; - use crate::server::MockServerState; - use crate::Method; - - /// TODO - #[test] - fn header_names_case_insensitive() {} - - /// TODO - #[test] - fn parsing_query_params_test() {} - - /// TODO - #[test] - fn parsing_query_contains_test() {} - - /// TODO - #[test] - fn header_exists_test() {} - - /// TODO - #[test] - fn path_contains_test() {} - - /// TODO - #[test] - fn path_pattern_test() {} - - #[test] - fn body_contains_test() { - // Arrange - let request1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string()) - .with_body("test".as_bytes().to_vec()); - let request2 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string()) - .with_body("test".as_bytes().to_vec()); - - let requirements1 = RequestRequirements::new().with_body_contains(vec!["xxx".to_string()]); - let requirements2 = RequestRequirements::new().with_body_contains(vec!["es".to_string()]); - - // Act - let does_match1 = request_matches( - &MockServerState::default(), - Arc::new(request1), - &requirements1, - ); - let does_match2 = request_matches( - &MockServerState::default(), - Arc::new(request2), - &requirements2, - ); - - // Assert - assert_eq!(false, does_match1); - assert_eq!(true, does_match2); - } - - #[test] - fn body_matches_query_params_exact_test() { - // Arrange - let mut params1 = Vec::new(); - params1.push(("k".to_string(), "v".to_string())); - - let mut params2 = Vec::new(); - params2.push(("h".to_string(), "o".to_string())); - - let request1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string()) - .with_query_params(params1.clone()); - let request2 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string()) - .with_query_params(params1.clone()); - - let requirements1 = RequestRequirements::new().with_query_param(params2); - let requirements2 = RequestRequirements::new().with_query_param(params1.clone()); - - // Act - let does_match1 = request_matches( - &MockServerState::default(), - Arc::new(request1), - &requirements1, - ); - let does_match2 = request_matches( - &MockServerState::default(), - Arc::new(request2), - &requirements2, - ); - - // Assert - assert_eq!(false, does_match1); - assert_eq!(true, does_match2); - } - - /// TODO - #[test] - fn body_contains_includes_json_test() {} - - /// TODO - #[test] - fn body_json_exact_match_test() {} - - /// This test makes sure that a request is considered "matched" if the paths of the - /// request and the mock are equal. - #[test] - fn request_matches_path_match() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string()); - - let req2 = RequestRequirements::new().with_path("/test-path".to_string()); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(true, does_match); - } - - /// This test makes sure that a request is considered "not matched" if the paths of the - /// request and the mock are not equal. - #[test] - fn request_matches_path_no_match() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test-path".to_string()); - - let req2 = RequestRequirements::new().with_path("/another-path".to_string()); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(false, does_match); - } - - /// This test makes sure that a request is considered "matched" if the methods of the - /// request and the mock are equal. - #[test] - fn request_matches_method_match() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()); - - let req2 = RequestRequirements::new().with_method("GET".to_string()); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(true, does_match); - } - - /// This test makes sure that a request is considered "not matched" if the methods of the - /// request and the mock are not equal. - #[test] - fn request_matches_method_no_match() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()); - - let req2 = RequestRequirements::new().with_method("POST".to_string()); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(false, does_match); - } - - /// This test makes sure that a request is considered "matched" if the bodies of both, - /// the request and the mock are present and have equal content. - #[test] - fn request_matches_body_match() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()) - .with_body("test".as_bytes().to_vec()); - - let req2 = RequestRequirements::new().with_body("test".to_string()); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(true, does_match); - } - - /// This test makes sure that a request is considered "not matched" if the bodies of both, - /// the request and the mock are present, but do have different content. - #[test] - fn request_matches_body_no_match() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()) - .with_body("some text".as_bytes().to_vec()); - - let req2 = RequestRequirements::new().with_body("some other text".to_string()); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(false, does_match); - } - - /// This test makes sure that a request is considered "matched" when the request contains - /// exactly the same as the mock expects. - #[test] - fn request_matches_headers_exact_match() { - // Arrange - let mut h1 = Vec::new(); - h1.push(("h1".to_string(), "v1".to_string())); - h1.push(("h2".to_string(), "v2".to_string())); - - let mut h2 = Vec::new(); - h2.push(("h1".to_string(), "v1".to_string())); - h2.push(("h2".to_string(), "v2".to_string())); - - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()).with_headers(h1); - - let req2 = RequestRequirements::new().with_headers(h2); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(true, does_match); - } - - /// This test makes sure that a request is considered "not matched" when the request misses - /// headers. - #[test] - fn request_matches_query_param() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()) - .with_body("test".as_bytes().to_vec()); - - let req2 = RequestRequirements::new().with_body("test".to_string()); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(true, does_match); - } - - /// This test makes sure that even the headers of a mock and a request differ, - /// the request still is considered "matched" when the request does contain more than - /// all expected headers that. Hence a request is allowed to contain headers that a mock - /// does not. - #[test] - fn request_matches_headers_match_superset() { - // Arrange - let mut h1 = Vec::new(); - h1.push(("h1".to_string(), "v1".to_string())); - h1.push(("h2".to_string(), "v2".to_string())); - - let mut h2 = Vec::new(); - h2.push(("h1".to_string(), "v1".to_string())); - - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()).with_headers(h1); - let req2 = RequestRequirements::new().with_headers(h2); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(true, does_match); // matches, because request contains more headers than the mock expects - } - - /// This test makes sure that even the headers of a mock and a request differ, - /// the request still is considered "matched" when the mock does not expect any headers - /// at all. Hence a request is allowed to contain headers that a mock does not. - #[test] - fn request_matches_headers_no_match_empty() { - // Arrange - let mut req_headers = Vec::new(); - req_headers.push(("req_headers".to_string(), "v1".to_string())); - req_headers.push(("h2".to_string(), "v2".to_string())); - - let req = - HttpMockRequest::new("GET".to_string(), "/test".to_string()).with_headers(req_headers); - - let mock = RequestRequirements::new(); - - // Act - let does_match_1 = request_matches(&MockServerState::default(), Arc::new(req), &mock); - - // Assert - assert_eq!(true, does_match_1); // effectively empty because mock does not expect any headers - } - - /// This test makes sure no present headers on both sides, the mock and the request, are - /// considered equal. - #[test] - fn request_matches_headers_match_empty() { - // Arrange - let req1 = HttpMockRequest::new("GET".to_string(), "/test".to_string()); - let req2 = RequestRequirements::new(); - - // Act - let does_match = request_matches(&MockServerState::default(), Arc::new(req1), &req2); - - // Assert - assert_eq!(true, does_match); - } - - /// This test ensures that mock request cannot contain a request method that cannot - /// be sent along with a request body. - #[test] - fn validate_mock_definition_no_body_method() { - // Arrange - let req = RequestRequirements::new() - .with_path("/test".to_string()) - .with_method("GET".to_string()) - .with_body("test".to_string()); - - let res = MockServerHttpResponse { - body: None, - delay: None, - status: Some(418), - headers: None, - }; - - let smr = MockDefinition::new(req, res); - - // Act - let result = validate_mock_definition(&smr); - - // Assert - assert_eq!(true, result.is_err()); - assert_eq!( - true, - result - .unwrap_err() - .eq("A body cannot be sent along with the specified method") - ); - } - - /// This test ensures that mock request cannot contain an empty path. - #[test] - fn validate_mock_definition_no_path() { - // Arrange - let req = RequestRequirements::new(); - let res = MockServerHttpResponse { - body: None, - delay: None, - status: Some(418), - headers: None, - }; - - let smr = MockDefinition::new(req, res); - - // Act - let result = validate_mock_definition(&smr); - - // Assert - assert_eq!(true, result.is_ok()); - } - - /// This test ensures that mock validation is being invoked. - #[test] - fn add_new_mock_validation_error() { - // Arrange - let state = MockServerState::default(); - let mut req = RequestRequirements::new(); - req.method = Some("GET".into()); - req.body = Some("body".into()); - - let res = MockServerHttpResponse { - body: None, - delay: None, - status: Some(200), - headers: None, - }; - - let mock_def = MockDefinition::new(req, res); - - // Act - let result = add_new_mock(&state, mock_def, false); - - // Assert - assert_eq!(result.is_err(), true); - assert_eq!(result.err().unwrap().contains("Validation error"), true); - } - - /// This test ensures that reading a non-existent mock does not result in an error but an - /// empty result. - #[test] - fn read_one_returns_none_test() { - // Arrange - let state = MockServerState::default(); - - // Act - let result = read_one_mock(&state, 6); - - // Assert - assert_eq!(result.is_ok(), true); - assert_eq!(result.unwrap().is_none(), true); - } - - /// This test checks if matching "path_contains" is working as expected. - #[test] - fn not_match_path_contains_test() { - // Arrange - let msr = Arc::new(HttpMockRequest::new("GET".into(), "test".into())); - let mut mock1 = RequestRequirements::new(); - mock1.path_contains = Some(vec!["x".into()]); - let mut mock2 = RequestRequirements::new(); - mock2.path_contains = Some(vec!["es".into()]); - - // Act - let result1 = request_matches(&MockServerState::default(), msr.clone(), &mock1); - let result2 = request_matches(&MockServerState::default(), msr.clone(), &mock2); - - // Assert - assert_eq!(result1, false); - assert_eq!(result2, true); - } - - /// This test checks if matching "path_matches" is working as expected. - #[test] - fn not_match_path_matches_test() { - // Arrange - let msr = Arc::new(HttpMockRequest::new("GET".into(), "test".into())); - let mut mock1 = RequestRequirements::new(); - mock1.path_matches = Some(vec![Pattern::from_regex(Regex::new(r#"x"#).unwrap())]); - let mut mock2 = RequestRequirements::new(); - mock2.path_matches = Some(vec![Pattern::from_regex(Regex::new(r#"test"#).unwrap())]); - - // Act - let result1 = request_matches(&MockServerState::default(), msr.clone(), &mock1); - let result2 = request_matches(&MockServerState::default(), msr.clone(), &mock2); - - // Assert - assert_eq!(result1, false); - assert_eq!(result2, true); - } - - /// This test checks if distance has influence on verification. - #[test] - fn verify_test() { - // Arrange - let mut mock_server_state = MockServerState::default(); - { - let mut mocks = mock_server_state.history.lock().unwrap(); - // 1: close request - mocks.push(Arc::new(HttpMockRequest::new( - String::from("POST"), - String::from("/Brians"), - ))); - // 2: closest request - mocks.push(Arc::new(HttpMockRequest::new( - String::from("GET"), - String::from("/Briann"), - ))); - // 3: distant request - mocks.push(Arc::new(HttpMockRequest::new( - String::from("DELETE"), - String::from("/xxxxxxx/xxxxxx"), - ))); - } - - let mut rr = RequestRequirements::new(); - rr.method = Some("GET".to_string()); - rr.path = Some("/Briann".to_string()); - - // Act - let result = verify(&mock_server_state, &rr); - - // Assert - assert_eq!(result.as_ref().is_ok(), true); - assert_eq!(result.as_ref().unwrap().is_some(), true); - assert_eq!(result.as_ref().unwrap().as_ref().unwrap().request_index, 0); - } -} diff --git a/src/server/web/mod.rs b/src/server/web/mod.rs deleted file mode 100644 index 8cf6b43c..00000000 --- a/src/server/web/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod handlers; -pub(crate) mod routes; diff --git a/src/server/web/routes.rs b/src/server/web/routes.rs deleted file mode 100644 index 8ab54801..00000000 --- a/src/server/web/routes.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::collections::BTreeMap; - -use serde::Serialize; - -use crate::common::data::{ - ErrorResponse, HttpMockRequest, MockDefinition, MockRef, MockServerHttpResponse, - RequestRequirements, -}; -use crate::server::web::handlers; -use crate::server::{MockServerState, ServerRequestHeader, ServerResponse}; -use std::time::Instant; -use tokio::time::Duration; - -/// This route is responsible for adding a new mock -pub(crate) fn ping() -> Result { - create_response(200, None, None) -} - -/// This route is responsible for adding a new mock -pub(crate) fn add(state: &MockServerState, body: Vec) -> Result { - let mock_def: serde_json::Result = serde_json::from_slice(&body); - - if let Err(e) = mock_def { - return create_json_response(500, None, ErrorResponse::new(&e)); - } - let mock_def = mock_def.unwrap(); - - let result = handlers::add_new_mock(&state, mock_def, false); - - match result { - Err(e) => create_json_response(500, None, ErrorResponse::new(&e)), - Ok(mock_id) => create_json_response(201, None, MockRef { mock_id }), - } -} - -/// This route is responsible for deleting mocks -pub(crate) fn delete_one(state: &MockServerState, id: usize) -> Result { - let result = handlers::delete_one_mock(state, id); - match result { - Err(e) => create_json_response(500, None, ErrorResponse::new(&e)), - Ok(found) => { - if found { - create_response(202, None, None) - } else { - create_response(404, None, None) - } - } - } -} - -/// This route is responsible for deleting all mocks -pub(crate) fn delete_all_mocks(state: &MockServerState) -> Result { - handlers::delete_all_mocks(state); - create_response(202, None, None) -} - -/// This route is responsible for deleting all mocks -pub(crate) fn delete_history(state: &MockServerState) -> Result { - handlers::delete_history(state); - create_response(202, None, None) -} - -/// This route is responsible for deleting mocks -pub(crate) fn read_one(state: &MockServerState, id: usize) -> Result { - let handler_result = handlers::read_one_mock(state, id); - match handler_result { - Err(e) => create_json_response(500, None, ErrorResponse { message: e }), - Ok(mock_opt) => match mock_opt { - Some(mock) => create_json_response(200, None, mock), - None => create_response(404, None, None), - }, - } -} - -/// This route is responsible for verification -pub(crate) fn verify(state: &MockServerState, body: Vec) -> Result { - let mock_rr: serde_json::Result = serde_json::from_slice(&body); - if let Err(e) = mock_rr { - return create_json_response(500, None, ErrorResponse::new(&e)); - } - - match handlers::verify(&state, &mock_rr.unwrap()) { - Err(e) => create_json_response(500, None, ErrorResponse::new(&e)), - Ok(closest_match) => match closest_match { - None => create_response(404, None, None), - Some(cm) => create_json_response(200, None, cm), - }, - } -} - -/// This route is responsible for finding a mock that matches the current request and serve a -/// response according to the mock specification -pub(crate) async fn serve( - state: &MockServerState, - req: &ServerRequestHeader, - body: Vec, -) -> Result { - let handler_request_result = to_handler_request(&req, body); - let result = match handler_request_result { - Ok(handler_request) => { - let handler_response = handlers::find_mock(&state, handler_request); - let handler_response = postprocess_response(handler_response).await; - to_route_response(handler_response) - } - Err(e) => create_json_response(500, None, ErrorResponse::new(&e)), - }; - return result; -} - -/// Maps the result of the serve handler to an HTTP response which the web framework understands -fn to_route_response( - handler_result: Result, String>, -) -> Result { - match handler_result { - Err(e) => create_json_response(500 as u16, None, ErrorResponse { message: e }), - Ok(res) => match res { - None => create_json_response( - 404, - None, - ErrorResponse::new(&"Request did not match any route or mock"), - ), - Some(res) => create_response(res.status.unwrap_or(200), res.headers, res.body), - }, - } -} - -fn create_json_response( - status: u16, - headers: Option>, - body: T, -) -> Result -where - T: Serialize, -{ - let body = serde_json::to_vec(&body); - if let Err(e) = body { - return Err(format!("Cannot serialize body: {}", e)); - } - - let mut headers = headers.unwrap_or_default(); - headers.push(("content-type".to_string(), "application/json".to_string())); - - create_response(status, Some(headers), Some(body.unwrap())) -} - -fn create_response( - status: u16, - headers: Option>, - body: Option>, -) -> Result { - let headers = headers.unwrap_or_default(); - let body = body.unwrap_or_default(); - Ok(ServerResponse::new(status, headers, body)) -} - -/// Maps the request of the serve handler to a request representation which the handlers understand -fn to_handler_request(req: &ServerRequestHeader, body: Vec) -> Result { - let query_params = extract_query_params(&req.query); - if let Err(e) = query_params { - return Err(format!("error parsing query_params: {}", e)); - } - - let request = HttpMockRequest::new(req.method.to_string(), req.path.to_string()) - .with_headers(req.headers.clone()) - .with_query_params(query_params.unwrap()) - .with_body(body); - - Ok(request) -} - -/// Extracts all query parameters from the URI of the given request. -fn extract_query_params(query_string: &str) -> Result, String> { - // HACK: There doesn't seem to be a way to just parse Query string with `url` crate - // Lets just prefix a dummy URL for parsing. - let url = format!("http://dummy?{}", query_string); - let url = url::Url::parse(&url).map_err(|e| e.to_string())?; - - let query_params = url - .query_pairs() - .map(|(k, v)| (k.into(), v.into())) - .collect(); - - Ok(query_params) -} - -/// Processes the response -async fn postprocess_response( - result: Result, String>, -) -> Result, String> { - if let Ok(Some(response_def)) = &result { - if let Some(duration) = response_def.delay { - tokio::time::sleep(duration).await; - } - } - result -} diff --git a/src/standalone.rs b/src/standalone.rs deleted file mode 100644 index 00d1fb1c..00000000 --- a/src/standalone.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::fs; -use std::fs::read_dir; -use std::future::Future; -use std::path::PathBuf; -use std::process::Output; -use std::str::FromStr; -use std::sync::Arc; - -use regex::Regex; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use tokio::time::Duration; - -use crate::common::data::{MockDefinition, MockServerHttpResponse, Pattern, RequestRequirements}; -use crate::common::util::read_file; -use crate::server::web::handlers::add_new_mock; -use crate::server::{start_server, MockServerState}; -use crate::Method; - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -pub struct NameValuePair { - name: String, - value: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct YAMLRequestRequirements { - pub path: Option, - pub path_contains: Option>, - pub path_matches: Option>, - pub method: Option, - pub header: Option>, - pub header_exists: Option>, - pub cookie: Option>, - pub cookie_exists: Option>, - pub body: Option, - pub json_body: Option, - pub json_body_partial: Option>, - pub body_contains: Option>, - pub body_matches: Option>, - pub query_param_exists: Option>, - pub query_param: Option>, - pub x_www_form_urlencoded_key_exists: Option>, - pub x_www_form_urlencoded_tuple: Option>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct YAMLHTTPResponse { - pub status: Option, - pub header: Option>, - pub body: Option, - pub delay: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -struct YAMLMockDefinition { - when: YAMLRequestRequirements, - then: YAMLHTTPResponse, -} - -pub async fn start_standalone_server( - port: u16, - expose: bool, - static_mock_dir_path: Option, - print_access_log: bool, - history_limit: usize, - shutdown: F, -) -> Result<(), String> -where - F: Future, -{ - let state = Arc::new(MockServerState::new(history_limit)); - - #[cfg(feature = "standalone")] - static_mock_dir_path.map(|path| { - read_static_mocks(path) - .into_iter() - .map(|d| map_to_mock_definition(d)) - .for_each(|static_mock| { - add_new_mock(&state, static_mock, true).expect("cannot add static mock"); - }) - }); - - start_server(port, expose, &state, None, print_access_log, shutdown).await -} - -#[cfg(feature = "standalone")] -fn read_static_mocks(path: PathBuf) -> Vec { - let mut definitions = Vec::new(); - - let paths = read_dir(path).expect("cannot list files in directory"); - for file_path in paths { - let file_path = file_path.unwrap().path(); - if let Some(ext) = file_path.extension() { - if !"yaml".eq(ext) && !"yml".eq(ext) { - continue; - } - } - - log::info!( - "Loading static mock file from '{}'", - file_path.to_string_lossy() - ); - let content = read_file(file_path).expect("cannot read from file"); - let content = String::from_utf8(content).expect("cannot convert file content"); - - definitions.push(serde_yaml::from_str(&content).unwrap()); - } - - return definitions; -} - -#[cfg(feature = "standalone")] -fn map_to_mock_definition(yaml_definition: YAMLMockDefinition) -> MockDefinition { - MockDefinition { - request: RequestRequirements { - path: yaml_definition.when.path, - path_contains: yaml_definition.when.path_contains, - path_matches: to_pattern_vec(yaml_definition.when.path_matches), - method: yaml_definition.when.method.map(|m| m.to_string()), - headers: to_pair_vec(yaml_definition.when.header), - header_exists: yaml_definition.when.header_exists, - cookies: to_pair_vec(yaml_definition.when.cookie), - cookie_exists: yaml_definition.when.cookie_exists, - body: yaml_definition.when.body, - json_body: yaml_definition.when.json_body, - json_body_includes: yaml_definition.when.json_body_partial, - body_contains: yaml_definition.when.body_contains, - body_matches: to_pattern_vec(yaml_definition.when.body_matches), - query_param_exists: yaml_definition.when.query_param_exists, - query_param: to_pair_vec(yaml_definition.when.query_param), - x_www_form_urlencoded: to_pair_vec(yaml_definition.when.x_www_form_urlencoded_tuple), - x_www_form_urlencoded_key_exists: yaml_definition.when.x_www_form_urlencoded_key_exists, - matchers: None, - }, - response: MockServerHttpResponse { - status: yaml_definition.then.status, - headers: to_pair_vec(yaml_definition.then.header), - body: yaml_definition.then.body.map(|b| b.into_bytes()), - delay: yaml_definition.then.delay.map(|v| Duration::from_millis(v)), - }, - } -} - -#[cfg(feature = "standalone")] -fn to_pattern_vec(vec: Option>) -> Option> { - vec.map(|vec| { - vec.iter() - .map(|val| Pattern::from_regex(Regex::from_str(val).expect("cannot parse regex"))) - .collect() - }) -} - -#[cfg(feature = "standalone")] -fn to_pair_vec(kvp: Option>) -> Option> { - kvp.map(|vec| vec.into_iter().map(|nvp| (nvp.name, nvp.value)).collect()) -} diff --git a/tarpaulin.full.toml b/tarpaulin.full.toml new file mode 100644 index 00000000..d1dad873 --- /dev/null +++ b/tarpaulin.full.toml @@ -0,0 +1,514 @@ +[none] +release = true + +[standalone] +features = "standalone" +release = true + +[cookies] +features = "cookies" +release = true + +[standalone.cookies] +features = "standalone,cookies" +release = true + +[remote] +features = "remote" +release = true + +[standalone.remote] +features = "standalone,remote" +release = true + +[cookies.remote] +features = "cookies,remote" +release = true + +[standalone.cookies.remote] +features = "standalone,cookies,remote" +release = true + +[proxy] +features = "proxy" +release = true + +[standalone.proxy] +features = "standalone,proxy" +release = true + +[cookies.proxy] +features = "cookies,proxy" +release = true + +[standalone.cookies.proxy] +features = "standalone,cookies,proxy" +release = true + +[remote.proxy] +features = "remote,proxy" +release = true + +[standalone.remote.proxy] +features = "standalone,remote,proxy" +release = true + +[cookies.remote.proxy] +features = "cookies,remote,proxy" +release = true + +[standalone.cookies.remote.proxy] +features = "standalone,cookies,remote,proxy" +release = true + +[static-mock] +features = "static-mock" +release = true + +[standalone.static-mock] +features = "standalone,static-mock" +release = true + +[cookies.static-mock] +features = "cookies,static-mock" +release = true + +[standalone.cookies.static-mock] +features = "standalone,cookies,static-mock" +release = true + +[remote.static-mock] +features = "remote,static-mock" +release = true + +[standalone.remote.static-mock] +features = "standalone,remote,static-mock" +release = true + +[cookies.remote.static-mock] +features = "cookies,remote,static-mock" +release = true + +[standalone.cookies.remote.static-mock] +features = "standalone,cookies,remote,static-mock" +release = true + +[proxy.static-mock] +features = "proxy,static-mock" +release = true + +[standalone.proxy.static-mock] +features = "standalone,proxy,static-mock" +release = true + +[cookies.proxy.static-mock] +features = "cookies,proxy,static-mock" +release = true + +[standalone.cookies.proxy.static-mock] +features = "standalone,cookies,proxy,static-mock" +release = true + +[remote.proxy.static-mock] +features = "remote,proxy,static-mock" +release = true + +[standalone.remote.proxy.static-mock] +features = "standalone,remote,proxy,static-mock" +release = true + +[cookies.remote.proxy.static-mock] +features = "cookies,remote,proxy,static-mock" +release = true + +[standalone.cookies.remote.proxy.static-mock] +features = "standalone,cookies,remote,proxy,static-mock" +release = true + +[https] +features = "https" +release = true + +[standalone.https] +features = "standalone,https" +release = true + +[cookies.https] +features = "cookies,https" +release = true + +[standalone.cookies.https] +features = "standalone,cookies,https" +release = true + +[remote.https] +features = "remote,https" +release = true + +[standalone.remote.https] +features = "standalone,remote,https" +release = true + +[cookies.remote.https] +features = "cookies,remote,https" +release = true + +[standalone.cookies.remote.https] +features = "standalone,cookies,remote,https" +release = true + +[proxy.https] +features = "proxy,https" +release = true + +[standalone.proxy.https] +features = "standalone,proxy,https" +release = true + +[cookies.proxy.https] +features = "cookies,proxy,https" +release = true + +[standalone.cookies.proxy.https] +features = "standalone,cookies,proxy,https" +release = true + +[remote.proxy.https] +features = "remote,proxy,https" +release = true + +[standalone.remote.proxy.https] +features = "standalone,remote,proxy,https" +release = true + +[cookies.remote.proxy.https] +features = "cookies,remote,proxy,https" +release = true + +[standalone.cookies.remote.proxy.https] +features = "standalone,cookies,remote,proxy,https" +release = true + +[static-mock.https] +features = "static-mock,https" +release = true + +[standalone.static-mock.https] +features = "standalone,static-mock,https" +release = true + +[cookies.static-mock.https] +features = "cookies,static-mock,https" +release = true + +[standalone.cookies.static-mock.https] +features = "standalone,cookies,static-mock,https" +release = true + +[remote.static-mock.https] +features = "remote,static-mock,https" +release = true + +[standalone.remote.static-mock.https] +features = "standalone,remote,static-mock,https" +release = true + +[cookies.remote.static-mock.https] +features = "cookies,remote,static-mock,https" +release = true + +[standalone.cookies.remote.static-mock.https] +features = "standalone,cookies,remote,static-mock,https" +release = true + +[proxy.static-mock.https] +features = "proxy,static-mock,https" +release = true + +[standalone.proxy.static-mock.https] +features = "standalone,proxy,static-mock,https" +release = true + +[cookies.proxy.static-mock.https] +features = "cookies,proxy,static-mock,https" +release = true + +[standalone.cookies.proxy.static-mock.https] +features = "standalone,cookies,proxy,static-mock,https" +release = true + +[remote.proxy.static-mock.https] +features = "remote,proxy,static-mock,https" +release = true + +[standalone.remote.proxy.static-mock.https] +features = "standalone,remote,proxy,static-mock,https" +release = true + +[cookies.remote.proxy.static-mock.https] +features = "cookies,remote,proxy,static-mock,https" +release = true + +[standalone.cookies.remote.proxy.static-mock.https] +features = "standalone,cookies,remote,proxy,static-mock,https" +release = true + +[http2] +features = "http2" +release = true + +[standalone.http2] +features = "standalone,http2" +release = true + +[cookies.http2] +features = "cookies,http2" +release = true + +[standalone.cookies.http2] +features = "standalone,cookies,http2" +release = true + +[remote.http2] +features = "remote,http2" +release = true + +[standalone.remote.http2] +features = "standalone,remote,http2" +release = true + +[cookies.remote.http2] +features = "cookies,remote,http2" +release = true + +[standalone.cookies.remote.http2] +features = "standalone,cookies,remote,http2" +release = true + +[proxy.http2] +features = "proxy,http2" +release = true + +[standalone.proxy.http2] +features = "standalone,proxy,http2" +release = true + +[cookies.proxy.http2] +features = "cookies,proxy,http2" +release = true + +[standalone.cookies.proxy.http2] +features = "standalone,cookies,proxy,http2" +release = true + +[remote.proxy.http2] +features = "remote,proxy,http2" +release = true + +[standalone.remote.proxy.http2] +features = "standalone,remote,proxy,http2" +release = true + +[cookies.remote.proxy.http2] +features = "cookies,remote,proxy,http2" +release = true + +[standalone.cookies.remote.proxy.http2] +features = "standalone,cookies,remote,proxy,http2" +release = true + +[static-mock.http2] +features = "static-mock,http2" +release = true + +[standalone.static-mock.http2] +features = "standalone,static-mock,http2" +release = true + +[cookies.static-mock.http2] +features = "cookies,static-mock,http2" +release = true + +[standalone.cookies.static-mock.http2] +features = "standalone,cookies,static-mock,http2" +release = true + +[remote.static-mock.http2] +features = "remote,static-mock,http2" +release = true + +[standalone.remote.static-mock.http2] +features = "standalone,remote,static-mock,http2" +release = true + +[cookies.remote.static-mock.http2] +features = "cookies,remote,static-mock,http2" +release = true + +[standalone.cookies.remote.static-mock.http2] +features = "standalone,cookies,remote,static-mock,http2" +release = true + +[proxy.static-mock.http2] +features = "proxy,static-mock,http2" +release = true + +[standalone.proxy.static-mock.http2] +features = "standalone,proxy,static-mock,http2" +release = true + +[cookies.proxy.static-mock.http2] +features = "cookies,proxy,static-mock,http2" +release = true + +[standalone.cookies.proxy.static-mock.http2] +features = "standalone,cookies,proxy,static-mock,http2" +release = true + +[remote.proxy.static-mock.http2] +features = "remote,proxy,static-mock,http2" +release = true + +[standalone.remote.proxy.static-mock.http2] +features = "standalone,remote,proxy,static-mock,http2" +release = true + +[cookies.remote.proxy.static-mock.http2] +features = "cookies,remote,proxy,static-mock,http2" +release = true + +[standalone.cookies.remote.proxy.static-mock.http2] +features = "standalone,cookies,remote,proxy,static-mock,http2" +release = true + +[https.http2] +features = "https,http2" +release = true + +[standalone.https.http2] +features = "standalone,https,http2" +release = true + +[cookies.https.http2] +features = "cookies,https,http2" +release = true + +[standalone.cookies.https.http2] +features = "standalone,cookies,https,http2" +release = true + +[remote.https.http2] +features = "remote,https,http2" +release = true + +[standalone.remote.https.http2] +features = "standalone,remote,https,http2" +release = true + +[cookies.remote.https.http2] +features = "cookies,remote,https,http2" +release = true + +[standalone.cookies.remote.https.http2] +features = "standalone,cookies,remote,https,http2" +release = true + +[proxy.https.http2] +features = "proxy,https,http2" +release = true + +[standalone.proxy.https.http2] +features = "standalone,proxy,https,http2" +release = true + +[cookies.proxy.https.http2] +features = "cookies,proxy,https,http2" +release = true + +[standalone.cookies.proxy.https.http2] +features = "standalone,cookies,proxy,https,http2" +release = true + +[remote.proxy.https.http2] +features = "remote,proxy,https,http2" +release = true + +[standalone.remote.proxy.https.http2] +features = "standalone,remote,proxy,https,http2" +release = true + +[cookies.remote.proxy.https.http2] +features = "cookies,remote,proxy,https,http2" +release = true + +[standalone.cookies.remote.proxy.https.http2] +features = "standalone,cookies,remote,proxy,https,http2" +release = true + +[static-mock.https.http2] +features = "static-mock,https,http2" +release = true + +[standalone.static-mock.https.http2] +features = "standalone,static-mock,https,http2" +release = true + +[cookies.static-mock.https.http2] +features = "cookies,static-mock,https,http2" +release = true + +[standalone.cookies.static-mock.https.http2] +features = "standalone,cookies,static-mock,https,http2" +release = true + +[remote.static-mock.https.http2] +features = "remote,static-mock,https,http2" +release = true + +[standalone.remote.static-mock.https.http2] +features = "standalone,remote,static-mock,https,http2" +release = true + +[cookies.remote.static-mock.https.http2] +features = "cookies,remote,static-mock,https,http2" +release = true + +[standalone.cookies.remote.static-mock.https.http2] +features = "standalone,cookies,remote,static-mock,https,http2" +release = true + +[proxy.static-mock.https.http2] +features = "proxy,static-mock,https,http2" +release = true + +[standalone.proxy.static-mock.https.http2] +features = "standalone,proxy,static-mock,https,http2" +release = true + +[cookies.proxy.static-mock.https.http2] +features = "cookies,proxy,static-mock,https,http2" +release = true + +[standalone.cookies.proxy.static-mock.https.http2] +features = "standalone,cookies,proxy,static-mock,https,http2" +release = true + +[remote.proxy.static-mock.https.http2] +features = "remote,proxy,static-mock,https,http2" +release = true + +[standalone.remote.proxy.static-mock.https.http2] +features = "standalone,remote,proxy,static-mock,https,http2" +release = true + +[cookies.remote.proxy.static-mock.https.http2] +features = "cookies,remote,proxy,static-mock,https,http2" +release = true + +[standalone.cookies.remote.proxy.static-mock.https.http2] +features = "standalone,cookies,remote,proxy,static-mock,https,http2" +release = true + +[report] +coveralls = "coveralls_key" +out = ["Html", "Xml"] diff --git a/tarpaulin.toml b/tarpaulin.toml new file mode 100644 index 00000000..59d71d07 --- /dev/null +++ b/tarpaulin.toml @@ -0,0 +1,12 @@ + +[remote] +features = "remote,proxy,http2,static-mock,cookies" +release = false + +[standalone] +features = "standalone,color,cookies,proxy,static-mock,http2" +release = true + +[report] +coveralls = "coveralls_key" +out = ["Html", "Xml"] diff --git a/tests/examples/binary_body_tests.rs b/tests/examples/binary_body_tests.rs index bb0e640e..c1375b26 100644 --- a/tests/examples/binary_body_tests.rs +++ b/tests/examples/binary_body_tests.rs @@ -1,5 +1,4 @@ use httpmock::prelude::*; -use isahc::Body; use std::io::Read; #[test] @@ -15,16 +14,18 @@ fn binary_body_test() { }); // Act - let mut response = isahc::get(server.url("/hello")).unwrap(); + let mut response = reqwest::blocking::get(&server.url("/hello")).unwrap(); // Assert m.assert(); assert_eq!(response.status(), 200); - assert_eq!(body_to_vec(response.body_mut()), binary_content.to_vec()); + assert_eq!(body_to_vec(&mut response), binary_content.to_vec()); } -fn body_to_vec(body: &mut Body) -> Vec { +fn body_to_vec(response: &mut reqwest::blocking::Response) -> Vec { let mut buf: Vec = Vec::new(); - body.read_to_end(&mut buf).expect("Cannot read from body"); + response + .read_to_end(&mut buf) + .expect("Cannot read from body"); buf } diff --git a/tests/examples/cookie_tests.rs b/tests/examples/cookie_tests.rs index 5db64790..7e7c6828 100644 --- a/tests/examples/cookie_tests.rs +++ b/tests/examples/cookie_tests.rs @@ -1,5 +1,5 @@ use httpmock::prelude::*; -use isahc::{prelude::*, Request}; +use reqwest::blocking::Client; #[test] fn cookie_matching_test() { @@ -7,21 +7,21 @@ fn cookie_matching_test() { let server = MockServer::start(); let mock = server.mock(|when, then| { - when.method(GET) + when.method("GET") .path("/") .cookie_exists("SESSIONID") .cookie("SESSIONID", "298zf09hf012fh2"); then.status(200); }); - // Act: Send the request and deserialize the response to JSON - let response = Request::get(&format!("http://{}", server.address())) + // Act: Send the request with cookies + let client = Client::new(); + let response = client + .get(&format!("http://{}", server.address())) .header( "Cookie", "OTHERCOOKIE1=01234; SESSIONID=298zf09hf012fh2; OTHERCOOKIE2=56789; HttpOnly", ) - .body(()) - .unwrap() .send() .unwrap(); diff --git a/tests/examples/custom_request_matcher_tests.rs b/tests/examples/custom_request_matcher_tests.rs index 6a588ecf..d7742dc5 100644 --- a/tests/examples/custom_request_matcher_tests.rs +++ b/tests/examples/custom_request_matcher_tests.rs @@ -1,21 +1,19 @@ use httpmock::prelude::*; -use isahc::get; #[test] -// TODO: Implement custom matcher fn my_custom_request_matcher_test() { // Arrange let server = MockServer::start(); let mock = server.mock(|when, then| { - when.matches(|req| req.path.to_lowercase().ends_with("test")); - then.status(200); + when.is_true(|req| req.uri().path().ends_with("Test")); + then.status(201); }); - // Act: Send the HTTP request - let response = get(server.url("/thisIsMyTest")).unwrap(); + // Act: Send the HTTP request using reqwest + let response = reqwest::blocking::get(&server.url("/thisIsMyTest")).unwrap(); // Assert mock.assert(); - assert_eq!(response.status(), 200); + assert_eq!(response.status(), 201); } diff --git a/tests/examples/delay_tests.rs b/tests/examples/delay_tests.rs index 3edd54d5..3dc06521 100644 --- a/tests/examples/delay_tests.rs +++ b/tests/examples/delay_tests.rs @@ -1,5 +1,4 @@ use httpmock::prelude::*; -use isahc::get; use std::time::{Duration, SystemTime}; #[test] @@ -15,8 +14,8 @@ fn delay_test() { then.status(200).delay(delay); }); - // Act: Send the HTTP request - let response = get(server.url("/delay")).unwrap(); + // Act: Send the HTTP request using reqwest + let response = reqwest::blocking::get(&server.url("/delay")).unwrap(); // Assert mock.assert(); diff --git a/tests/examples/delete_mock_tests.rs b/tests/examples/delete_mock_tests.rs index 596c9f4a..a141e3d0 100644 --- a/tests/examples/delete_mock_tests.rs +++ b/tests/examples/delete_mock_tests.rs @@ -1,5 +1,4 @@ use httpmock::prelude::*; -use isahc::get; #[test] fn explicit_delete_mock_test() { @@ -7,12 +6,12 @@ fn explicit_delete_mock_test() { let server = MockServer::start(); let mut m = server.mock(|when, then| { - when.method(GET).path("/health"); + when.method("GET").path("/health"); then.status(205); }); - // Act: Send the HTTP request - let response = get(&format!( + // Act: Send the HTTP request using reqwest + let response = reqwest::blocking::get(&format!( "http://{}:{}/health", server.host(), server.port() @@ -26,8 +25,8 @@ fn explicit_delete_mock_test() { // Delete the mock and send the request again m.delete(); - let response = get(&format!("http://{}/health", server.address())).unwrap(); + let response = reqwest::blocking::get(&format!("http://{}/health", server.address())).unwrap(); - // Assert that the request failed, because the mock has been deleted + // Assert that the request failed because the mock has been deleted assert_eq!(response.status(), 404); } diff --git a/tests/examples/file_body_tests.rs b/tests/examples/file_body_tests.rs index 74473d58..dd26ced7 100644 --- a/tests/examples/file_body_tests.rs +++ b/tests/examples/file_body_tests.rs @@ -1,5 +1,4 @@ use httpmock::prelude::*; -use isahc::prelude::*; #[test] fn file_body_test() { @@ -12,7 +11,7 @@ fn file_body_test() { }); // Act - let mut response = isahc::get(server.url("/hello")).unwrap(); + let response = reqwest::blocking::get(&server.url("/hello")).unwrap(); // Assert m.assert(); diff --git a/tests/examples/forwarding_tests.rs b/tests/examples/forwarding_tests.rs new file mode 100644 index 00000000..59f1acba --- /dev/null +++ b/tests/examples/forwarding_tests.rs @@ -0,0 +1,70 @@ +use httpmock::prelude::*; +use reqwest::blocking::Client; + +// @example-start: forwarding +#[cfg(feature = "proxy")] +#[test] +fn forwarding_test() { + // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.). + let target_server = MockServer::start(); + target_server.mock(|when, then| { + when.any_request(); + then.status(200).body("Hi from fake GitHub!"); + }); + + // Let's create our mock server for the test + let server = MockServer::start(); + + // We configure our server to forward the request to the target host instead of + // answering with a mocked response. The 'when' variable lets you configure + // rules under which forwarding should take place. + server.forward_to(target_server.base_url(), |rule| { + rule.filter(|when| { + when.any_request(); // We want all requests to be forwarded. + }); + }); + + // Now let's send an HTTP request to the mock server. The request will be forwarded + // to the target host, as we configured before. + let client = Client::new(); + + // Since the request was forwarded, we should see the target host's response. + let response = client.get(server.url("/get")).send().unwrap(); + assert_eq!(response.status().as_u16(), 200); + assert_eq!(response.text().unwrap(), "Hi from fake GitHub!"); +} +// @example-end + +// @example-start: forwarding-github +#[cfg(feature = "proxy")] +#[test] +fn forward_to_github_test() { + // Let's create our mock server for the test + let server = MockServer::start(); + + // We configure our server to forward the request to the target + // host instead of answering with a mocked response. The 'when' + // variable lets you configure rules under which forwarding + // should take place. + server.forward_to("https://api.github.com", |rule| { + rule.filter(|when| { + when.any_request(); // Ensure all requests are forwarded. + }); + }); + + // Now let's send an HTTP request to the mock server. The request + // will be forwarded to the GitHub API, as we configured before. + let client = Client::new(); + + let response = client + .get(server.url("/repos/torvalds/linux")) + // GitHub requires us to send a user agent header + .header("User-Agent", "httpmock-test") + .send() + .unwrap(); + + // Since the request was forwarded, we should see a GitHub API response. + assert_eq!(response.status().as_u16(), 200); + assert_eq!(true, response.text().unwrap().contains("\"private\":false")); +} +// @example-end diff --git a/tests/examples/getting_started_tests.rs b/tests/examples/getting_started_tests.rs index 3c19badf..a4c2e7d8 100644 --- a/tests/examples/getting_started_tests.rs +++ b/tests/examples/getting_started_tests.rs @@ -1,8 +1,7 @@ -use httpmock::prelude::*; -use isahc::{get, get_async}; - #[test] fn getting_started_test() { + use httpmock::prelude::*; + // Start a lightweight mock server. let server = MockServer::start(); @@ -17,7 +16,7 @@ fn getting_started_test() { }); // Send an HTTP request to the mock server. This simulates your code. - let response = get(server.url("/translate?word=hello")).unwrap(); + let response = reqwest::blocking::get(server.url("/translate?word=hello")).unwrap(); // Ensure the specified mock was called. hello_mock.assert(); @@ -26,27 +25,37 @@ fn getting_started_test() { assert_eq!(response.status(), 200); } -#[async_std::test] +#[tokio::test] async fn async_getting_started_test() { - // Start a local mock server for exclusive use by this test function. + use httpmock::prelude::*; + + // Start a lightweight mock server. let server = MockServer::start_async().await; - // Create a mock on the mock server. The mock will return HTTP status code 200 whenever - // the mock server receives a GET-request with path "/hello". // Create a mock on the server. - let hello_mock = server + let mock = server .mock_async(|when, then| { - when.method("GET").path("/hello"); - then.status(200); + when.method(GET) + .path("/translate") + .query_param("word", "hello"); + then.status(200) + .header("content-type", "text/html; charset=UTF-8") + .body("Привет"); }) .await; // Send an HTTP request to the mock server. This simulates your code. - let url = format!("http://{}/hello", server.address()); - let response = get_async(&url).await.unwrap(); + let client = reqwest::Client::new(); + let response = client + .get(server.url("/translate?word=hello")) + .send() + .await + .unwrap(); + + // Ensure the specified mock was called exactly one time (or fail with a + // detailed error description). + mock.assert(); - // Ensure the specified mock responded exactly one time. - hello_mock.assert_async().await; - // Ensure the mock server did respond as specified above. + // Ensure the mock server did respond as specified. assert_eq!(response.status(), 200); } diff --git a/tests/examples/headers_tests.rs b/tests/examples/headers_tests.rs index 77cc4974..3d95fe97 100644 --- a/tests/examples/headers_tests.rs +++ b/tests/examples/headers_tests.rs @@ -1,5 +1,5 @@ use httpmock::prelude::*; -use isahc::{prelude::*, Request}; +use reqwest::blocking::Client; #[test] fn headers_test() { @@ -13,11 +13,11 @@ fn headers_test() { then.status(201).header("Content-Length", "0"); }); - // Act: Send the request and deserialize the response to JSON - let response = Request::post(&format!("http://{}/test", server.address())) + // Act: Send the request using reqwest + let client = Client::new(); + let response = client + .post(&format!("http://{}/test", server.address())) .header("Authorization", "token 123456789") - .body(()) - .unwrap() .send() .unwrap(); @@ -34,3 +34,28 @@ fn headers_test() { "0" ); } + +#[test] +fn headers_test_2() { + // Arrange + let server = MockServer::start(); + + // Create a mock that expects at least 2 headers whose keys match the regex "^X-Custom-Header.*" + // and values match the regex "value.*" + let mock = server.mock(|when, then| { + when.header_count("^X-Custom-Header.*", "value.*", 2); + then.status(200); // Respond with a 200 status code if the condition is met + }); + + // Act: Make a request that includes the required headers using reqwest + let client = Client::new(); + client + .post(&format!("http://{}/test", server.address())) + .header("x-custom-header-1", "value1") + .header("X-Custom-Header-2", "value2") + .send() + .unwrap(); + + // Assert: Verify that the mock was called at least once + mock.assert(); +} diff --git a/tests/examples/https_tests.rs b/tests/examples/https_tests.rs new file mode 100644 index 00000000..9530331a --- /dev/null +++ b/tests/examples/https_tests.rs @@ -0,0 +1,62 @@ +#[cfg(feature = "https")] +#[tokio::test] +async fn test_http_get_request() { + use httpmock::MockServer; + + // Arrange + let server = MockServer::start_async().await; + + server + .mock_async(|when, then| { + when.any_request(); + then.header("X-Hello", "test").status(200); + }) + .await; + + let base_url = format!("https://{}", server.address()); + + let client = reqwest::Client::new(); + let res = client.get(&base_url).send().await.unwrap(); + + assert_eq!(res.status(), 200, "HTTP status should be 200 OK"); +} +#[cfg(feature = "https")] +#[cfg(feature = "remote")] +#[tokio::test] +async fn https_test_reqwest() { + use httpmock::MockServer; + use reqwest::{tls::Certificate, Client}; + use std::{fs::read, path::PathBuf}; + + // Arrange + let server = MockServer::connect_async("localhost:5050").await; + + server + .mock_async(|when, then| { + when.any_request(); + then.header("X-Hello", "test").status(200); + }) + .await; + + let base_url = format!("https://localhost:{}", server.address().port()); + + // Load the CA certificate from the project path + let project_dir = env!("CARGO_MANIFEST_DIR"); + let cert_path = PathBuf::from(project_dir).join("certs/ca.pem"); + let cert = Certificate::from_pem(&read(cert_path).unwrap()).unwrap(); + + // Build the client with the CA certificate + let client = Client::builder() + .add_root_certificate(cert) + .build() + .unwrap(); + + let res = client.get(&base_url).send().await.unwrap(); + + assert_eq!(res.status(), 200); + assert_eq!( + res.headers().get("X-Hello").unwrap().to_str().unwrap(), + "test" + ); + assert!(base_url.starts_with("https://")); +} diff --git a/tests/examples/json_body_tests.rs b/tests/examples/json_body_tests.rs index 278084a6..e81f637e 100644 --- a/tests/examples/json_body_tests.rs +++ b/tests/examples/json_body_tests.rs @@ -1,5 +1,5 @@ use httpmock::prelude::*; -use isahc::{prelude::*, Request}; +use reqwest::blocking::Client; use serde_json::{json, Value}; #[test] @@ -18,19 +18,21 @@ fn json_value_body_test() { }); // Act: Send the request and deserialize the response to JSON - let mut response = Request::post(&format!("http://{}/users", server.address())) + let client = Client::new(); + let response = client + .post(&format!("http://{}/users", server.address())) .header("content-type", "application/json") .body(json!({ "name": "Fred" }).to_string()) - .unwrap() .send() .unwrap(); + let status = response.status().as_u16(); let user: Value = serde_json::from_str(&response.text().unwrap()).expect("cannot deserialize JSON"); // Assert m.assert(); - assert_eq!(response.status(), 201); + assert_eq!(status, 201); assert_eq!(user.as_object().unwrap().get("name").unwrap(), "Hans"); } @@ -60,7 +62,9 @@ fn json_body_object_serde_test() { }); // Act: Send the request and deserialize the response to JSON - let mut response = Request::post(&format!("http://{}/users", server.address())) + let client = Client::new(); + let response = client + .post(&format!("http://{}/users", server.address())) .header("content-type", "application/json") .body( json!(&TestUser { @@ -68,16 +72,16 @@ fn json_body_object_serde_test() { }) .to_string(), ) - .unwrap() .send() .unwrap(); + let status = response.status().as_u16(); let user: TestUser = serde_json::from_str(&response.text().unwrap()).expect("cannot deserialize JSON"); // Assert m.assert(); - assert_eq!(response.status(), 201); + assert_eq!(status, 201); assert_eq!(user.name, "Hans"); } @@ -100,7 +104,7 @@ fn partial_json_body_test() { // Arranging the test by creating HTTP mocks. let m = server.mock(|when, then| { - when.method(POST).path("/users").json_body_partial( + when.method(POST).path("/users").json_body_includes( r#" { "child" : { @@ -113,8 +117,10 @@ fn partial_json_body_test() { }); // Simulates application that makes the request to the mock. + let client = Client::new(); let uri = format!("http://{}/users", m.server_address()); - let response = Request::post(&uri) + let response = client + .post(&uri) .header("content-type", "application/json") .header("User-Agent", "rust-test") .body( @@ -126,7 +132,6 @@ fn partial_json_body_test() { }) .unwrap(), ) - .unwrap() .send() .unwrap(); diff --git a/tests/examples/mod.rs b/tests/examples/mod.rs index fc42eb46..5a23fb1a 100644 --- a/tests/examples/mod.rs +++ b/tests/examples/mod.rs @@ -4,17 +4,18 @@ mod custom_request_matcher_tests; mod delay_tests; mod delete_mock_tests; mod file_body_tests; +mod forwarding_tests; mod getting_started_tests; mod headers_tests; +mod https_tests; mod json_body_tests; mod multi_server_tests; +mod proxy_tests; mod query_param_tests; +mod record_and_playback_tests; mod reset_tests; mod showcase_tests; +mod standalone_tests; mod string_body_tests; mod url_matching_tests; - -#[cfg(feature = "remote")] -mod standalone_tests; -#[cfg(feature = "remote")] mod x_www_form_urlencoded_tests; diff --git a/tests/examples/multi_server_tests.rs b/tests/examples/multi_server_tests.rs index 12def364..57a034a8 100644 --- a/tests/examples/multi_server_tests.rs +++ b/tests/examples/multi_server_tests.rs @@ -1,11 +1,8 @@ use httpmock::prelude::*; -use isahc::config::RedirectPolicy; -use isahc::prelude::*; -use isahc::HttpClientBuilder; +use reqwest::{blocking::Client, redirect::Policy}; #[test] fn multi_server_test() { - // Arrange let server1 = MockServer::start(); let server2 = MockServer::start(); @@ -21,15 +18,13 @@ fn multi_server_test() { then.status(200); }); - // Act: Send the HTTP request with an HTTP client that automatically follows redirects! - let http_client = HttpClientBuilder::new() - .redirect_policy(RedirectPolicy::Follow) + let client = Client::builder() + .redirect(Policy::limited(10)) .build() .unwrap(); - let response = http_client.get(server1.url("/redirectTest")).unwrap(); + let response = client.get(&server1.url("/redirectTest")).send().unwrap(); - // Assert redirect_mock.assert(); target_mock.assert(); assert_eq!(response.status(), 200); diff --git a/tests/examples/proxy_tests.rs b/tests/examples/proxy_tests.rs new file mode 100644 index 00000000..5656d9a2 --- /dev/null +++ b/tests/examples/proxy_tests.rs @@ -0,0 +1,46 @@ +use httpmock::prelude::*; +use reqwest::blocking::Client; + +#[cfg(feature = "proxy")] +#[test] +fn proxy_test() { + env_logger::try_init().unwrap(); + + // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.). + let target_server = MockServer::start(); + target_server.mock(|when, then| { + when.any_request(); + then.status(200).body("Hi from fake GitHub!"); + }); + + // Let's create our mock server for the test + let proxy_server = MockServer::start(); + + // We configure our server to proxy the request to the target host instead of + // answering with a mocked response. The 'when' variable lets you configure + // rules under which requests are allowed to be proxied. If you do not restrict, + // any request will be proxied. + proxy_server.proxy(|rule| { + rule.filter(|when| { + // Here we only allow to proxy requests to our target server. + when.host(target_server.host()).port(target_server.port()); + }); + }); + + // The following will send a request to the mock server. The request will be forwarded + // to the target host, as we configured before. + let client = Client::builder() + .proxy(reqwest::Proxy::all(proxy_server.base_url()).unwrap()) // <<- Here we configure to use a proxy server + .build() + .unwrap(); + + // Since the request was forwarded, we should see the target host's response. + let response = client.get(target_server.url("/get")).send().unwrap(); + + // Extract the status code before calling .text() which consumes the response + let status_code = response.status().as_u16(); + let response_text = response.text().unwrap(); // Store the text response in a variable + + assert_eq!("Hi from fake GitHub!", response_text); // Use the stored text for comparison + assert_eq!(status_code, 200); // Now compare the status code +} diff --git a/tests/examples/query_param_tests.rs b/tests/examples/query_param_tests.rs index d7114edf..a8685a30 100644 --- a/tests/examples/query_param_tests.rs +++ b/tests/examples/query_param_tests.rs @@ -1,6 +1,4 @@ use httpmock::prelude::*; -use isahc::get as http_get; -use ureq::get as httpget; #[test] fn url_param_matching_test() { @@ -13,8 +11,8 @@ fn url_param_matching_test() { then.status(200); }); - // Act: Send the request and deserialize the response to JSON - http_get(server.url("/search?query=Metallica")).unwrap(); + // Act: Send the request using the fully qualified path + reqwest::blocking::get(&server.url("/search?query=Metallica")).unwrap(); // Assert m.assert(); @@ -31,8 +29,8 @@ fn url_param_urlencoded_matching_test() { then.status(200); }); - // Act: Send the request - http_get(server.url("/search?query=Mot%C3%B6rhead")).unwrap(); + // Act: Send the request using the fully qualified path + reqwest::blocking::get(&server.url("/search?query=Mot%C3%B6rhead")).unwrap(); // Assert m.assert(); @@ -49,10 +47,8 @@ fn url_param_unencoded_matching_test() { then.status(200); }); - // Act: Send the request - httpget(&server.url("/search?query=Motörhead")) - .send_string("") - .unwrap(); + // Act: Send the request using the fully qualified path + reqwest::blocking::get(&server.url("/search?query=Motörhead")).unwrap(); // Assert m.assert(); @@ -68,10 +64,8 @@ fn url_param_encoding_issue_56() { then.status(200); }); - // Act: Send the request - httpget(&server.url("/search?query=Metallica+is+cool")) - .send_string("") - .unwrap(); + // Act: Send the request using the fully qualified path + reqwest::blocking::get(&server.url("/search?query=Metallica+is+cool")).unwrap(); // Assert m.assert(); diff --git a/tests/examples/record_and_playback_tests.rs b/tests/examples/record_and_playback_tests.rs new file mode 100644 index 00000000..fcff934e --- /dev/null +++ b/tests/examples/record_and_playback_tests.rs @@ -0,0 +1,206 @@ +use httpmock::prelude::*; +use reqwest::blocking::Client; + +#[cfg(feature = "record")] +#[test] +fn record_with_forwarding_test() { + let target_server = MockServer::start(); + target_server.mock(|when, then| { + when.any_request(); + then.status(200).body("Hi from fake GitHub!"); + }); + + let recording_server = MockServer::start(); + + recording_server.forward_to(target_server.base_url(), |rule| { + rule.filter(|when| { + when.path("/hello"); + }); + }); + + let recording = recording_server.record(|rule| { + rule.record_response_delays(true) + .record_request_headers(vec!["Accept", "Content-Type"]) + .filter(|when| { + when.path("/hello"); + }); + }); + + let github_client = Client::builder().build().unwrap(); + + let response = github_client + .get(format!("{}/hello", recording_server.base_url())) + .send() + .unwrap(); + assert_eq!(response.text().unwrap(), "Hi from fake GitHub!"); + + let target_path = recording.save("my_test_scenario").unwrap(); + + let playback_server = MockServer::start(); + + playback_server.playback(target_path); + + let response = github_client + .get(format!("{}/hello", playback_server.base_url())) + .send() + .unwrap(); + assert_eq!(response.text().unwrap(), "Hi from fake GitHub!"); +} + +// @example-start: record-proxy-github +#[cfg(all(feature = "proxy", feature = "record", feature = "experimental"))] +#[test] +fn record_with_proxy_test() { + // Start a mock server to act as a proxy for the HTTP client + let server = MockServer::start(); + + // Configure the mock server to proxy all incoming requests + server.proxy(|rule| { + rule.filter(|when| { + when.any_request(); // Intercept all requests + }); + }); + + // Set up recording on the mock server to capture all proxied + // requests and responses + let recording = server.record(|rule| { + rule.filter(|when| { + when.any_request(); // Record all requests + }); + }); + + // Create an HTTP client configured to route requests + // through the mock proxy server + let github_client = Client::builder() + // Set the proxy URL to the mock server's URL + .proxy(reqwest::Proxy::all(server.base_url()).unwrap()) + .build() + .unwrap(); + + // Send a GET request using the client, which will be proxied by the mock server + let response = github_client.get(server.base_url()).send().unwrap(); + + // Verify that the response matches the expected mock response + assert_eq!(response.text().unwrap(), "This is a mock response"); + + // Save the recorded HTTP interactions to a file for future reference or testing + recording + .save("my_scenario_name") + .expect("could not save the recording"); +} +// @example-end + +// @example-start: record-forwarding-github +#[cfg(all(feature = "proxy", feature = "record"))] +#[test] +fn record_github_api_with_forwarding_test() { + // Let's create our mock server for the test + let server = MockServer::start(); + + // We configure our server to forward the request to the target + // host instead of answering with a mocked response. The 'when' + // variable lets you configure rules under which forwarding + // should take place. + server.forward_to("https://api.github.com", |rule| { + rule.filter(|when| { + when.any_request(); // Ensure all requests are forwarded. + }); + }); + + let recording = server.record(|rule| { + rule + // Specify which headers to record. + // Only the headers listed here will be captured and stored + // as part of the recorded mock. This selective recording is + // necessary because some headers may vary between requests + // and could cause issues when replaying the mock later. + // For instance, headers like 'Authorization' or 'Date' may + // change with each request. + .record_request_header("User-Agent") + .filter(|when| { + when.any_request(); // Ensure all requests are recorded. + }); + }); + + // Now let's send an HTTP request to the mock server. The request + // will be forwarded to the GitHub API, as we configured before. + let client = Client::new(); + + let response = client + .get(server.url("/repos/torvalds/linux")) + // GitHub requires us to send a user agent header + .header("User-Agent", "httpmock-test") + .send() + .unwrap(); + + // Since the request was forwarded, we should see a GitHub API response. + assert_eq!(response.status().as_u16(), 200); + assert_eq!(true, response.text().unwrap().contains("\"private\":false")); + + // Save the recording to + // "target/httpmock/recordings/github-torvalds-scenario_.yaml". + recording + .save("github-torvalds-scenario") + .expect("cannot store scenario on disk"); +} +// @example-end + +// @example-start: playback-forwarding-github +#[cfg(all(feature = "proxy", feature = "record"))] +#[test] +fn playback_github_api() { + // Start a mock server for the test + let server = MockServer::start(); + + // Configure the mock server to forward requests to the target + // host (GitHub API) instead of responding with a mock. The 'rule' + // parameter allows you to define conditions under which forwarding + // should occur. + server.forward_to("https://api.github.com", |rule| { + rule.filter(|when| { + when.any_request(); // Forward all requests. + }); + }); + + // Set up recording to capture all forwarded requests and responses + let recording = server.record(|rule| { + rule.filter(|when| { + when.any_request(); // Record all requests and responses. + }); + }); + + // Send an HTTP request to the mock server, which will be forwarded + // to the GitHub API + let client = Client::new(); + let response = client + .get(server.url("/repos/torvalds/linux")) + // GitHub requires a User-Agent header + .header("User-Agent", "httpmock-test") + .send() + .unwrap(); + + // Assert that the response from the forwarded request is as expected + assert_eq!(response.status().as_u16(), 200); + assert!(response.text().unwrap().contains("\"private\":false")); + + // Save the recorded interactions to a file + let target_path = recording + .save("github-torvalds-scenario") + .expect("Failed to save the recording to disk"); + + // Start a new mock server instance for playback + let playback_server = MockServer::start(); + + // Load the recorded interactions into the new mock server + playback_server.playback(target_path); + + // Send a request to the playback server and verify the response + // matches the recorded data + let response = client + .get(playback_server.url("/repos/torvalds/linux")) + .send() + .unwrap(); + assert_eq!(response.status().as_u16(), 200); + assert!(response.text().unwrap().contains("\"private\":false")); +} +// @example-end diff --git a/tests/examples/reset_tests.rs b/tests/examples/reset_tests.rs index 90a82f8f..d4692a52 100644 --- a/tests/examples/reset_tests.rs +++ b/tests/examples/reset_tests.rs @@ -1,12 +1,9 @@ use httpmock::prelude::*; -use isahc::get; -#[async_std::test] +#[tokio::test] async fn reset_server_test() { - // Start a lightweight mock server. let server = MockServer::start(); - // Create a mock on the server that will be reset later server.mock(|when, then| { when.method("GET") .path("/translate") @@ -16,10 +13,8 @@ async fn reset_server_test() { .body("Привет"); }); - // Delete all previously created mocks server.reset_async().await; - // Create a new mock that will replace the previous one let hello_mock = server.mock(|when, then| { when.method("GET") .path("/translate") @@ -29,12 +24,10 @@ async fn reset_server_test() { .body("Привет"); }); - // Send an HTTP request to the mock server. This simulates your code. - let response = get(server.url("/translate?word=hello")).unwrap(); + let response = reqwest::get(&server.url("/translate?word=hello")) + .await + .unwrap(); - // Ensure the specified mock was called. hello_mock.assert(); - - // Ensure the mock server did respond as specified. assert_eq!(response.status(), 200); } diff --git a/tests/examples/showcase_tests.rs b/tests/examples/showcase_tests.rs index 111b50fc..9202274e 100644 --- a/tests/examples/showcase_tests.rs +++ b/tests/examples/showcase_tests.rs @@ -1,49 +1,47 @@ use httpmock::prelude::*; -use isahc::{prelude::*, Request}; +use regex::Regex; +use reqwest::blocking::Client; use serde_json::json; #[test] fn showcase_test() { - // This is a temporary type that we will use for this test #[derive(serde::Serialize, serde::Deserialize)] struct TransferItem { number: usize, } - // Arrange let server = MockServer::start(); let m = server.mock(|when, then| { when.method(POST) .path("/test") - .path_contains("test") + .path_includes("test") .query_param("myQueryParam", "überschall") .query_param_exists("myQueryParam") .path_matches(Regex::new(r#"test"#).unwrap()) .header("content-type", "application/json") .header_exists("User-Agent") .body("{\"number\":5}") - .body_contains("number") + .body_includes("number") .body_matches(Regex::new(r#"(\d+)"#).unwrap()) .json_body(json!({ "number": 5 })) - .matches(|req: &HttpMockRequest| req.path.contains("es")); + .is_true(|req: &HttpMockRequest| req.uri().path().contains("es")); then.status(200); }); - // Act: Send the HTTP request let uri = format!( "http://{}/test?myQueryParam=%C3%BCberschall", server.address() ); - let response = Request::post(&uri) + let client = Client::new(); + let response = client + .post(&uri) .header("content-type", "application/json") .header("User-Agent", "rust-test") .body(serde_json::to_string(&TransferItem { number: 5 }).unwrap()) - .unwrap() .send() .unwrap(); - // Assert m.assert(); assert_eq!(response.status(), 200); } diff --git a/tests/examples/standalone_tests.rs b/tests/examples/standalone_tests.rs index c986680d..8ce093f1 100644 --- a/tests/examples/standalone_tests.rs +++ b/tests/examples/standalone_tests.rs @@ -1,18 +1,17 @@ -use httpmock::prelude::*; -use isahc::{get_async, Body, Request, RequestExt}; -use std::io::Read; - -use crate::simulate_standalone_server; - #[test] +#[cfg(feature = "remote")] fn standalone_test() { + use crate::with_standalone_server; + use httpmock::MockServer; + use reqwest::blocking::Client; + // Arrange - // This starts up a standalone server in the background running on port 5000 - simulate_standalone_server(); + // This starts up a standalone server in the background running on port 5050 + with_standalone_server(); // Instead of creating a new MockServer using new(), we connect to an existing remote instance. - let server = MockServer::connect("localhost:5000"); + let server = MockServer::connect("localhost:5050"); let search_mock = server.mock(|when, then| { when.path("/search").body("wow so large".repeat(1000000)); @@ -20,9 +19,10 @@ fn standalone_test() { }); // Act: Send the HTTP request - let response = Request::post(server.url("/search")) + let client = Client::new(); + let response = client + .post(&server.url("/search")) .body("wow so large".repeat(1000000)) - .unwrap() .send() .unwrap(); @@ -31,59 +31,73 @@ fn standalone_test() { assert_eq!(response.status(), 202); } -#[async_std::test] +#[cfg(feature = "remote")] +#[tokio::test] async fn async_standalone_test() { + use crate::with_standalone_server; + use httpmock::MockServer; + use reqwest::Client; + // Arrange - // This starts up a standalone server in the background running on port 5000 - simulate_standalone_server(); + // This starts up a standalone server in the background running on port 5050 + with_standalone_server(); // Instead of creating a new MockServer using connect_from_env_async(), we connect by // reading the host and port from the environment (HTTPMOCK_HOST / HTTPMOCK_PORT) or - // falling back to defaults (localhost on port 5000) + // falling back to defaults (localhost on port 5050) let server = MockServer::connect_from_env_async().await; - let mut search_mock = server + let search_mock = server .mock_async(|when, then| { - when.path_contains("/search") + when.path_includes("/search") .query_param("query", "metallica"); then.status(202); }) .await; // Act: Send the HTTP request - let response = get_async(&format!( - "http://{}/search?query=metallica", - server.address() - )) - .await - .unwrap(); + let client = Client::new(); + let response = client + .get(&format!( + "http://{}/search?query=metallica", + server.address() + )) + .send() + .await + .unwrap(); // Assert 1 assert_eq!(response.status(), 202); - assert_eq!(search_mock.hits_async().await, 1); + assert_eq!(search_mock.calls_async().await, 1); // Act 2: Delete the mock and send a request to show that it is not present on the server anymore - search_mock.delete(); - let response = get_async(&format!( - "http://{}:{}/search?query=metallica", - server.host(), - server.port() - )) - .await - .unwrap(); + search_mock.delete_async().await; + let response = client + .get(&format!( + "http://{}:{}/search?query=metallica", + server.host(), + server.port() + )) + .send() + .await + .unwrap(); // Assert: The mock was not found assert_eq!(response.status(), 404); } +#[cfg(feature = "remote")] #[test] #[should_panic] fn unsupported_features() { + use crate::with_standalone_server; + use httpmock::MockServer; + // Arrange - // This starts up a standalone server in the background running on port 5000 - simulate_standalone_server(); + // This starts up a standalone server in the background running on port 5050 + with_standalone_server(); // Instead of creating a new MockServer using connect_from_env(), we connect by reading the // host and port from the environment (HTTPMOCK_HOST / HTTPMOCK_PORT) or falling back to defaults @@ -92,16 +106,21 @@ fn unsupported_features() { // Creating this mock will panic because expect_match is not supported when using // a remote mock server. let _ = server.mock(|when, _then| { - when.matches(|_| true); + when.is_true(|_| true); }); } +#[cfg(feature = "remote")] #[test] fn binary_body_standalone_test() { + use crate::with_standalone_server; + use httpmock::MockServer; + use reqwest::blocking::get; + // Arrange - // This starts up a standalone server in the background running on port 5000 - simulate_standalone_server(); + // This starts up a standalone server in the background running on port 5050 + with_standalone_server(); let binary_content = b"\x80\x02\x03\xF0\x90\x80"; @@ -112,16 +131,14 @@ fn binary_body_standalone_test() { }); // Act - let mut response = isahc::get(server.url("/hello")).unwrap(); + let mut response = get(&server.url("/hello")).unwrap(); // Assert m.assert(); assert_eq!(response.status(), 200); - assert_eq!(body_to_vec(response.body_mut()), binary_content.to_vec()); -} -fn body_to_vec(body: &mut Body) -> Vec { let mut buf: Vec = Vec::new(); - body.read_to_end(&mut buf).expect("Cannot read from body"); - buf + response.copy_to(&mut buf).expect("Cannot read from body"); + + assert_eq!(buf, binary_content.to_vec()); } diff --git a/tests/examples/string_body_tests.rs b/tests/examples/string_body_tests.rs index 52ca0f11..6ca75181 100644 --- a/tests/examples/string_body_tests.rs +++ b/tests/examples/string_body_tests.rs @@ -1,5 +1,6 @@ use httpmock::prelude::*; -use isahc::{prelude::*, Request}; +use regex::Regex; +use reqwest::blocking::Client; #[test] fn body_test() { @@ -10,15 +11,16 @@ fn body_test() { when.method(POST) .path("/books") .body("The Fellowship of the Ring") - .body_contains("Ring") + .body_includes("Ring") .body_matches(Regex::new("Fellowship").unwrap()); then.status(201).body("The Lord of the Rings"); }); - // Act: Send the request and deserialize the response to JSON - let response = Request::post(&format!("http://{}/books", server.address())) + // Act: Send the request + let client = Client::new(); + let response = client + .post(&format!("http://{}/books", server.address())) .body("The Fellowship of the Ring") - .unwrap() .send() .unwrap(); diff --git a/tests/examples/url_matching_tests.rs b/tests/examples/url_matching_tests.rs index 22d082f1..f955952a 100644 --- a/tests/examples/url_matching_tests.rs +++ b/tests/examples/url_matching_tests.rs @@ -1,5 +1,5 @@ use httpmock::prelude::*; -use isahc::get; +use regex::Regex; #[test] fn url_matching_test() { @@ -8,13 +8,13 @@ fn url_matching_test() { let m = server.mock(|when, then| { when.path("/appointments/20200922") - .path_contains("appointments") - .path_matches(Regex::new(r"\d{4}\d{2}\d{2}$").unwrap()); + .path_includes("appointments") + .path_matches(Regex::new(r"\d{8}$").unwrap()); then.status(201); }); - // Act: Send the request and deserialize the response to JSON - get(server.url("/appointments/20200922")).unwrap(); + // Act: Send the request + reqwest::blocking::get(&server.url("/appointments/20200922")).unwrap(); // Assert m.assert(); diff --git a/tests/examples/x_www_form_urlencoded_tests.rs b/tests/examples/x_www_form_urlencoded_tests.rs index 3588d452..76b36f82 100644 --- a/tests/examples/x_www_form_urlencoded_tests.rs +++ b/tests/examples/x_www_form_urlencoded_tests.rs @@ -1,26 +1,27 @@ use httpmock::prelude::*; -use isahc::{prelude::*, Request}; +use reqwest::blocking::Client; #[test] -fn body_test() { +fn body_test_xxx_form_url_encoded() { // Arrange - let server = MockServer::connect("127.0.0.1:5000"); + let server = MockServer::start(); let m = server.mock(|when, then| { when.method(POST) .path("/example") .header("content-type", "application/x-www-form-urlencoded") - .x_www_form_urlencoded_tuple("name", "Peter Griffin") - .x_www_form_urlencoded_tuple("town", "Quahog") - .x_www_form_urlencoded_key_exists("name") - .x_www_form_urlencoded_key_exists("town"); + .form_urlencoded_tuple("name", "Peter Griffin") + .form_urlencoded_tuple("town", "Quahog") + .form_urlencoded_tuple_exists("name") + .form_urlencoded_tuple_exists("town"); then.status(202); }); - let response = Request::post(server.url("/example")) + let client = Client::new(); + let response = client + .post(&server.url("/example")) .header("content-type", "application/x-www-form-urlencoded") .body("name=Peter%20Griffin&town=Quahog") - .unwrap() .send() .unwrap(); diff --git a/tests/internal/runtimes_test.rs b/tests/internal/runtimes_test.rs deleted file mode 100644 index 711c902c..00000000 --- a/tests/internal/runtimes_test.rs +++ /dev/null @@ -1,42 +0,0 @@ -use httpmock::prelude::*; -use isahc::get_async; - -#[test] -fn all_runtimes_test() { - // Tokio - assert_eq!( - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(test_fn()), - 202 - ); - - // actix - assert_eq!(actix_rt::Runtime::new().unwrap().block_on(test_fn()), 202); - - // async_std - assert_eq!(async_std::task::block_on(test_fn()), 202); -} - -async fn test_fn() -> u16 { - // Instead of creating a new MockServer using new(), we connect to an existing remote instance. - let server = MockServer::start_async().await; - - let search_mock = server - .mock_async(|when, then| { - when.path("/test"); - then.status(202); - }) - .await; - - // Act: Send the HTTP request - let response = get_async(server.url("/test")).await.unwrap(); - - // Assert - search_mock.assert_async().await; - assert_eq!(response.status(), 202); - - response.status().as_u16() -} diff --git a/tests/lib.rs b/tests/lib.rs index ba58a6ad..a531c9f6 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,31 +1,39 @@ -#[macro_use] extern crate lazy_static; -use std::sync::Mutex; -use std::thread::{spawn, JoinHandle}; - -use httpmock::standalone::start_standalone_server; +use httpmock::server::{HttpMockServer, HttpMockServerBuilder}; +use std::{sync::Mutex, thread}; use tokio::task::LocalSet; - mod examples; -mod internal; +mod matchers; +mod misc; -/// ==================================================================================== /// The rest of this file is only required to simulate that a standalone mock server is -/// running somewhere else. The tests above will is. -/// ==================================================================================== -pub fn simulate_standalone_server() { - let _unused = STANDALONE_SERVER.lock().unwrap_or_else(|e| e.into_inner()); -} +/// running somewhere else. +pub fn with_standalone_server() { + let disable_server = std::env::var("HTTPMOCK_TESTS_DISABLE_SIMULATED_STANDALONE_SERVER") + .unwrap_or_else(|_| "0".to_string()); -lazy_static! { - static ref STANDALONE_SERVER: Mutex>> = Mutex::new(spawn(|| { - let srv = - start_standalone_server(5000, false, None, false, usize::MAX, std::future::pending()); - let mut runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - LocalSet::new().block_on(&mut runtime, srv) - })); + if disable_server == "1" { + log::info!("Skipping creating a simulated mock server."); + return; + } + + let mut started = SERVER_STARTED.lock().unwrap(); + if !*started { + thread::spawn(move || { + let srv: HttpMockServer = HttpMockServerBuilder::new() + .port(5050) + .build() + .expect("cannot create mock server"); + + let mut runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + LocalSet::new().block_on(&mut runtime, srv.start()) + }); + } + *started = true } + +static SERVER_STARTED: Mutex = Mutex::new(false); diff --git a/tests/matchers/body.rs b/tests/matchers/body.rs new file mode 100644 index 00000000..176fd929 --- /dev/null +++ b/tests/matchers/body.rs @@ -0,0 +1,419 @@ +use crate::matchers::{expect_fails_with2, SingleValueMatcherDataSet}; +use httpmock::{MockServer, When}; + +#[test] +fn body() { + for (idx, data) in generate_data().attribute.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_fail_message() { + run_test( + "fail message format", + |when| when.body("test"), + "not-test", + Some(vec![ + "Expected body equals:", + "test", + "", + "Received:", + "not-test", + "", + "Diff:", + "---| test", + "+++| not-test", + ]), + ) +} + +#[test] +fn body_not() { + for (idx, data) in generate_data().attribute_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_not(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_not_fail_message() { + run_test( + "fail message format", + |when| when.body_not("test"), + "test", + Some(vec![ + "Expected body not equal to:", + "test", + "", + "Received:", + "test", + "", + "Diff:", + " | test", + "", + "Matcher: body_not", + ]), + ) +} + +#[test] +fn body_includes() { + for (idx, data) in generate_data().attribute_includes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_includes(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_includes_fail_message() { + run_test( + "fail message format", + |when| when.body_includes("x"), + "a\n. \n test \n abc \nline", + Some(vec![ + "Expected body includes:", + "x", + "", + "Received:", + "a", + ".", + " test", + " abc", + "line", + "", + "Diff:", + "---| x", + "+++| a", + "+++| .", + "+++| test", + "+++| abc", + "+++| line", + ]), + ) +} + +#[test] +fn body_includes_multiline() { + let expect = "\"onclick\": \"CreateDoc()\",\n \"value\": \"New\""; + let actual = r#" +{ + "menu": { + "id": "file", + "popup": { + "menuitem": [ + { + "onclick": "CreateDoc()", + "value": "New" + }, + { + "onclick": "OpenDoc()", + "value": "Open" + }, + { + "onclick": "SaveDoc()", + "value": "Save" + } + ] + }, + "value": "File" + } +} +"#; + + run_test( + "multi-line body", + |when| when.body_includes(expect), + actual, + None, + ); +} + +#[test] +fn body_excludes() { + for (idx, data) in generate_data().attribute_excludes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_excludes(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_excludes_fail_message() { + run_test( + "fail message format", + |when| when.body_excludes("test"), + "a\n. \n test \n abc \nline", + Some(vec![ + "Expected body excludes:", + "test", + "", + "Received:", + "a", + ".", + " test", + " abc", + "line", + "", + "Diff:", + "---| test", + "+++| a", + "+++| .", + "+++| test", + "+++| abc", + "+++| line", + ]), + ) +} + +#[test] +fn body_prefix() { + for (idx, data) in generate_data().attribute_prefix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_prefix(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_prefix_fail_message() { + run_test( + "fail message format", + |when| when.body_prefix("test"), + "a test", + Some(vec![ + "Expected body has prefix:", + "test", + "", + "Received:", + "a test", + "", + "Diff:", + "---| test", + "+++| a test", + "", + "Matcher: body_prefix", + ]), + ) +} + +#[test] +fn body_prefix_not() { + for (idx, data) in generate_data().attribute_prefix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_prefix_not(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_prefix_not_fail_message() { + run_test( + "fail message format", + |when| when.body_prefix_not("test"), + "test it is", + Some(vec![ + "Expected body prefix not:", + "test", + "", + "Received:", + "test it is", + "", + "Diff:", + "---| test", + "+++| test it is", + "", + "Matcher: body_prefix_not", + ]), + ) +} + +#[test] +fn body_suffix() { + for (idx, data) in generate_data().attribute_suffix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_suffix(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_suffix_fail_message() { + run_test( + "fail message format", + |when| when.body_suffix("test"), + "it is test not", + Some(vec![ + "Expected body has suffix:", + "test", + "", + "Received:", + "it is test not", + "", + "Diff:", + "---| test", + "+++| it is test not", + ]), + ) +} + +#[test] +fn body_suffix_not() { + for (idx, data) in generate_data().attribute_suffix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_suffix_not(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_suffix_not_fail_message() { + run_test( + "fail message format", + |when| when.body_suffix_not("test"), + "it is test", + Some(vec![ + "Expected body suffix not:", + "test", + "", + "Received:", + "it is test", + "", + "Diff:", + "---| test", + "+++| it is test", + ]), + ) +} + +#[test] +fn body_matches() { + for (idx, data) in generate_data().attribute_matches.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.body_matches(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn body_matches_fail_message() { + run_test( + "fail message format", + |when| when.body_matches("def"), + "abcghijklmn", + Some(vec![ + "Expected body matches regex:", + "def", + "", + "Received:", + "abcghijklmn", + "", + "Diff:", + "---| def", + "+++| abcghijklmn", + "", + "Matcher: body_matches", + ]), + ) +} + +fn generate_data() -> SingleValueMatcherDataSet<&'static str, &'static str> { + SingleValueMatcherDataSet::generate("body", "Body Mismatch", true) +} + +fn run_test( + name: S, + set_expectation: F, + actual: &'static str, + error_msg: Option>, +) where + F: Fn(When) -> When + std::panic::UnwindSafe + std::panic::RefUnwindSafe, + S: Into, +{ + println!("{}", name.into()); + + let run = || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + set_expectation(when); + then.status(200); + }); + + // Act + let response = reqwest::blocking::Client::new() + .get(server.url("/test")) + .body(actual) + .send() + .unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); + }; + + if let Some(err_msg) = error_msg { + expect_fails_with2(err_msg, run); + } else { + run(); + } +} diff --git a/tests/matchers/cookies.rs b/tests/matchers/cookies.rs new file mode 100644 index 00000000..ae1f4fc6 --- /dev/null +++ b/tests/matchers/cookies.rs @@ -0,0 +1,252 @@ +use crate::matchers::{expect_fails_with2, MultiValueMatcherTestSet}; +use http::{HeaderMap, HeaderValue}; +use httpmock::{MockServer, When}; + +#[test] +#[cfg(feature = "cookies")] +fn cookie() { + for (idx, data) in generate_data().attribute.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_not() { + for (idx, data) in generate_data().attribute_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_exists() { + for (idx, data) in generate_data().attribute_exists.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_exists(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_missing() { + for (idx, data) in generate_data().attribute_missing.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_missing(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_includes() { + for (idx, data) in generate_data().attribute_includes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_includes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_excludes() { + for (idx, data) in generate_data().attribute_excludes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_excludes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_prefix() { + for (idx, data) in generate_data().attribute_prefix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_prefix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_suffix() { + for (idx, data) in generate_data().attribute_suffix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_suffix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_prefix_not() { + for (idx, data) in generate_data().attribute_prefix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_prefix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_suffix_not() { + for (idx, data) in generate_data().attribute_suffix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_suffix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_matches() { + for (idx, data) in generate_data().attribute_matches.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_matches(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +#[cfg(feature = "cookies")] +fn cookie_count() { + for (idx, data) in generate_data().attribute_count.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.cookie_count(data.expect.0, data.expect.1, data.expect.2), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +fn generate_data() -> MultiValueMatcherTestSet<&'static str, &'static str, usize, &'static str> { + MultiValueMatcherTestSet::generate("cookie", "Cookie Mismatch", false) +} + +fn run_test( + name: S, + set_expectation: F, + actual: Vec<(&'static str, &'static str)>, + error_msg: Option>, +) where + F: Fn(When) -> When + std::panic::UnwindSafe + std::panic::RefUnwindSafe, + S: Into, +{ + println!("{}", name.into()); + + let run = || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + set_expectation(when); + then.status(200); + }); + + // Act + let mut content = Vec::new(); + for (key, value) in actual { + if value.contains(' ') || value.contains(';') || value.contains(',') { + content.push(format!("{}={}", key, value.replace('"', "\\\""))) + } else { + content.push(format!("{}={}", key, value)) + } + } + + let value = content.join(";"); + + let mut headers = HeaderMap::new(); + headers.insert("cookie", HeaderValue::from_str(&value).unwrap()); + + let response = reqwest::blocking::Client::new() + .get(server.url("/test")) + .headers(headers) + .send() + .unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); + }; + + if let Some(err_msg) = error_msg { + expect_fails_with2(err_msg, run); + } else { + run(); + } +} diff --git a/tests/matchers/headers.rs b/tests/matchers/headers.rs new file mode 100644 index 00000000..412817ac --- /dev/null +++ b/tests/matchers/headers.rs @@ -0,0 +1,229 @@ +use crate::matchers::{expect_fails_with2, MultiValueMatcherTestSet}; +use httpmock::{MockServer, When}; + +#[test] +fn header() { + for (idx, data) in generate_data().attribute.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_not() { + for (idx, data) in generate_data().attribute_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_exists() { + for (idx, data) in generate_data().attribute_exists.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_exists(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_missing() { + for (idx, data) in generate_data().attribute_missing.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_missing(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_includes() { + for (idx, data) in generate_data().attribute_includes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_includes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_excludes() { + for (idx, data) in generate_data().attribute_excludes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_excludes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_prefix() { + for (idx, data) in generate_data().attribute_prefix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_prefix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_suffix() { + for (idx, data) in generate_data().attribute_suffix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_suffix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_prefix_not() { + for (idx, data) in generate_data().attribute_prefix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_prefix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_suffix_not() { + for (idx, data) in generate_data().attribute_suffix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_suffix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_matches() { + for (idx, data) in generate_data().attribute_matches.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_matches(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn header_count() { + for (idx, data) in generate_data().attribute_count.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.header_count(data.expect.0, data.expect.1, data.expect.2), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +fn generate_data() -> MultiValueMatcherTestSet<&'static str, &'static str, usize, &'static str> { + MultiValueMatcherTestSet::generate("header", "Header Mismatch", false) +} + +fn run_test( + name: S, + set_expectation: F, + actual: Vec<(&'static str, &'static str)>, + error_msg: Option>, +) where + F: Fn(When) -> When + std::panic::UnwindSafe + std::panic::RefUnwindSafe, + S: Into, +{ + println!("{}", name.into()); + + let run = || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + set_expectation(when); + then.status(200); + }); + + // Act + // Build the request with custom headers + let client = reqwest::blocking::Client::new(); + let mut request_builder = client.get(&server.url("/test")); + + for (key, value) in actual { + request_builder = request_builder.header(key, value); + } + + let response = request_builder.send().unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); + }; + + if let Some(err_msg) = error_msg { + expect_fails_with2(err_msg, run); + } else { + run(); + } +} diff --git a/tests/matchers/host.rs b/tests/matchers/host.rs new file mode 100644 index 00000000..cd22ee34 --- /dev/null +++ b/tests/matchers/host.rs @@ -0,0 +1,147 @@ +use crate::matchers::expect_fails_with2; +use httpmock::{MockServer, When}; + +#[cfg(feature = "proxy")] +#[test] +fn path_success_table_test() { + struct TestData { + expectation: fn(when: When) -> When, + } + + let tests = vec![ + TestData { + expectation: |when| when.host("127.0.0.1"), + }, + TestData { + expectation: |when| when.host("localhost"), + }, + TestData { + expectation: |when| when.host("LOCALHOST"), + }, + TestData { + expectation: |when| when.host_not("127.0.0.2"), + }, + TestData { + expectation: |when| when.host_includes("7.0.0"), + }, + TestData { + expectation: |when| when.host_excludes("28.0.0"), + }, + TestData { + expectation: |when| when.host_prefix("127"), + }, + TestData { + expectation: |when| when.host_prefix_not("128"), + }, + TestData { + expectation: |when| when.host_suffix(".0.1"), + }, + TestData { + expectation: |when| when.host_suffix_not("0.0.2"), + }, + TestData { + expectation: |when| when.host_matches(".*27.*"), + }, + ]; + + for (idx, test_data) in tests.iter().enumerate() { + println!("Running test case with index '{}'", idx); + + let target_server = MockServer::start(); + target_server.mock(|when, then| { + when.any_request(); + then.status(200); + }); + + let proxy_server = MockServer::start(); + + proxy_server.proxy(|rule| { + rule.filter(|when| { + (test_data.expectation)(when).port(target_server.port()); + }); + }); + + let client = reqwest::blocking::Client::builder() + .proxy(reqwest::Proxy::all(proxy_server.base_url()).unwrap()) + .build() + .unwrap(); + + let response = client.get(&target_server.url("/get")).send().unwrap(); + assert_eq!(response.status(), 200); + } +} + +#[cfg(feature = "proxy")] +#[test] +fn path_failure_table_test() { + pub struct TestData { + expectation: fn(when: When) -> When, + failure_message: Vec<&'static str>, + } + + let tests = vec![ + TestData { + expectation: |when| when.host("127.0.0.2"), + failure_message: vec!["No request has been received by the mock server"], + }, + TestData { + expectation: |when| when.host_not("127.0.0.1"), + failure_message: vec!["No request has been received by the mock server"], + }, + TestData { + expectation: |when| when.host_includes("192"), + failure_message: vec!["No request has been received by the mock server"], + }, + TestData { + expectation: |when| when.host_excludes("127"), + failure_message: vec!["No request has been received by the mock server"], + }, + TestData { + expectation: |when| when.host_prefix("192"), + failure_message: vec!["No request has been received by the mock server"], + }, + TestData { + expectation: |when| when.host_prefix_not("127"), + failure_message: vec!["No request has been received by the mock server"], + }, + TestData { + expectation: |when| when.host_suffix("2"), + failure_message: vec!["No request has been received by the mock server"], + }, + TestData { + expectation: |when| when.host_suffix_not("1"), + failure_message: vec!["No request has been received by the mock server"], + }, + ]; + + for (idx, test_data) in tests.iter().enumerate() { + println!("Running test case with index '{}'", idx); + + let err_msg = test_data.failure_message.clone(); + + expect_fails_with2(err_msg, || { + let target_server = MockServer::start(); + let m = target_server.mock(|when, then| { + when.any_request(); + then.status(200); + }); + + let proxy_server = MockServer::start(); + proxy_server.proxy(|rule| { + rule.filter(|when| { + (test_data.expectation)(when).port(target_server.port()); + }); + }); + + let client = reqwest::blocking::Client::builder() + .proxy(reqwest::Proxy::all(proxy_server.base_url()).unwrap()) + .build() + .unwrap(); + + let response = client.get(&target_server.url("/get")).send().unwrap(); + assert_eq!(404, response.status()); + + m.assert(); + }); + } +} diff --git a/tests/matchers/method.rs b/tests/matchers/method.rs new file mode 100644 index 00000000..40827598 --- /dev/null +++ b/tests/matchers/method.rs @@ -0,0 +1,96 @@ +use crate::matchers::expect_fails_with; +use httpmock::{ + Method::{GET, POST}, + MockServer, +}; +use reqwest::blocking::get; + +#[test] +fn success_method() { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.method(GET); + then.status(200); + }); + + // Act + let response = get(&server.base_url()).unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); +} + +#[test] +fn failure_method() { + expect_fails_with( + || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.method(POST); + then.status(200); + }); + + // Act + get(&server.base_url()).unwrap(); + + m.assert() + }, + vec![ + "Method Mismatch", + "Expected method equals", + "POST", + "Received", + "GET", + ], + ) +} + +#[test] +fn success_method_not() { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.method_not(POST); + then.status(200); + }); + + // Act + let response = get(&server.base_url()).unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); +} + +#[test] +fn failure_method_not() { + expect_fails_with( + || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.method_not(GET); + then.status(200); + }); + + // Act + get(&server.base_url()).unwrap(); + + m.assert() + }, + vec![ + "Method Mismatch", + "Expected method not equal to", + "GET", + "Received", + "GET", + ], + ) +} diff --git a/tests/matchers/mod.rs b/tests/matchers/mod.rs new file mode 100644 index 00000000..68cc5159 --- /dev/null +++ b/tests/matchers/mod.rs @@ -0,0 +1,775 @@ +mod body; +mod cookies; +mod headers; +mod host; +mod method; +mod path; +mod port; +mod query_param; +mod scheme; +mod urlencoded_body; + +use std::{ + convert::TryInto, + panic::{self, AssertUnwindSafe, UnwindSafe}, +}; + +pub fn expect_fails_with(f: F, expected_texts: Vec<&str>) +where + F: FnOnce() + UnwindSafe, +{ + let result = panic::catch_unwind(AssertUnwindSafe(f)); + + match result { + Err(err) => { + let err_msg: &str = if let Some(err_msg) = err.downcast_ref::() { + err_msg + } else if let Some(err_msg) = err.downcast_ref::<&str>() { + err_msg + } else { + panic!( + "Expected error message containing:\n{:?}\nBut got a different type of panic.", + expected_texts + ); + }; + + // Check that all expected texts appear in order in the error message + let mut start_index = 0; + for expected_text in &expected_texts { + if let Some(index) = err_msg[start_index..].find(expected_text) { + start_index += index + expected_text.len(); + } else { + panic!( + "Expected error message to contain in order:\n{:?}\nBut got:\n{}", + expected_texts, err_msg + ); + } + } + } + _ => panic!( + "Expected panic with error message containing in order:\n{:?}", + expected_texts + ), + } +} + +pub fn expect_fails_with2(expected_texts: V, f: F) +where + F: FnOnce() + panic::UnwindSafe, + V: Into>, + S: ToString, +{ + // Convert expected texts into a Vec + let expected_texts: Vec = expected_texts + .into() + .into_iter() + .map(|s| s.to_string()) + .collect(); + + // Suppress panic output for this invocation + let default_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| {})); + + // Catch panic and unwind safely + let result = panic::catch_unwind(AssertUnwindSafe(f)); + + // Restore the default panic hook + panic::set_hook(default_hook); + + match result { + Err(err) => { + // Extract the error message from the panic + let err_msg: &str = if let Some(err_msg) = err.downcast_ref::() { + err_msg + } else if let Some(err_msg) = err.downcast_ref::<&str>() { + err_msg + } else { + panic!( + "Expected error message containing:\n{:?}\nBut got a different type of panic.", + expected_texts + ); + }; + + // Check that all expected texts appear in order in the error message + let mut start_index = 0; + for expected_text in &expected_texts { + if let Some(index) = err_msg[start_index..].find(expected_text) { + start_index += index + expected_text.len(); + } else { + panic!( + "Expected error message to contain in order:\n{:?}\nBut got:\n{}", + expected_texts, err_msg + ); + } + } + } + Ok(_) => panic!( + "Expected panic with error message containing in order:\n{:?}", + expected_texts + ), + } +} + +enum SubstringPart { + Prefix, + Mid, + Suffix, +} + +fn substring_of(s: S, part: SubstringPart) -> String { + let s = s.to_string(); + + let len = s.len(); + let half_length = len / 2; + + match part { + SubstringPart::Prefix => (&s[..half_length]).to_string(), + SubstringPart::Mid => { + let start = (len - half_length) / 2; + (&s[start..start + half_length]).to_string() + } + SubstringPart::Suffix => (&s[len - half_length..]).to_string(), + } +} + +fn inverse_char(c: char) -> char { + match c { + 'a'..='z' => (b'z' - (c as u8 - b'a')) as char, + 'A'..='Z' => (b'Z' - (c as u8 - b'A')) as char, + '0'..='9' => (b'9' - (c as u8 - b'0')) as char, + _ => c, + } +} + +fn string_inverse(s: S) -> String { + s.to_string().chars().map(inverse_char).collect() +} + +#[derive(Debug)] +pub struct MultiValueMatcherData +where + K: Into, + V: Into, + M: Into, +{ + scenario_name: String, + expect: ExpectedValue, + actual: Vec<(K, V)>, + failure_msg: Option>, +} + +#[derive(Debug)] +pub struct MultiValueMatcherTestSet +where + K: Into, + V: Into, + C: TryInto, + M: Into, +{ + attribute: Vec>, + attribute_not: Vec>, + attribute_exists: Vec>, + attribute_missing: Vec>, + attribute_includes: Vec>, + attribute_excludes: Vec>, + attribute_prefix: Vec>, + attribute_suffix: Vec>, + attribute_prefix_not: Vec>, + attribute_suffix_not: Vec>, + attribute_matches: Vec>, + attribute_count: Vec>, +} + +impl MultiValueMatcherTestSet<&'static str, &'static str, usize, &'static str> { + pub fn generate( + entity: &'static str, + mismatch_header: &'static str, + case_sensitive: bool, + ) -> Self { + return MultiValueMatcherTestSet { + attribute: vec![ + MultiValueMatcherData { + scenario_name: format!("{} where 'word' equals 'hello'", entity), + expect: ("word", "hello"), + actual: vec![("lang", "en"), ("word", "hello"), ("short", "hi")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{} with multiple keys in the request not matching", entity), + expect: ("word", "hello world"), + actual: vec![ + ("lang", "en"), + ("weird", "hello world"), + ("short", "hi"), + ("word", "hallo welt"), + ], + failure_msg: Some(vec![ + mismatch_header, + "key", + "equals", + "word", + "value", + "equals", + "hello world", + entity, + ]), + }], + attribute_not: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_not where 'word' does not equal 'hello'", entity), + expect: ("word", "hello"), + actual: vec![("word", "hallo")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_not where 'word' is empty", entity), + expect: ("word", "hello"), + actual: vec![("word", "")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_not where 'word' does not exactly equal 'hello'", entity), + expect: ("word", "hello"), + actual: vec![("word", "hello world")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_not with correct value but missing key", entity), + expect: ("hello", "world"), + actual: vec![("wrong_key", "world")], + failure_msg: Some(vec![ + mismatch_header, + "Expected", + "key", + "equals", + "hello", + "value", + "not equal to", + "world", + "Received", + "wrong_key=world", + entity, + "_not", + ]), + }, + MultiValueMatcherData { + scenario_name: format!("{}_not with one key non-matching key", entity), + expect: ("hello", "world"), + actual: vec![("hello", "world")], + failure_msg: Some(vec![ + mismatch_header, + "Expected", + "key", + "equals", + "hello", + "value", + "not equal to", + "world", + "Received", + "hello=world", + entity, + "_not", + ]), + }, + MultiValueMatcherData { + scenario_name: format!("{}_not where 'word' key should not match 'hello' but is not present", entity), + expect: ("word", "hello"), + actual: vec![("not_word", "hello world")], + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "value", "not equal to", "hello", + "Received", "not_word=hello world", + entity, + "_not", + ]), + }, + ], + attribute_exists: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_exists where 'word' is present with value", entity), + expect: "word", + actual: vec![("word", "hello")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_exists where 'word' is present without value", entity), + expect: "word", + actual: vec![("word", "")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_exists where parameter should be present but is missing", entity), + expect: "word", + actual: vec![("wald", "word"), ("world", "hello")], + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "to be in the request, but none was provided", + entity, + "_exists", + ]), + }, + ], + attribute_missing: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_missing where 'word' is absent", entity), + expect: "word", + actual: vec![("something", "different")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_missing where parameter 'word' should not be present but is found", entity), + expect: "word", + actual: vec![("welt", "different"), ("word", "")], // Capturing 'word' as empty + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "not equal to", "word", + "not to be present, but the request contained it", + entity, + "_missing", + ]), + }, + ], + attribute_includes: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_includes where 'word' includes 'ello'", entity), + expect: ("word", "ello"), + actual: vec![("word", "hello")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_includes where 'word' value should include 'ello'", entity), + expect: ("word", "ello"), + actual: vec![("word", "world")], // Actual value that fails to meet the expectation + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "value", "includes", "ello", + "Received", "word=world", + entity, + "_includes", + ]), + }, + ], + attribute_excludes: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_excludes where 'word' excludes 'ello'", entity), + expect: ("word", "ello"), + actual: vec![("word", "hallo")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_excludes where 'word' value should exclude 'ello'", entity), + expect: ("word", "ello"), + actual: vec![("word", "hello")], + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "value", "excludes", "ello", + "Received", "word=hello", + entity, + "_excludes", + ]), + }, + ], + attribute_prefix: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_prefix where 'word' starts with 'ha'", entity), + expect: ("word", "ha"), + actual: vec![("word", "hallo")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_prefix where 'word' value should start with 'ha'", entity), + expect: ("word", "ha"), + actual: vec![("word", "hello")], // Actual value that correctly matches the prefix condition + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "value", "prefix", "ha", + "Received", "word=hello", + entity, + "_prefix", + ]), + }, + ], + attribute_suffix: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_suffix where 'word' ends with 'llo'", entity), + expect: ("word", "llo"), + actual: vec![("word", "hello")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_suffix where 'word' value should end with 'llo'", entity), + expect: ("word", "llo"), + actual: vec![("word", "world")], // Actual value that fails to meet the suffix condition + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "value", "suffix", "llo", + "Received", "word=world", + entity, + "_suffix", + ]), + }, + ], + attribute_prefix_not: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_prefix_not where 'word' does not start with 'ha'", entity), + expect: ("word", "ha"), + actual: vec![("word", "hello")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_prefix_not where 'word' value should not start with 'ha'", entity), + expect: ("word", "ha"), + actual: vec![("word", "hallo")], // Actual value that incorrectly matches the prefix condition + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "value", "prefix not", "ha", + "Received", "word=hallo", + entity, + "_prefix_not", + ]), + }, + ], + attribute_suffix_not: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_suffix_not where 'word' does not end with 'll'", entity), + expect: ("word", "ll"), + actual: vec![("word", "hallo")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_suffix_not where 'word' value should not end with 'ld'", entity), + expect: ("word", "ld"), + actual: vec![("word", "world")], // Actual value that incorrectly matches the suffix condition + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "equals", "word", + "value", "suffix not", "ld", + "Received", "word=world", + entity, + "_suffix_not", + ]), + }, + ], + attribute_matches: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_matches where key matches '.*ll.*' and value matches '.*or.*'", entity), + expect: (".*ll.*", ".*or.*"), + actual: vec![("hello", "world")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_matches where key and value should match regex patterns", entity), + expect: (".*ll.*", ".*or.*"), + actual: vec![("hello", "peter")], // Actual key-value that fails to match the expected regex patterns fully + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "matches regex", ".*ll.*", + "value", "matches regex", ".*or.*", + "Received", "hello=peter", + entity, + "_matches", + ]), + }, + MultiValueMatcherData { + scenario_name: format!("{}_matches where key and value should match regex patterns again", entity), + expect: (".*ll.*", ".*or.*"), + actual: vec![("peter", "world")], // Actual key-value that fails both expected regex conditions + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "matches regex", ".*ll.*", + "value", "matches regex", ".*or.*", + "Received", "peter=world", + entity, + "_matches", + ]), + }, + ], + attribute_count: vec![ + MultiValueMatcherData { + scenario_name: format!("{}_count where key matches '.*el.*' and value matches '.*al.*' appears 2 times", entity), + expect: (".*el.*", ".*al.*", 2), + actual: vec![("hello", "peter"), ("hello", "wallie"), ("nothing", ""), ("hello", ""), ("hello", "metallica")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_count where key matches '.*el.*' and value matches '.*al.*' appears 2 times", entity), + expect: (".*el.*", ".*al.*", 2), + actual: vec![("hello", "peter"), ("hello", "wallie"), ("nothing", ""), ("hello", ""), ("hello", "metallica")], + failure_msg: None, + }, + MultiValueMatcherData { + scenario_name: format!("{}_count where parameters should match key and value regex and appear a specified number of times", entity), + expect: (".*ll.*", ".*", 10), + actual: vec![("hello", "peter"), ("hello", "wallie"), ("nothing", ""), ("hello", ""), ("hello", "metallica")], + failure_msg: Some(vec![ + mismatch_header, + "Expected", "key", "matches regex", ".*ll.*", + "value", "matches regex", ".*", + "to appear 10 times but appeared 4", + entity, + "_count", + ]), + }, + ], + }; + } +} + +#[derive(Debug)] +pub struct SingleValueMatcherData +where + V: Into, + M: Into, +{ + scenario_name: String, + expect: ExpectedValue, + actual: V, + failure_msg: Option>, +} + +#[derive(Debug)] +pub struct SingleValueMatcherDataSet +where + V: Into, + M: Into, +{ + attribute: Vec>, + attribute_not: Vec>, + attribute_includes: Vec>, + attribute_excludes: Vec>, + attribute_prefix: Vec>, + attribute_suffix: Vec>, + attribute_prefix_not: Vec>, + attribute_suffix_not: Vec>, + attribute_matches: Vec>, +} + +impl SingleValueMatcherDataSet<&'static str, &'static str> { + pub fn generate( + entity: &'static str, + mismatch_header: &'static str, + case_sensitive: bool, + ) -> Self { + return SingleValueMatcherDataSet { + attribute: vec![ + SingleValueMatcherData { + scenario_name: format!("{} TODO", entity), + expect: "test", + actual: "test", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{} TODO", entity), + expect: "test", + actual: "not-test", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "equals", + "test", + "Received", + "not-test", + ]), + }, + ], + attribute_not: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_not TODO", entity), + expect: "test", + actual: "twist", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_not TODO", entity), + expect: "test", + actual: "test", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "not equal to", + "test", + "Received", + "test", + ]), + }, + ], + attribute_includes: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_includes TODO", entity), + expect: "is-a", + actual: "this-is-a-value", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_includes TODO", entity), + expect: "dog", + actual: "tomato", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "includes", + "dog", + "Received", + "tomato", + ]), + }, + ], + attribute_excludes: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_excludes TODO", entity), + expect: "is-a", + actual: "this-is-the-value", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_excludes TODO", entity), + expect: "na", + actual: "banana", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "excludes", + "na", + "Received", + "banana", + ]), + }, + ], + attribute_prefix: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_prefix TODO", entity), + expect: "this", + actual: "this-is-the-value", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_prefix TODO", entity), + expect: "thi", + actual: "that", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "has prefix", + "thi", + "Received", + "that", + ]), + }, + ], + attribute_suffix: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_includes TODO", entity), + expect: "value", + actual: "this-is-the-value", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_includes TODO", entity), + expect: "bear", + actual: "banana", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "suffix", + "bear", + "Received", + "banana", + ]), + }, + ], + attribute_prefix_not: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_prefix_not TODO", entity), + expect: "value", + actual: "that-is-the-value", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_prefix_not TODO", entity), + expect: "this", + actual: "this-is-the-value", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "prefix not", + "this", + "Received", + "this-is-the-value", + ]), + }, + ], + attribute_suffix_not: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_suffix_not TODO", entity), + expect: "thing", + actual: "that-is-the-value", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_suffix_not TODO", entity), + expect: "to", + actual: "potato", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "suffix not", + "to", + "Received", + "potato", + ]), + }, + ], + attribute_matches: vec![ + SingleValueMatcherData { + scenario_name: format!("{}_matches TODO", entity), + expect: ".*ll.*", + actual: "hello", + failure_msg: None, + }, + SingleValueMatcherData { + scenario_name: format!("{}_matches TODO", entity), + expect: ".*is-the.*", + actual: "giggity", + failure_msg: Some(vec![ + mismatch_header, + "Expected", + entity, + "matches", + ".*is-the.*", + "Received", + "giggity", + ]), + }, + ], + }; + } +} + +fn to_urlencoded_query_string(params: Vec<(&str, &str)>) -> String { + params + .into_iter() + .map(|(key, value)| { + format!( + "{}={}", + urlencoding::encode(key), + urlencoding::encode(value) + ) + }) + .collect::>() + .join("&") +} diff --git a/tests/matchers/path.rs b/tests/matchers/path.rs new file mode 100644 index 00000000..069fa85f --- /dev/null +++ b/tests/matchers/path.rs @@ -0,0 +1,179 @@ +use crate::matchers::{expect_fails_with2, SingleValueMatcherDataSet}; +use httpmock::{MockServer, When}; + +#[test] +fn path() { + for (idx, data) in generate_data().attribute.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path(format!("/{}", data.expect)), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_not() { + for (idx, data) in generate_data().attribute_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_not(format!("/{}", data.expect)), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_includes() { + for (idx, data) in generate_data().attribute_includes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_includes(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_excludes() { + for (idx, data) in generate_data().attribute_excludes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_excludes(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_prefix() { + for (idx, data) in generate_data().attribute_prefix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_prefix(format!("/{}", data.expect)), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_prefix_not() { + for (idx, data) in generate_data().attribute_prefix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_prefix_not(format!("/{}", data.expect)), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_suffix() { + for (idx, data) in generate_data().attribute_suffix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_suffix(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_suffix_not() { + for (idx, data) in generate_data().attribute_suffix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_suffix_not(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +#[test] +fn path_matches() { + for (idx, data) in generate_data().attribute_matches.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.path_matches(data.expect), + data.actual, + data.failure_msg.clone(), + ) + } +} + +fn generate_data() -> SingleValueMatcherDataSet<&'static str, &'static str> { + SingleValueMatcherDataSet::generate("path", "Path Mismatch", true) +} + +fn run_test( + name: S, + set_expectation: F, + actual: &'static str, + error_msg: Option>, +) where + F: Fn(When) -> When + std::panic::UnwindSafe + std::panic::RefUnwindSafe, + S: Into, +{ + println!("{}", name.into()); + + let run = || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + set_expectation(when); + then.status(200); + }); + + // Act + let response = reqwest::blocking::Client::new() + .get(server.url(format!("/{}", actual))) + .send() + .unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); + }; + + if let Some(err_msg) = error_msg { + expect_fails_with2(err_msg, run); + } else { + run(); + } +} diff --git a/tests/matchers/port.rs b/tests/matchers/port.rs new file mode 100644 index 00000000..7a2c6e50 --- /dev/null +++ b/tests/matchers/port.rs @@ -0,0 +1,113 @@ +use crate::matchers::expect_fails_with; +use httpmock::MockServer; +use reqwest::blocking::get; + +#[test] +fn host_tests() { + // Arrange + let server = MockServer::start(); + let port = server.port(); + + let m = server.mock(|when, then| { + when.port(port); + then.status(200); + }); + + // Act + let response = get(&server.base_url()).unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); +} + +#[test] +fn host_failure() { + expect_fails_with( + || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.port(0); // explicitly matching against port 0 will always fail + then.status(200); + }); + + // Act + get(&server.base_url()).unwrap(); + + m.assert() + }, + vec!["Port Mismatch", "Expected port equals", "0", "Received"], + ) +} + +#[test] +fn host_not_success_name() { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.port_not(0); // Port is never 0, so this will always match. + then.status(200); + }); + + // Act + let response = get(&server.base_url()).unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); +} + +#[test] +fn host_not_failure() { + expect_fails_with( + || { + // Arrange + let server = MockServer::start(); + let port = server.port(); + + let m = server.mock(|when, then| { + when.host_not("127.0.0.1"); + then.status(200); + }); + + // Act + get(&server.base_url()).unwrap(); + + m.assert() + }, + vec![ + "Host Mismatch", + "Expected host not equal to", + "127.0.0.1", + "Received", + "127.0.0.1", + ], + ); + + expect_fails_with( + || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.host_not("localhost"); + then.status(200); + }); + + // Act + get(&server.base_url()).unwrap(); + + m.assert() + }, + vec![ + "Host Mismatch", + "Expected host not equal to", + "localhost", + "Received", + "127.0.0.1", + ], + ); +} diff --git a/tests/matchers/query_param.rs b/tests/matchers/query_param.rs new file mode 100644 index 00000000..34eee150 --- /dev/null +++ b/tests/matchers/query_param.rs @@ -0,0 +1,222 @@ +use crate::matchers::{expect_fails_with2, to_urlencoded_query_string, MultiValueMatcherTestSet}; +use httpmock::{MockServer, When}; + +#[test] +fn query_param() { + for (idx, data) in generate_data().attribute.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_not() { + for (idx, data) in generate_data().attribute_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_exists() { + for (idx, data) in generate_data().attribute_exists.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_exists(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_missing() { + for (idx, data) in generate_data().attribute_missing.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_missing(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_includes() { + for (idx, data) in generate_data().attribute_includes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_includes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_excludes() { + for (idx, data) in generate_data().attribute_excludes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_excludes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_prefix() { + for (idx, data) in generate_data().attribute_prefix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_prefix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_suffix() { + for (idx, data) in generate_data().attribute_suffix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_suffix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_prefix_not() { + for (idx, data) in generate_data().attribute_prefix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_prefix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_suffix_not() { + for (idx, data) in generate_data().attribute_suffix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_suffix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_matches() { + for (idx, data) in generate_data().attribute_matches.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_matches(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn query_param_count() { + for (idx, data) in generate_data().attribute_count.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.query_param_count(data.expect.0, data.expect.1, data.expect.2), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +fn generate_data() -> MultiValueMatcherTestSet<&'static str, &'static str, usize, &'static str> { + MultiValueMatcherTestSet::generate("query_param", "Query Parameter Mismatch", false) +} + +fn run_test( + name: S, + set_expectation: F, + actual: Vec<(&'static str, &'static str)>, + error_msg: Option>, +) where + F: Fn(When) -> When + std::panic::UnwindSafe + std::panic::RefUnwindSafe, + S: Into, +{ + println!("{}", name.into()); + + let run = || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + set_expectation(when); + then.status(200); + }); + + // Act + let url = server.url(&format!("/test?{}", to_urlencoded_query_string(actual))); + let response = reqwest::blocking::get(&url).unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); + }; + + if let Some(err_msg) = error_msg { + expect_fails_with2(err_msg, run); + } else { + run(); + } +} diff --git a/tests/matchers/scheme.rs b/tests/matchers/scheme.rs new file mode 100644 index 00000000..4d974b2c --- /dev/null +++ b/tests/matchers/scheme.rs @@ -0,0 +1,95 @@ +use crate::matchers::expect_fails_with; +use httpmock::MockServer; +use reqwest::blocking::get; + +#[test] +fn scheme_tests() { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.scheme("http"); + then.status(200); + }); + + // Act + let response = get(&server.base_url()).unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); +} + +#[test] +fn scheme_failure() { + expect_fails_with( + || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.scheme("https"); + then.status(200); + }); + + // Act + get(&server.base_url()).unwrap(); + + m.assert() + }, + vec![ + "Scheme Mismatch", + "Expected", + "scheme equals", + "https", + "Received", + "http", + ], + ) +} + +#[test] +fn scheme_not_tests() { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.scheme_not("https"); + then.status(200); + }); + + // Act + let response = get(&server.base_url()).unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); +} + +#[test] +fn scheme_not_failure() { + expect_fails_with( + || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + when.scheme_not("http"); + then.status(200); + }); + + // Act + get(&server.base_url()).unwrap(); + + m.assert() + }, + vec![ + "Scheme Mismatch", + "Expected", + "scheme not equal to", + "http", + "Received", + "http", + ], + ) +} diff --git a/tests/matchers/urlencoded_body.rs b/tests/matchers/urlencoded_body.rs new file mode 100644 index 00000000..0316b8bc --- /dev/null +++ b/tests/matchers/urlencoded_body.rs @@ -0,0 +1,244 @@ +use crate::matchers::{expect_fails_with2, MultiValueMatcherTestSet}; +use httpmock::{MockServer, When}; + +#[test] +fn form_urlencoded_tuple() { + for (idx, data) in generate_data().attribute.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] +fn form_urlencoded_tuple_not() { + for (idx, data) in generate_data().attribute_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_exists() { + for (idx, data) in generate_data().attribute_exists.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_exists(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_missing() { + for (idx, data) in generate_data().attribute_missing.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_missing(data.expect), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_includes() { + for (idx, data) in generate_data().attribute_includes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_includes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_excludes() { + for (idx, data) in generate_data().attribute_excludes.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_excludes(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_prefix() { + for (idx, data) in generate_data().attribute_prefix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_prefix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_suffix() { + for (idx, data) in generate_data().attribute_suffix.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_suffix(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_prefix_not() { + for (idx, data) in generate_data().attribute_prefix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_prefix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_suffix_not() { + for (idx, data) in generate_data().attribute_suffix_not.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_suffix_not(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_matches() { + for (idx, data) in generate_data().attribute_matches.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_matches(data.expect.0, data.expect.1), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +#[test] + +fn form_urlencoded_tuple_count() { + for (idx, data) in generate_data().attribute_count.iter().enumerate() { + run_test( + format!( + "Running test case with index '{}' and test data: {:?}", + idx, data + ), + |when| when.form_urlencoded_tuple_count(data.expect.0, data.expect.1, data.expect.2), + data.actual.clone(), + data.failure_msg.clone(), + ) + } +} + +fn generate_data() -> MultiValueMatcherTestSet<&'static str, &'static str, usize, &'static str> { + MultiValueMatcherTestSet::generate( + "form_urlencoded_tuple", + "Form-urlencoded Body Mismatch", + false, + ) +} + +fn run_test( + name: S, + set_expectation: F, + actual: Vec<(&'static str, &'static str)>, + error_msg: Option>, +) where + F: Fn(When) -> When + std::panic::UnwindSafe + std::panic::RefUnwindSafe, + S: Into, +{ + println!("{}", name.into()); + + let run = || { + // Arrange + let server = MockServer::start(); + + let m = server.mock(|when, then| { + set_expectation(when); + then.status(200); + }); + + // Act + let mut params = form_urlencoded::Serializer::new(String::new()); + for (key, value) in actual { + params.append_pair(key, value); + } + + let response = reqwest::blocking::Client::new() + .post(server.url("/test")) + .body(params.finish()) + .send() + .unwrap(); + + // Assert + m.assert(); + assert_eq!(response.status(), 200); + }; + + if let Some(err_msg) = error_msg { + expect_fails_with2(err_msg, run); + } else { + run(); + } +} diff --git a/tests/internal/extensions_test.rs b/tests/misc/extensions_test.rs similarity index 92% rename from tests/internal/extensions_test.rs rename to tests/misc/extensions_test.rs index 286b9956..aa3c2ae5 100644 --- a/tests/internal/extensions_test.rs +++ b/tests/misc/extensions_test.rs @@ -1,7 +1,6 @@ extern crate httpmock; -use self::httpmock::prelude::*; -use self::httpmock::Mock; +use self::httpmock::{prelude::*, Mock}; use std::cell::RefCell; // Test for issue https://github.com/alexliesenfeld/httpmock/issues/26 @@ -33,5 +32,5 @@ fn wrapper_test() { drop(mock); let mock = Mock::new(sw.mocks.borrow_mut().get(0).unwrap().id, &sw.server); - mock.hits(); + mock.calls(); } diff --git a/tests/internal/large_body_test.rs b/tests/misc/large_body_test.rs similarity index 52% rename from tests/internal/large_body_test.rs rename to tests/misc/large_body_test.rs index bc0ed181..31330231 100644 --- a/tests/internal/large_body_test.rs +++ b/tests/misc/large_body_test.rs @@ -1,27 +1,29 @@ use httpmock::prelude::*; -use isahc::{Request, RequestExt}; +use reqwest::blocking::Client; -use crate::simulate_standalone_server; +use crate::with_standalone_server; #[test] fn large_body_test() { // Arrange - // This starts up a standalone server in the background running on port 5000 - simulate_standalone_server(); + // This starts up a standalone server in the background running on port 5050 + with_standalone_server(); // Instead of creating a new MockServer using new(), we connect to an existing remote instance. - let server = MockServer::connect("localhost:5000"); + let server = MockServer::connect("localhost:5050"); let search_mock = server.mock(|when, then| { - when.path("/search").body("wow so large".repeat(1000000)); // ~12 MB body + when.path("/search") + .body("wow so large".repeat(1024 * 1024 * 10)); // 10 MB body then.status(202); }); // Act: Send the HTTP request - let response = Request::post(server.url("/search")) - .body("wow so large".repeat(1000000)) // ~12 MB body - .unwrap() + let client = Client::new(); + let response = client + .post(&server.url("/search")) + .body("wow so large".repeat(1024 * 1024 * 10)) // 10 MB body .send() .unwrap(); diff --git a/tests/internal/loop_test.rs b/tests/misc/loop_test.rs similarity index 72% rename from tests/internal/loop_test.rs rename to tests/misc/loop_test.rs index 92d71d8a..d9d41d78 100644 --- a/tests/internal/loop_test.rs +++ b/tests/misc/loop_test.rs @@ -1,21 +1,21 @@ extern crate httpmock; use httpmock::prelude::*; -use isahc::get; +use reqwest::blocking::get; #[cfg(feature = "remote")] -use crate::simulate_standalone_server; +use crate::with_standalone_server; #[cfg(feature = "remote")] #[test] fn loop_with_standalone_test() { // Arrange - // This starts up a standalone server in the background running on port 5000 - simulate_standalone_server(); + // This starts up a standalone server in the background running on port 5050 + with_standalone_server(); // Instead of creating a new MockServer using new(), we connect to an existing remote instance. - let server = MockServer::connect("localhost:5000"); + let server = MockServer::connect("localhost:5050"); for x in 0..1000 { let search_mock = server.mock(|when, then| { @@ -24,7 +24,7 @@ fn loop_with_standalone_test() { }); // Act: Send the HTTP request - let response = get(server.url(&format!("/test/{}", x))).unwrap(); + let response = get(&server.url(&format!("/test/{}", x))).unwrap(); // Assert search_mock.assert(); @@ -36,12 +36,12 @@ fn loop_with_standalone_test() { fn loop_with_local_test() { // Arrange - // Instead of creating a new MockServer using new(), we connect to an existing remote instance. + // Create a new local MockServer instance. let server = MockServer::start(); let _mock = server.mock(|when, then| { when.path("/test") - .path_contains("test") + .path_includes("test") .query_param("myQueryParam", "überschall"); then.status(202); }); @@ -53,11 +53,10 @@ fn loop_with_local_test() { }); // Act: Send the HTTP request - let response = get(server.url(&format!("/test/{}", x))).unwrap(); + let response = get(&server.url(&format!("/test/{}", x))).unwrap(); // Assert search_mock.assert(); - assert_eq!(response.status(), 202); } } diff --git a/tests/internal/mod.rs b/tests/misc/mod.rs similarity index 66% rename from tests/internal/mod.rs rename to tests/misc/mod.rs index 6733e7a2..97cdc025 100644 --- a/tests/internal/mod.rs +++ b/tests/misc/mod.rs @@ -2,4 +2,5 @@ mod extensions_test; #[cfg(feature = "remote")] mod large_body_test; mod loop_test; +#[cfg(all(feature = "proxy", feature = "remote"))] mod runtimes_test; diff --git a/tests/misc/runtimes_test.rs b/tests/misc/runtimes_test.rs new file mode 100644 index 00000000..a8b3ed85 --- /dev/null +++ b/tests/misc/runtimes_test.rs @@ -0,0 +1,72 @@ +use crate::with_standalone_server; +use httpmock::prelude::*; +use reqwest::Client; + +#[test] +fn all_runtimes_test() { + return; // TODO: This needs to be fixed. New HTTP client requires tokio runtime! + + with_standalone_server(); + + // Tokio + assert_eq!( + tokio::runtime::Runtime::new().unwrap().block_on(test_fn()), + 202 + ); + + // Actix + assert_eq!(actix_rt::Runtime::new().unwrap().block_on(test_fn()), 202); + + // async_std + assert_eq!(async_std::task::block_on(test_fn()), 202); +} + +async fn test_fn() -> u16 { + // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.). + let server3 = MockServer::start_async().await; + server3 + .mock_async(|when, then| { + when.any_request(); + then.status(202).body("Hi from fake GitHub!"); + }) + .await; + + let server2 = MockServer::connect_async("localhost:5050").await; + server2 + .forward_to_async(server3.base_url(), |rule| { + rule.filter(|when| { + when.any_request(); // We want all requests to be proxied. + }); + }) + .await; + + // Let's create our mock server for the test + let server1 = MockServer::start_async().await; + + // We configure our server to proxy the request to the target host instead of + // answering with a mocked response. The 'when' variable lets you configure + // rules under which requests are proxied. + server1 + .proxy_async(|rule| { + rule.filter(|when| { + when.any_request(); // We want all requests to be proxied. + }); + }) + .await; + + // The following will send a request to the mock server. The request will be forwarded + // to the target host, as we configured before. + let client = Client::builder() + .proxy(reqwest::Proxy::all(server1.base_url()).unwrap()) // Configure to use a proxy server + .build() + .unwrap(); + + // Since the request was forwarded, we should see the target host's response. + let response = client.get(server2.url("/get")).send().await.unwrap(); + let status_code = response.status().as_u16(); + + assert_eq!("Hi from fake GitHub!", response.text().await.unwrap()); + assert_eq!(status_code, 202); + + status_code +} diff --git a/tests/resources/simple_static_mock.yaml b/tests/resources/simple_static_mock.yaml new file mode 100644 index 00000000..477a7449 --- /dev/null +++ b/tests/resources/simple_static_mock.yaml @@ -0,0 +1,6 @@ +when: + method: GET + path: /static-mock/examples/simple +then: + status: 200 + json_body: '{ "response" : "hello" }' diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 00000000..838dde32 --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tools" +version = "0.1.0" +edition = "2018" + +[dependencies] +httpmock = { path = ".." , features = ["full"]} +serde_json = "1.0" +syn = { version = "1.0", features = ["full"] } +proc-macro2 = { version = "1.0", features = ["default", "span-locations"] } +quote = "1.0" + +[[bin]] +name = "extract_docs" +path = "src/extract_docs.rs" + +[[bin]] +name = "extract_code" +path = "src/extract_code.rs" + +[[bin]] +name = "extract_groups" +path = "src/extract_groups.rs" + +[[bin]] +name = "extract_example_tests" +path = "src/extract_example_tests.rs" \ No newline at end of file diff --git a/tools/src/extract_code.rs b/tools/src/extract_code.rs new file mode 100644 index 00000000..3e3bbcef --- /dev/null +++ b/tools/src/extract_code.rs @@ -0,0 +1,78 @@ +use serde_json::json; +use std::fs; +use syn::{File, Item, ItemImpl}; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn main() { + let file_content = fs::read_to_string("../src/api/spec.rs").expect("Unable to read file"); + let syntax_tree: File = syn::parse_file(&file_content).expect("Unable to parse file"); + + let mut when_docs = serde_json::Map::new(); + let mut then_docs = serde_json::Map::new(); + + for item in syntax_tree.items { + if let Item::Impl(ItemImpl { self_ty, items, .. }) = &item { + if let syn::Type::Path(type_path) = &**self_ty { + let ident = &type_path.path.segments.last().unwrap().ident; + if ident == "When" { + extract_docs_for_impl(&mut when_docs, items); + } else if ident == "Then" { + extract_docs_for_impl(&mut then_docs, items); + } + } + } + } + + let json_output = json!({ + "when": when_docs, + "then": then_docs + }); + + let json_output_str = serde_json::to_string_pretty(&json_output).expect("Unable to serialize JSON"); + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + fs::write("target/generated/code_examples.json", json_output_str).expect("Unable to write file"); +} + +fn extract_docs_for_impl(docs: &mut serde_json::Map, items: &Vec) { + for item in items { + if let syn::ImplItem::Method(method) = item { + if let Some(example) = extract_code_example(&method.attrs) { + docs.insert(method.sig.ident.to_string(), json!(example)); + } + } + } +} + +fn extract_code_example(attrs: &Vec) -> Option { + let mut example = String::new(); + let mut in_code_block = false; + + for attr in attrs { + if attr.path.is_ident("doc") { + if let Ok(meta) = attr.parse_meta() { + if let syn::Meta::NameValue(nv) = meta { + if let syn::Lit::Str(lit) = nv.lit { + let doc_line = lit.value(); + if doc_line.trim().starts_with("```rust") { + example.push_str("```rust\n"); + in_code_block = true; + } else if doc_line.trim().starts_with("```") && in_code_block { + example.push_str("```\n"); + in_code_block = false; + } else if in_code_block { + example.push_str(&doc_line); + example.push('\n'); + } + } + } + } + } + } + + if example.is_empty() { + None + } else { + Some(example) + } +} diff --git a/tools/src/extract_docs.rs b/tools/src/extract_docs.rs new file mode 100644 index 00000000..3c236fb8 --- /dev/null +++ b/tools/src/extract_docs.rs @@ -0,0 +1,72 @@ +mod extract_example_tests; + +use serde_json::json; +use std::fs; +use syn::{File, Item, ItemImpl}; +use std::time::{SystemTime, UNIX_EPOCH}; +use syn::spanned::Spanned; +use std::collections::BTreeMap; + +fn main() { + let file_content = fs::read_to_string("../src/api/spec.rs").expect("Unable to read file"); + let syntax_tree: File = syn::parse_file(&file_content).expect("Unable to parse file"); + + let mut when_docs = BTreeMap::new(); + let mut then_docs = BTreeMap::new(); + + for item in syntax_tree.items { + if let Item::Impl(ItemImpl { self_ty, items, .. }) = &item { + if let syn::Type::Path(type_path) = &**self_ty { + let ident = &type_path.path.segments.last().unwrap().ident; + if ident == "When" { + extract_docs_for_impl(&mut when_docs, items); + } else if ident == "Then" { + extract_docs_for_impl(&mut then_docs, items); + } + } + } + } + + let json_output = json!({ + "when": when_docs, + "then": then_docs + }); + + let json_output_str = serde_json::to_string_pretty(&json_output).expect("Unable to serialize JSON"); + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + fs::write("target/generated/docs.json", json_output_str).expect("Unable to write file"); +} + +fn extract_docs_for_impl(docs: &mut BTreeMap, items: &Vec) { + for item in items { + if let syn::ImplItem::Method(method) = item { + let method_name = method.sig.ident.to_string(); + let method_docs = extract_docs(&method.attrs); + + docs.insert(method_name, method_docs); + } + } +} + +fn extract_docs(attrs: &Vec) -> String { + let mut doc_string = String::new(); + for attr in attrs { + if attr.path.is_ident("doc") { + if let Ok(meta) = attr.parse_meta() { + if let syn::Meta::NameValue(nv) = meta { + if let syn::Lit::Str(lit) = nv.lit { + let trimmed_line = if lit.value().starts_with(' ') { + lit.value()[1..].to_owned() + } else { + lit.value().to_owned() + }; + doc_string.push_str(&trimmed_line); + doc_string.push('\n'); + } + } + } + } + } + doc_string +} diff --git a/tools/src/extract_example_tests.rs b/tools/src/extract_example_tests.rs new file mode 100644 index 00000000..a754e9f3 --- /dev/null +++ b/tools/src/extract_example_tests.rs @@ -0,0 +1,49 @@ +use std::fs; +use std::io::{self, BufRead, BufReader}; +use std::collections::HashMap; + +fn main() { + let directory_path = "../tests/examples"; + let paths = fs::read_dir(directory_path).expect("Unable to read directory"); + + let mut example_map: HashMap = HashMap::new(); + + for path in paths { + let path = path.expect("Error reading path").path(); + if path.is_file() { + let file = fs::File::open(&path).expect("Unable to open file"); + let reader = BufReader::new(file); + + let mut recording = false; + let mut example_code = Vec::new(); + let mut example_id = String::new(); + + for line in reader.lines() { + let line = line.expect("Error reading line"); + if line.contains("// @example-start:") { + recording = true; + // Extract the ID or name from the line + example_id = line.split(':').nth(1).unwrap_or("").trim().to_string(); + example_code.clear(); + } else if line.contains("// @example-end") { + if recording { + recording = false; + // Insert the collected code into the map with the example ID as the key + example_map.insert(example_id.clone(), example_code.join("\n")); + } + } else if recording { + example_code.push(line); + } + } + } + } + + // Ensure the target directory exists + fs::create_dir_all("target").expect("Unable to create target directory"); + + // Serialize the example map to JSON + let json_output_str = serde_json::to_string_pretty(&example_map).expect("Unable to serialize JSON"); + + // Write the output to 'target/extract_example_tests.json' + fs::write("target/generated/example_tests.json", json_output_str).expect("Unable to write file"); +} diff --git a/tools/src/extract_groups.rs b/tools/src/extract_groups.rs new file mode 100644 index 00000000..7b0e5439 --- /dev/null +++ b/tools/src/extract_groups.rs @@ -0,0 +1,72 @@ +use serde_json::json; +use std::fs; +use syn::{File, Item, ItemImpl}; +use std::time::{SystemTime, UNIX_EPOCH}; +use syn::spanned::Spanned; + +fn main() { + let file_content = fs::read_to_string("../src/api/spec.rs").expect("Unable to read file"); + let syntax_tree: File = syn::parse_file(&file_content).expect("Unable to parse file"); + + let mut when_docs = vec![]; + let mut then_docs = vec![]; + + for item in syntax_tree.items { + if let Item::Impl(ItemImpl { self_ty, items, .. }) = &item { + if let syn::Type::Path(type_path) = &**self_ty { + let ident = &type_path.path.segments.last().unwrap().ident; + if ident == "When" { + extract_docs_for_impl(&mut when_docs, items, &file_content); + } else if ident == "Then" { + extract_docs_for_impl(&mut then_docs, items, &file_content); + } + } + } + } + + let json_output = json!({ + "when": when_docs, + "then": then_docs + }); + + let json_output_str = serde_json::to_string_pretty(&json_output).expect("Unable to serialize JSON"); + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + fs::write("target/generated/groups.json", json_output_str).expect("Unable to write file"); +} + +fn extract_docs_for_impl(docs: &mut Vec, items: &Vec, file_content: &str) { + for item in items { + if let syn::ImplItem::Method(method) = item { + let method_name = method.sig.ident.to_string(); + let method_span = method.span().end(); + let line_number = method_span.line - 1; + + println!("Processing method: {}", method_name); + + if let Some(group) = extract_group_marker(file_content, line_number) { + docs.push(json!({ + "method": method_name, + "group": group, + })); + } else { + println!("No group marker found for method: {}", method_name); + docs.push(json!({ + "method": method_name, + "group": "No group", + })); + } + } + } +} + +fn extract_group_marker(file_content: &str, line_number: usize) -> Option { + let lines: Vec<&str> = file_content.lines().collect(); + if line_number + 1 < lines.len() { + let marker_line = lines[line_number + 1].trim(); + if marker_line.starts_with("// @docs-group:") { + return Some(marker_line.trim_start_matches("// @docs-group:").trim().to_string()); + } + } + None +} \ No newline at end of file From a208109fa4f0e12431c09b72f5af63b1f6ae0431 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 20:51:46 +0200 Subject: [PATCH 02/13] Avoid copying Cargo.lock --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index addcc892..a07fada6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ ENV HTTPMOCK_REQUEST_HISTORY_LIMIT 100 WORKDIR /httpmock COPY Cargo.toml . -COPY Cargo.lock . +# COPY Cargo.lock . COPY src/ ./src/ COPY certs/ ./certs/ From a518a93f671f98801e4f5a147a1aec586c7a344a Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 20:57:27 +0200 Subject: [PATCH 03/13] Avoid copying Cargo.lock --- Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 848c8788..097f1643 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,8 +63,8 @@ syn = { version = "2.0", features = ["full"] } urlencoding = "2.1.2" [features] -default = ["cookies", "http2", "record"] -standalone = ["clap", "env_logger", "static-mock", "remote", "http2", "https", "cookies", "color"] # enables standalone mode +default = ["cookies"] +standalone = ["clap", "env_logger", "static-mock", "http2", "cookies", "remote", "remote-https"] # enables standalone mode color = ["colored"] # enables colorful output in standalone mode cookies = ["headers"] # enables support for matching cookies remote = ["hyper-util/client-legacy", "hyper-util/http2"] # allows to connect to remote mock servers @@ -75,7 +75,6 @@ https = ["rustls", "rcgen", "tokio-rustls", "rustls-pemfile", "rustls/ring", "tl http2 = ["hyper/http2", "hyper-util/http2"] # enables httpmocks server support for HTTP2 record = ["static-mock", "proxy"] experimental = [] # marker feature for experimental features -full = ["default", "standalone", "color", "cookies", "remote", "remote-https", "proxy", "static-mock", "https", "http2", "record", "experimental"] [[bin]] name = "httpmock" From ecbd26f1692b86a4a522889828967f5eb683e2f1 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 21:31:49 +0200 Subject: [PATCH 04/13] Avoid copying Cargo.lock --- .github/workflows/deploy-docs.yml | 37 +++ Cargo.toml | 5 +- docs/website/astro.config.mjs | 3 +- .../docs/getting_started/fundamentals.mdx | 3 +- src/api/adapter/local.rs | 2 + src/api/adapter/mod.rs | 4 + src/api/adapter/remote.rs | 6 +- src/api/proxy.rs | 7 +- src/api/server.rs | 35 ++- src/common/data.rs | 1 - src/server/builder.rs | 15 +- src/server/handler.rs | 12 + src/server/mod.rs | 2 +- src/server/state.rs | 14 +- tarpaulin.full.toml | 256 +++++++++--------- tarpaulin.toml | 4 +- 16 files changed, 247 insertions(+), 159 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..356cdbf1 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,37 @@ +name: Deploy to GitHub Pages + +on: + # Trigger the workflow every time you push to the `main` branch + # Using a different branch name? Replace `main` with your branch’s name + push: + branches: [ development ] + # Allows you to run this workflow manually from the Actions tab on GitHub. + workflow_dispatch: + +# Allow this job to clone the repo and create a page deployment +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout your repository using git + uses: actions/checkout@v4 + - name: Install, build, and upload your site + uses: withastro/action@v2 + with: + path: ./docs/website # The root location of your Astro project inside the repository. (optional) + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 097f1643..5cda4271 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,16 +64,15 @@ urlencoding = "2.1.2" [features] default = ["cookies"] -standalone = ["clap", "env_logger", "static-mock", "http2", "cookies", "remote", "remote-https"] # enables standalone mode +standalone = ["clap", "env_logger", "record", "http2", "cookies", "remote", "remote-https"] # enables standalone mode color = ["colored"] # enables colorful output in standalone mode cookies = ["headers"] # enables support for matching cookies remote = ["hyper-util/client-legacy", "hyper-util/http2"] # allows to connect to remote mock servers remote-https = ["remote", "rustls", "hyper-rustls", "hyper-rustls/http2"] # allows to connect to remote mock servers via HTTPS proxy = ["remote-https", "hyper-util/client-legacy", "hyper-util/http2", "hyper-rustls", "hyper-rustls/http2"] # enables proxy functionality -static-mock = ["serde_yaml"] # allows to read mock definitions from the file system https = ["rustls", "rcgen", "tokio-rustls", "rustls-pemfile", "rustls/ring", "tls-detect"] # enables httpmock server support for TLS/HTTPS http2 = ["hyper/http2", "hyper-util/http2"] # enables httpmocks server support for HTTP2 -record = ["static-mock", "proxy"] +record = ["proxy", "serde_yaml"] experimental = [] # marker feature for experimental features [[bin]] diff --git a/docs/website/astro.config.mjs b/docs/website/astro.config.mjs index b4e89961..e6548963 100644 --- a/docs/website/astro.config.mjs +++ b/docs/website/astro.config.mjs @@ -3,7 +3,8 @@ import starlight from '@astrojs/starlight'; // https://astro.build/config export default defineConfig({ - site: 'https://alexliesenfeld.github.io/httpmock', + site: 'https://alexliesenfeld.github.io', + base: 'httpmock', // so that 'https://alexliesenfeld.github.io/httpmock' will be set as the base path integrations: [ starlight({ title: 'httpmock Tutorial', diff --git a/docs/website/src/content/docs/getting_started/fundamentals.mdx b/docs/website/src/content/docs/getting_started/fundamentals.mdx index 3f228d51..769caf5f 100644 --- a/docs/website/src/content/docs/getting_started/fundamentals.mdx +++ b/docs/website/src/content/docs/getting_started/fundamentals.mdx @@ -108,8 +108,7 @@ The crate provides the following Cargo features: - `cookies`: Enables request matchers for parsing and matching values in cookies - `proxy`: Enables the mock server to function as a proxy server -- `record`: Enables functionality to record requests and responses (most useful in combination with the `proxy` feature) -- `static-mock`: Enables reading mock specifications from YAML files (e.g., recorded responses) +- `record`: Enables functionality to record requests and responses (most useful in combination with the `proxy` feature). Enables reading mock specifications from YAML files (e.g., recorded responses) - `https`: Enables the mock server to provide a unified port for both, HTTP and HTTPS. Attention: This feature is experimental. Hence, there are no guarantees that this feature will work. - `http2`: Enables mock server support for HTTP2 - `standalone`: Enables standalone mode diff --git a/src/api/adapter/local.rs b/src/api/adapter/local.rs index 1b7d9c4d..34c293e9 100644 --- a/src/api/adapter/local.rs +++ b/src/api/adapter/local.rs @@ -164,6 +164,7 @@ impl MockServerAdapter for LocalMockServerAdapter { Ok(()) } + #[cfg(feature = "record")] async fn export_recording(&self, id: usize) -> Result, ServerAdapterError> { Ok(self .state @@ -171,6 +172,7 @@ impl MockServerAdapter for LocalMockServerAdapter { .map_err(|err| UpstreamError(err.to_string()))?) } + #[cfg(feature = "record")] async fn create_mocks_from_recording<'a>( &self, recording_file_content: &'a str, diff --git a/src/api/adapter/mod.rs b/src/api/adapter/mod.rs index 5d40f3ce..f1ad3f1f 100644 --- a/src/api/adapter/mod.rs +++ b/src/api/adapter/mod.rs @@ -76,7 +76,11 @@ pub trait MockServerAdapter { ) -> Result; async fn delete_recording(&self, id: usize) -> Result<(), ServerAdapterError>; async fn delete_all_recordings(&self) -> Result<(), ServerAdapterError>; + + #[cfg(feature = "record")] async fn export_recording(&self, id: usize) -> Result, ServerAdapterError>; + + #[cfg(feature = "record")] async fn create_mocks_from_recording<'a>( &self, recording_file_content: &'a str, diff --git a/src/api/adapter/remote.rs b/src/api/adapter/remote.rs index eab7742a..8fecf89b 100644 --- a/src/api/adapter/remote.rs +++ b/src/api/adapter/remote.rs @@ -1,4 +1,9 @@ use std::{borrow::Borrow, net::SocketAddr, sync::Arc}; +use crate::{ + common::{ + data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig} + }, +}; use crate::api::{ adapter::{ @@ -22,7 +27,6 @@ use crate::{ }, http::HttpClient, }, - ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig, }; pub struct RemoteMockServerAdapter { diff --git a/src/api/proxy.rs b/src/api/proxy.rs index afa3eff0..049b3436 100644 --- a/src/api/proxy.rs +++ b/src/api/proxy.rs @@ -2,9 +2,10 @@ use crate::{ api::server::MockServer, common::{ data::RequestRequirements, + data::RecordingRuleConfig, util::{write_file, Join}, }, - RecordingRuleConfig, When, + When, }; use std::{ cell::Cell, @@ -163,6 +164,7 @@ impl<'a> Recording<'a> { /// /// # Errors /// Errors if the file cannot be written due to issues like directory permissions, unavailable disk space, or other I/O errors. + #[cfg(feature = "record")] pub fn save_to, IntoString: Into>( &self, dir: PathRef, @@ -179,6 +181,7 @@ impl<'a> Recording<'a> { /// /// # Returns /// Returns an `async` `Result` with the `PathBuf` of the saved file or an error if unable to save. + #[cfg(feature = "record")] pub async fn save_to_async, IntoString: Into>( &self, dir: PathRef, @@ -214,6 +217,7 @@ impl<'a> Recording<'a> { /// /// # Returns /// Returns a `Result` with the `PathBuf` to the saved file or an error. + #[cfg(feature = "record")] pub fn save>( &self, scenario_name: IntoString, @@ -228,6 +232,7 @@ impl<'a> Recording<'a> { /// /// # Returns /// Returns an `async` `Result` with the `PathBuf` of the saved file or an error. + #[cfg(feature = "record")] pub async fn save_async>( &self, scenario: IntoString, diff --git a/src/api/server.rs b/src/api/server.rs index 536430d3..950a9b52 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,42 +1,53 @@ #[cfg(feature = "remote")] use crate::api::RemoteMockServerAdapter; use crate::api::{ - proxy::{ - ForwardingRule, ForwardingRuleBuilder, ProxyRule, ProxyRuleBuilder, Recording, - RecordingRuleBuilder, - }, spec::{Then, When}, }; #[cfg(feature = "remote")] use crate::common::http::HttpMockHttpClient; -use crate::common::data::ForwardingRuleConfig; - use crate::{ api::{LocalMockServerAdapter, MockServerAdapter}, common::{ data::{MockDefinition, MockServerHttpResponse, RequestRequirements}, runtime, - util::{read_env, read_file_async, with_retry, Join}, + util::{read_env, with_retry, Join}, + }, +}; + +#[cfg(feature = "proxy")] +use crate::{ + api::proxy::{ForwardingRuleBuilder, ForwardingRule, ProxyRuleBuilder, ProxyRule}, + common::{ + util::read_file_async, + data::{ForwardingRuleConfig, ProxyRuleConfig}, }, }; +#[cfg(feature = "record")] +use crate::{ + api::{ + common::data::RecordingRuleConfig, + mock::MockSet, + proxy::{Recording, RecordingRuleBuilder} + }, +}; + +#[cfg(feature = "record")] +use std::{path::PathBuf}; + use crate::{ - api::mock::MockSet, server::{state::HttpMockStateManager, HttpMockServerBuilder}, }; -#[cfg(feature = "proxy")] -use crate::ProxyRuleConfig; -use crate::{Mock, RecordingRuleConfig}; +use crate::Mock; use async_object_pool::Pool; use lazy_static::lazy_static; use std::{ cell::Cell, future::pending, net::{SocketAddr, ToSocketAddrs}, - path::PathBuf, rc::Rc, sync::Arc, thread, diff --git a/src/common/data.rs b/src/common/data.rs index 0e34b0ff..64b5ccd6 100644 --- a/src/common/data.rs +++ b/src/common/data.rs @@ -918,7 +918,6 @@ pub struct Mismatch { // Configs and Builders // ************************************************************************************************* -#[cfg(feature = "record")] #[derive(Serialize, Deserialize, Clone, Default)] pub struct RecordingRuleConfig { pub request_requirements: RequestRequirements, diff --git a/src/server/builder.rs b/src/server/builder.rs index a095970d..5b0ca71b 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -4,10 +4,13 @@ use crate::common::http::{HttpClient, HttpMockHttpClient}; use crate::server::server::MockServerHttpsConfig; #[cfg(feature = "https")] use crate::server::tls::{CertificateResolverFactory, GeneratingCertificateResolverFactory}; +#[cfg(any(feature = "record", feature = "record"))] +use crate::server::{ + persistence::read_static_mock_definitions, +}; use crate::server::{ handler::HttpMockHandler, - persistence::read_static_mock_definitions, server::{MockServer, MockServerConfig}, state::{HttpMockStateManager, StateManager}, HttpMockServer, @@ -190,7 +193,7 @@ pub struct HttpMockServerBuilder { expose: Option, print_access_log: Option, history_limit: Option, - #[cfg(feature = "static-mock")] + #[cfg(feature = "record")] static_mock_dir: Option, #[cfg(feature = "https")] https_config_builder: HttpsConfigBuilder, @@ -209,7 +212,7 @@ impl HttpMockServerBuilder { port: None, expose: None, history_limit: None, - #[cfg(feature = "static-mock")] + #[cfg(feature = "record")] static_mock_dir: None, #[cfg(feature = "proxy")] http_client: None, @@ -321,7 +324,7 @@ impl HttpMockServerBuilder { /// /// # Returns /// A modified `HttpMockServerBuilder` instance for method chaining. - #[cfg(feature = "static-mock")] + #[cfg(feature = "record")] pub fn static_mock_dir(mut self, path: PathBuf) -> Self { self.static_mock_dir = Some(path); self @@ -334,7 +337,7 @@ impl HttpMockServerBuilder { /// /// # Returns /// A modified `HttpMockServerBuilder` instance for method chaining. - #[cfg(feature = "static-mock")] + #[cfg(feature = "record")] pub fn static_mock_dir_option(mut self, path: Option) -> Self { self.static_mock_dir = path; self @@ -482,7 +485,7 @@ impl HttpMockServerBuilder { .http_client .unwrap_or_else(|| Arc::new(HttpMockHttpClient::new(None))); - #[cfg(feature = "static-mock")] + #[cfg(feature = "record")] if let Some(dir) = self.static_mock_dir { read_static_mock_definitions(dir, state.as_ref())?; } diff --git a/src/server/handler.rs b/src/server/handler.rs index 66696a6a..a8e63d2c 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -81,7 +81,9 @@ enum RoutePath { ForwardingRuleCollection, ProxyRuleCollection, SingleProxyRule, + #[cfg(feature = "record")] RecordingCollection, + #[cfg(feature = "record")] SingleRecording, } @@ -157,11 +159,13 @@ where Method::DELETE => return self.handle_delete_proxy_rule(params), _ => {} }, + #[cfg(feature = "record")] RoutePath::RecordingCollection => match method { Method::POST => return self.handle_add_recording_matcher(req), Method::DELETE => return self.handle_delete_all_recording_matchers(), _ => {} }, + #[cfg(feature = "record")] RoutePath::SingleRecording => match method { Method::GET => return self.handle_read_recording(params), Method::DELETE => return self.handle_delete_recording(params), @@ -196,7 +200,10 @@ where "/__httpmock__/forwarding_rules", RoutePath::ForwardingRuleCollection, ); + + #[cfg(feature = "record")] path_tree.insert("/__httpmock__/proxy_rules", RoutePath::ProxyRuleCollection); + #[cfg(feature = "record")] path_tree.insert("/__httpmock__/recordings", RoutePath::RecordingCollection); } @@ -306,12 +313,14 @@ where return response::<()>(StatusCode::NO_CONTENT, None); } + #[cfg(feature = "record")] fn handle_add_recording_matcher(&self, req: Request) -> Result, Error> { let req_req: RecordingRuleConfig = parse_json_body(req)?; let active_recording = self.state.create_recording(req_req); return response(StatusCode::CREATED, Some(active_recording)); } + #[cfg(feature = "record")] fn handle_delete_recording(&self, params: Path) -> Result, Error> { let deleted = self.state.delete_proxy_rule(param("id", params)?); let status_code = if deleted.is_some() { @@ -322,11 +331,13 @@ where return response::<()>(status_code, None); } + #[cfg(feature = "record")] fn handle_delete_all_recording_matchers(&self) -> Result, Error> { self.state.delete_all_recordings(); return response::<()>(StatusCode::NO_CONTENT, None); } + #[cfg(feature = "record")] fn handle_read_recording(&self, params: Path) -> Result, Error> { let rec = self.state.export_recording(param("id", params)?)?; let status_code = rec @@ -335,6 +346,7 @@ where return response(status_code, rec); } + #[cfg(feature = "record")] fn handle_load_recording(&self, req: Request) -> Result, Error> { let recording_file_content = std::str::from_utf8(&req.body()) .map_err(|err| RequestConversionError(err.to_string()))?; diff --git a/src/server/mod.rs b/src/server/mod.rs index 7e29efdc..862d8c1a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -17,7 +17,7 @@ mod server; pub mod state; mod util; -#[cfg(feature = "static-mock")] +#[cfg(feature = "record")] mod persistence; #[cfg(feature = "https")] diff --git a/src/server/state.rs b/src/server/state.rs index 900fa785..ed17d9e9 100644 --- a/src/server/state.rs +++ b/src/server/state.rs @@ -10,11 +10,17 @@ use crate::{ server::{ matchers, matchers::{all, Matcher}, - persistence::{deserialize_mock_defs_from_yaml, serialize_mock_defs_to_yaml}, state::Error::{BodyMethodInvalid, DataConversionError, StaticMockError, ValidationError}, }, }; +#[cfg(feature = "record")] +use crate::{ + server::{ + persistence::{deserialize_mock_defs_from_yaml, serialize_mock_defs_to_yaml}, + }, +}; + use crate::common::data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig}; use bytes::Bytes; use std::{ @@ -95,7 +101,11 @@ pub(crate) trait StateManager { fn create_recording(&self, config: RecordingRuleConfig) -> ActiveRecording; fn delete_recording(&self, recording_id: usize) -> Option; fn delete_all_recordings(&self); + + #[cfg(feature = "record")] fn export_recording(&self, id: usize) -> Result, Error>; + + #[cfg(feature = "record")] fn load_mocks_from_recording(&self, recording_file_content: &str) -> Result, Error>; fn find_forward_rule<'a>( @@ -396,6 +406,7 @@ impl StateManager for HttpMockStateManager { log::debug!("Deleted all recorders"); } + #[cfg(feature = "record")] fn export_recording(&self, id: usize) -> Result, Error> { let mut state = self.state.lock().unwrap(); @@ -409,6 +420,7 @@ impl StateManager for HttpMockStateManager { Ok(None) } + #[cfg(feature = "record")] fn load_mocks_from_recording(&self, recording_file_content: &str) -> Result, Error> { let all_static_mock_defs = deserialize_mock_defs_from_yaml(recording_file_content) .map_err(|err| DataConversionError(err.to_string()))?; diff --git a/tarpaulin.full.toml b/tarpaulin.full.toml index d1dad873..eb28857e 100644 --- a/tarpaulin.full.toml +++ b/tarpaulin.full.toml @@ -61,68 +61,68 @@ release = true features = "standalone,cookies,remote,proxy" release = true -[static-mock] -features = "static-mock" +[record] +features = "record" release = true -[standalone.static-mock] -features = "standalone,static-mock" +[standalone.record] +features = "standalone,record" release = true -[cookies.static-mock] -features = "cookies,static-mock" +[cookies.record] +features = "cookies,record" release = true -[standalone.cookies.static-mock] -features = "standalone,cookies,static-mock" +[standalone.cookies.record] +features = "standalone,cookies,record" release = true -[remote.static-mock] -features = "remote,static-mock" +[remote.record] +features = "remote,record" release = true -[standalone.remote.static-mock] -features = "standalone,remote,static-mock" +[standalone.remote.record] +features = "standalone,remote,record" release = true -[cookies.remote.static-mock] -features = "cookies,remote,static-mock" +[cookies.remote.record] +features = "cookies,remote,record" release = true -[standalone.cookies.remote.static-mock] -features = "standalone,cookies,remote,static-mock" +[standalone.cookies.remote.record] +features = "standalone,cookies,remote,record" release = true -[proxy.static-mock] -features = "proxy,static-mock" +[proxy.record] +features = "proxy,record" release = true -[standalone.proxy.static-mock] -features = "standalone,proxy,static-mock" +[standalone.proxy.record] +features = "standalone,proxy,record" release = true -[cookies.proxy.static-mock] -features = "cookies,proxy,static-mock" +[cookies.proxy.record] +features = "cookies,proxy,record" release = true -[standalone.cookies.proxy.static-mock] -features = "standalone,cookies,proxy,static-mock" +[standalone.cookies.proxy.record] +features = "standalone,cookies,proxy,record" release = true -[remote.proxy.static-mock] -features = "remote,proxy,static-mock" +[remote.proxy.record] +features = "remote,proxy,record" release = true -[standalone.remote.proxy.static-mock] -features = "standalone,remote,proxy,static-mock" +[standalone.remote.proxy.record] +features = "standalone,remote,proxy,record" release = true -[cookies.remote.proxy.static-mock] -features = "cookies,remote,proxy,static-mock" +[cookies.remote.proxy.record] +features = "cookies,remote,proxy,record" release = true -[standalone.cookies.remote.proxy.static-mock] -features = "standalone,cookies,remote,proxy,static-mock" +[standalone.cookies.remote.proxy.record] +features = "standalone,cookies,remote,proxy,record" release = true [https] @@ -189,68 +189,68 @@ release = true features = "standalone,cookies,remote,proxy,https" release = true -[static-mock.https] -features = "static-mock,https" +[record.https] +features = "record,https" release = true -[standalone.static-mock.https] -features = "standalone,static-mock,https" +[standalone.record.https] +features = "standalone,record,https" release = true -[cookies.static-mock.https] -features = "cookies,static-mock,https" +[cookies.record.https] +features = "cookies,record,https" release = true -[standalone.cookies.static-mock.https] -features = "standalone,cookies,static-mock,https" +[standalone.cookies.record.https] +features = "standalone,cookies,record,https" release = true -[remote.static-mock.https] -features = "remote,static-mock,https" +[remote.record.https] +features = "remote,record,https" release = true -[standalone.remote.static-mock.https] -features = "standalone,remote,static-mock,https" +[standalone.remote.record.https] +features = "standalone,remote,record,https" release = true -[cookies.remote.static-mock.https] -features = "cookies,remote,static-mock,https" +[cookies.remote.record.https] +features = "cookies,remote,record,https" release = true -[standalone.cookies.remote.static-mock.https] -features = "standalone,cookies,remote,static-mock,https" +[standalone.cookies.remote.record.https] +features = "standalone,cookies,remote,record,https" release = true -[proxy.static-mock.https] -features = "proxy,static-mock,https" +[proxy.record.https] +features = "proxy,record,https" release = true -[standalone.proxy.static-mock.https] -features = "standalone,proxy,static-mock,https" +[standalone.proxy.record.https] +features = "standalone,proxy,record,https" release = true -[cookies.proxy.static-mock.https] -features = "cookies,proxy,static-mock,https" +[cookies.proxy.record.https] +features = "cookies,proxy,record,https" release = true -[standalone.cookies.proxy.static-mock.https] -features = "standalone,cookies,proxy,static-mock,https" +[standalone.cookies.proxy.record.https] +features = "standalone,cookies,proxy,record,https" release = true -[remote.proxy.static-mock.https] -features = "remote,proxy,static-mock,https" +[remote.proxy.record.https] +features = "remote,proxy,record,https" release = true -[standalone.remote.proxy.static-mock.https] -features = "standalone,remote,proxy,static-mock,https" +[standalone.remote.proxy.record.https] +features = "standalone,remote,proxy,record,https" release = true -[cookies.remote.proxy.static-mock.https] -features = "cookies,remote,proxy,static-mock,https" +[cookies.remote.proxy.record.https] +features = "cookies,remote,proxy,record,https" release = true -[standalone.cookies.remote.proxy.static-mock.https] -features = "standalone,cookies,remote,proxy,static-mock,https" +[standalone.cookies.remote.proxy.record.https] +features = "standalone,cookies,remote,proxy,record,https" release = true [http2] @@ -317,68 +317,68 @@ release = true features = "standalone,cookies,remote,proxy,http2" release = true -[static-mock.http2] -features = "static-mock,http2" +[record.http2] +features = "record,http2" release = true -[standalone.static-mock.http2] -features = "standalone,static-mock,http2" +[standalone.record.http2] +features = "standalone,record,http2" release = true -[cookies.static-mock.http2] -features = "cookies,static-mock,http2" +[cookies.record.http2] +features = "cookies,record,http2" release = true -[standalone.cookies.static-mock.http2] -features = "standalone,cookies,static-mock,http2" +[standalone.cookies.record.http2] +features = "standalone,cookies,record,http2" release = true -[remote.static-mock.http2] -features = "remote,static-mock,http2" +[remote.record.http2] +features = "remote,record,http2" release = true -[standalone.remote.static-mock.http2] -features = "standalone,remote,static-mock,http2" +[standalone.remote.record.http2] +features = "standalone,remote,record,http2" release = true -[cookies.remote.static-mock.http2] -features = "cookies,remote,static-mock,http2" +[cookies.remote.record.http2] +features = "cookies,remote,record,http2" release = true -[standalone.cookies.remote.static-mock.http2] -features = "standalone,cookies,remote,static-mock,http2" +[standalone.cookies.remote.record.http2] +features = "standalone,cookies,remote,record,http2" release = true -[proxy.static-mock.http2] -features = "proxy,static-mock,http2" +[proxy.record.http2] +features = "proxy,record,http2" release = true -[standalone.proxy.static-mock.http2] -features = "standalone,proxy,static-mock,http2" +[standalone.proxy.record.http2] +features = "standalone,proxy,record,http2" release = true -[cookies.proxy.static-mock.http2] -features = "cookies,proxy,static-mock,http2" +[cookies.proxy.record.http2] +features = "cookies,proxy,record,http2" release = true -[standalone.cookies.proxy.static-mock.http2] -features = "standalone,cookies,proxy,static-mock,http2" +[standalone.cookies.proxy.record.http2] +features = "standalone,cookies,proxy,record,http2" release = true -[remote.proxy.static-mock.http2] -features = "remote,proxy,static-mock,http2" +[remote.proxy.record.http2] +features = "remote,proxy,record,http2" release = true -[standalone.remote.proxy.static-mock.http2] -features = "standalone,remote,proxy,static-mock,http2" +[standalone.remote.proxy.record.http2] +features = "standalone,remote,proxy,record,http2" release = true -[cookies.remote.proxy.static-mock.http2] -features = "cookies,remote,proxy,static-mock,http2" +[cookies.remote.proxy.record.http2] +features = "cookies,remote,proxy,record,http2" release = true -[standalone.cookies.remote.proxy.static-mock.http2] -features = "standalone,cookies,remote,proxy,static-mock,http2" +[standalone.cookies.remote.proxy.record.http2] +features = "standalone,cookies,remote,proxy,record,http2" release = true [https.http2] @@ -445,68 +445,68 @@ release = true features = "standalone,cookies,remote,proxy,https,http2" release = true -[static-mock.https.http2] -features = "static-mock,https,http2" +[record.https.http2] +features = "record,https,http2" release = true -[standalone.static-mock.https.http2] -features = "standalone,static-mock,https,http2" +[standalone.record.https.http2] +features = "standalone,record,https,http2" release = true -[cookies.static-mock.https.http2] -features = "cookies,static-mock,https,http2" +[cookies.record.https.http2] +features = "cookies,record,https,http2" release = true -[standalone.cookies.static-mock.https.http2] -features = "standalone,cookies,static-mock,https,http2" +[standalone.cookies.record.https.http2] +features = "standalone,cookies,record,https,http2" release = true -[remote.static-mock.https.http2] -features = "remote,static-mock,https,http2" +[remote.record.https.http2] +features = "remote,record,https,http2" release = true -[standalone.remote.static-mock.https.http2] -features = "standalone,remote,static-mock,https,http2" +[standalone.remote.record.https.http2] +features = "standalone,remote,record,https,http2" release = true -[cookies.remote.static-mock.https.http2] -features = "cookies,remote,static-mock,https,http2" +[cookies.remote.record.https.http2] +features = "cookies,remote,record,https,http2" release = true -[standalone.cookies.remote.static-mock.https.http2] -features = "standalone,cookies,remote,static-mock,https,http2" +[standalone.cookies.remote.record.https.http2] +features = "standalone,cookies,remote,record,https,http2" release = true -[proxy.static-mock.https.http2] -features = "proxy,static-mock,https,http2" +[proxy.record.https.http2] +features = "proxy,record,https,http2" release = true -[standalone.proxy.static-mock.https.http2] -features = "standalone,proxy,static-mock,https,http2" +[standalone.proxy.record.https.http2] +features = "standalone,proxy,record,https,http2" release = true -[cookies.proxy.static-mock.https.http2] -features = "cookies,proxy,static-mock,https,http2" +[cookies.proxy.record.https.http2] +features = "cookies,proxy,record,https,http2" release = true -[standalone.cookies.proxy.static-mock.https.http2] -features = "standalone,cookies,proxy,static-mock,https,http2" +[standalone.cookies.proxy.record.https.http2] +features = "standalone,cookies,proxy,record,https,http2" release = true -[remote.proxy.static-mock.https.http2] -features = "remote,proxy,static-mock,https,http2" +[remote.proxy.record.https.http2] +features = "remote,proxy,record,https,http2" release = true -[standalone.remote.proxy.static-mock.https.http2] -features = "standalone,remote,proxy,static-mock,https,http2" +[standalone.remote.proxy.record.https.http2] +features = "standalone,remote,proxy,record,https,http2" release = true -[cookies.remote.proxy.static-mock.https.http2] -features = "cookies,remote,proxy,static-mock,https,http2" +[cookies.remote.proxy.record.https.http2] +features = "cookies,remote,proxy,record,https,http2" release = true -[standalone.cookies.remote.proxy.static-mock.https.http2] -features = "standalone,cookies,remote,proxy,static-mock,https,http2" +[standalone.cookies.remote.proxy.record.https.http2] +features = "standalone,cookies,remote,proxy,record,https,http2" release = true [report] diff --git a/tarpaulin.toml b/tarpaulin.toml index 59d71d07..59a12c39 100644 --- a/tarpaulin.toml +++ b/tarpaulin.toml @@ -1,10 +1,10 @@ [remote] -features = "remote,proxy,http2,static-mock,cookies" +features = "remote,proxy,http2,record,cookies" release = false [standalone] -features = "standalone,color,cookies,proxy,static-mock,http2" +features = "standalone,color,cookies,proxy,record,http2" release = true [report] From b9f8e6ddfded06080d21b77b003477373c393df4 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 22:35:33 +0200 Subject: [PATCH 05/13] Avoid copying Cargo.lock --- Makefile | 2 +- docs/website/generated/example_tests.json | 6 +++--- .../docs/record-and-playback/recording.mdx | 19 +++++++++++++------ .../src/content/docs/server/debugging.mdx | 11 ++++++++--- .../website/src/content/docs/server/https.mdx | 7 +++++-- .../src/content/docs/server/standalone.mdx | 16 ++++++++++------ .../{custom.md => custom.mdx} | 8 +++++++- src/api/adapter/remote.rs | 2 ++ src/common/http.rs | 1 + tools/Cargo.toml | 2 +- 10 files changed, 51 insertions(+), 23 deletions(-) rename docs/website/templates/matching_requests/{custom.md => custom.mdx} (51%) diff --git a/Makefile b/Makefile index f98794ac..8430e275 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ docs: cd tools && cargo run --bin extract_groups cd tools && cargo run --bin extract_example_tests rm -rf docs/website/generated && cp -r tools/target/generated docs/website/generated - cd docs/website && npm run generate-docs + cd docs/website && npm install && npm run generate-docs .PHONY: fmt diff --git a/docs/website/generated/example_tests.json b/docs/website/generated/example_tests.json index 81a8eea2..9d7ce7fe 100644 --- a/docs/website/generated/example_tests.json +++ b/docs/website/generated/example_tests.json @@ -1,7 +1,7 @@ { "record-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn record_github_api_with_forwarding_test() {\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target\n // host instead of answering with a mocked response. The 'when'\n // variable lets you configure rules under which forwarding\n // should take place.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Ensure all requests are forwarded.\n });\n });\n\n let recording = server.record(|rule| {\n rule\n // Specify which headers to record.\n // Only the headers listed here will be captured and stored\n // as part of the recorded mock. This selective recording is\n // necessary because some headers may vary between requests\n // and could cause issues when replaying the mock later.\n // For instance, headers like 'Authorization' or 'Date' may\n // change with each request.\n .record_request_header(\"User-Agent\")\n .filter(|when| {\n when.any_request(); // Ensure all requests are recorded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request\n // will be forwarded to the GitHub API, as we configured before.\n let client = Client::new();\n\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires us to send a user agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Since the request was forwarded, we should see a GitHub API response.\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(true, response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recording to\n // \"target/httpmock/recordings/github-torvalds-scenario_.yaml\".\n recording\n .save(\"github-torvalds-scenario\")\n .expect(\"cannot store scenario on disk\");\n}", - "playback-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn playback_github_api() {\n // Start a mock server for the test\n let server = MockServer::start();\n\n // Configure the mock server to forward requests to the target\n // host (GitHub API) instead of responding with a mock. The 'rule'\n // parameter allows you to define conditions under which forwarding\n // should occur.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Forward all requests.\n });\n });\n\n // Set up recording to capture all forwarded requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests and responses.\n });\n });\n\n // Send an HTTP request to the mock server, which will be forwarded\n // to the GitHub API\n let client = Client::new();\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires a User-Agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Assert that the response from the forwarded request is as expected\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recorded interactions to a file\n let target_path = recording\n .save(\"github-torvalds-scenario\")\n .expect(\"Failed to save the recording to disk\");\n\n // Start a new mock server instance for playback\n let playback_server = MockServer::start();\n\n // Load the recorded interactions into the new mock server\n playback_server.playback(target_path);\n\n // Send a request to the playback server and verify the response\n // matches the recorded data\n let response = client\n .get(playback_server.url(\"/repos/torvalds/linux\"))\n .send()\n .unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}", - "record-proxy-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn record_with_proxy_test() {\n // Start a mock server to act as a proxy for the HTTP client\n let server = MockServer::start();\n\n // Configure the mock server to proxy all incoming requests\n server.proxy(|rule| {\n rule.filter(|when| {\n when.any_request(); // Intercept all requests\n });\n });\n\n // Set up recording on the mock server to capture all proxied\n // requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests\n });\n });\n\n // Create an HTTP client configured to route requests\n // through the mock proxy server\n let github_client = Client::builder()\n // Set the proxy URL to the mock server's URL\n .proxy(reqwest::Proxy::all(server.base_url()).unwrap())\n .build()\n .unwrap();\n\n // Send a GET request using the client, which will be proxied by the mock server\n let response = github_client.get(server.base_url()).send().unwrap();\n\n // Verify that the response matches the expected mock response\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n\n // Save the recorded HTTP interactions to a file for future reference or testing\n recording\n .save(\"my_scenario_name\")\n .expect(\"could not save the recording\");\n}", + "record-proxy-github": "#[cfg(all(feature = \"proxy\", feature = \"record\", feature = \"experimental\"))]\n#[test]\nfn record_with_proxy_test() {\n // Start a mock server to act as a proxy for the HTTP client\n let server = MockServer::start();\n\n // Configure the mock server to proxy all incoming requests\n server.proxy(|rule| {\n rule.filter(|when| {\n when.any_request(); // Intercept all requests\n });\n });\n\n // Set up recording on the mock server to capture all proxied\n // requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests\n });\n });\n\n // Create an HTTP client configured to route requests\n // through the mock proxy server\n let github_client = Client::builder()\n // Set the proxy URL to the mock server's URL\n .proxy(reqwest::Proxy::all(server.base_url()).unwrap())\n .build()\n .unwrap();\n\n // Send a GET request using the client, which will be proxied by the mock server\n let response = github_client.get(server.base_url()).send().unwrap();\n\n // Verify that the response matches the expected mock response\n assert_eq!(response.text().unwrap(), \"This is a mock response\");\n\n // Save the recorded HTTP interactions to a file for future reference or testing\n recording\n .save(\"my_scenario_name\")\n .expect(\"could not save the recording\");\n}", "forwarding-github": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forward_to_github_test() {\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target\n // host instead of answering with a mocked response. The 'when'\n // variable lets you configure rules under which forwarding\n // should take place.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Ensure all requests are forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request\n // will be forwarded to the GitHub API, as we configured before.\n let client = Client::new();\n\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires us to send a user agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Since the request was forwarded, we should see a GitHub API response.\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(true, response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}", - "forwarding": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forwarding_test() {\n // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.).\n let target_server = MockServer::start();\n target_server.mock(|when, then| {\n when.any_request();\n then.status(200).body(\"Hi from fake GitHub!\");\n });\n\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target host instead of\n // answering with a mocked response. The 'when' variable lets you configure\n // rules under which forwarding should take place.\n server.forward_to(target_server.base_url(), |rule| {\n rule.filter(|when| {\n when.any_request(); // We want all requests to be forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request will be forwarded\n // to the target host, as we configured before.\n let client = Client::new();\n\n // Since the request was forwarded, we should see the target host's response.\n let response = client.get(server.url(\"/get\")).send().unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(response.text().unwrap(), \"Hi from fake GitHub!\");\n}" + "forwarding": "#[cfg(feature = \"proxy\")]\n#[test]\nfn forwarding_test() {\n // We will create this mock server to simulate a real service (e.g., GitHub, AWS, etc.).\n let target_server = MockServer::start();\n target_server.mock(|when, then| {\n when.any_request();\n then.status(200).body(\"Hi from fake GitHub!\");\n });\n\n // Let's create our mock server for the test\n let server = MockServer::start();\n\n // We configure our server to forward the request to the target host instead of\n // answering with a mocked response. The 'when' variable lets you configure\n // rules under which forwarding should take place.\n server.forward_to(target_server.base_url(), |rule| {\n rule.filter(|when| {\n when.any_request(); // We want all requests to be forwarded.\n });\n });\n\n // Now let's send an HTTP request to the mock server. The request will be forwarded\n // to the target host, as we configured before.\n let client = Client::new();\n\n // Since the request was forwarded, we should see the target host's response.\n let response = client.get(server.url(\"/get\")).send().unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert_eq!(response.text().unwrap(), \"Hi from fake GitHub!\");\n}", + "playback-forwarding-github": "#[cfg(all(feature = \"proxy\", feature = \"record\"))]\n#[test]\nfn playback_github_api() {\n // Start a mock server for the test\n let server = MockServer::start();\n\n // Configure the mock server to forward requests to the target\n // host (GitHub API) instead of responding with a mock. The 'rule'\n // parameter allows you to define conditions under which forwarding\n // should occur.\n server.forward_to(\"https://api.github.com\", |rule| {\n rule.filter(|when| {\n when.any_request(); // Forward all requests.\n });\n });\n\n // Set up recording to capture all forwarded requests and responses\n let recording = server.record(|rule| {\n rule.filter(|when| {\n when.any_request(); // Record all requests and responses.\n });\n });\n\n // Send an HTTP request to the mock server, which will be forwarded\n // to the GitHub API\n let client = Client::new();\n let response = client\n .get(server.url(\"/repos/torvalds/linux\"))\n // GitHub requires a User-Agent header\n .header(\"User-Agent\", \"httpmock-test\")\n .send()\n .unwrap();\n\n // Assert that the response from the forwarded request is as expected\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n\n // Save the recorded interactions to a file\n let target_path = recording\n .save(\"github-torvalds-scenario\")\n .expect(\"Failed to save the recording to disk\");\n\n // Start a new mock server instance for playback\n let playback_server = MockServer::start();\n\n // Load the recorded interactions into the new mock server\n playback_server.playback(target_path);\n\n // Send a request to the playback server and verify the response\n // matches the recorded data\n let response = client\n .get(playback_server.url(\"/repos/torvalds/linux\"))\n .send()\n .unwrap();\n assert_eq!(response.status().as_u16(), 200);\n assert!(response.text().unwrap().contains(\"\\\"private\\\":false\"));\n}" } \ No newline at end of file diff --git a/docs/website/src/content/docs/record-and-playback/recording.mdx b/docs/website/src/content/docs/record-and-playback/recording.mdx index d46252d4..e8c1ba16 100644 --- a/docs/website/src/content/docs/record-and-playback/recording.mdx +++ b/docs/website/src/content/docs/record-and-playback/recording.mdx @@ -5,6 +5,7 @@ description: Explains how requests can be recorded. import codeExamples from "../../../../generated/example_tests.json" import { Code } from '@astrojs/starlight/components'; +import { Aside } from '@astrojs/starlight/components'; `httpmock` provides functionality to record both requests to third-party services and their responses. There are two strategies how you can achieve that: Forwarding and Proxy. @@ -34,8 +35,10 @@ server.forward_to("https://github.com", |rule| { }); ``` -> Hint: If no forwarding rule matches a request, the mock server reverts to its standard mocking strategy and attempts -to serve a configured mock response. + You can use the forwarding functionality to record requests sent to the remote service. @@ -48,8 +51,10 @@ the responses it returns. ## Proxy Strategy -> Note: This feature is currently **unstable** and is available only under the `experimental` feature -flag. There is **no guarantee** that it will be included in a future stable release. + The proxy feature in `httpmock`, while functional on its own, is particularly useful for recording in scenarios where modifying or injecting the base URL used by the client is not possible. @@ -73,8 +78,10 @@ The proxy server then tunnels or forwards the request to the target host, which When configured as a proxy, `httpmock` can intercept, record, and forward both requests and responses. -> Hint: If no proxy rule matches a request, the mock server reverts to its standard mocking strategy and attempts -to serve a configured mock response. + ### Full Example diff --git a/docs/website/src/content/docs/server/debugging.mdx b/docs/website/src/content/docs/server/debugging.mdx index 6d106d51..d2944fd1 100644 --- a/docs/website/src/content/docs/server/debugging.mdx +++ b/docs/website/src/content/docs/server/debugging.mdx @@ -2,6 +2,7 @@ title: Debugging description: Describes what features are available to make debugging easier. --- +import { Aside } from '@astrojs/starlight/components'; ## Test Failure Output @@ -75,7 +76,11 @@ This output is useful for investigating issues, like figuring out why a request The debug log level is usually the most helpful, but you can use trace to get even more details. -> **Note**: To see the log output during test execution, add the `--nocapture` argument when running your tests. + -> **Hint**: If you're using the `env_logger` backend, set the `RUST_LOG` environment variable to `httpmock=debug` -to see `httpmock` logs. \ No newline at end of file + diff --git a/docs/website/src/content/docs/server/https.mdx b/docs/website/src/content/docs/server/https.mdx index a4f8676f..4b70e3be 100644 --- a/docs/website/src/content/docs/server/https.mdx +++ b/docs/website/src/content/docs/server/https.mdx @@ -2,9 +2,12 @@ title: HTTPS description: Describes how the mock server can be configured to support HTTPS --- +import { Aside } from '@astrojs/starlight/components'; -> **Note**: This feature is currently **unstable** and there is no guarantee it will be included in a future stable release. -There is progress on stabilizing it, but at the moment, HTTPS **does not work with the proxy feature**. + By default, `httpmock` does not enable HTTPS support for testing. However, you can enable it on demand by using the Cargo feature `https`. When this feature is enabled, `httpmock` automatically uses HTTPS for all internal communication diff --git a/docs/website/src/content/docs/server/standalone.mdx b/docs/website/src/content/docs/server/standalone.mdx index 9ab08995..de1cfdbf 100644 --- a/docs/website/src/content/docs/server/standalone.mdx +++ b/docs/website/src/content/docs/server/standalone.mdx @@ -2,6 +2,7 @@ title: Standalone Server description: Describes how to set up and use a standalone mock server. --- +import { Aside } from '@astrojs/starlight/components'; You can use `httpmock` to run a standalone mock server in a separate process, such as a Docker container. This setup allows the mock server to be accessible to multiple applications, not just within your Rust tests. @@ -82,9 +83,10 @@ This means that test functions may be blocked when connecting to the remote serv This is in contrast to tests that use a local mock server where parallel test execution is possible, because each test uses its own mock server. -> **Note**: Sequential execution is only enforced on a per-process basis. This means that if multiple test runs or -applications use the remote server simultaneously, interference may still occur. - + ## Usage Without Rust @@ -108,9 +110,11 @@ then: json_body: '{ "status" : "created" }' ``` -> **Note**: Defining mocks with YAML files is straightforward because the field names directly match the corresponding -methods in the Rust API, found in the [`When`](https://docs.rs/httpmock/latest/httpmock/struct.When.html) or -[`Then`](https://docs.rs/httpmock/latest/httpmock/struct.Then.html) data structures. + Please refer to [this example file](https://github.com/alexliesenfeld/httpmock/blob/master/tests/resources/static_yaml_mock.yaml), which includes many of the usable fields. \ No newline at end of file diff --git a/docs/website/templates/matching_requests/custom.md b/docs/website/templates/matching_requests/custom.mdx similarity index 51% rename from docs/website/templates/matching_requests/custom.md rename to docs/website/templates/matching_requests/custom.mdx index 35997f06..69dda4dc 100644 --- a/docs/website/templates/matching_requests/custom.md +++ b/docs/website/templates/matching_requests/custom.mdx @@ -3,10 +3,16 @@ title: Request Matchers description: Using request matchers to specify which requests should respond. TODO --- +import { Aside } from '@astrojs/starlight/components'; + This section describes matcher functions that enable developers to implement custom matchers. These matchers execute user-defined code to determine if a request meets specific criteria. -> **Attention:** Custom matchers are **not available** when connecting to standalone mock servers (e.g., by using one of the `connect` methods, such as [`MockServer::connect`](https://docs.rs/httpmock/0.7.0/httpmock/struct.MockServer.html#method.connect)). + ## is_true {{{docs.when.is_true}}} diff --git a/src/api/adapter/remote.rs b/src/api/adapter/remote.rs index 8fecf89b..3f079db5 100644 --- a/src/api/adapter/remote.rs +++ b/src/api/adapter/remote.rs @@ -506,6 +506,7 @@ impl MockServerAdapter for RemoteMockServerAdapter { Ok(()) } + #[cfg(feature = "record")] async fn export_recording(&self, id: usize) -> Result, ServerAdapterError> { let request = Request::builder() .method("GET") @@ -531,6 +532,7 @@ impl MockServerAdapter for RemoteMockServerAdapter { Ok(Some(body)) } + #[cfg(feature = "record")] async fn create_mocks_from_recording<'a>( &self, recording_file_content: &'a str, diff --git a/src/common/http.rs b/src/common/http.rs index d3a4ac44..6501af92 100644 --- a/src/common/http.rs +++ b/src/common/http.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use bytes::Bytes; use http::{Request, Response}; use http_body_util::{BodyExt, Full}; +#[cfg(any(feature = "remote-https", feature = "https"))] use hyper_rustls::HttpsConnector; use hyper_util::{ client::legacy::{connect::HttpConnector, Client}, diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 838dde32..60d415a2 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2018" [dependencies] -httpmock = { path = ".." , features = ["full"]} +httpmock = { path = ".." , features = ["default", "standalone", "color", "cookies", "remote", "remote-https", "proxy", "https", "http2", "record", "experimental"]} serde_json = "1.0" syn = { version = "1.0", features = ["full"] } proc-macro2 = { version = "1.0", features = ["default", "span-locations"] } From ba31cf162660ffdb9d6d0a5efe0cd11741e367e9 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 22:37:44 +0200 Subject: [PATCH 06/13] Avoid copying Cargo.lock --- docs/website/astro.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/website/astro.config.mjs b/docs/website/astro.config.mjs index e6548963..6513ca72 100644 --- a/docs/website/astro.config.mjs +++ b/docs/website/astro.config.mjs @@ -3,7 +3,7 @@ import starlight from '@astrojs/starlight'; // https://astro.build/config export default defineConfig({ - site: 'https://alexliesenfeld.github.io', + site: 'https://alexliesenfeld.github.io/httpmock', base: 'httpmock', // so that 'https://alexliesenfeld.github.io/httpmock' will be set as the base path integrations: [ starlight({ From 5fe1efbd9260344f96baa3178b9aa63a712af52a Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 22:42:09 +0200 Subject: [PATCH 07/13] Avoid copying Cargo.lock --- docs/website/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/website/package.json b/docs/website/package.json index 219d842b..1faea42e 100644 --- a/docs/website/package.json +++ b/docs/website/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "astro dev", "start": "astro dev", - "build": "astro check && astro build", + "build": "npm run generate-docs && astro check && astro build", "preview": "astro preview", "astro": "astro", "generate-when-method-docs" : "rm -rf src/content/docs/matching_requests && mkdir -p src/content/docs/matching_requests && node tools/generate-docs.cjs generated/docs.json ./templates/matching_requests src/content/docs/matching_requests", From c4823c038a595b4c50a676aaeabefe6683a67194 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 22:44:28 +0200 Subject: [PATCH 08/13] Fix links --- docs/website/src/content/docs/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/website/src/content/docs/index.mdx b/docs/website/src/content/docs/index.mdx index a233b018..64975653 100644 --- a/docs/website/src/content/docs/index.mdx +++ b/docs/website/src/content/docs/index.mdx @@ -10,7 +10,7 @@ hero: light: ../../assets/logo-light.svg actions: - text: Get started - link: /getting_started/quick_introduction/ + link: /httpmock/getting_started/quick_introduction/ icon: right-arrow variant: primary - text: View on GitHub From f19f209cbd96044b9cdefc32bce157b103e91082 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 22:54:32 +0200 Subject: [PATCH 09/13] Fix links --- rustfmt.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index 78c67260..4e727a08 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1 @@ -imports_granularity="Crate" reorder_imports = true \ No newline at end of file From 559c7179d3a3ce8b3d14a03693e0f4ff99b36343 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 23:06:09 +0200 Subject: [PATCH 10/13] Fix links --- src/api/adapter/remote.rs | 18 ++++++------------ src/api/proxy.rs | 2 +- src/api/server.rs | 25 +++++++++---------------- src/server/builder.rs | 6 ++---- src/server/state.rs | 6 +----- 5 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/api/adapter/remote.rs b/src/api/adapter/remote.rs index 3f079db5..ad53b32d 100644 --- a/src/api/adapter/remote.rs +++ b/src/api/adapter/remote.rs @@ -1,9 +1,5 @@ +use crate::common::data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig}; use std::{borrow::Borrow, net::SocketAddr, sync::Arc}; -use crate::{ - common::{ - data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig} - }, -}; use crate::api::{ adapter::{ @@ -19,14 +15,12 @@ use async_trait::async_trait; use bytes::Bytes; use http::{Request, StatusCode}; -use crate::{ - common::{ - data::{ - ActiveForwardingRule, ActiveMock, ActiveProxyRule, ActiveRecording, ClosestMatch, - MockDefinition, RequestRequirements, - }, - http::HttpClient, +use crate::common::{ + data::{ + ActiveForwardingRule, ActiveMock, ActiveProxyRule, ActiveRecording, ClosestMatch, + MockDefinition, RequestRequirements, }, + http::HttpClient, }; pub struct RemoteMockServerAdapter { diff --git a/src/api/proxy.rs b/src/api/proxy.rs index 049b3436..1d1e0814 100644 --- a/src/api/proxy.rs +++ b/src/api/proxy.rs @@ -1,8 +1,8 @@ use crate::{ api::server::MockServer, common::{ - data::RequestRequirements, data::RecordingRuleConfig, + data::RequestRequirements, util::{write_file, Join}, }, When, diff --git a/src/api/server.rs b/src/api/server.rs index 950a9b52..18e4bb74 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,8 +1,6 @@ +use crate::api::spec::{Then, When}; #[cfg(feature = "remote")] use crate::api::RemoteMockServerAdapter; -use crate::api::{ - spec::{Then, When}, -}; #[cfg(feature = "remote")] use crate::common::http::HttpMockHttpClient; @@ -17,29 +15,24 @@ use crate::{ #[cfg(feature = "proxy")] use crate::{ - api::proxy::{ForwardingRuleBuilder, ForwardingRule, ProxyRuleBuilder, ProxyRule}, + api::proxy::{ForwardingRule, ForwardingRuleBuilder, ProxyRule, ProxyRuleBuilder}, common::{ - util::read_file_async, data::{ForwardingRuleConfig, ProxyRuleConfig}, + util::read_file_async, }, }; #[cfg(feature = "record")] -use crate::{ - api::{ - common::data::RecordingRuleConfig, - mock::MockSet, - proxy::{Recording, RecordingRuleBuilder} - }, +use crate::api::{ + common::data::RecordingRuleConfig, + mock::MockSet, + proxy::{Recording, RecordingRuleBuilder}, }; #[cfg(feature = "record")] -use std::{path::PathBuf}; - -use crate::{ - server::{state::HttpMockStateManager, HttpMockServerBuilder}, -}; +use std::path::PathBuf; +use crate::server::{state::HttpMockStateManager, HttpMockServerBuilder}; use crate::Mock; use async_object_pool::Pool; diff --git a/src/server/builder.rs b/src/server/builder.rs index 5b0ca71b..249e7ff5 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -1,13 +1,11 @@ #[cfg(any(feature = "proxy"))] use crate::common::http::{HttpClient, HttpMockHttpClient}; +#[cfg(any(feature = "record", feature = "record"))] +use crate::server::persistence::read_static_mock_definitions; #[cfg(feature = "https")] use crate::server::server::MockServerHttpsConfig; #[cfg(feature = "https")] use crate::server::tls::{CertificateResolverFactory, GeneratingCertificateResolverFactory}; -#[cfg(any(feature = "record", feature = "record"))] -use crate::server::{ - persistence::read_static_mock_definitions, -}; use crate::server::{ handler::HttpMockHandler, diff --git a/src/server/state.rs b/src/server/state.rs index ed17d9e9..e558fafa 100644 --- a/src/server/state.rs +++ b/src/server/state.rs @@ -15,11 +15,7 @@ use crate::{ }; #[cfg(feature = "record")] -use crate::{ - server::{ - persistence::{deserialize_mock_defs_from_yaml, serialize_mock_defs_to_yaml}, - }, -}; +use crate::server::persistence::{deserialize_mock_defs_from_yaml, serialize_mock_defs_to_yaml}; use crate::common::data::{ForwardingRuleConfig, ProxyRuleConfig, RecordingRuleConfig}; use bytes::Bytes; From 6e7fc21f78bf25f2119a04abcae901ebd8c28a61 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 23:16:11 +0200 Subject: [PATCH 11/13] Fix links --- src/server/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/builder.rs b/src/server/builder.rs index 249e7ff5..5c1479e2 100644 --- a/src/server/builder.rs +++ b/src/server/builder.rs @@ -1,4 +1,4 @@ -#[cfg(any(feature = "proxy"))] +#[cfg(feature = "proxy")] use crate::common::http::{HttpClient, HttpMockHttpClient}; #[cfg(any(feature = "record", feature = "record"))] use crate::server::persistence::read_static_mock_definitions; From 9a4b6d9cfe9f45686718744eac84fcf9eada5e4c Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sat, 31 Aug 2024 23:45:24 +0200 Subject: [PATCH 12/13] Refactor feature configuration --- README.md | 2 +- tests/examples/record_and_playback_tests.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d82d24e8..f3bbb638 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@

httpmock

-

HTTP mocking library for Rust.

+

Simple yet powerful HTTP mocking library for Rust

[![Build](https://github.com/alexliesenfeld/httpmock/actions/workflows/build.yml/badge.svg)](https://github.com/alexliesenfeld/httpmock/actions/workflows/build.yml) @@ -37,17 +37,16 @@ * Simple, expressive, fluent API. * Many built-in helpers for easy request matching ([Regex](https://docs.rs/regex/), JSON, [serde](https://crates.io/crates/serde), cookies, and more). -* Parallel test execution. -* Custom request matchers. * Record and Playback * Forward and Proxy Mode * HTTPS support * Fault and network delay simulation. +* Custom request matchers. * Standalone mode with an accompanying [Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock). * Helpful error messages * [Advanced verification and debugging support](https://alexliesenfeld.github.io/posts/mocking-http--services-in-rust/#creating-mocks) (including diff generation between actual and expected HTTP request values) +* Parallel test execution. * Fully asynchronous core with synchronous and asynchronous APIs. -* Support for [Regex](https://docs.rs/regex/) matching, JSON, [serde](https://crates.io/crates/serde), cookies, and more. * Support for [mock configuration using YAML files](https://github.com/alexliesenfeld/httpmock/tree/master#file-based-mock-specification). ## Getting Started @@ -92,32 +91,39 @@ The above example will spin up a lightweight HTTP mock server and configure it t to path `/translate` with query parameter `word=hello`. The corresponding HTTP response will contain the text body `Привет`. -In case the request fails, `httpmock` would show you a detailed error description including a diff between the -expected and the actual HTTP request: +When the specified expectations do not match the received request, `httpmock` provides a detailed error description, +including a diff that shows the differences between the expected and actual HTTP requests. Example: -![colored-diff.png](https://raw.githubusercontent.com/alexliesenfeld/httpmock/master/docs/diff.png) +```bash +0 of 1 expected requests matched the mock specification. +Here is a comparison with the most similar unmatched request (request number 1): -# Usage +------------------------------------------------------------ +1 : Query Parameter Mismatch +------------------------------------------------------------ +Expected: + key [equals] word + value [equals] hello-rustaceans -See the [reference docs](https://docs.rs/httpmock/) for detailed API documentation. +Received (most similar query parameter): + word=hello -## Examples +All received query parameter values: + 1. word=hello -You can find examples in the -[`httpmock` test directory](https://github.com/alexliesenfeld/httpmock/blob/master/tests/). -The [reference docs](https://docs.rs/httpmock/) also contain _**a lot**_ of examples. There is an [online tutorial](https://alexliesenfeld.com/mocking-http-services-in-rust) as well. +Matcher: query_param +Docs: https://docs.rs/httpmock/0.8.0/httpmock/struct.When.html#method.query_param +``` -## Standalone Mock Server +# Usage -You can use `httpmock` to run a standalone mock server that is executed in a separate process. There is a -[Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock) available at Dockerhub to get started quickly. +See the [official documentation](http://alexliesenfeld.github.io/httpmock) for detailed API documentation. -The standalone mode allows you to mock HTTP based APIs for many API clients, not only the ones -inside your Rust tests, but also completely different programs running on remote hosts. -This is especially useful if you want to use `httpmock` in system or end-to-end tests that require mocked services -(such as REST APIs, data stores, authentication providers, etc.). +## Examples -Please refer to [the docs](https://docs.rs/httpmock/0.5.8/httpmock/#standalone-mode) for more information +You can find examples in the +[`httpmock` test directory](https://github.com/alexliesenfeld/httpmock/blob/master/tests/). +The [official documentation](http://alexliesenfeld.github.io/httpmock) and [reference docs](https://docs.rs/httpmock/) also contain _**a lot**_ of examples. ## License diff --git a/src/lib.rs b/src/lib.rs index a12a58ed..c029fca3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,17 +4,16 @@ //! # Features //! * Simple, expressive, fluent API. //! * Many built-in helpers for easy request matching ([Regex](https://docs.rs/regex/), JSON, [serde](https://crates.io/crates/serde), cookies, and more). -//! * Parallel test execution. -//! * Custom request matchers. //! * Record and Playback //! * Forward and Proxy Mode //! * HTTPS support //! * Fault and network delay simulation. +//! * Custom request matchers. //! * Standalone mode with an accompanying [Docker image](https://hub.docker.com/r/alexliesenfeld/httpmock). //! * Helpful error messages //! * [Advanced verification and debugging support](https://alexliesenfeld.github.io/posts/mocking-http--services-in-rust/#creating-mocks) (including diff generation between actual and expected HTTP request values) +//! * Parallel test execution. //! * Fully asynchronous core with synchronous and asynchronous APIs. -//! * Support for [Regex](https://docs.rs/regex/) matching, JSON, [serde](https://crates.io/crates/serde), cookies, and more. //! * Support for [mock configuration using YAML files](https://github.com/alexliesenfeld/httpmock/tree/master#file-based-mock-specification). //! //! # Getting Started @@ -52,22 +51,48 @@ //! assert_eq!(response.status(), 200); //! ``` //! -//! In case the request fails, `httpmock` would show you a detailed error description including a diff between the -//! expected and the actual HTTP request: +//! When the specified expectations do not match the received request, `httpmock` provides a detailed error description, +//! including a diff that shows the differences between the expected and actual HTTP requests. Example: +//! +//! ```bash +//! 0 of 1 expected requests matched the mock specification. +//! Here is a comparison with the most similar unmatched request (request number 1): +//! +//! ------------------------------------------------------------ +//! 1 : Query Parameter Mismatch +//! ------------------------------------------------------------ +//! Expected: +//! key [equals] word +//! value [equals] hello-rustaceans +//! +//! Received (most similar query parameter): +//! word=hello +//! +//! All received query parameter values: +//! 1. word=hello +//! +//! Matcher: query_param +//! Docs: https://docs.rs/httpmock/0.8.0/httpmock/struct.When.html#method.query_param +//! ``` +//! +//! # Usage +//! +//! See the [official documentation](http://alexliesenfeld.github.io/httpmock) a for detailed +//! API documentation. +//! +//! ## Examples //! -//! ![colored-diff.png](https://raw.githubusercontent.com/alexliesenfeld/httpmock/master/docs/diff.png) +//! You can find examples in the +//! [`httpmock` test directory](https://github.com/alexliesenfeld/httpmock/blob/master/tests/). +//! The [official documentation](http://alexliesenfeld.github.io/httpmock) and [reference docs](https://docs.rs/httpmock/) +//! also contain _**a lot**_ of examples. //! -//! # Online Documentation -//! Please find the official `httpmock` documentation and website at: http://alexliesenfeld.github.io/httpmock +//! ## License //! -//! # License -//! `httpmock` is free software: you can redistribute it and/or modify it under the terms -//! of the MIT Public License. +//! `httpmock` is free software: you can redistribute it and/or modify it under the terms of the MIT Public License. //! -//! This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -//! without even the implied -//! warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MIT Public -//! License for more details. +//! This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied +//! warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MIT Public License for more details. extern crate lazy_static; use std::{borrow::BorrowMut, net::ToSocketAddrs};

- Online Documentation + Documentation · API Reference · diff --git a/tests/examples/record_and_playback_tests.rs b/tests/examples/record_and_playback_tests.rs index fcff934e..c024887f 100644 --- a/tests/examples/record_and_playback_tests.rs +++ b/tests/examples/record_and_playback_tests.rs @@ -48,7 +48,7 @@ fn record_with_forwarding_test() { } // @example-start: record-proxy-github -#[cfg(all(feature = "proxy", feature = "record", feature = "experimental"))] +#[cfg(all(feature = "proxy", feature = "experimental"))] #[test] fn record_with_proxy_test() { // Start a mock server to act as a proxy for the HTTP client @@ -91,7 +91,7 @@ fn record_with_proxy_test() { // @example-end // @example-start: record-forwarding-github -#[cfg(all(feature = "proxy", feature = "record"))] +#[cfg(feature = "record")] #[test] fn record_github_api_with_forwarding_test() { // Let's create our mock server for the test @@ -146,7 +146,7 @@ fn record_github_api_with_forwarding_test() { // @example-end // @example-start: playback-forwarding-github -#[cfg(all(feature = "proxy", feature = "record"))] +#[cfg(feature = "record")] #[test] fn playback_github_api() { // Start a mock server for the test From 58a77a401f643621b60e8b897152e13be8dc1409 Mon Sep 17 00:00:00 2001 From: Alexander Liesenfeld Date: Sun, 1 Sep 2024 09:55:07 +0200 Subject: [PATCH 13/13] Improve docs --- README.md | 48 ++++++++++++++++++++++++++--------------------- src/lib.rs | 55 +++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index f3bbb638..d4ddf4f8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@