From 0f0fb337761ce2b132a70b82ba0ac4246e62ad7b Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 26 Dec 2023 08:46:02 +0200 Subject: [PATCH 01/25] add fission-codes rust-template --- CHANGELOG.md | 5 + CODE_OF_CONDUCT.md | 107 +- CONTRIBUTING.md | 232 +- Cargo.lock | 4226 ++++++++++++++++++++++++++- Cargo.toml | 116 +- Dockerfile | 79 + LICENSE-APACHE | 377 +-- LICENSE-MIT | 40 +- README.md | 523 +++- SECURITY.md | 17 + assets/a_logo.png | Bin 0 -> 19259 bytes benches/a_benchmark.rs | 16 + config/settings.toml | 11 + deny.toml | 200 ++ docs/specs/latest.json | 99 + flake.lock | 91 +- flake.nix | 104 +- rust-toolchain.toml | 2 + src/bin/openapi.rs | 35 + src/docs.rs | 21 + src/error.rs | 217 ++ src/extract/json.rs | 315 ++ src/extract/mod.rs | 3 + src/headers/header.rs | 109 + src/headers/mod.rs | 3 + src/lib.rs | 26 + src/main.rs | 213 +- src/metrics/mod.rs | 4 + src/metrics/process.rs | 117 + src/metrics/prom.rs | 23 + src/middleware/client/metrics.rs | 88 + src/middleware/client/mod.rs | 3 + src/middleware/logging.rs | 368 +++ src/middleware/metrics.rs | 29 + src/middleware/mod.rs | 10 + src/middleware/request_ext.rs | 45 + src/middleware/request_ulid.rs | 21 + src/middleware/reqwest_retry.rs | 180 ++ src/middleware/reqwest_tracing.rs | 32 + src/middleware/runtime.rs | 56 + src/router.rs | 24 + src/routes/fallback.rs | 9 + src/routes/health.rs | 18 + src/routes/mod.rs | 5 + src/routes/ping.rs | 18 + src/settings.rs | 245 ++ src/test_utils/mod.rs | 5 + src/test_utils/rvg.rs | 63 + src/tracer.rs | 63 + src/tracing_layers/format_layer.rs | 672 +++++ src/tracing_layers/metrics_layer.rs | 84 + src/tracing_layers/mod.rs | 9 + src/tracing_layers/storage_layer.rs | 195 ++ tests/integration_test.rs | 118 + 54 files changed, 9343 insertions(+), 348 deletions(-) create mode 100644 Dockerfile create mode 100644 SECURITY.md create mode 100644 assets/a_logo.png create mode 100644 benches/a_benchmark.rs create mode 100644 config/settings.toml create mode 100644 deny.toml create mode 100644 docs/specs/latest.json create mode 100644 rust-toolchain.toml create mode 100644 src/bin/openapi.rs create mode 100644 src/docs.rs create mode 100644 src/error.rs create mode 100644 src/extract/json.rs create mode 100644 src/extract/mod.rs create mode 100644 src/headers/header.rs create mode 100644 src/headers/mod.rs create mode 100644 src/lib.rs create mode 100644 src/metrics/mod.rs create mode 100644 src/metrics/process.rs create mode 100644 src/metrics/prom.rs create mode 100644 src/middleware/client/metrics.rs create mode 100644 src/middleware/client/mod.rs create mode 100644 src/middleware/logging.rs create mode 100644 src/middleware/metrics.rs create mode 100644 src/middleware/mod.rs create mode 100644 src/middleware/request_ext.rs create mode 100644 src/middleware/request_ulid.rs create mode 100644 src/middleware/reqwest_retry.rs create mode 100644 src/middleware/reqwest_tracing.rs create mode 100644 src/middleware/runtime.rs create mode 100644 src/router.rs create mode 100644 src/routes/fallback.rs create mode 100644 src/routes/health.rs create mode 100644 src/routes/mod.rs create mode 100644 src/routes/ping.rs create mode 100644 src/settings.rs create mode 100644 src/test_utils/mod.rs create mode 100644 src/test_utils/rvg.rs create mode 100644 src/tracer.rs create mode 100644 src/tracing_layers/format_layer.rs create mode 100644 src/tracing_layers/metrics_layer.rs create mode 100644 src/tracing_layers/mod.rs create mode 100644 src/tracing_layers/storage_layer.rs create mode 100644 tests/integration_test.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ef88a6b..9dc5bc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### upcoming changes. + +## v1.0.1 +add fission-codes/rust-template. + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 278b1f2..31be868 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,108 @@ # Code of Conduct -This project adheres to the Rust Code of Conduct, which can be found [here](https://www.rust-lang.org/conduct.html). +**TL;DR Be kind, inclusive, and considerate.** + +In the interest of fostering an open, inclusive, and welcoming environment, all +members, contributors, and maintainers interacting within our online community +(including Discord, Discourse, etc.), on affiliated projects and repositories +(including issues, pull requests, and discussions on Github), and/or involved +with associated events pledge to accept and observe the following Code of +Conduct. + +As members, contributors, and maintainers, we pledge to make participation in +our projects and community a harassment-free experience, ensuring a safe +environment for all, regardless of background, gender, gender identity and +expression, age, sexual orientation, disability, physical appearance, body size, +race, ethnicity, religion (or lack thereof), or any other dimension of +diversity. + +Sexual language and imagery will not be accepted in any way. Be kind to others. +Do not insult or put down people within the community. Behave professionally. +Remember that harassment and sexist, racist, or exclusionary jokes are not +appropriate in any form. Participants violating these rules may be sanctioned or +expelled from the community and related projects. + +## Spelling it out. + +Harassment includes offensive verbal comments or actions related to or involving + +- background +- gender +- gender identity and expression +- age +- sexual orientation +- disability +- physical appearance +- body size +- race +- ethnicity +- religion (or lack thereof) +- economic status +- geographic location +- technology choices +- sexual imagery +- deliberate intimidation +- violence and threats of violence +- stalking +- doxing +- inappropriate or unwelcome physical contact in public spaces +- unwelcomed sexual attention +- influencing unacceptable behavior +- any other dimension of diversity + +## Our Responsibilities + +Maintainers of the community and associated projects are not only subject to the +anti-harassment policy, but also responsible for executing the policy, +moderating related forums, and for taking appropriate and fair corrective action +in response to any instances of unacceptable behavior that breach the policy. + +Maintainers have the right to remove and reject comments, threads, commits, +code, documentation, pull requests, issues, and contributions not aligned with +this Code of Conduct. + +## Scope + +This Code of Conduct applies within all project and community spaces, as well as +in any public spaces where an individual representing the community is involved. +This covers + +- Interactions on the Github repository, including discussions, issues, pull + requests, commits, and wikis +- Interactions on any affiliated Discord, Slack, IRC, or related online + communities and forums like Discourse, etc. +- Any official project emails and social media posts +- Individuals representing the community at public events like meetups, talks, + and presentations + +## Enforcement + +All instances of abusive, harassing, or otherwise unacceptable behavior should +be reported by contacting the project and community maintainers at +[alexeusgr@gmail.com][support-email]. All complaints will be reviewed and +investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. + +Maintainers of the community and associated projects are obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Anyone asked to stop abusive, harassing, or otherwise unacceptable behavior are +expected to comply immediately and accept the response decided on by the +maintainers of the community and associated projects. + +## Need help? + +If you are experiencing harassment, witness an incident or have concerns about +content please contact us at [alexeusgr@gmail.com][support-email]. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant, v2.1][contributor-cov], +among other sources like [!!con’s Code of Conduct][!!con] and +[Mozilla’s Community Participation Guidelines][mozilla]. + +[!!con]: https://bangbangcon.com/conduct.html +[contributor-cov]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ +[mozilla]: https://www.mozilla.org/en-US/about/governance/policies/participation/ +[support-email]: mailto:alexeusgr@gmail.com diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cc0177..fe8babc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,80 +1,152 @@ -# Contribution guidelines - -First off, thank you for considering contributing to efected-cotoemory. - -If your contribution is not straightforward, please first discuss the change you -wish to make by creating a new issue before making the change. - -## Reporting issues - -Before reporting an issue on the -[issue tracker](https://github.com/aleeusgr/efected-cotoemory/issues), -please check that it has not already been reported by searching for some related -keywords. - -## Pull requests - -Try to do one pull request per change. - -### Updating the changelog - -Update the changes you have made in -[CHANGELOG](https://github.com/aleeusgr/efected-cotoemory/blob/main/CHANGELOG.md) -file under the **Unreleased** section. - -Add the changes of your pull request to one of the following subsections, -depending on the types of changes defined by -[Keep a changelog](https://keepachangelog.com/en/1.0.0/): - -- `Added` for new features. -- `Changed` for changes in existing functionality. -- `Deprecated` for soon-to-be removed features. -- `Removed` for now removed features. -- `Fixed` for any bug fixes. -- `Security` in case of vulnerabilities. - -If the required subsection does not exist yet under **Unreleased**, create it! - -## Developing - -### Set up - -This is no different than other Rust projects. - -```shell -git clone https://github.com/aleeusgr/efected-cotoemory -cd efected-cotoemory -cargo test -``` - -### Useful Commands - -- Build and run release version: - - ```shell - cargo build --release && cargo run --release - ``` - -- Run Clippy: - - ```shell - cargo clippy --all-targets --all-features --workspace - ``` - -- Run all tests: - - ```shell - cargo test --all-features --workspace - ``` - -- Check to see if there are code formatting issues - - ```shell - cargo fmt --all -- --check - ``` - -- Format the code in the project - - ```shell - cargo fmt --all - ``` +# Contributing to efected-coto-emmory + +We welcome everyone to contribute what and where they can. Whether you are brand +new, just want to contribute a little bit, or want to contribute a lot there is +probably something you can help out with. Check out our +[good first issues][good-first-issues] label for in the issues tab to see a list +of issue that good for those new to the project. + +## Where to Get Help + +The main way to get help is by sending an email to [alexeusgr@gmail.com][support-email]. +Though, this guide should help you get started. It may be slightly lengthy, but it's +designed for those who are new so please don't let length intimidate you. + +## Code of Conduct + +Please be kind, inclusive, and considerate when interacting when interacting +with others and follow our [code of conduct](./CODE_OF_CONDUCT.md). + +## How to Contribute + +If the code adds a feature that is not already present in an issue, you can +create a new issue for the feature and add the pull request to it. If the code +adds a feature that is not already present in an issue, you can create a new +issue for the feature and add the pull request to it. + +### Contributing by Adding a Topic for Discussion + +#### Issues + +If you have found a bug and would like to report it or if you have a feature +that you feel we should add, then we'd love it if you opened an issue! ❤️ +Before you do, please search the other issues to avoid creating a duplicate +issue. + +To submit a new issue just hit the issue button and a choice between two +templates should appear. Then, follow along with the template you chose. If you +don't know how to fill in all parts of the template go ahead and skip those +parts. You can edit the issue later. + +#### Discussion + +If you have a new discussion you want to start but it isn't a bug or feature +add, then you can start a [GitHub discussion][gh-discussions]. Some examples of +what kinds of things that are good discussion topics can include, but are not +limited to the following: + +- Community announcements and/or asking the community for feedback +- Discussing a new release +- Asking questions, Q&A that isn't for sure a bug report + +### Contributing through Code + +In order to contribute through code follow the steps below. Note that you don't +need to be the best programmer to contribute. + + 1. **Pick a feature** you would like to add or a bug you would like to fix + - If you wish to contribute but what you want to fix/add is not already + covered in an existing issue, please open a new issue. + + 2. **Discuss** the issue with the rest of the community + - Before you write any code, it is recommended that you discuss your + intention to write the code on the issue you are attempting to edit. + - This helps to stop you from wasting your time duplicating the work of + others that maybe working on the same issue; at the same time. + - This step also allows you to get helpful pointers on the community on some + problems they may have encountered on similar issues. + + 3. **Fork** the repository + - A fork creates a copy of the code on your Github, so you can work on it + separately from everyone else. + - You can learn more about forking [here][forking]. + + 4. Ensure that you have **commit signing** enabled + - This ensures that the code you submit was committed by you and not someone + else who claims to be you. + - You can learn more about how to setup commit signing [here][commit-signing]. + - If you have already made some commits that you wish to put in a pull + request without signing them, then you can follow [this guide][post-signing] + on how to fix that. + + 5. **Clone** the repository to your local computer + - This puts a copy of your fork on your computer so you can edit it + - You can learn more about cloning repositories [here][git-clone]. + + 6. **Build** the project + - For a detailed look on how to build efected-coto-emmory look at our + [README file](./README.md). + + 7. **Start writing** your code + - Open up your favorite code editor and make the changes that you wanted to + make to the repository. + - Make sure to test your code with the test command(s) found in our + [README file](./README.md). + + 8. **Write tests** for your code + - If you are adding a new feature, you should write tests that ensure that + if someone make changes to the code it cannot break your new feature + without breaking the test. + - If your code adds a new feature, you should also write at least one + documentation test. The documentation test's purpose is to demonstrate and + document how to use the API feature. + - If your code fixes a bug, you should write tests that ensure that if + someone makes code changes in the future the bug does not re-emerge + without breaking test. + - Please create integration tests, if the addition is large enough to + warrant them, and unit tests. + * Unit tests are tests that ensure the functionality of a single + function or small section of code. + * Integration tests test large large sections of code. + * Read more about the differences [here][unit-and-integration]. + - For more information on test organization, take a look [here][test-org]. + + 9. Ensure that the code that you made follows our Rust **coding guidelines** + - You can find a list of some Rust guidelines [here][rust-style-guide]. This + is a courtesy to the programmers that come after you. The easier your code + is to read, the easier it will be for the next person to make modifications. + - If you find it difficult to follow the guidelines or if the guidelines or + unclear, please reach out to us through our email linked above, or you + can just continue and leave a comment at the pull request stage. + + 10. **Commit and Push** your code + - This sends your changes to your repository branch. + - You can learn more about committing code [here][commiting-code] and + pushing it to a remote repository [here][push-remote]. + - We use conventional commits for the names and description of commits. + You can find out more about them [here][conventional-commits]. + + 11. The final step is to create **pull request** to our main branch 🎉 + - A pull request is how you merge the code you just worked so hard on with + the code everyone else has access to. + - Once you have submitted your pull request, we will review your code and + check to make sure the code implements the feature or fixes the bug. We + may leave some feedback and suggest edits. You can make the changes we + suggest by committing more code to your fork. + - You can learn more about pull requests [here][prs]. + + +[conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ +[commiting-code]: https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project +[commit-signing]: https://www.freecodecamp.org/news/what-is-commit-signing-in-git/ +[forking]: https://docs.github.com/en/get-started/quickstart/fork-a-repo +[gh-discussions]: https://docs.github.com/en/discussions +[git-clone]: https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository +[good-first-issues]: [https://build.prestashop-project.org/news/a-definition-of-the-good-first-issue-label/] +[post-signing]: https://dev.to/jmarhee/signing-existing-commits-with-gpg-5b58 +[prs]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests +[push-remote]: https://docs.github.com/en/get-started/using-git/pushing-commits-to-a-remote-repository +[rust-style-guide]: https://rust-lang.github.io/api-guidelines/about.html +[test-org]: https://doc.rust-lang.org/book/ch11-03-test-organization.html +[support-email]: mailto:alexeusgr@gmail.com +[unit-and-integration]: https://www.geeksforgeeks.org/difference-between-unit-testing-and-integration-testing/ diff --git a/Cargo.lock b/Cargo.lock index 0b3ce8f..e830733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,5 +3,4229 @@ version = 3 [[package]] -name = "efected-cotoemory" +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom 0.2.11", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" +dependencies = [ + "backtrace", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "async-trait" +version = "0.1.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-tracing-opentelemetry" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164b95427e83b79583c7699a72b4a6b485a12bbdef5b5c054ee5ff2296d82f52" +dependencies = [ + "axum", + "futures", + "http", + "opentelemetry 0.18.0", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "tower", + "tower-http 0.3.5", + "tracing", + "tracing-opentelemetry 0.18.0", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.48.5", +] + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "console-api" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2895653b4d9f1538a83970077cb01dfc77a4810524e51a110944688e916b18e" +dependencies = [ + "prost", + "prost-types", + "tonic 0.9.2", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cf42660ac07fcebed809cfe561dd8730bcd35b075215e6479c516bcd0d11cb" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures", + "hdrhistogram", + "humantime", + "parking_lot 0.12.1", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic 0.9.2", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.43", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "deadpool" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +dependencies = [ + "async-trait", + "deadpool-runtime", + "num_cpus", + "retain_mut", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "efected-coto-emmory" +version = "0.1.0" +dependencies = [ + "ansi_term", + "anyhow", + "assert-json-diff", + "async-trait", + "axum", + "axum-tracing-opentelemetry", + "base64 0.21.5", + "chrono", + "config", + "console-subscriber", + "const_format", + "criterion", + "futures", + "headers", + "http", + "http-serde", + "hyper", + "metrics", + "metrics-exporter-prometheus", + "metrics-util", + "mime", + "num_cpus", + "once_cell", + "openssl", + "opentelemetry 0.18.0", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "parking_lot 0.12.1", + "proptest", + "reqwest", + "reqwest-middleware", + "reqwest-retry", + "reqwest-tracing", + "retry-policies", + "rsa", + "serde", + "serde_json", + "serde_path_to_error", + "serde_with", + "sysinfo", + "task-local-extensions", + "thiserror", + "time", + "tokio", + "tokio-test", + "tonic 0.8.3", + "tower", + "tower-http 0.4.4", + "tracing", + "tracing-appender", + "tracing-opentelemetry 0.18.0", + "tracing-subscriber", + "ulid", + "url", + "utoipa", + "utoipa-swagger-ui", + "wiremock", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.5", + "byteorder", + "flate2", + "nom", + "num-traits", +] + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.5", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "http-serde" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f560b665ad9f1572cfcaf034f7fb84338a7ce945216d64a90fd81f046a3caee" +dependencies = [ + "http", + "serde", +] + +[[package]] +name = "http-types" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" +dependencies = [ + "anyhow", + "async-channel", + "base64 0.13.1", + "futures-lite", + "http", + "infer", + "pin-project-lite", + "rand 0.7.3", + "serde", + "serde_json", + "serde_qs", + "serde_urlencoded", + "url", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "infer" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "metrics" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849" +dependencies = [ + "ahash", + "metrics-macros", + "portable-atomic 0.3.20", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8603921e1f54ef386189335f288441af761e0fc61bcb552168d9cedfe63ebc70" +dependencies = [ + "hyper", + "indexmap 1.9.3", + "ipnet", + "metrics", + "metrics-util", + "parking_lot 0.12.1", + "portable-atomic 0.3.20", + "quanta", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "metrics-util" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d24dc2dbae22bff6f1f9326ffce828c9f07ef9cc1e8002e5279f845432a30a" +dependencies = [ + "aho-corasick 0.7.20", + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "metrics", + "num_cpus", + "ordered-float", + "parking_lot 0.12.1", + "portable-atomic 0.3.20", + "quanta", + "radix_trie", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.3", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "openssl" +version = "0.10.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.2.1+3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fe476c29791a5ca0d1273c697e96085bbabbbea2ef7afd5617e78a4b40332d3" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "opentelemetry" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d6c3d7288a106c0a363e4b0e8d308058d56902adefb16f4936f417ffef086e" +dependencies = [ + "opentelemetry_api", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry-http" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc79add46364183ece1a4542592ca593e6421c60807232f5b8f7a31703825d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry_api", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c928609d087790fc936a1067bdc310ae702bdf3b090c3f281b713622c8bbde" +dependencies = [ + "async-trait", + "futures", + "futures-util", + "http", + "opentelemetry 0.18.0", + "opentelemetry-http", + "opentelemetry-proto", + "prost", + "thiserror", + "tokio", + "tonic 0.8.3", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61a2f56df5574508dd86aaca016c917489e589ece4141df1b5e349af8d66c28" +dependencies = [ + "futures", + "futures-util", + "opentelemetry 0.18.0", + "prost", + "tonic 0.8.3", + "tonic-build", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b02e0230abb0ab6636d18e2ba8fa02903ea63772281340ccac18e0af3ec9eeb" +dependencies = [ + "opentelemetry 0.18.0", +] + +[[package]] +name = "opentelemetry_api" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c24f96e21e7acc813c7a8394ee94978929db2bcc46cf6b5014fc612bf7760c22" +dependencies = [ + "fnv", + "futures-channel", + "futures-util", + "indexmap 1.9.3", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca41c4933371b61c2a2f214bf16931499af4ec90543604ec828f7a625c09113" +dependencies = [ + "async-trait", + "crossbeam-channel", + "dashmap", + "fnv", + "futures-channel", + "futures-executor", + "futures-util", + "once_cell", + "opentelemetry_api", + "percent-encoding", + "rand 0.8.5", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der", + "pkcs8", + "spki", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "portable-atomic" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" +dependencies = [ + "portable-atomic 1.6.0", +] + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.1", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax 0.8.2", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] + +[[package]] +name = "quanta" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e31331286705f455e56cca62e0e717158474ff02b7936c1fa596d983f4ae27" +dependencies = [ + "crossbeam-utils", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "wasi 0.10.2+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.11", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom 0.2.11", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick 1.1.2", + "memchr", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick 1.1.2", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "reqwest-middleware" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a3e86aa6053e59030e7ce2d2a3b258dd08fc2d337d52f73f6cb480f5858690" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "task-local-extensions", + "thiserror", +] + +[[package]] +name = "reqwest-retry" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6a11c05102e5bec712c0619b8c7b7eda8b21a558a0bd981ceee15c38df8be4" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "futures", + "getrandom 0.2.11", + "http", + "hyper", + "parking_lot 0.11.2", + "reqwest", + "reqwest-middleware", + "retry-policies", + "task-local-extensions", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "reqwest-tracing" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b1e66540e0cac90acadaf7109bf99c90d95abcc94b4c096bfa16a2d7aa7a71" +dependencies = [ + "anyhow", + "async-trait", + "getrandom 0.2.11", + "matchit", + "opentelemetry 0.17.0", + "reqwest", + "reqwest-middleware", + "task-local-extensions", + "tracing", + "tracing-opentelemetry 0.17.4", +] + +[[package]] +name = "retain_mut" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" + +[[package]] +name = "retry-policies" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b" +dependencies = [ + "anyhow", + "chrono", + "rand 0.8.5", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.11", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rsa" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a77d189da1fee555ad95b7e50e7457d91c0e089ec68ca69ad2989413bbdab4" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-embed" +version = "6.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 2.0.43", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.7", + "untrusted 0.9.0", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.5", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "sketches-ddsketch" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a406c1882ed7f29cd5e248c9848a80e7cb6ae0fea82346d2746f2f941c07e1" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sysinfo" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "task-local-extensions" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" +dependencies = [ + "pin-utils", +] + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand 2.0.1", + "redox_syscall 0.4.1", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "thiserror" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.13.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "rustls-native-certs", + "rustls-pemfile", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +dependencies = [ + "async-trait", + "axum", + "base64 0.21.5", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" +dependencies = [ + "once_cell", + "opentelemetry 0.17.0", + "tracing", + "tracing-core", + "tracing-log 0.1.4", + "tracing-subscriber", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ebb87a95ea13271332df069020513ab70bdb5637ca42d6e492dc3bbbad48de" +dependencies = [ + "once_cell", + "opentelemetry 0.18.0", + "tracing", + "tracing-core", + "tracing-log 0.1.4", + "tracing-subscriber", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "parking_lot 0.12.1", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log 0.2.0", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "ulid" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e37c4b6cbcc59a8dcd09a6429fbc7890286bcbb79215cea7b38a3c4c0921d93" +dependencies = [ + "rand 0.8.5", + "serde", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utoipa" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82b1bc5417102a73e8464c686eef947bdfb99fcdfc0a4f228e81afa9526470a" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d96dcd6fc96f3df9b3280ef480770af1b7c5d14bc55192baa9b067976d920c" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.43", + "uuid", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84614caa239fb25b2bb373a52859ffd94605ceb256eeb1d63436325cf81e3653" +dependencies = [ + "axum", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "utoipa", + "zip", +] + +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom 0.2.11", + "serde", +] + +[[package]] +name = "valuable" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.43", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.7", + "untrusted 0.9.0", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wiremock" +version = "0.5.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a3a53eaf34f390dd30d7b1b078287dd05df2aa2e21a589ccb80f5c7253c2e9" +dependencies = [ + "assert-json-diff", + "async-trait", + "base64 0.21.5", + "deadpool", + "futures", + "futures-timer", + "http-types", + "hyper", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] diff --git a/Cargo.toml b/Cargo.toml index d7e50e1..3f15390 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,117 @@ [package] -name = "efected-cotoemory" +name = "efected-coto-emmory" version = "0.1.0" +description = "a cloud service" +keywords = [] +categories = [] +include = ["/src", "/benches", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] +license = "Apache-2.0 or MIT" +readme = "README.md" edition = "2021" -description = "a web service" -repository = "https://github.com/aleeusgr/efected-cotoemory" -license = "MIT OR Apache-2.0" +rust-version = "1.67" +documentation = "https://docs.rs/efected-coto-emmory" +repository = "https://github.com/aleeusgr/efected-coto-emmory" +authors = ["Alex "] +default-run = "efected-coto-emmory-app" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +path = "src/lib.rs" +bench = false +doctest = true + +[[bin]] +name = "efected-coto-emmory-app" +path = "src/main.rs" +doc = false +bench = false + +[[bin]] +name = "openapi" +path = "src/bin/openapi.rs" +test = false +doc = false +bench = false + +[[bench]] +name = "a_benchmark" +harness = false +required-features = ["test_utils"] [dependencies] +ansi_term = { version = "0.12", optional = true, default-features = false } +anyhow = { version = "1.0", features = ["backtrace"] } +async-trait = "0.1" +axum = { version = "0.6", features = ["headers"] } +axum-tracing-opentelemetry = { version = "0.10", features = ["otlp"] } +base64 = "0.21" +chrono = { version = "0.4", default-features = false, features = ["clock"] } +config = "0.13" +console-subscriber = { version = "0.1", default-features = false, features = [ "parking_lot" ], optional = true } +const_format = "0.2" +futures = "0.3" +headers = "0.3" +http = "0.2" +http-serde = "1.1" +hyper = "0.14" +metrics = "0.20" +metrics-exporter-prometheus = "0.11" +metrics-util = { version = "0.14", default-features = true } +mime = "0.3" +num_cpus = "1.0" +once_cell = "1.14" +openssl = { version = "0.10", features = ["vendored"], default-features = false } +opentelemetry = { version = "0.18", features = ["rt-tokio", "trace"] } +opentelemetry-otlp = { version = "0.11", features = ["metrics", "grpc-tonic", "tls-roots"], default-features = false } +opentelemetry-semantic-conventions = "0.10" +parking_lot = "0.12" +proptest = { version = "1.1", optional = true } +reqwest = { version = "0.11", features = ["json"] } +reqwest-middleware = "0.2" +reqwest-retry = "0.2" +reqwest-tracing = { version = "0.4", features = ["opentelemetry_0_17"] } +retry-policies = "0.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_path_to_error = "0.1" +serde_with = "3.0" +sysinfo = "0.28" +task-local-extensions = "0.1" +thiserror = "1.0" +time = { version = "0.3", features = ["serde-well-known", "serde-human-readable"] } +tokio = { version = "1.26", features = ["full", "parking_lot"] } +## Tied to opentelemetry-otlp dependency +tonic = { version = "0.8" } +tower = "0.4" +tower-http = { version = "0.4", features = ["catch-panic", "request-id", "sensitive-headers", "timeout", "trace", "util"] } +tracing = "0.1" +tracing-appender = "0.2" +tracing-opentelemetry = "0.18" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json", "parking_lot", "registry"] } +ulid = { version = "1.0", features = ["serde"] } +url = "2.3" +utoipa = { version = "3.3", features = ["uuid", "axum_extras"] } +utoipa-swagger-ui = { version = "3.1", features = ["axum"] } + +[dev-dependencies] +assert-json-diff = "2.0" +criterion = "0.4" +proptest = "1.1" +rsa = { version = "0.8" } +tokio-test = "0.4" +wiremock = "0.5" + +[features] +ansi-logs = ["ansi_term"] +console = ["console-subscriber"] +default = [] +test_utils = ["proptest"] + +[package.metadata.docs.rs] +all-features = true +# defines the configuration attribute `docsrs` +rustdoc-args = ["--cfg", "docsrs"] + +# Speedup build on macOS +# See https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html#splitting-debug-information +[profile.dev] +split-debuginfo = "unpacked" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1bb4114 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +# syntax=docker/dockerfile:1 +ARG RUST_BUILD_IMG=rust:1.67-slim-bullseye +ARG DEBIAN_TAG=bullseye-slim + +FROM $RUST_BUILD_IMG as base + +# AMD64 +FROM --platform=$BUILDPLATFORM base as builder-amd64 +ARG TARGET="x86_64-unknown-linux-gnu" + +# ARM64 +FROM --platform=$BUILDPLATFORM base as builder-arm64 +ARG TARGET="aarch64-unknown-linux-gnu" + +FROM builder-$TARGETARCH as builder + +RUN adduser --disabled-password --disabled-login --gecos "" --no-create-home efected-coto-emmory +RUN apt update && apt install -y g++ build-essential protobuf-compiler +RUN rustup target add $TARGET + +RUN cargo init efected-coto-emmory + +WORKDIR /efected-coto-emmory + +# touch lib.rs as we combine both +Run touch src/lib.rs + +# touch benches as it's part of Cargo.toml +RUN mkdir benches +RUN touch benches/a_benchmark.rs + +# copy cargo.* +COPY Cargo.lock ./Cargo.lock +COPY Cargo.toml ./Cargo.toml + +# cache depencies +RUN mkdir .cargo +RUN cargo vendor > .cargo/config +RUN --mount=type=cache,target=$CARGO_HOME/registry \ + --mount=type=cache,target=$CARGO_HOME/.git \ + --mount=type=cache,target=efected-coto-emmory/target,sharing=locked \ + cargo build --target $TARGET --bin efected-coto-emmory-app --release + +COPY src ./src +# copy src +COPY src ./src +# copy benches +COPY benches ./benches + +# copy config +COPY config ./config + +# final build for release +RUN rm ./target/$TARGET/release/deps/*efected_coto_emmory* +RUN cargo build --target $TARGET --bin efected-coto-emmory-app --release + +RUN strip ./target/$TARGET/release/efected-coto-emmory-app + +RUN mv ./target/$TARGET/release/efected-coto-emmory* /usr/local/bin +RUN mv ./config /etc/config + +FROM debian:${DEBIAN_TAG} + +ARG backtrace=0 +ARG log_level=info + +ENV RUST_BACKTRACE=${backtrace} \ + RUST_LOG=${log_level} + +COPY --from=builder /usr/local/bin/efected-coto-emmory* . +COPY --from=builder /etc/config ./config +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/group /etc/group + +USER efected-coto-emmory:efected-coto-emmory + +EXPOSE 3000 +EXPOSE 4000 +ENTRYPOINT ["./efected-coto-emmory-app"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 1b5ec8b..afd62f4 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -1,176 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Aleeusgr + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT index 4e1a27d..31aa793 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,21 +1,23 @@ -MIT License +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: -Copyright (c) 2023 Alex +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 9115e80..ef8ddb7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,521 @@ -# efected-coto-emmory -Cloud API in Rust. +
+ + efected-coto-emmory Logo + + +

efected-coto-emmory

+ +

+ + Crate + + + License-Apache + + + License-MIT + + + Docs + +

+
+ +
:warning: Work in progress :warning:
+ +## + +## Outline + +- [Running the Webserver](#running-the-webserver) +- [Testing the Project](#testing-the-project) +- [Benchmarking the Project](#benchmarking-the-project) +- [Running efected-coto-emmory on Docker](#running-efected-coto-emmory-on-docker) +- [Contributing](#contributing) +- [Getting Help](#getting-help) +- [External Resources](#external-resources) +- [License](#license) + +## Running the Webserver + +To start-up the [axum][axum] webserver, just run: + +```console +cargo run +``` + +This will start-up the service, running on 2 ports: + +* `3000`: main `efected-coto-emmory` application, including `/healthcheck`, etc. +* `4000`: `/metrics` + +Upon running the application locally, [OpenAPI][openapi] +documentation is available as a [swagger-ui][swagger] +at `http://localhost:3000/swagger-ui/`. Read more in +[Docs and OpenAPI](#docs-and-openapi). + +For local development with logs displayed using ANSI terminal colors, we +recommend running: + +```console +cargo run --features ansi-logs +``` + +### Debugging and Diagnostics + +To better help diagnose and debug your server application, you can run: + +``` console +RUSTFLAGS="--cfg tokio_unstable" cargo run --features console, ansi-logs +``` + +This command uses a compile-time feature-flag, `console`, to give us local +access to [`tokio-console`][tokio-console], a diagnostics and debugging +tool for asynchronous Rust programs, akin to [pprof][pprof], `htop`/`top`, +etc. You can install `tokio-console` using `cargo`: + +``` console +cargo install --locked tokio-console +``` + +Once executed, just run `tokio-console --retain-for <*>min` to use it and explore. + +### Configuration + +`efected-coto-emmory` contains a file for [configuration settings](./config/settings.toml), +loaded by the application when it starts. Configuration can be overridden using +environment variables that begin with an *APP* prefix. To allow for underscores +in variable names, use separators with two underscores between *APP* and the name +of the setting, for example: + +```bash +export APP__SERVER__ENVIRONMENT="dev" +``` + +This export would override this setting in the [default config](./config/settings.toml): + +```toml +[server] +environment = "local" +``` + +### Making HTTP Client Requests with [Reqwest][reqwest] + +This web framework includes the [reqwest][reqwest] HTTP Client library for +making requests to external APIs and services, separate from the `axum` webserver +itself. We use the [reqwest-middleware][reqwest-middleware] crate for +wrapping around `reqwest` requests for client middleware chaining, giving us +metrics, retries, and tracing out of the box. We have an +[integration test](./efected-coto-emmory/tests/integration_test.rs), +which demonstrates how to build a client with middleware and configuration: + +```rust + // reqwest::Client by default has a timeout of 30s + let reqwest_client = Client::builder() + .pool_idle_timeout(settings.http_client.pool_idle_timeout()) + .timeout(Duration::from_millis(settings.http_client.timeout_ms)) + .build(); + + Ok(Self { + client: ClientBuilder::new(reqwest_client?) + .with(TracingMiddleware::::new()) + .with(Logger) + .with(RetryTransientMiddleware::new_with_policy( + retry_policy, + "AClient".to_string(), + )) + .with(Metrics { + name: "AClient".to_string(), + }) + .build(), + + url: settings.url.to_string(), + }) +``` + +*Note*: Our [logging middleware](./efected-coto-emmory/src/middleware/logging.rs) +implements traits for both `axum` and `reqwest` `Request` types. Additionally, +we implement an HTTP Client-specific middleware for deriving metrics for each +external, `reqwest` request in +[middleware/client.metrics.rs](./efected-coto-emmory/src/middleware/client/metrics.rs). +For the `axum` webserver itself, metrics are derived via +[middleware/metrics.rs](./efected-coto-emmory/src/middleware/metrics.rs). + +## Testing the Project + +- Run tests + + ```console + cargo test + ``` + +## Benchmarking the Project + +For benchmarking and measuring performance, this project leverages +[criterion][criterion] and a `test_utils` feature flag +for integrating [proptest][proptest] within the the suite for working with +[strategies][strategies] and sampling from randomly generated values. + +- Run benchmarks + + ```console + cargo bench --features test_utils + ``` + +## Running efected-coto-emmory on Docker + +We recommend setting your [Docker Engine][docker-engine] configuration +with `experimental` and `buildkit` set to `true`, for example: + +``` json +{ + "builder": { + "gc": { + "defaultKeepStorage": "20GB", + "enabled": true + } + }, + "experimental": true, + "features": { + "buildkit": true + } +} +``` + +- Build a multi-plaform Docker image via [buildx][buildx]: + + ```console + docker buildx build --platform=linux/amd64,linux/arm64 -t efected-coto-emmory --progress=plain . + ``` + +- Run a Docker image (depending on your platform): + + ```console + docker run --platform=linux/amd64 -t efected-coto-emmory + ``` + +## Contributing + +:balloon: We're thankful for any feedback and help in improving our project! +We have a [contributing guide](./CONTRIBUTING.md) to help you get involved. We +also adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md). + +### Nix + +This repository contains a [Nix flake][nix-flake] that initiates both the Rust +toolchain set in [rust-toolchain.toml](./rust-toolchain.toml) and a +[pre-commit hook](#pre-commit-hook). It also installs helpful cargo binaries for +development. Please install [nix][nix] and [direnv][direnv] to get started. + +Run `nix develop` or `direnv allow` to load the `devShell` flake output, +according to your preference. + +### Formatting + +For formatting Rust in particular, we automatically format on `nightly`, as it +uses specific nightly features we recommend by default. + +### Pre-commit Hook + +This project recommends using [pre-commit][pre-commit] for running pre-commit +hooks. Please run this before every commit and/or push. + +- If you are doing interim commits locally, and for some reason if you _don't_ + want pre-commit hooks to fire, you can run + `git commit -a -m "Your message here" --no-verify`. + +### Docs and OpenAPI + +If you make any changes to [axum][axum] routes/handlers, make sure to add/update +OpenAPI specifications. You can run `cargo run --bin openapi` +to generate an updated specification .json file, located +[here](./docs/specs/latest.json). + +An example of adding an OpenAPI specification is the following: + +```rust +#[utoipa::path( + get, + path = "/ping", + responses( + (status = 200, description = "Ping successful"), + (status = 500, description = "Ping not successful", body=AppError) + ) +)] +pub async fn get() -> AppResult { + Ok(StatusCode::OK) +} + ``` + +Of note, once you add the [*utoipa*][utoipa] attribute macro to a route, +you should also update the `ApiDoc` struct in [*src/docs.rs*](./src/docs.rs): + +``` rust +/// API documentation generator. +#[derive(OpenApi)] +#[openapi( + paths(health::healthcheck, ping::get), + components(schemas(AppError)), + tags( + (name = "", description = "efected-coto-emmory service/middleware") + ) + )] + +/// Tied to OpenAPI documentation. +#[derive(Debug)] +pub struct ApiDoc; +``` + +### Recording: Logging, Tracing, and Metrics Layers + +For logs, traces, and metrics, `efected-coto-emmory` utilizes several log levels +and middleware trace layers to control how events are recorded. The trace layers +include a: (1) *storage layer*; (2) *otel layer*; (3) *format layer*; and a +(4) *metrics layer*. The log levels include: (1) _trace_; (2) _debug_; +(3) _info_; (4) _warn_; and (5) _error_. All of this leverages the +[tracing][tracing] library and it's related extensions. This approach +is heavily inspired by [*Composing an observable Rust application*][composing-rust]. + +At its core, the [storage layer](./src/tracing_layers/storage_layer.rs) exists +to capture everything flowing through `efected-coto-emmory` before events are +diffracted to their respective log levels--this way it is possible for +`efected-coto-emmory` to maintain contextual trace information throughout the +lifetime of any event, no matter the log level. + +The final layer is the [metrics layer](./src/tracing_layers/metrics_layer.rs), +which, **of note**, removes the stored span information upon span closure. + +#### How Does Logging Work? + +The [logging middleware](./src/middleware/logging.rs) automatically drives +request/response logging, taking into account status codes and helpful +contextual information. + +For logging, we use the [tracing][tracing-log] library and structure logs in +[`logfmt`][logfmt] style. The implementation of the log generation is inspired +by [influxdata's (Influx DB's) version][influx-logfmt]. +When defining log functions for output, please define them like so: + +```rust +self.healthcheck() + .await + .map(|_| { + info!( + subject = "postgres", + category = "db", + "connection to PostgresDB successful" + ) + }) + .map_err(|e| { + error!( + subject = "postgres", + category = "db", + error=?e, + "failed to connect to PostgresDB", + ); +``` + +#### How Does Tracing work? + +`efected-coto-emmory` implements hooks around the creation and closing of spans +across the lifetime of events and requests in order to track the entire *trace* +of that event or request. Each created span has a unique span id that will match +its close. Below is an example which demonstrates the opening of span with an id +of `2251799813685249`, then a logging event which occurs within that span, and +then closing of that span once it's complete. + +```console +level=INFO span_name="HTTP request" span=2251799813685249 span_event=new_span timestamp=2023-01-29T15:06:42.188395Z http.method=GET http.client_ip=127.0.0.1:59965 http.host=localhost:3000 trace_id=fa9754fa3142db2c100a8c47f6dd391d http.route=/ping +level=INFO subject=request category=http.request msg="started processing request" request_path=/ping authorization=null target="project::middleware::logging" location="project/src/middleware/logging.rs:123" timestamp=2023-01-29T15:06:42.188933Z span=2251799813685249 otel.name="GET /ping" http.method=GET http.scheme=HTTP http.client_ip=127.0.0.1:59965 http.flavor=1.1 otel.kind=server http.user_agent=curl/7.85.0 http.host=localhost:3000 trace_id=fa9754fa3142db2c100a8c47f6dd391d http.target=/ping http.route=/ping +level=INFO span_name="HTTP request" span=2251799813685249 span_event=close_span timestamp=2023-01-29T15:06:42.192221Z http.method=GET latency_ms=3 http.client_ip=127.0.0.1:59965 http.host=localhost:3000 trace_id=fa9754fa3142db2c100a8c47f6dd391d http.route=/ping +``` + +#### How Does Instrumentation Work? + +When leveraging [tracing's][tracing] [instrument][tracing-instr] functionality, +we can instrument a function to create and enter a tracing span every time that +function is called. There are two ways to use instrumentation: + +* instrumentation *macros* +* instrumentation *methods*. + +#### Instrumentation Macros (used above fn signatures) + +```rust +#[instrument( + level = "info", + name = "efected-coto-emmory.songs.handler.POST", + skip_all, + fields(category = "http.handler", subject = "songs") +)] +pub async fn post(db: Extension,...) +``` + +#### Instrumentation Method (used with async closures, follows_from reference) + +```rust +// Start a span around the context process spawn +let process_span = debug_span!( + parent: None, + "process.async", + subject = "songs.async", + category = "songs" +); +process_span.follows_from(Span::current()); + +tokio::spawn( + async move { + match context.process().await { + Ok(r) => debug!(event=?r, "successfully processed song addition"), + Err(e) => warn!(error=?e, "failed processing song"), + } + } + .instrument(process_span), +); +``` + +#### Deriving Metrics through Instrumentation + +If a function is instrumented with a special `.record` prefix in the `name` +field, then, as part of the its execution, a `counter` will +automatically be incremented and a `histogram` recorded for that +function's span context (start-to-end): + +```rust +#[instrument( + level = "info", + name = "record.save_event", + skip_all, + fields(category="db", subject="postgres", event_id = %event.event_id, + event_type=%event.event_type, + metric_name="db_event", + metric_label_event_type=%event.event_type + ) + err(Display) +)] +async fn save_event(...) -> ... { +``` + +These metrics are derived via the [metrics layer](./src/tracing_layers/metrics_layer.rs) +where the metrics are stripped off the `.record` prefix and then recorded with +the [metrics-rs][metrics-rs] library: + +```rust +let span_name = span + .name() + .strip_prefix(METRIC_META_PREFIX) + .unwrap_or_else(|| span.name()); +... +... +metrics::increment_counter!(format!("{name}_total"), &labels); +metrics::histogram!( + format!("{name}_duration_seconds"), + elapsed_secs_f64, + &labels +); +``` + +#### How is [OTEL][otel] incorporated for exporting (distributed) tracing information? + +The [axum-tracing-opentelemetry][axum-otel] crate provides middleware for adding +OTEL integration to a `tower` service, an extended [`Tracelayer`][tower-tracelayer], +setting OTEL span information when `efected-coto-emmory` application +[routes](./src/routes) are executed. + +OTEL trace information is exported using a [opentelemetry propagation layer][otel-layer], +which is registered along with the other layers, for example storage, logging, metrics. +This information is exported over [grpc][grpc], using a Rust implementation of +the [opentelemetry otlp][otel-otlp] specification, codified in our +[tracer module](./src/tracer.rs). With the proper settings and setup, this will +work for local development, exporting to a service like [Jaeger][jaeger] or for +sending traces to [Honeycomb][honeycomb] or a similar cloud service. + +### Recommended Development Flow + +- We recommend leveraging [cargo-watch][cargo-watch], + [cargo-expand][cargo-expand] and [irust][irust] for Rust development. +- We recommend using [cargo-udeps][cargo-udeps] for removing unused dependencies + before commits and pull-requests. + +### Conventional Commits + +This project *lightly* follows the [Conventional Commits +convention][commit-spec-site] to help explain +commit history and tie in with our release process. The full specification +can be found [here][commit-spec]. We recommend prefixing your commits with +a type of `fix`, `feat`, `docs`, `ci`, `refactor`, etc..., structured like so: + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +## Getting Help + +For usage questions, usecases, or issues please open an issue in our repository. + +We would be happy to try to answer your question or try opening a new issue on Github. + +## External Resources + +These are references to specifications, talks and presentations, etc. + +## License + +This project is licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0][apache]) +- MIT license ([LICENSE-MIT](./LICENSE-MIT) or [http://opensource.org/licenses/MIT][mit]) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. + + +[apache]: https://www.apache.org/licenses/LICENSE-2.0 +[axum]: https://docs.rs/axum/latest/axum/ +[axum-otel]: https://github.com/davidB/axum-tracing-opentelemetry +[buildx]: https://github.com/docker/buildx +[cargo-expand]: https://github.com/dtolnay/cargo-expand +[cargo-udeps]: https://github.com/est31/cargo-udeps +[cargo-watch]: https://github.com/watchexec/cargo-watch +[commit-spec]: https://www.conventionalcommits.org/en/v1.0.0/#specification +[commit-spec-site]: https://www.conventionalcommits.org/ +[composing-rust]: https://blog.logrocket.com/composing-underpinnings-observable-rust-application/ +[config-rs]: https://github.com/mehcode/config-rs +[criterion]: https://github.com/bheisler/criterion.rs +[docker-engine]: https://docs.docker.com/engine/ +[direnv]:https://direnv.net/ +[honeycomb]: https://www.honeycomb.io/ +[influx-logfmt]: https://github.com/influxdata/influxdb_iox/tree/main/logfmt +[irust]: https://github.com/sigmaSd/IRust +[jaeger]: https://www.jaegertracing.io/ +[logfmt]: https://brandur.org/logfmt +[mit]: http://opensource.org/licenses/MIT +[nix]:https://nixos.org/download.html +[nix-flake]: https://nixos.wiki/wiki/Flakes +[openapi]: https://swagger.io/specification/ +[otel]: https://opentelemetry.io/docs/ +[otel-issue]: https://github.com/open-telemetry/opentelemetry-rust/issues/934 +[otel-layer]: https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/struct.OpenTelemetryLayer.html +[otel-otlp]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md +[pprof]: https://github.com/google/pprof +[pre-commit]: https://pre-commit.com/ +[proptest]: https://github.com/proptest-rs/proptest +[reqwest]: https://github.com/seanmonstar/reqwest +[reqwest-middleware]: https://github.com/TrueLayer/reqwest-middleware +[strategies]: https://docs.rs/proptest/latest/proptest/strategy/trait.Strategy.html +[swagger]: https://swagger.io/tools/swagger-ui/ +[tokio-console]: https://tokio-console.netlify.app/console_subscriber/ +[tower-tracelayer]: https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html +[tracing]: https://github.com/tokio-rs/tracing +[tracing-instr]: https://docs.rs/tracing-attributes/latest/tracing_attributes/attr.instrument.html +[utoipa]: https://github.com/juhaku/utoipa diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..dded5b5 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +## Report a security issue or vulnerability + +The efected-coto-emmory team welcomes security reports and is committed to +providing prompt attention to security issues. Security issues should be +reported privately via [alexeusgr@gmail.com][support-email]. Security issues should +not be reported via the public GitHub Issue tracker. + +## Security advisories + +The project team is committed to transparency in the security issue disclosure +process. The efected-coto-emmory team announces security advisories through our +Github respository's [security portal][sec-advisories] and and the +[RustSec advisory database][rustsec-db]. + +[rustsec-db]: https://github.com/RustSec/advisory-db +[sec-advisories]: https://github.com/aleeusgr/efected-coto-emmory/security/advisories +[support-email]: mailto:alexeusgr@gmail.com diff --git a/assets/a_logo.png b/assets/a_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f86c0ffbc0a7858a9787553f44bedf1561f19a08 GIT binary patch literal 19259 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 z zf-}FN1XYn|-|^_go7NU(Js#hmx?p?TUAqayyEoQ_&lD?3-&K(#`;d0&gsx61fafXz zqvxbka;DF3Ud3g{&7RPWf9gY=`o2e|)`0I)9D{;npGue;`5Kmi#=AWCrx?^Wdd%0G z$!Snh?Gn$r>#@UUVlC+6UPq(fJMsA~gxc=`joYZ#-kAnb=HHj`=*16L!UV~sEg2ib zFGqt`fA`EQ_pM^!002yR|0D0UwswnEGAu)m1|$NuCQLK|K|?hffFC7|*IvbA-qMl3 z@FFXF{IC9U#GG~YBYJiGaMIi@yDF965E#EvG4G7VER*~9T1acU|VR#xhxmrVWer-7^hF|*d%{k41kaLl5<;IKe+W|wpDD&NuK)#fCnJlR$=X~+a5cr z&nL?CNzFjpLHAWgZnf130KOt{-1@_~7jMiTl$sGbMJ7j}u#~_pLk97416F;*(3nTP z>z-|)`2Pv!AVGTq*j~~I0<9970Yu5|i2-y0>HxJ8(%&UKL-MzZ`0Km={E^o-ECJGo z$G1-U>t?NLIgQX+797Wobh$l$-lKB3f{K3?HSx>m%-G}E0bp*t*p)PJ z^h3axGCU{E+c~oF+hw91A&dnzQsO8=!$mejiU7tSx(F_ZwMz*uwBU;}da5#dx5qzq iIlBgKNe8= literal 0 HcmV?d00001 diff --git a/benches/a_benchmark.rs b/benches/a_benchmark.rs new file mode 100644 index 0000000..286409b --- /dev/null +++ b/benches/a_benchmark.rs @@ -0,0 +1,16 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +pub fn add_benchmark(c: &mut Criterion) { + let mut rvg = efected_coto_emmory::test_utils::Rvg::deterministic(); + let int_val_1 = rvg.sample(&(0..100i32)); + let int_val_2 = rvg.sample(&(0..100i32)); + + c.bench_function("add", |b| { + b.iter(|| { + efected_coto_emmory::add(int_val_1, int_val_2); + }) + }); +} + +criterion_group!(benches, add_benchmark); +criterion_main!(benches); diff --git a/config/settings.toml b/config/settings.toml new file mode 100644 index 0000000..8f12d8e --- /dev/null +++ b/config/settings.toml @@ -0,0 +1,11 @@ +[monitoring] +process_collector_interval = 10 + +[otel] +exporter_otlp_endpoint = "http://localhost:4317" + +[server] +environment = "local" +metrics_port = 4000 +port = 3000 +timeout_ms = 30000 diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..c749950 --- /dev/null +++ b/deny.toml @@ -0,0 +1,200 @@ +# 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 + +# 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 + #{ triple = "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"] }, +] + +# 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 database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "deny" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +#ignore = [ +#] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# 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] +# The lint level for crates which do not have a detectable license +unlicensed = "warn" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +allow = [ + "Apache-2.0", + "CC0-1.0", + "MIT", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Zlib" +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "deny" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# 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 = 0.8 +# 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 = [ + # The Unicode-DFS-2016 license is necessary for unicode-ident because they + # use data from the unicode tables to generate the tables which are + # included in the application. We do not distribute those data files so + # this is not a problem for us. See https://github.com/dtolnay/unicode-ident/pull/9/files + { allow = ["Unicode-DFS-2016"], name = "unicode-ident", version = "*"}, +] + +# 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 name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# 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 +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" +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# 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 = [ + #{ name = "ansi_term", version = "=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/docs/specs/latest.json b/docs/specs/latest.json new file mode 100644 index 0000000..1d6f24d --- /dev/null +++ b/docs/specs/latest.json @@ -0,0 +1,99 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "efected-coto-emmory", + "description": "a cloud service", + "contact": { + "name": "Zeeshan Lakhani", + "email": "zeeshan.lakhani@gmail.com" + }, + "license": { + "name": "Apache-2.0 or MIT" + }, + "version": "0.1.0" + }, + "paths": { + "/healthcheck": { + "get": { + "tags": [ + "health" + ], + "summary": "GET handler for checking service health.", + "description": "GET handler for checking service health.", + "operationId": "healthcheck", + "responses": { + "200": { + "description": "efected-coto-emmory healthy" + }, + "500": { + "description": "efected-coto-emmory not healthy", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppError" + } + } + } + } + }, + "deprecated": false + } + }, + "/ping": { + "get": { + "tags": [ + "ping" + ], + "summary": "GET handler for internal pings and availability", + "description": "GET handler for internal pings and availability", + "operationId": "get", + "responses": { + "200": { + "description": "Ping successful" + }, + "500": { + "description": "Ping not successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppError" + } + } + } + } + }, + "deprecated": false + } + } + }, + "components": { + "schemas": { + "AppError": { + "type": "object", + "description": "Encodes [JSONAPI error object responses](https://jsonapi.org/examples/#error-objects).\n\nJSONAPI error object - ALL Fields are technically optional.\n\nThis struct uses the following guidelines:\n\n1. Always encode the StatusCode of the response\n2. Set the title to the `canonical_reason` of the status code.\nAccording to spec, this should NOT change over time.\n3. For unrecoverable errors, encode the detail as the to_string of the error\n\nOther fields not currently captured (but can be added)\n\n- id - a unique identifier for the problem\n- links - a link object with further information about the problem\n- source - a JSON pointer indicating a problem in the request json OR\na parameter specifying a problematic query parameter\n- meta - a meta object containing arbitrary information about the error", + "required": [ + "status" + ], + "properties": { + "detail": { + "type": "string" + }, + "status": { + "type": "integer", + "format": "int32", + "example": 200 + }, + "title": { + "type": "string" + } + } + } + } + }, + "tags": [ + { + "name": "", + "description": "efected-coto-emmory service/middleware" + } + ] +} diff --git a/flake.lock b/flake.lock index ad68d6f..1682adf 100644 --- a/flake.lock +++ b/flake.lock @@ -1,39 +1,40 @@ { "nodes": { - "naersk": { - "inputs": { - "nixpkgs": "nixpkgs" - }, + "flake-compat": { + "flake": false, "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", - "owner": "nix-community", - "repo": "naersk", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { - "owner": "nix-community", - "ref": "master", - "repo": "naersk", + "owner": "edolstra", + "repo": "flake-compat", "type": "github" } }, - "nixpkgs": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1703134684, - "narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "d6863cbcbbb80e71cecfc03356db1cda38919523", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { - "id": "nixpkgs", - "type": "indirect" + "owner": "numtide", + "repo": "flake-utils", + "type": "github" } }, - "nixpkgs_2": { + "nixpkgs": { "locked": { "lastModified": 1703134684, "narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=", @@ -51,9 +52,33 @@ }, "root": { "inputs": { - "naersk": "naersk", - "nixpkgs": "nixpkgs_2", - "utils": "utils" + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1703470538, + "narHash": "sha256-NVFMSr99F0TIqVWBwDqMH6lWoM4PVyzMtI+CPGRIscg=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "f2b937756343365f9b1ba66ec7a1ca489aef745c", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" } }, "systems": { @@ -70,24 +95,6 @@ "repo": "default", "type": "github" } - }, - "utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 2f907a6..14b56f2 100644 --- a/flake.nix +++ b/flake.nix @@ -1,21 +1,99 @@ { + description = "efected-coto-emmory"; + inputs = { - naersk.url = "github:nix-community/naersk/master"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - utils.url = "github:numtide/flake-utils"; + flake-utils.url = "github:numtide/flake-utils"; + + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.flake-utils.follows = "flake-utils"; + }; }; - outputs = { self, nixpkgs, utils, naersk }: - utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; }; - naersk-lib = pkgs.callPackage naersk { }; - in + outputs = { + self, + nixpkgs, + flake-compat, + flake-utils, + rust-overlay, + } @ inputs: + flake-utils.lib.eachDefaultSystem ( + system: let + overlays = [(import rust-overlay)]; + pkgs = import nixpkgs {inherit system overlays;}; + + rust-toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { + extensions = ["cargo" "clippy" "rustfmt" "rust-src" "rust-std"]; + }; + + nightly-rustfmt = pkgs.rust-bin.nightly.latest.rustfmt; + + format-pkgs = with pkgs; [ + nixpkgs-fmt + alejandra + ]; + + cargo-installs = with pkgs; [ + cargo-deny + cargo-expand + cargo-nextest + cargo-outdated + cargo-spellcheck + cargo-sort + cargo-udeps + cargo-watch + ]; + in rec { - defaultPackage = naersk-lib.buildPackage ./.; - devShell = with pkgs; mkShell { - buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy ]; - RUST_SRC_PATH = rustPlatform.rustLibSrc; + devShells.default = pkgs.mkShell { + name = "efected-coto-emmory"; + nativeBuildInputs = with pkgs; + [ + # The ordering of these two items is important. For nightly rustfmt to be used instead of + # the rustfmt provided by `rust-toolchain`, it must appear first in the list. This is + # because native build inputs are added to $PATH in the order they're listed here. + nightly-rustfmt + rust-toolchain + pre-commit + protobuf + direnv + self.packages.${system}.irust + ] + ++ format-pkgs + ++ cargo-installs + ++ lib.optionals stdenv.isDarwin [ + darwin.apple_sdk.frameworks.Security + darwin.apple_sdk.frameworks.CoreFoundation + darwin.apple_sdk.frameworks.Foundation + ]; + + shellHook = '' + [ -e .git/hooks/pre-commit ] || pre-commit install --install-hooks && pre-commit install --hook-type commit-msg + ''; + }; + + packages.irust = pkgs.rustPlatform.buildRustPackage rec { + pname = "irust"; + version = "1.70.0"; + src = pkgs.fetchFromGitHub { + owner = "sigmaSd"; + repo = "IRust"; + rev = "v${version}"; + sha256 = "sha256-chZKesbmvGHXwhnJRZbXyX7B8OwJL9dJh0O1Axz/n2E="; + }; + + doCheck = false; + cargoSha256 = "sha256-FmsD3ajMqpPrTkXCX2anC+cmm0a2xuP+3FHqzj56Ma4="; }; - }); + + formatter = pkgs.alejandra; + } + ); } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..292fe49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable" diff --git a/src/bin/openapi.rs b/src/bin/openapi.rs new file mode 100644 index 0000000..e802e87 --- /dev/null +++ b/src/bin/openapi.rs @@ -0,0 +1,35 @@ +use std::{fs::File, io::prelude::*, path::PathBuf}; +use utoipa::OpenApi; +use efected_coto_emmory::docs::ApiDoc; + +fn main() { + let json_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docs/specs/latest.json"); + let json_path_show = json_path.as_path().display().to_string(); + + let mut file = match File::create(json_path) { + Ok(file) => file, + Err(err) => { + eprintln!("error creating file: {err:?}"); + std::process::exit(1) + } + }; + + let json = match ApiDoc::openapi().to_pretty_json() { + Ok(mut json) => { + json.push('\n'); + json + } + Err(err) => { + eprintln!("error generating OpenAPI json: {err:?}"); + std::process::exit(1) + } + }; + + match file.write_all(json.as_bytes()) { + Ok(_) => println!("OpenAPI json written to path: {json_path_show}\n\n{json}"), + Err(err) => { + eprintln!("error writing to file. {err:?}"); + std::process::exit(1) + } + } +} diff --git a/src/docs.rs b/src/docs.rs new file mode 100644 index 0000000..0842870 --- /dev/null +++ b/src/docs.rs @@ -0,0 +1,21 @@ +//! OpenAPI doc generation. + +use crate::{ + error::AppError, + routes::{health, ping}, +}; +use utoipa::OpenApi; + +/// API documentation generator. +#[derive(OpenApi)] +#[openapi( + paths(health::healthcheck, ping::get), + components(schemas(AppError)), + tags( + (name = "", description = "efected-coto-emmory service/middleware") + ) + )] + +/// Tied to OpenAPI documentation. +#[derive(Debug)] +pub struct ApiDoc; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..88b390f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,217 @@ +//! Generic result/error resprentation(s). + +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, + Json, +}; + +use serde::{Deserialize, Serialize}; +use tracing::warn; +use ulid::Ulid; +use utoipa::ToSchema; + +/// Standard return type out of routes / handlers +pub type AppResult = std::result::Result; + +/// Encodes [JSONAPI error object responses](https://jsonapi.org/examples/#error-objects). +/// +/// JSONAPI error object - ALL Fields are technically optional. +/// +/// This struct uses the following guidelines: +/// +/// 1. Always encode the StatusCode of the response +/// 2. Set the title to the `canonical_reason` of the status code. +/// According to spec, this should NOT change over time. +/// 3. For unrecoverable errors, encode the detail as the to_string of the error +/// +/// Other fields not currently captured (but can be added) +/// +/// - id - a unique identifier for the problem +/// - links - a link object with further information about the problem +/// - source - a JSON pointer indicating a problem in the request json OR +/// a parameter specifying a problematic query parameter +/// - meta - a meta object containing arbitrary information about the error +#[derive(ToSchema, thiserror::Error, Eq, PartialEq, Debug, Deserialize, Serialize)] +pub struct AppError { + #[schema(value_type = u16, example = 200)] + #[serde(with = "crate::error::serde_status_code")] + status: StatusCode, + detail: Option, + title: Option, +} + +impl AppError { + /// New instance of [AppError]. + pub fn new(status_code: StatusCode, message: Option) -> AppError { + Self { + status: status_code, + title: Self::canonical_reason_to_string(&status_code), + detail: message.map(|m| m.to_string()), + } + } + + /// [AppError] for [StatusCode::NOT_FOUND]. + pub fn not_found(id: Ulid) -> AppError { + Self::new( + StatusCode::NOT_FOUND, + Some(format!("Entity with id {id} not found")), + ) + } + + fn canonical_reason_to_string(status_code: &StatusCode) -> Option { + status_code.canonical_reason().map(|r| r.to_string()) + } +} + +#[derive(Debug, Deserialize, Serialize)] +/// Error in JSON API response format. +pub struct ErrorResponse { + errors: Vec, +} + +impl From for ErrorResponse { + fn from(e: AppError) -> Self { + Self { errors: vec![e] } + } +} + +impl From for (StatusCode, Json) { + fn from(app_error: AppError) -> Self { + (app_error.status, Json(app_error.into())) + } +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + let error_response: (StatusCode, Json) = self.into(); + error_response.into_response() + } +} + +impl From for AppError { + fn from(err: anyhow::Error) -> Self { + warn!( + subject = "app_error", + category = "app_error", + "encountered unexpected error {:#}: {:#}", + err, + err.backtrace() + ); + Self { + status: StatusCode::INTERNAL_SERVER_ERROR, + title: StatusCode::INTERNAL_SERVER_ERROR + .canonical_reason() + .map(|r| r.to_string()), + detail: Some(err.to_string()), + } + } +} + +/// Serialize/Deserializer for status codes. +/// +/// This is needed because status code according to JSON API spec must +/// be the status code as a STRING. +/// +/// We could have used http_serde, but it encodes the status code as a NUMBER. +pub mod serde_status_code { + use http::StatusCode; + use serde::{de::Unexpected, Deserialize, Deserializer, Serialize, Serializer}; + + /// Serialize [StatusCode]s. + pub fn serialize(status: &StatusCode, ser: S) -> Result { + String::serialize(&status.as_u16().to_string(), ser) + } + + /// Deserialize [StatusCode]s. + pub fn deserialize<'de, D>(de: D) -> Result + where + D: Deserializer<'de>, + { + let str = String::deserialize(de)?; + StatusCode::from_bytes(str.as_bytes()).map_err(|_| { + serde::de::Error::invalid_value( + Unexpected::Str(str.as_str()), + &"A valid http status code", + ) + }) + } +} + +// Needed to support thiserror::Error, outputs debug for AppError +impl std::fmt::Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +#[cfg(test)] +/// Parse the app error out of the json body +pub async fn parse_error(response: Response) -> AppError { + let body_bytes = hyper::body::to_bytes(response.into_body()).await.unwrap(); + let mut err_response: ErrorResponse = serde_json::from_slice(&body_bytes).unwrap(); + err_response.errors.remove(0) +} + +#[cfg(test)] +mod tests { + use super::*; + use axum::{http::StatusCode, response::IntoResponse}; + use ulid::Ulid; + + #[test] + fn test_from_anyhow_error() { + let err: AppError = anyhow::anyhow!("FAIL").into(); + assert_eq!(err.detail.unwrap(), "FAIL".to_string()); + assert_eq!( + err.title, + StatusCode::INTERNAL_SERVER_ERROR + .canonical_reason() + .map(|r| r.to_string()) + ); + + assert_eq!(err.status, StatusCode::INTERNAL_SERVER_ERROR); + } + + #[test] + fn test_not_found() { + let id = Ulid::new(); + let err = AppError::not_found(id); + + assert_eq!(err.status, StatusCode::NOT_FOUND); + assert_eq!( + err.title, + StatusCode::NOT_FOUND + .canonical_reason() + .map(|r| r.to_string()) + ); + assert_eq!( + err.detail.unwrap(), + format!("Entity with id {id} not found") + ); + } + + #[tokio::test] + async fn test_json_api_error_response() { + // verify that our json api response complies with the standard + let id = Ulid::new(); + let err = AppError::not_found(id); + let response = err.into_response(); + assert_eq!(response.status(), StatusCode::NOT_FOUND); + + let err = parse_error(response).await; + + // Check that the result is all good + assert_eq!(err.status, StatusCode::NOT_FOUND); + assert_eq!( + err.title, + StatusCode::NOT_FOUND + .canonical_reason() + .map(|r| r.to_string()) + ); + assert_eq!( + err.detail.unwrap(), + format!("Entity with id {id} not found") + ); + } +} diff --git a/src/extract/json.rs b/src/extract/json.rs new file mode 100644 index 0000000..7ccaa08 --- /dev/null +++ b/src/extract/json.rs @@ -0,0 +1,315 @@ +//! JSON Extrator / Response replacement for [axum::extract::Json]. + +use async_trait::async_trait; +use axum::{ + body::{Bytes, HttpBody}, + extract::FromRequest, + response::{IntoResponse, Response}, + BoxError, +}; +use http::{ + header::{self, HeaderMap, HeaderValue}, + Request, StatusCode, +}; +use serde::{de::DeserializeOwned, Serialize}; +use std::ops::{Deref, DerefMut}; +use tracing::warn; + +use crate::error::AppError; + +/// JSON Extractor / Response. +/// Built to replace axum::extract::Json, due to the manner of error response. +/// [axum::extract::Json] does not provide much useful information when json parsing fails. +/// This will parse the json using [`serde_path_to_error`] which adds much better +/// context on parsing errors. +/// +/// When used as an extractor, it can deserialize request bodies into some type that +/// implements [serde::Deserialize]. The request will be rejected (and a [AppError] will +/// be returned) if: +/// +/// - The request doesn't have a `Content-Type: application/json` (or similar) header. +/// - The body doesn't contain syntactically valid JSON. +/// - The body contains syntactically valid JSON but it couldn't be deserialized into the target +/// type. +/// - Buffering the request body fails. +/// +/// See [AppError] for more details. +/// +/// # Extractor example +/// +/// ```rust,no_run +/// use axum::{ +/// response, +/// routing::post, +/// Router, +/// }; +/// use efected_coto_emmory::extract; +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct CreateUser { +/// email: String, +/// password: String, +/// } +/// +/// async fn create_user(extract::json::Json(payload): extract::json::Json) { +/// // payload is a `CreateUser` +/// } +/// +/// let app = Router::new().route("/users", post(create_user)); +/// # async { +/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); +/// # }; +/// ``` +#[derive(Debug, Clone, Copy, Default)] +pub struct Json(pub T); + +#[async_trait] +impl FromRequest for Json +where + T: DeserializeOwned, + B: HttpBody + Send + 'static, + B::Data: Send, + B::Error: Into, + S: Send + Sync, +{ + type Rejection = AppError; + + async fn from_request(req: Request, state: &S) -> Result { + if json_content_type(req.headers()) { + let bytes = Bytes::from_request(req, state).await.map_err(|err| { + warn!( + subject = "request", + category = "parsing", + "unable to parse request body {:#}", + err, + ); + AppError::new( + StatusCode::BAD_REQUEST, + Some("Unable to parse request body"), + ) + })?; + let jd = &mut serde_json::Deserializer::from_slice(bytes.as_ref()); + let result: Result = serde_path_to_error::deserialize(jd); + match result { + Ok(value) => Ok(Json(value)), + Err(err) => { + let err_response = match err.inner().classify() { + serde_json::error::Category::Data => { + warn!( + subject = "request", + category = "parsing", + json_error_path = ?err.path().to_string(), + "failed to deserialize the JSON body into the target type, json error: {:#}", + err.inner() + ); + AppError::new( + StatusCode::UNPROCESSABLE_ENTITY, + Some(format!( + "failed to deserialize the JSON body into the target type, json error path: {}; json error {:#}", + err.path(), + err.inner() + )) + ) + } + _ => { + warn!( + subject = "request", + category = "parsing", + "failed to parse the request body as JSON; json error {:#}", + err.inner() + ); + AppError::new( + StatusCode::BAD_REQUEST, + Some(format!( + "failed to parse the request body as JSON; json error {:#}", + err.inner() + )), + ) + } + }; + Err(err_response) + } + } + } else { + Err(AppError::new( + StatusCode::UNSUPPORTED_MEDIA_TYPE, + Some("Expected request with `Content-Type: application/json`"), + )) + } + } +} + +fn json_content_type(headers: &HeaderMap) -> bool { + headers + .get(header::CONTENT_TYPE) + .and_then(|content_type| content_type.to_str().ok()) + .and_then(|content_type| content_type.parse::().ok()) + .map(|mime| { + mime.type_() == "application" + && (mime.subtype() == "json" || mime.suffix().map_or(false, |name| name == "json")) + }) + .unwrap_or(false) +} + +impl Deref for Json { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Json { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for Json { + fn from(inner: T) -> Self { + Self(inner) + } +} + +impl IntoResponse for Json +where + T: Serialize, +{ + fn into_response(self) -> Response { + match serde_json::to_vec(&self.0) { + Ok(bytes) => ( + [( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), + )], + bytes, + ) + .into_response(), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + [( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()), + )], + err.to_string(), + ) + .into_response(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use axum::routing::{get, Router}; + use http::Request; + use hyper::Body; + use serde::Deserialize; + use tower::ServiceExt; + + #[derive(Debug, Deserialize)] + struct Input { + foo: String, + } + + #[tokio::test] + async fn deserialize_body() { + let app = Router::new().route( + "/", + get(|input: Json| async { (StatusCode::OK, input.0.foo) }), + ); + let contents = r#"{ "foo": "bar" }"#; + let req_body: Body = contents.into(); + let response = app + .oneshot( + Request::builder() + .uri("/") + .header("Content-Type", "application/json") + .body(req_body) + .unwrap(), + ) + .await + .unwrap(); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + let body_text = std::str::from_utf8(&body[..]).unwrap(); + dbg!(body_text); + assert_eq!(body_text, "bar"); + } + + #[tokio::test] + async fn consume_body_to_json_requires_json_content_type() { + let app = Router::new().route( + "/", + get(|input: Json| async { (StatusCode::OK, input.0.foo) }), + ); + let contents = r#"{ "foo": "bar" }"#; + let req_body: Body = contents.into(); + let response = app + .oneshot( + Request::builder() + .uri("/") + .header("Content-Type", "application/text") + .body(req_body) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE); + } + + #[tokio::test] + async fn json_content_types() { + async fn valid_json_content_type(content_type: &str) -> bool { + dbg!(content_type); + + let app = Router::new().route( + "/", + get(|input: Json| async { (StatusCode::OK, input.0.foo) }), + ); + let contents = r#"{ "foo": "bar" }"#; + let req_body: Body = contents.into(); + let response = app + .oneshot( + Request::builder() + .uri("/") + .header("Content-Type", content_type) + .body(req_body) + .unwrap(), + ) + .await + .unwrap(); + + response.status() == StatusCode::OK + } + + assert!(valid_json_content_type("application/json").await); + assert!(valid_json_content_type("application/json; charset=utf-8").await); + assert!(valid_json_content_type("application/json;charset=utf-8").await); + assert!(valid_json_content_type("application/cloudevents+json").await); + assert!(!valid_json_content_type("text/json").await); + } + + #[tokio::test] + async fn invalid_json_syntax() { + let app = Router::new().route( + "/", + get(|input: Json| async { (StatusCode::OK, input.0.foo) }), + ); + let contents = "{"; + let req_body: Body = contents.into(); + let response = app + .oneshot( + Request::builder() + .uri("/") + .header("Content-Type", "application/json") + .body(req_body) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + } +} diff --git a/src/extract/mod.rs b/src/extract/mod.rs new file mode 100644 index 0000000..502ae65 --- /dev/null +++ b/src/extract/mod.rs @@ -0,0 +1,3 @@ +//! Custom [axum::extract] Extractors. + +pub mod json; diff --git a/src/headers/header.rs b/src/headers/header.rs new file mode 100644 index 0000000..d042e04 --- /dev/null +++ b/src/headers/header.rs @@ -0,0 +1,109 @@ +use axum::{extract::TypedHeader, headers::Header}; + +/// Generate String-focused, generic, custom typed [`Header`]'s. +#[allow(unused)] +macro_rules! header { + ($tname:ident, $hname:ident, $sname:expr) => { + static $hname: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| axum::headers::HeaderName::from_static($sname)); + + #[doc = "Generated custom [`axum::headers::Header`] for "] + #[doc = $sname] + #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + pub(crate) struct $tname(pub(crate) String); + + impl std::fmt::Display for $tname { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + + impl std::convert::From<&str> for $tname { + fn from(item: &str) -> Self { + $tname(item.to_string()) + } + } + + impl axum::headers::Header for $tname { + fn name() -> &'static axum::headers::HeaderName { + &$hname + } + + fn decode<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { + values + .next() + .and_then(|v| v.to_str().ok()) + .map(|x| $tname(x.to_string())) + .ok_or_else(axum::headers::Error::invalid) + } + + fn encode(&self, values: &mut E) + where + E: Extend, + { + if let Ok(value) = axum::headers::HeaderValue::from_str(&self.0) { + values.extend(std::iter::once(value)); + } + } + } + }; +} + +/// Trait for returning header value directly for passing +/// along to client calls. +pub(crate) trait HeaderValue { + fn header_value(&self) -> String; +} + +impl HeaderValue for TypedHeader +where + T: Header + std::fmt::Display, +{ + fn header_value(&self) -> String { + self.0.to_string() + } +} + +impl HeaderValue for &TypedHeader +where + T: Header + std::fmt::Display, +{ + fn header_value(&self) -> String { + self.0.to_string() + } +} + +#[cfg(test)] +pub(crate) mod tests { + use axum::{ + headers::{Header, HeaderMapExt}, + http, + }; + + header!(XDummyId, XDUMMY_ID, "x-dummy-id"); + + fn test_decode(values: &[&str]) -> Option { + let mut map = http::HeaderMap::new(); + for val in values { + map.append(T::name(), val.parse().unwrap()); + } + map.typed_get() + } + + fn test_encode(header: T) -> http::HeaderMap { + let mut map = http::HeaderMap::new(); + map.typed_insert(header); + map + } + + #[test] + fn test_dummy_header() { + let s = "18312349-3139-498C-84B6-87326BF1F2A7"; + let dummy_id = test_decode::(&[s]).unwrap(); + let headers = test_encode(dummy_id); + assert_eq!(headers["x-dummy-id"], s); + } +} diff --git a/src/headers/mod.rs b/src/headers/mod.rs new file mode 100644 index 0000000..98dbe75 --- /dev/null +++ b/src/headers/mod.rs @@ -0,0 +1,3 @@ +//! Working-with and generating custom headers. + +pub(crate) mod header; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..dd42ac6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,26 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] +#![deny(unreachable_pub)] + +//! efected-coto-emmory + +pub mod docs; +pub mod error; +pub mod extract; +pub mod headers; +pub mod metrics; +pub mod middleware; +pub mod router; +pub mod routes; +pub mod settings; +pub mod tracer; +pub mod tracing_layers; + +/// Test utilities. +#[cfg(any(test, feature = "test_utils"))] +#[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] +pub mod test_utils; +/// Add two integers together. +pub fn add(a: i32, b: i32) -> i32 { + a + b +} diff --git a/src/main.rs b/src/main.rs index b35ed0f..f02a8af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,210 @@ -use std::io; +//! efected-coto-emmory -fn main() { - println!("Guess the number!"); +use anyhow::Result; +use axum::{extract::Extension, headers::HeaderName, routing::get, Router}; +use axum_tracing_opentelemetry::{opentelemetry_tracing_layer, response_with_trace_layer}; +use http::header; +use std::{ + future::ready, + io, + net::{IpAddr, Ipv4Addr, SocketAddr}, + time::Duration, +}; +use tokio::signal::{ + self, + unix::{signal, SignalKind}, +}; +use tower::ServiceBuilder; +use tower_http::{ + catch_panic::CatchPanicLayer, sensitive_headers::SetSensitiveHeadersLayer, + timeout::TimeoutLayer, ServiceBuilderExt, +}; +use tracing::info; +use tracing_subscriber::{ + filter::{dynamic_filter_fn, filter_fn, LevelFilter}, + prelude::*, + EnvFilter, +}; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; +use efected_coto_emmory::{ + docs::ApiDoc, + metrics::{process, prom::setup_metrics_recorder}, + middleware::{self, request_ulid::MakeRequestUlid, runtime}, + router, + routes::fallback::notfound_404, + settings::{Otel, Settings}, + tracer::init_tracer, + tracing_layers::{ + format_layer::LogFmtLayer, + metrics_layer::{MetricsLayer, METRIC_META_PREFIX}, + storage_layer::StorageLayer, + }, +}; - println!("Please input your guess."); +/// Request identifier field. +const REQUEST_ID: &str = "request_id"; - let mut guess = String::new(); +#[tokio::main] +async fn main() -> Result<()> { + let (stdout_writer, _stdout_guard) = tracing_appender::non_blocking(io::stdout()); - io::stdin() - .read_line(&mut guess) - .expect("Failed to read line"); + let settings = Settings::load()?; + setup_tracing(stdout_writer, settings.otel())?; - println!("You guessed: {guess}"); + info!( + subject = "app_settings", + category = "init", + "starting with settings: {:?}", + settings, + ); + + let env = settings.environment(); + let recorder_handle = setup_metrics_recorder()?; + + let app_metrics = async { + let metrics_router = Router::new() + .route("/metrics", get(move || ready(recorder_handle.render()))) + .fallback(notfound_404); + + let router = metrics_router.layer(CatchPanicLayer::custom(runtime::catch_panic)); + + // Spawn tick-driven process collection task + tokio::task::spawn(process::collect_metrics( + settings.monitoring().process_collector_interval, + )); + + serve("Metrics", router, settings.server().metrics_port).await + }; + + let app = async { + let req_id = HeaderName::from_static(REQUEST_ID); + let router = router::setup_app_router() + .route_layer(axum::middleware::from_fn(middleware::metrics::track)) + .layer(Extension(env)) + // Include trace context as header into the response. + .layer(response_with_trace_layer()) + // Opentelemetry tracing middleware. + // This returns a `TraceLayer` configured to use + // OpenTelemetry’s conventional span field names. + .layer(opentelemetry_tracing_layer()) + // Set and propagate "request_id" (as a ulid) per request. + .layer( + ServiceBuilder::new() + .set_request_id(req_id.clone(), MakeRequestUlid) + .propagate_request_id(req_id), + ) + // Applies the `tower_http::timeout::Timeout` middleware which + // applies a timeout to requests. + .layer(TimeoutLayer::new(Duration::from_millis( + settings.server().timeout_ms, + ))) + // Catches runtime panics and converts them into + // `500 Internal Server` responses. + .layer(CatchPanicLayer::custom(runtime::catch_panic)) + // Mark headers as sensitive on both requests and responses. + .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) + .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); + + serve("Application", router, settings.server().port).await + }; + + tokio::try_join!(app, app_metrics)?; + Ok(()) +} + +async fn serve(name: &str, app: Router, port: u16) -> Result<()> { + let bind_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port); + info!( + subject = "app_start", + category = "init", + "{} server listening on {}", + name, + bind_addr + ); + + axum::Server::bind(&bind_addr) + .serve(app.into_make_service_with_connect_info::()) + .with_graceful_shutdown(shutdown()) + .await?; + + Ok(()) +} + +/// Captures and waits for system signals. +async fn shutdown() { + #[cfg(unix)] + let term = async { + signal(SignalKind::terminate()) + .expect("Failed to listen for SIGTERM") + .recv() + .await + }; + + #[cfg(not(unix))] + let term = std::future::pending::<()>(); + + tokio::select! { + _ = signal::ctrl_c() => {} + _ = term => {} + } +} + +/// Setup all [tracing][tracing] layers for storage, request/response tracing, +/// logging and metrics. +fn setup_tracing( + writer: tracing_appender::non_blocking::NonBlocking, + settings_otel: &Otel, +) -> Result<()> { + let tracer = init_tracer(settings_otel)?; + + let registry = tracing_subscriber::Registry::default() + .with(StorageLayer.with_filter(LevelFilter::TRACE)) + .with( + tracing_opentelemetry::layer() + .with_tracer(tracer) + .with_filter(LevelFilter::DEBUG) + .with_filter(dynamic_filter_fn(|_metadata, ctx| { + !ctx.lookup_current() + // Exclude the rustls session "Connection" events + // which don't have a parent span + .map(|s| s.parent().is_none() && s.name() == "Connection") + .unwrap_or_default() + })), + ) + .with(LogFmtLayer::new(writer).with_target(true).with_filter( + EnvFilter::try_from_default_env().unwrap_or_else(|_| { + EnvFilter::new( + std::env::var("RUST_LOG") + .unwrap_or_else(|_| "efected_coto_emmory=info,tower_http=info,reqwest_retry=info,axum_tracing_opentelemetry=info".into()), + ) + }), + )) + .with( + MetricsLayer + .with_filter(LevelFilter::TRACE) + .with_filter(filter_fn(|metadata| { + // Filter and allow only: + // a) special metric prefix; + // b) any event + metadata.name().starts_with(METRIC_META_PREFIX) || metadata.is_event() + })), + ); + + #[cfg(all(feature = "console", tokio_unstable))] + #[cfg_attr(docsrs, doc(cfg(feature = "console")))] + { + let console_layer = console_subscriber::ConsoleLayer::builder() + .retention(Duration::from_secs(60)) + .spawn(); + + registry.with(console_layer).init(); + } + + #[cfg(any(not(feature = "console"), not(tokio_unstable)))] + { + registry.init(); + } + + Ok(()) } diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs new file mode 100644 index 0000000..4760b7d --- /dev/null +++ b/src/metrics/mod.rs @@ -0,0 +1,4 @@ +//! Metrics capture and Prometheus recorder. + +pub mod process; +pub mod prom; diff --git a/src/metrics/process.rs b/src/metrics/process.rs new file mode 100644 index 0000000..9ae4aa4 --- /dev/null +++ b/src/metrics/process.rs @@ -0,0 +1,117 @@ +//! Server process metrics, including cpu, memory, disk, etc. + +use anyhow::{anyhow, Context, Result}; +use metrics::{describe_gauge, Unit}; +use std::time::Duration; +use sysinfo::{get_current_pid, ProcessExt, System, SystemExt}; +use tracing::{info, warn}; + +/// Create and describe gauges for process metrics. +pub(crate) fn describe() { + describe_gauge!( + "process_cpu_usage_percentage", + Unit::Percent, + "The CPU percentage used." + ); + describe_gauge!( + "process_virtual_memory_bytes", + Unit::Bytes, + "The virtual memory size in bytes." + ); + describe_gauge!("process_memory_bytes", Unit::Bytes, "Memory size in bytes."); + describe_gauge!( + "process_disk_total_written_bytes", + Unit::Bytes, + "The total bytes written to disk." + ); + describe_gauge!( + "process_disk_written_bytes", + Unit::Bytes, + "The bytes written to disk." + ); + describe_gauge!( + "process_disk_total_read_bytes", + Unit::Bytes, + "Total bytes Read from disk." + ); + describe_gauge!( + "process_disk_read_bytes", + Unit::Bytes, + "The bytes read from disk." + ); + describe_gauge!( + "process_disk_written_bytes", + Unit::Bytes, + "The bytes written to disk." + ); + describe_gauge!( + "process_uptime_seconds", + Unit::Seconds, + "How much time the process has been running in seconds." + ); +} + +/// Collection process metrics on a settings-defined interval. +pub async fn collect_metrics(interval: u64) { + let mut interval = tokio::time::interval(Duration::from_secs(interval)); + + loop { + interval.tick().await; + let sys_info = System::new(); + if let Err(err) = get_proc_stats(sys_info).await { + warn!( + subject = "metrics.process_collection", + category = "metrics", + "failure to get process statistics {:#?}", + err + ); + } + } +} + +async fn get_proc_stats(mut sys: System) -> Result<()> { + let pid = get_current_pid().map_err(|e| anyhow!("no process pid found {}", e))?; + + let is_process_refreshed = sys.refresh_process(pid); + + if is_process_refreshed { + let proc = sys.process(pid).context("no process associated with pid")?; + let cpus = num_cpus::get(); + let disk = proc.disk_usage(); + + // cpu-usage divided by # of cores. + metrics::gauge!( + "process_cpu_usage_percentage", + f64::from(proc.cpu_usage() / (cpus as f32)) + ); + + // The docs for sysinfo indicate that `virtual_memory` + // returns in KB, but that is incorrect. + // See this issue: https://github.com/GuillaumeGomez/sysinfo/issues/428#issuecomment-774098021 + // And this PR: https://github.com/GuillaumeGomez/sysinfo/pull/430/files + metrics::gauge!( + "process_virtual_memory_bytes", + (proc.virtual_memory()) as f64 + ); + metrics::gauge!("process_memory_bytes", (proc.memory() * 1_000) as f64); + metrics::gauge!("process_uptime_seconds", proc.run_time() as f64); + metrics::gauge!( + "process_disk_total_written_bytes", + disk.total_written_bytes as f64, + ); + metrics::gauge!("process_disk_written_bytes", disk.written_bytes as f64); + metrics::gauge!( + "process_disk_total_read_bytes", + disk.total_read_bytes as f64, + ); + metrics::gauge!("process_disk_read_bytes", disk.read_bytes as f64); + } else { + info!( + subject = "metrics.process_collection", + category = "metrics", + "failed to refresh process information, metrics may show old results" + ); + } + + Ok(()) +} diff --git a/src/metrics/prom.rs b/src/metrics/prom.rs new file mode 100644 index 0000000..abff339 --- /dev/null +++ b/src/metrics/prom.rs @@ -0,0 +1,23 @@ +//! Metrics Prometheus recorder. + +use crate::metrics::process; + +use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle}; + +/// Sets up Prometheus buckets for matched metrics and installs recorder. +pub fn setup_metrics_recorder() -> anyhow::Result { + const EXPONENTIAL_SECONDS: &[f64] = &[ + 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, + ]; + + let builder = PrometheusBuilder::new() + .set_buckets_for_metric( + Matcher::Suffix("_duration_seconds".to_string()), + EXPONENTIAL_SECONDS, + )? + .install_recorder()?; + + process::describe(); + + Ok(builder) +} diff --git a/src/middleware/client/metrics.rs b/src/middleware/client/metrics.rs new file mode 100644 index 0000000..0d70507 --- /dev/null +++ b/src/middleware/client/metrics.rs @@ -0,0 +1,88 @@ +//! Middleware for tracking metrics on each client [reqwest::Request]. + +use reqwest_middleware::Middleware as ReqwestMiddleware; +use std::time::Instant; +use task_local_extensions::Extensions; + +const OK: &str = "ok"; +const ERROR: &str = "error"; +const MIDDLEWARE_ERROR: &str = "middleware_error"; +const NONE: &str = "none"; +const RESULT: &str = "result"; +const STATUS: &str = "status"; + +/// Metrics struct for use as part of middleware. +#[derive(Debug)] +pub struct Metrics { + /// Client name for metric(s) gathering. + pub name: String, +} + +#[async_trait::async_trait] +impl ReqwestMiddleware for Metrics { + async fn handle( + &self, + request: reqwest::Request, + extensions: &mut Extensions, + next: reqwest_middleware::Next<'_>, + ) -> Result { + let now = Instant::now(); + + let url = request.url().clone(); + let request_path: String = url.path().to_string(); + let method = request.method().clone(); + + let result = next.run(request, extensions).await; + let latency = now.elapsed().as_secs_f64(); + + let labels = vec![ + ("client", self.name.to_string()), + ("method", method.to_string()), + ("request_path", request_path), + ]; + + let extended_labels = extend_labels_for_response(labels, &result); + + metrics::increment_counter!("client_http_requests_total", &extended_labels); + metrics::histogram!( + "client_http_request_duration_seconds", + latency, + &extended_labels + ); + + result + } +} + +/// Extend a set of metrics label tuples with dynamic properties +/// around reqwest responses for `result` and `status` fields. +pub fn extend_labels_for_response<'a>( + mut labels: Vec<(&'a str, String)>, + result: &Result, +) -> Vec<(&'a str, String)> { + match result { + Ok(ref success) => { + match success.status().as_u16() { + 200..=299 => labels.push((RESULT, OK.to_string())), + _ => labels.push((RESULT, ERROR.to_string())), + } + + labels.push((STATUS, success.status().as_u16().to_string())); + } + Err(reqwest_middleware::Error::Reqwest(ref err)) => { + labels.push((RESULT, ERROR.to_string())); + labels.push(( + STATUS, + err.status() + .map(|status| status.as_u16().to_string()) + .unwrap_or_else(|| NONE.to_string()), + )); + } + Err(reqwest_middleware::Error::Middleware(ref _err)) => { + labels.push((RESULT, MIDDLEWARE_ERROR.to_string())); + labels.push((STATUS, NONE.to_string())); + } + }; + + labels +} diff --git a/src/middleware/client/mod.rs b/src/middleware/client/mod.rs new file mode 100644 index 0000000..001fd3d --- /dev/null +++ b/src/middleware/client/mod.rs @@ -0,0 +1,3 @@ +//! Middleware for calls to outside client APIs. + +pub mod metrics; diff --git a/src/middleware/logging.rs b/src/middleware/logging.rs new file mode 100644 index 0000000..276ff10 --- /dev/null +++ b/src/middleware/logging.rs @@ -0,0 +1,368 @@ +//! Middleware for logging requests/responses for server and client calls. + +use crate::{error::AppError, middleware::request_ext::RequestExt, settings::AppEnvironment}; +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use axum::{ + body::{Body, BoxBody, Bytes}, + http::{Request, StatusCode}, + middleware::Next, + response::{IntoResponse, Response}, +}; +use http::header; +use reqwest_middleware::Middleware as ReqwestMiddleware; +use task_local_extensions::Extensions; +use tracing::{debug, info, warn}; + +/// Generic "null" field for unset logs/fields. +const NULL: &str = "null"; +/// None field. +const NONE: &str = "none"; +/// Request identifier field. +const REQUEST_ID: &str = "request_id"; + +/// Middleware function for logging request and response body data. +pub async fn log_request_response( + request: Request, + next: Next, +) -> Result { + let req = L::log_request(request).await?; + let path = req.path(); + let res = next.run(req).await; + let res = L::log_response(res, path).await; + Ok(res) +} + +/// Debug-only Request/Response Logger. +#[derive(Debug)] +pub struct DebugOnlyLogger; +/// Request/Response Logger. +#[derive(Debug)] +pub struct Logger; + +/// Trait for request/response for logging. +/// Involves turning the request into bytes and re-forming it. +#[async_trait] +pub trait RequestResponseLogger { + /// Log requests. + /// + /// Note: always on for debugging. + async fn log_request(request: Request) -> Result, AppError> { + let path = request.path(); + let (parts, body) = request.into_parts(); + + debug!( + subject = "request", + category="http.request", + msg = "started processing request", + request_path = %path, + query_string = parts.uri.query()); + + let bytes = buffer("Request", body).await?; + if let Ok(body) = std::str::from_utf8(&bytes) { + debug!(subject="request", category="http.request", body=?body, request_path=%path); + } + let req = Request::from_parts(parts, Body::from(bytes)); + Ok(req) + } + + /// Log responses at different levels based on [StatusCode]. + async fn log_response( + response: Response, + path: String, + ) -> Result, AppError> { + let status_code = response.status().as_u16(); + let headers = response.headers().clone(); + + match status_code { + 200..=299 => { + debug!( + subject = "response", + category="http.response", + status = ?status_code, + response_headers = ?headers, + "finished processing request") + } + 500..=599 => { + warn!( + subject = "response", + category="http.response", + status = ?status_code, + response_headers = ?headers, + "finished processing request") + } + _ => { + info!( + subject = "response", + category="http.response", + status = ?status_code, + response_headers = ?headers, + "finished processing request") + } + } + + let (parts, body) = response.into_parts(); + + let bytes = buffer("Response", body).await?; + if let Ok(body) = std::str::from_utf8(&bytes) { + debug!(subject="response", category="http.response", body=?body, request_path=%path); + } + let res = Response::from_parts(parts, Body::from(bytes)); + Ok(res) + } +} + +#[async_trait] +impl RequestResponseLogger for DebugOnlyLogger {} + +#[async_trait] +impl RequestResponseLogger for Logger { + async fn log_request(request: Request) -> Result, AppError> { + let path = request.path(); + let (parts, body) = request.into_parts(); + + match parts.extensions.get::() { + Some(&AppEnvironment::Local) | Some(&AppEnvironment::Dev) => info!( + subject = "request", + category="http.request", + msg = "started processing request", + request_id = parts + .headers + .get(REQUEST_ID) + .map(|h| h.to_str().unwrap_or(NULL)), + request_path = %path, + query_string = parts.uri.query(), + authorization = parts + .headers + .get(header::AUTHORIZATION) + .map(|h| h.to_str().unwrap_or(NULL)) + .unwrap_or(NULL)), + _ => { + info!( + subject = "request", + category="http.request", + msg = "started processing request", + request_id = parts + .headers + .get(REQUEST_ID) + .map(|h| h.to_str().unwrap_or(NULL)), + request_path = %path, + query_string = parts.uri.query(), + authorization= parts + .headers + .get(header::AUTHORIZATION) + .map(|h| if h.is_sensitive() { + "" + } else { + h.to_str().unwrap_or(NULL) + }) + .unwrap_or(NULL)) + } + }; + + let bytes = buffer("Request", body).await?; + + if let Ok(body) = std::str::from_utf8(&bytes) { + debug!(subject="request", category="http.request", body=?body); + } + + let req = Request::from_parts(parts, Body::from(bytes)); + Ok(req) + } +} + +#[async_trait] +impl ReqwestMiddleware for Logger { + async fn handle( + &self, + request: reqwest::Request, + extensions: &mut Extensions, + next: reqwest_middleware::Next<'_>, + ) -> Result { + log_reqwest(&request, extensions); + let url = request.url().clone(); + let _ = extensions.insert(url); + match next.run(request, extensions).await { + Ok(success) => { + let response = log_reqwest_response(success, extensions).await?; + Ok(response) + } + Err(reqwest_middleware::Error::Reqwest(err)) => { + log_reqwest_error(&err, extensions)?; + Err(reqwest_middleware::Error::Reqwest(err)) + } + Err(reqwest_middleware::Error::Middleware(err)) => { + log_middleware_error(&err, extensions)?; + Err(reqwest_middleware::Error::Middleware(err)) + } + } + } +} + +fn log_reqwest(request: &reqwest::Request, extensions: &mut Extensions) { + let user_agent = request + .headers() + .get(header::USER_AGENT) + .map(|h| h.to_str().unwrap_or(NULL)) + .unwrap_or(NULL); + + let host_hdr = request + .headers() + .get(header::HOST) + .map(|h| h.to_str().unwrap_or(NULL)) + .unwrap_or(NULL); + let host = request.url().host_str().unwrap_or(host_hdr); + + match extensions.get::() { + Some(&AppEnvironment::Local) | Some(&AppEnvironment::Dev) => { + info!( + subject = "client.request", + category="http.request", + client.method = %request.method(), + client.url = %request.url(), + client.host = host, + client.request_path = request.url().path(), + client.query_string = request.url().query(), + client.user_agent = user_agent, + client.version = ?request.version(), + client.authorization= request + .headers() + .get(header::AUTHORIZATION) + .map(|h| h.to_str().unwrap_or(NULL)) + .unwrap_or(NULL), + "started processing client request") + } + _ => { + info!( + subject = "client.request", + category="http.request", + client.method = %request.method(), + client.url = %request.url(), + client.host = host, + client.request_path = request.url().path(), + client.query_string = request.url().query(), + client.user_agent = user_agent, + client.version = ?request.version(), + client.authorization= request + .headers() + .get(header::AUTHORIZATION) + .map(|_h| "") + .unwrap_or(NULL), + "started processing client request") + } + } +} + +async fn log_reqwest_response( + response: reqwest::Response, + extensions: &mut Extensions, +) -> Result { + /// Turn reqwest body, headers, status, and version into + /// a generic [`http::Response`] and to capture body + parts, + /// and then turn it back into a [`reqwest::Response`]. + /// + /// For logging body information, the original response is + /// eliminated, and a new one is formed. There's no way to + /// from a Response::from_parts as in http/axum. + async fn into_reqwest_response( + body: reqwest::Body, + headers: reqwest::header::HeaderMap, + status_code: u16, + version: reqwest::Version, + ) -> Result { + let mut builder = http::Response::builder() + .status(StatusCode::from_u16(status_code)?) + .version(version); + + let headers_iter = headers.into_iter(); + let headers = builder + .headers_mut() + .ok_or_else(|| anyhow!("failed to convert response headers"))?; + + headers.extend(headers_iter.map(|(k, v)| (k, v))); + + let res = builder.body(body)?; + Ok(reqwest::Response::from(res)) + } + + let url = extensions + .get::() + .ok_or_else(|| anyhow!("failed to find Url extension"))?; + + let status_code = response.status().as_u16(); + + let post_log_response = match status_code { + 400..=599 => { + let version = response.version(); + let headers = response.headers().clone(); + let bytes = response.bytes().await?; + if let Ok(body) = std::str::from_utf8(&bytes) { + warn!( + subject = "client.response", + category="http.response", + body = ?body, + client.status = ?status_code, + client.response_headers = ?headers, + client.url = %url, + client.request_path = url.path(), + "error while processing client request"); + } + + let body = reqwest::Body::from(bytes); + into_reqwest_response(body, headers, status_code, version).await? + } + _ => response, + }; + + Ok(post_log_response) +} + +fn log_reqwest_error(error: &reqwest::Error, extensions: &mut Extensions) -> Result<()> { + let url = extensions + .get::() + .ok_or_else(|| anyhow!("failed to find Url extension"))?; + + warn!( + subject = "client.response", + category="http.response", + client.error = format!("{:#?}", error.to_string()), + client.request_path = url.path(), + client.status = ?error.status().map(|status_code| status_code.as_u16().to_string()).unwrap_or_else(|| NONE.to_string()), + client.url = %url, + "error processing client request"); + + Ok(()) +} + +fn log_middleware_error(error: &anyhow::Error, extensions: &mut Extensions) -> Result<()> { + let url = extensions + .get::() + .ok_or_else(|| anyhow!("failed to find Url extension"))?; + + warn!( + subject = "client.response", + category="http.response", + error = format!("{:#?}", error.to_string()), + client.url = %url, + client.request_path = url.path(), + client.status = NONE, + "error processing client request within efected-coto-emmory middleware"); + + Ok(()) +} + +async fn buffer(direction: &str, body: B) -> Result +where + B: axum::body::HttpBody, + B::Error: std::fmt::Display, +{ + let bytes = match hyper::body::to_bytes(body).await { + Ok(bytes) => bytes, + Err(err) => anyhow::bail!(AppError::new( + StatusCode::BAD_REQUEST, + Some(format!("failed to read {direction} body: {err}")), + )), + }; + + Ok(bytes) +} diff --git a/src/middleware/metrics.rs b/src/middleware/metrics.rs new file mode 100644 index 0000000..4795462 --- /dev/null +++ b/src/middleware/metrics.rs @@ -0,0 +1,29 @@ +//! Middleware for tracking metrics on each [axum::http::Request]. + +use crate::middleware::request_ext::RequestExt; +use axum::{http::Request, middleware::Next, response::IntoResponse}; +use std::time::Instant; + +/// Middleware function called to track (and update) http metrics when a route +/// is requested. +pub async fn track(req: Request, next: Next) -> impl IntoResponse { + let start = Instant::now(); + + let method = req.method().clone(); + let path = req.path(); + + let res = next.run(req).await; + let latency = start.elapsed().as_secs_f64(); + let status = res.status().as_u16().to_string(); + + let labels = [ + ("method", method.to_string()), + ("request_path", path), + ("status", status), + ]; + + metrics::increment_counter!("http_requests_total", &labels); + metrics::histogram!("http_request_duration_seconds", latency, &labels); + + res +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs new file mode 100644 index 0000000..f62a40e --- /dev/null +++ b/src/middleware/mod.rs @@ -0,0 +1,10 @@ +//! Additional [axum::middleware]. + +pub mod client; +pub mod logging; +pub mod metrics; +pub(crate) mod request_ext; +pub mod request_ulid; +pub mod reqwest_retry; +pub mod reqwest_tracing; +pub mod runtime; diff --git a/src/middleware/request_ext.rs b/src/middleware/request_ext.rs new file mode 100644 index 0000000..986c553 --- /dev/null +++ b/src/middleware/request_ext.rs @@ -0,0 +1,45 @@ +//! Middleware for additional [axum::http::Request] methods. + +use axum::{ + extract::{MatchedPath, OriginalUri}, + http::Request, +}; + +/// Trait for extra methods on [`Request`](axum::http::Request) +pub(crate) trait RequestExt { + /// Parse request path on the request. + fn path(&self) -> String; +} + +impl RequestExt for Request { + fn path(&self) -> String { + if let Some(matched_path) = self.extensions().get::() { + matched_path.as_str().to_string() + } else if let Some(uri) = self.extensions().get::() { + uri.0.path().to_string() + } else { + self.uri().path().to_string() + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use axum::http::Request; + + #[test] + fn parse_path() { + let mut req1: Request<()> = Request::default(); + *req1.uri_mut() = "https://www.rust-lang.org/users/:id".parse().unwrap(); + assert_eq!(req1.path(), "/users/:id"); + + let mut req2: Request<()> = Request::default(); + *req2.uri_mut() = "https://www.rust-lang.org/api/users".parse().unwrap(); + assert_eq!(req2.path(), "/api/users"); + + let mut req3: Request<()> = Request::default(); + *req3.uri_mut() = "/api/users".parse().unwrap(); + assert_eq!(req3.path(), "/api/users"); + } +} diff --git a/src/middleware/request_ulid.rs b/src/middleware/request_ulid.rs new file mode 100644 index 0000000..4f334ef --- /dev/null +++ b/src/middleware/request_ulid.rs @@ -0,0 +1,21 @@ +//! Middleware for generating [ulid::Ulid]s on requests. + +use axum::http::Request; +use tower_http::request_id::{MakeRequestId, RequestId}; +use ulid::Ulid; + +/// Make/generate ulid on requests. +#[derive(Copy, Clone, Debug)] +pub struct MakeRequestUlid; + +/// Implement the trait for producing a request ID from the incoming request. +/// In our case, we want to generate a new UUID that we can associate with a single request. +impl MakeRequestId for MakeRequestUlid { + fn make_request_id(&mut self, _: &Request) -> Option { + let req_id = Ulid::new().to_string().parse(); + match req_id { + Ok(id) => Some(RequestId::new(id)), + _ => None, + } + } +} diff --git a/src/middleware/reqwest_retry.rs b/src/middleware/reqwest_retry.rs new file mode 100644 index 0000000..b39c58f --- /dev/null +++ b/src/middleware/reqwest_retry.rs @@ -0,0 +1,180 @@ +//! [RetryTransientMiddleware] implements retrying requests on transient errors. +//! This variant minorly extends [TrueLayer's request-retry middleware]. +//! +//! [TrueLayer's request-retry middleware]: +//! + +use crate::middleware::client; +use anyhow::anyhow; +use chrono::Utc; +use reqwest::{Request, Response}; +use reqwest_middleware::{Error, Middleware, Next, Result}; +use reqwest_retry::{RetryPolicy, Retryable}; +use retry_policies::RetryDecision; +use task_local_extensions::Extensions; +use tracing::warn; + +/// We limit the number of retries to a maximum of `10` to avoid stack-overflow issues due to the recursion. +static MAXIMUM_NUMBER_OF_RETRIES: u32 = 10; + +/// `RetryTransientMiddleware` offers retry logic for requests that fail in a transient manner +/// and can be safely executed again. +/// +/// Currently, it allows setting a [RetryPolicy][retry_policies::RetryPolicy] algorithm for calculating the __wait_time__ +/// between each request retry. +/// +///```rust,no_run +/// use efected_coto_emmory::middleware::reqwest_retry::RetryTransientMiddleware; +/// use reqwest_middleware::ClientBuilder; +/// +/// use reqwest_retry::policies::ExponentialBackoff; +/// use reqwest::Client; +/// +/// // We create a ExponentialBackoff retry policy which implements `RetryPolicy`. +/// let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); +/// +/// let client_name = "YoMTVDocs"; +/// let retry_transient_middleware = RetryTransientMiddleware::new_with_policy(retry_policy, client_name.to_string()); +/// let reqwest_client = Client::builder() +/// .pool_idle_timeout(std::time::Duration::from_millis(50)) +/// .timeout(std::time::Duration::from_millis(100)) +/// .build()?; +/// let client = ClientBuilder::new(reqwest_client).with(retry_transient_middleware).build(); +/// # Ok::<(), reqwest::Error>(()) +///``` +/// +/// # Note +/// +/// This middleware always errors when given requests with streaming bodies, before even executing +/// the request. When this happens you'll get an [`Error::Middleware`] with the message +/// 'Request object is not clonable. Are you passing a streaming body?'. +/// +/// Some workaround suggestions: +/// * If you can fit the data in memory, you can instead build static request bodies e.g. with +/// `Body`'s `From` or `From` implementations. +/// * You can wrap this middleware in a custom one which skips retries for streaming requests. +/// * You can write a custom retry middleware that builds new streaming requests from the data +/// source directly, avoiding the issue of streaming requests not being clonable. +#[derive(Debug)] +pub struct RetryTransientMiddleware { + client_name: String, + retry_policy: T, +} + +impl RetryTransientMiddleware { + /// Construct `RetryTransientMiddleware` with a [retry_policy][retry_policies::RetryPolicy]. + pub fn new_with_policy(retry_policy: T, client_name: String) -> Self { + Self { + client_name, + retry_policy, + } + } +} + +#[async_trait::async_trait] +impl Middleware for RetryTransientMiddleware { + async fn handle( + &self, + request: Request, + extensions: &mut Extensions, + next: Next<'_>, + ) -> Result { + // TODO: Ideally we should create a new instance of the `Extensions` map to pass + // downstream. This will guard against previous retries poluting `Extensions`. + // That is, we only return what's populated in the typemap for the last retry attempt + // and copy those into the the `global` Extensions map. + self.execute_with_retry(request, next, extensions).await + } +} + +impl RetryTransientMiddleware { + /// This function will try to execute the request, if it fails + /// with an error classified as transient it will call itself + /// to retry the request. + async fn execute_with_retry<'a>( + &'a self, + request: Request, + next: Next<'a>, + extensions: &'a mut Extensions, + ) -> Result { + let mut n_past_retries = 0; + loop { + // Cloning the request object before-the-fact is not ideal.. + // However, if the body of the request is not static, e.g of type `Bytes`, + // the Clone operation should be of constant complexity and not O(N) + // since the byte abstraction is a shared pointer over a buffer. + let duplicate_request = request.try_clone().ok_or_else(|| { + Error::Middleware(anyhow!( + "Request object is not clonable. Are you passing a streaming body?".to_string() + )) + })?; + + // Only generate metrics here upon retries, i.e. after + // `n_past_retries`==0, 0 being the init index + let result = if n_past_retries > 0 { + self.handle_retry_metric(duplicate_request, extensions, next.clone()) + .await + } else { + next.clone().run(duplicate_request, extensions).await + }; + + // We classify the response which will return None if not + // errors were returned. + break match Retryable::from_reqwest_response(&result) { + Some(retryable) + if retryable == Retryable::Transient + && n_past_retries < MAXIMUM_NUMBER_OF_RETRIES => + { + // If the response failed and the error type was transient + // we can safely try to retry the request. + let retry_decicion = self.retry_policy.should_retry(n_past_retries); + if let RetryDecision::Retry { execute_after } = retry_decicion { + let duration = (execute_after - Utc::now()) + .to_std() + .map_err(Error::middleware)?; + warn!( + subject = "client.retry", + category = "client", + retry_attempt = n_past_retries + 1, + wait_duration = ?duration, + "retrying call with backoff policy", + ); + // Sleep the requested amount before we try again. + tokio::time::sleep(duration).await; + + n_past_retries += 1; + continue; + } else { + result + } + } + Some(_) | None => result, + }; + } + } + + /// Handle response metrics associated with a retry in the loop. + async fn handle_retry_metric<'a>( + &'a self, + request: Request, + extensions: &mut Extensions, + next: Next<'a>, + ) -> Result { + let url = request.url().clone(); + let request_path: String = url.path().to_string(); + let method = request.method().clone(); + + let result = next.run(request, extensions).await; + + let labels = vec![ + ("client", self.client_name.to_string()), + ("method", method.to_string()), + ("request_path", request_path), + ]; + + let extended_labels = client::metrics::extend_labels_for_response(labels, &result); + + metrics::increment_counter!("client_http_requests_retry_total", &extended_labels); + result + } +} diff --git a/src/middleware/reqwest_tracing.rs b/src/middleware/reqwest_tracing.rs new file mode 100644 index 0000000..32f3cd7 --- /dev/null +++ b/src/middleware/reqwest_tracing.rs @@ -0,0 +1,32 @@ +//! Adding trace information to [reqwest::Request]s. + +use reqwest::{Request, Response}; +use reqwest_middleware::Result; +use reqwest_tracing::{default_on_request_end, reqwest_otel_span, ReqwestOtelSpanBackend}; +use std::time::Instant; +use task_local_extensions::Extensions; +use tracing::Span; + +/// Latency string. +const LATENCY_FIELD: &str = "latency_ms"; + +/// Struct for extending [reqwest_tracing::TracingMiddleware]. +#[derive(Debug)] +pub struct ExtendedTrace; + +impl ReqwestOtelSpanBackend for ExtendedTrace { + fn on_request_start(req: &Request, extension: &mut Extensions) -> Span { + extension.insert(Instant::now()); + reqwest_otel_span!( + name = "reqwest-http-request", + req, + latency_ms = tracing::field::Empty + ) + } + + fn on_request_end(span: &Span, outcome: &Result, extension: &mut Extensions) { + let elapsed_milliseconds = extension.get::().unwrap().elapsed().as_millis() as i64; + default_on_request_end(span, outcome); + span.record(LATENCY_FIELD, elapsed_milliseconds); + } +} diff --git a/src/middleware/runtime.rs b/src/middleware/runtime.rs new file mode 100644 index 0000000..191206d --- /dev/null +++ b/src/middleware/runtime.rs @@ -0,0 +1,56 @@ +//! Middleware for runtime, [tower_http] extensions. + +use crate::error::AppError; + +use axum::response::{IntoResponse, Response}; +use std::any::Any; + +/// Middleware function for catching runtime panics, logging +/// them, and converting them into a `500 Internal Server` response. +pub fn catch_panic(err: Box) -> Response { + let details = if let Some(s) = err.downcast_ref::() { + s.clone() + } else if let Some(s) = err.downcast_ref::<&str>() { + s.to_string() + } else { + "Unknown panic message".to_string() + }; + + let err: AppError = anyhow::anyhow!(details).into(); + err.into_response() +} + +#[cfg(test)] +mod test { + use super::*; + use crate::error::{parse_error, AppError}; + use axum::{ + body::Body, + http::{Request, StatusCode}, + routing::get, + Router, + }; + use tower::{ServiceBuilder, ServiceExt}; + use tower_http::catch_panic::CatchPanicLayer; + + #[tokio::test] + async fn catch_panic_error() { + let middleware = ServiceBuilder::new().layer(CatchPanicLayer::custom(catch_panic)); + + let app = Router::new() + .route("/", get(|| async { panic!("hi") })) + .layer(middleware); + + let res = app + .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) + .await + .unwrap(); + + let err = parse_error(res).await; + + assert_eq!( + err, + AppError::new(StatusCode::INTERNAL_SERVER_ERROR, Some("hi")) + ); + } +} diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 0000000..0cc6820 --- /dev/null +++ b/src/router.rs @@ -0,0 +1,24 @@ +//! Main [axum::Router] interface for webserver. + +use crate::{ + middleware::logging::{log_request_response, DebugOnlyLogger, Logger}, + routes::{fallback::notfound_404, health, ping}, +}; +use axum::{routing::get, Router}; + +/// Setup main router for application. +pub fn setup_app_router() -> Router { + let mut router = Router::new() + .route("/ping", get(ping::get)) + .fallback(notfound_404); + + router = router.layer(axum::middleware::from_fn(log_request_response::)); + + let mut healthcheck_router = Router::new().route("/healthcheck", get(health::healthcheck)); + + healthcheck_router = healthcheck_router.layer(axum::middleware::from_fn( + log_request_response::, + )); + + Router::merge(router, healthcheck_router) +} diff --git a/src/routes/fallback.rs b/src/routes/fallback.rs new file mode 100644 index 0000000..802dfdb --- /dev/null +++ b/src/routes/fallback.rs @@ -0,0 +1,9 @@ +//! Fallback routes. + +use crate::error::AppError; +use axum::http::StatusCode; + +/// 404 fallback. +pub async fn notfound_404() -> AppError { + AppError::new(StatusCode::NOT_FOUND, Some("Route does not exist!")) +} diff --git a/src/routes/health.rs b/src/routes/health.rs new file mode 100644 index 0000000..6a5dc39 --- /dev/null +++ b/src/routes/health.rs @@ -0,0 +1,18 @@ +//! Healthcheck route. + +use crate::error::AppResult; +use axum::{self, http::StatusCode}; +use serde_json::json; + +/// GET handler for checking service health. +#[utoipa::path( + get, + path = "/healthcheck", + responses( + (status = 200, description = "efected-coto-emmory healthy"), + (status = 500, description = "efected-coto-emmory not healthy", body=AppError) + ) +)] +pub async fn healthcheck() -> AppResult<(StatusCode, axum::Json)> { + Ok((StatusCode::OK, axum::Json(json!({ "msg": "Healthy"})))) +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..397b57b --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,5 @@ +//! Routes for [axum::Router]. + +pub mod fallback; +pub mod health; +pub mod ping; diff --git a/src/routes/ping.rs b/src/routes/ping.rs new file mode 100644 index 0000000..8ccd564 --- /dev/null +++ b/src/routes/ping.rs @@ -0,0 +1,18 @@ +//! Generic ping route. + +use crate::error::AppResult; +use axum::{self, http::StatusCode}; + +/// GET handler for internal pings and availability +#[utoipa::path( + get, + path = "/ping", + responses( + (status = 200, description = "Ping successful"), + (status = 500, description = "Ping not successful", body=AppError) + ) +)] + +pub async fn get() -> AppResult { + Ok(StatusCode::OK) +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..f788b9f --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,245 @@ +//! Settings / Configuration. + +use config::{Config, ConfigError, Environment, File}; +use http::Uri; +use serde::Deserialize; +use serde_with::serde_as; +use std::{path::PathBuf, time::Duration}; + +/// Names of environments for efected-coto-emmory. +/// Overrides serialization to force lower case in settings and +/// environment variables +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum AppEnvironment { + /// Local environment (local testing). + Local, + /// Official Develop environment. + Dev, + /// Official environment. + Staging, + /// Official Production environment. + Prod, +} + +/// Implement display to force environment to lower case +impl std::fmt::Display for AppEnvironment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", format!("{self:?}").to_lowercase()) + } +} + +/// Server settings. +#[derive(Debug, Deserialize)] +pub struct Server { + /// Server [AppEnvironment]. + pub environment: AppEnvironment, + /// Server port. + pub port: u16, + /// Server metrics port. + pub metrics_port: u16, + /// Server timeout in milliseconds. + pub timeout_ms: u64, +} + +/// Process monitoring settings. +#[derive(Debug, Deserialize)] +pub struct Monitoring { + /// Monitoring collection interval. + pub process_collector_interval: u64, +} + +/// [Opentelemetry] settings. +/// +/// [Opentelemetry]: https://opentelemetry.io/ +#[serde_as] +#[derive(Deserialize)] +pub struct Otel { + /// Exporter [Uri] for OTEL protocol. + #[serde(with = "http_serde::uri")] + pub exporter_otlp_endpoint: Uri, +} + +impl std::fmt::Debug for Otel { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Otel") + .field("exporter_otlp_endpoint", &self.exporter_otlp_endpoint) + .finish() + } +} + +#[derive(Debug, Deserialize)] +/// Application settings. +pub struct Settings { + monitoring: Monitoring, + server: Server, + otel: Otel, +} + +impl Settings { + /// Environment settings getter. + pub fn environment(&self) -> AppEnvironment { + self.server().environment + } + + /// Monitoring settings getter. + pub fn monitoring(&self) -> &Monitoring { + &self.monitoring + } + + /// OTEL settings getter. + pub fn otel(&self) -> &Otel { + &self.otel + } + + /// Server settings getter. + pub fn server(&self) -> &Server { + &self.server + } +} + +impl Settings { + /// Load settings. + pub fn load() -> Result { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("config/settings.toml"); + // inject environment variables naming them properly on the settings + // e.g. [database] url="foo" + // would be injected with environment variable APP__DATABASE__URL="foo" + // using a double underscore as defined by the separator below + let s = Config::builder() + .add_source(File::with_name(&path.as_path().display().to_string())) + .add_source(Environment::with_prefix("APP").separator("__")) + .build()?; + s.try_deserialize() + } +} + +/// Http-client retry options. +#[derive(Clone, Debug, Deserialize)] +pub struct HttpClientRetryOptions { + /// Retry count. + pub count: u8, + /// Retry lower bounds for [reqwest_retry::policies::ExponentialBackoff]. + pub bounds_low_ms: u64, + /// Retry upper bounds for [reqwest_retry::policies::ExponentialBackoff]. + pub bounds_high_ms: u64, +} + +impl Default for HttpClientRetryOptions { + fn default() -> Self { + Self { + bounds_high_ms: 5_000, + bounds_low_ms: 100, + count: 3, + } + } +} + +/// Settings for Http clients. +#[derive(Clone, Debug, Deserialize)] +pub struct HttpClient { + /// Optional timeout for idle sockets being kept-alive. + /// Using `None` to disable timeout. + pub pool_idle_timeout_ms: Option, + #[serde(default)] + /// Http-client retry options. + pub retry_options: HttpClientRetryOptions, + /// Client timeout in milliseconds. + pub timeout_ms: u64, +} + +impl Default for HttpClient { + fn default() -> Self { + Self { + pool_idle_timeout_ms: Some(5_000), + retry_options: HttpClientRetryOptions::default(), + timeout_ms: 30_000, + } + } +} + +impl HttpClient { + /// Convert `pool_idle_timeout_ms` to [Duration]. + pub fn pool_idle_timeout(&self) -> Option { + self.pool_idle_timeout_ms.and_then(|timeout| { + if timeout != 0 { + Some(Duration::from_millis(timeout)) + } else { + None + } + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[serde_as] + #[derive(Debug, Deserialize)] + pub(crate) struct Client { + #[serde(default)] + http_client: HttpClient, + } + + #[test] + fn test_default_http_client_settings() { + let settings = Client { + http_client: HttpClient::default(), + }; + + assert_eq!( + settings.http_client.pool_idle_timeout(), + Some(Duration::from_millis(5_000)) + ); + assert_eq!(settings.http_client.retry_options.bounds_high_ms, 5_000); + assert_eq!(settings.http_client.retry_options.bounds_low_ms, 100); + assert_eq!(settings.http_client.retry_options.count, 3); + assert_eq!(settings.http_client.timeout_ms, 30_000); + } + + #[test] + fn test_http_client_overrides() { + let settings = Client { + http_client: HttpClient { + pool_idle_timeout_ms: Some(0), + retry_options: HttpClientRetryOptions { + bounds_low_ms: 10, + bounds_high_ms: 100, + count: 10, + }, + timeout_ms: 100, + }, + }; + + assert_eq!(settings.http_client.pool_idle_timeout(), None); + assert_eq!(settings.http_client.retry_options.bounds_high_ms, 100); + assert_eq!(settings.http_client.retry_options.bounds_low_ms, 10); + assert_eq!(settings.http_client.retry_options.count, 10); + assert_eq!(settings.http_client.timeout_ms, 100); + } + + #[test] + fn test_http_client_partial_overrides() { + let settings = Client { + http_client: HttpClient { + pool_idle_timeout_ms: Some(5_000), + retry_options: HttpClientRetryOptions { + bounds_low_ms: 10, + bounds_high_ms: 5_000, + count: 1, + }, + timeout_ms: 10_000, + }, + }; + + assert_eq!( + settings.http_client.pool_idle_timeout(), + Some(Duration::from_millis(5_000)) + ); + assert_eq!(settings.http_client.retry_options.bounds_high_ms, 5_000); + assert_eq!(settings.http_client.retry_options.bounds_low_ms, 10); + assert_eq!(settings.http_client.retry_options.count, 1); + assert_eq!(settings.http_client.timeout_ms, 10_000); + } +} diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs new file mode 100644 index 0000000..4a30e2a --- /dev/null +++ b/src/test_utils/mod.rs @@ -0,0 +1,5 @@ +/// Random value generator for sampling data. +#[cfg(feature = "test_utils")] +mod rvg; +#[cfg(feature = "test_utils")] +pub use rvg::*; diff --git a/src/test_utils/rvg.rs b/src/test_utils/rvg.rs new file mode 100644 index 0000000..748c69c --- /dev/null +++ b/src/test_utils/rvg.rs @@ -0,0 +1,63 @@ +use proptest::{ + collection::vec, + strategy::{Strategy, ValueTree}, + test_runner::{Config, TestRunner}, +}; + +/// A random value generator (RVG), which, given proptest strategies, will +/// generate random values based on those strategies. +#[derive(Debug, Default)] +pub struct Rvg { + runner: TestRunner, +} + +impl Rvg { + /// Creates a new RVG with the default random number generator. + pub fn new() -> Self { + Rvg { + runner: TestRunner::new(Config::default()), + } + } + + /// Creates a new RVG with a deterministic random number generator, + /// using the same seed across test runs. + pub fn deterministic() -> Self { + Rvg { + runner: TestRunner::deterministic(), + } + } + + /// Samples a value for the given strategy. + /// + /// # Example + /// + /// ``` + /// use efected_coto_emmory::test_utils::Rvg; + /// + /// let mut rvg = Rvg::new(); + /// let int = rvg.sample(&(0..100i32)); + /// ``` + pub fn sample(&mut self, strategy: &S) -> S::Value { + strategy + .new_tree(&mut self.runner) + .expect("No value can be generated") + .current() + } + + /// Samples a vec of some length with a value for the given strategy. + /// + /// # Example + /// + /// ``` + /// use efected_coto_emmory::test_utils::Rvg; + /// + /// let mut rvg = Rvg::new(); + /// let ints = rvg.sample_vec(&(0..100i32), 10); + /// ``` + pub fn sample_vec(&mut self, strategy: &S, len: usize) -> Vec { + vec(strategy, len..=len) + .new_tree(&mut self.runner) + .expect("No value can be generated") + .current() + } +} diff --git a/src/tracer.rs b/src/tracer.rs new file mode 100644 index 0000000..a91ec81 --- /dev/null +++ b/src/tracer.rs @@ -0,0 +1,63 @@ +//! Opentelemetry tracing extensions and setup. + +use crate::settings::Otel; +use anyhow::{anyhow, Result}; +use const_format::formatcp; +use http::Uri; +use opentelemetry::{ + global, runtime, + sdk::{self, propagation::TraceContextPropagator, trace::Tracer, Resource}, +}; +use opentelemetry_otlp::{TonicExporterBuilder, WithExportConfig}; +use opentelemetry_semantic_conventions as otel_semcov; +use tonic::{metadata::MetadataMap, transport::ClientTlsConfig}; + +//const PKG_NAME: &str = env!("CARGO_PKG_NAME"); +const PKG_NAME: &str = "application"; +const VERSION: &str = formatcp!("v{}", env!("CARGO_PKG_VERSION")); +const LANG: &str = "rust"; + +/// Initialize Opentelemetry tracing via the [OTLP protocol]. +/// +/// [OTLP protocol]: +pub fn init_tracer(settings: &Otel) -> Result { + global::set_text_map_propagator(TraceContextPropagator::new()); + + let resource = Resource::new(vec![ + otel_semcov::resource::SERVICE_NAME.string(PKG_NAME), + otel_semcov::resource::SERVICE_VERSION.string(VERSION), + otel_semcov::resource::TELEMETRY_SDK_LANGUAGE.string(LANG), + ]); + + let endpoint = &settings.exporter_otlp_endpoint; + + let map = MetadataMap::with_capacity(2); + + let trace = opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter(exporter(map, endpoint)?) + .with_trace_config(sdk::trace::config().with_resource(resource)) + .install_batch(runtime::Tokio) + .map_err(|e| anyhow!("failed to intialize tracer: {:#?}", e))?; + + Ok(trace) +} + +fn exporter(map: MetadataMap, endpoint: &Uri) -> Result { + // Over grpc transport + let exporter = opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint(endpoint.to_string()) + .with_metadata(map); + + match endpoint.scheme_str() { + Some("https") => { + let host = endpoint + .host() + .ok_or_else(|| anyhow!("failed to parse host"))?; + + Ok(exporter.with_tls_config(ClientTlsConfig::new().domain_name(host.to_string()))) + } + _ => Ok(exporter), + } +} diff --git a/src/tracing_layers/format_layer.rs b/src/tracing_layers/format_layer.rs new file mode 100644 index 0000000..669cd30 --- /dev/null +++ b/src/tracing_layers/format_layer.rs @@ -0,0 +1,672 @@ +//! [Logfmt]'ed event logging [Layer] with augmented telemetry info. +//! +//! Inspired by [influxdata's (Influx DB's) version]. +//! +//! [Logfmt]: +//! [Layer]: tracing_subscriber::Layer +//! [influxdata's (Influx DB's) version]: + +use crate::tracing_layers::storage_layer::Storage; +use parking_lot::RwLock; +use std::{ + borrow::Cow, + fmt, + io::{self, Write}, + time::SystemTime, +}; +use time::{format_description::well_known::Rfc3339, OffsetDateTime}; +use tracing::{ + field::{Field, Visit}, + metadata::LevelFilter, + span::{Attributes, Id}, + Event, Level, Subscriber, +}; +use tracing_subscriber::{fmt::MakeWriter, layer::Context, registry::LookupSpan, Layer}; + +/// Fields to persist from [Storage](Storage) for `new_span` logs via context. +const SPAN_FIELDS: [&str; 13] = [ + "category", + "follows_from", + "follows_from.trace_id", + "http.client_ip", + "http.host", + "http.method", + "http.route", + "latency_ms", + "parent_span", + "request_id", + "span", + "subject", + "trace_id", +]; + +/// Fields to skip from [Storage](Storage) spans for `on_event` logs via +/// context. +const ON_EVENT_SKIP_FIELDS: [&str; 6] = [ + "authorization", + "category", + "error", + "msg", + "return", + "subject", +]; + +/// Fields to persist from [Storage](Storage) for `on_close` span logs via +/// context. +const ON_CLOSE_FIELDS: [&str; 12] = [ + "category", + "follows_from", + "follows_from.trace_id", + "http.client_ip", + "http.host", + "http.method", + "http.route", + "latency_ms", + "parent_span", + "request_id", + "subject", + "trace_id", +]; + +#[cfg(feature = "ansi-logs")] +const GRAY: u8 = 245; + +/// Logging layer for formatting and outputting event-driven logs. +#[derive(Debug)] +pub struct LogFmtLayer io::Stdout> +where + Wr: Write, + W: for<'writer> MakeWriter<'writer>, +{ + writer: W, + printer: RwLock>, +} + +impl LogFmtLayer +where + Wr: Write, + W: for<'writer> MakeWriter<'writer, Writer = Wr>, +{ + /// Create a new logfmt Layer to pass into tracing_subscriber + /// + /// Note this layer simply formats and writes to the specified writer. It + /// does not do any filtering for levels itself. Filtering can be done + /// using a EnvFilter. + /// + /// # Example + /// + /// ```rust,no_run + /// use efected_coto_emmory::tracing_layers::format_layer::LogFmtLayer; + /// use tracing_subscriber::{EnvFilter, prelude::*, self}; + /// + /// // setup debug logging level + /// std::env::set_var("RUST_LOG", "debug"); + /// + /// // setup formatter to write to stderr + /// let formatter = + /// LogFmtLayer::new(std::io::stderr); + /// + /// tracing_subscriber::registry() + /// .with(EnvFilter::from_default_env()) + /// .with(formatter) + /// .init(); + /// ``` + pub fn new(writer: W) -> Self { + let make_writer = writer.make_writer(); + Self { + writer, + printer: RwLock::new(FieldPrinter::new(make_writer, true)), + } + } + + /// Control whether target and location attributes are displayed (on by default). + /// + /// Note: this API mimics that of other fmt layers in tracing-subscriber crate. + pub fn with_target(self, display_target: bool) -> Self { + let make_writer = self.writer.make_writer(); + Self { + writer: self.writer, + printer: RwLock::new(FieldPrinter::new(make_writer, display_target)), + } + } +} + +impl Layer for LogFmtLayer +where + Wr: Write + 'static, + W: for<'writer> MakeWriter<'writer> + 'static, + S: Subscriber + for<'span> LookupSpan<'span>, +{ + fn max_level_hint(&self) -> Option { + None + } + + fn on_new_span(&self, _attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { + let mut p = self.printer.write(); + + let metadata = ctx.metadata(id).expect("Span missing metadata"); + p.write_level(metadata.level()); + p.write_span_name(metadata.name()); + p.write_span_id(id); + p.write_span_event("new_span"); + p.write_timestamp(); + + let span = ctx.span(id).expect("Span not found"); + let extensions = span.extensions(); + if let Some(visitor) = extensions.get::>() { + for (key, value) in visitor.values() { + match *metadata.level() { + Level::TRACE | Level::DEBUG => p.write_kv( + decorate_field_name(translate_field_name(key)), + value.to_string(), + ), + + _ => { + if SPAN_FIELDS.contains(key) { + p.write_kv( + decorate_field_name(translate_field_name(key)), + value.to_string(), + ) + } + } + } + } + } + p.write_newline(); + } + + fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { + let mut p = self.printer.write(); + + p.write_level(event.metadata().level()); + event.record(&mut *p); + + //record source information + p.write_source_info(event); + p.write_timestamp(); + + ctx.lookup_current().map(|current_span| { + p.write_span_id(¤t_span.id()); + let extensions = current_span.extensions(); + extensions.get::>().map(|visitor| { + for (key, value) in visitor.values() { + if !ON_EVENT_SKIP_FIELDS.contains(key) { + p.write_kv( + decorate_field_name(translate_field_name(key)), + value.to_string(), + ) + } + } + }) + }); + + p.write_newline(); + } + + fn on_close(&self, id: Id, ctx: Context<'_, S>) { + let mut p = self.printer.write(); + + let metadata = ctx.metadata(&id).expect("Span missing metadata"); + let span = ctx.span(&id).expect("Span not found"); + + p.write_level(metadata.level()); + p.write_span_name(metadata.name()); + p.write_span_id(&span.id()); + p.write_span_event("close_span"); + p.write_timestamp(); + + let mut extensions = span.extensions_mut(); + + if let Some(visitor) = extensions.get_mut::>() { + for (key, value) in visitor.values() { + if ON_CLOSE_FIELDS.contains(key) { + p.write_kv( + decorate_field_name(translate_field_name(key)), + value.to_string(), + ) + } + } + } + + p.write_newline(); + } +} + +/// This is responsible for actually printing log information to +/// the layer's writer. +#[derive(Debug)] +struct FieldPrinter { + writer: Wr, + display_target: bool, +} + +impl FieldPrinter { + fn new(writer: W, display_target: bool) -> Self { + Self { + writer, + display_target, + } + } + + #[cfg(feature = "ansi-logs")] + fn write_level(&mut self, level: &Level) { + let level_str = match *level { + Level::TRACE => "trace", + Level::DEBUG => "debug", + Level::INFO => "info", + Level::WARN => "warn", + Level::ERROR => "error", + } + .to_uppercase(); + + let level_name = match *level { + Level::TRACE => ansi_term::Color::Purple, + Level::DEBUG => ansi_term::Color::Blue, + Level::INFO => ansi_term::Color::Green, + Level::WARN => ansi_term::Color::Yellow, + Level::ERROR => ansi_term::Color::Red, + } + .bold() + .paint(level_str); + + write!( + self.writer, + r#"{}={}"#, + decorate_field_name("level"), + level_name + ) + .ok(); + } + + #[cfg(not(feature = "ansi-logs"))] + fn write_level(&mut self, level: &Level) { + let level_str = match *level { + Level::TRACE => "trace", + Level::DEBUG => "debug", + Level::INFO => "info", + Level::WARN => "warn", + Level::ERROR => "error", + }; + + write!( + self.writer, + r#"{}={}"#, + decorate_field_name("level"), + level_str + ) + .ok(); + } + + fn write_span_name(&mut self, value: &str) { + write!( + self.writer, + " {}={}", + decorate_field_name("span_name"), + quote_and_escape(value) + ) + .ok(); + } + + fn write_source_info(&mut self, event: &Event<'_>) { + if !self.display_target { + return; + } + + let metadata = event.metadata(); + + if metadata.target() != "log" { + write!( + self.writer, + " {}=\"{}\"", + decorate_field_name("target"), + quote_and_escape(metadata.target()) + ) + .ok(); + } + + if let Some(module_path) = metadata.module_path() { + if metadata.target() != module_path { + write!( + self.writer, + " {}=\"{}\"", + decorate_field_name("module_path"), + module_path + ) + .ok(); + } + } + if let (Some(file), Some(line)) = (metadata.file(), metadata.line()) { + write!( + self.writer, + " {}=\"{}:{}\"", + decorate_field_name("location"), + file, + line + ) + .ok(); + } + } + + fn write_span_id(&mut self, id: &Id) { + write!( + self.writer, + " {}={}", + decorate_field_name("span"), + id.into_u64() + ) + .ok(); + } + + fn write_span_event(&mut self, hook: &str) { + write!( + self.writer, + " {}={}", + decorate_field_name("span_event"), + hook + ) + .ok(); + } + + fn write_timestamp(&mut self) { + write!( + self.writer, + " {}={}", + decorate_field_name("timestamp"), + to_rfc3339(&SystemTime::now()) + ) + .ok(); + } + + fn write_kv(&mut self, key: String, value: String) { + write!(self.writer, " {}={}", key, quote_and_escape(value.as_str())).ok(); + } + + fn write_newline(&mut self) { + writeln!(self.writer).ok(); + } +} + +impl Visit for FieldPrinter { + /// Visit a signed 64-bit integer value. + fn record_i64(&mut self, field: &Field, value: i64) { + write!( + self.writer, + " {}={}", + decorate_field_name(translate_field_name(field.name())), + value + ) + .ok(); + } + + /// Visit an unsigned 64-bit integer value. + fn record_u64(&mut self, field: &Field, value: u64) { + write!( + self.writer, + " {}={}", + decorate_field_name(translate_field_name(field.name())), + value + ) + .ok(); + } + + /// Visit a boolean value. + fn record_bool(&mut self, field: &Field, value: bool) { + write!( + self.writer, + " {}={}", + decorate_field_name(translate_field_name(field.name())), + value + ) + .ok(); + } + + /// Visit a string value. + fn record_str(&mut self, field: &Field, value: &str) { + write!( + self.writer, + " {}={}", + decorate_field_name(translate_field_name(field.name())), + quote_and_escape(value) + ) + .ok(); + } + + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + // Note this appears to be invoked via `debug!` and `info! macros + let formatted_value = format!("{value:?}"); + write!( + self.writer, + " {}={}", + decorate_field_name(translate_field_name(field.name())), + quote_and_escape(&formatted_value) + ) + .ok(); + } + + fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { + let field_name = translate_field_name(field.name()); + + let debug_formatted = format!("{value:?}"); + write!( + self.writer, + " {}={:?}", + decorate_field_name(field_name), + quote_and_escape(&debug_formatted) + ) + .ok(); + + let display_formatted = format!("{value}"); + write!( + self.writer, + " {}.display={}", + decorate_field_name(field_name), + quote_and_escape(&display_formatted) + ) + .ok(); + } +} + +/// The type of record we are dealing with: entering a span, exiting a span, an event. +#[derive(Debug)] +pub enum Type { + /// Starting span. + EnterSpan, + /// Exiting span. + ExitSpan, + /// Event. + Event, +} + +impl fmt::Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let repr = match self { + Type::EnterSpan => "START", + Type::ExitSpan => "END", + Type::Event => "EVENT", + }; + write!(f, "{repr}") + } +} + +/// Translate the field name from tracing into the logfmt style. +fn translate_field_name(name: &str) -> &str { + let name = slice_field_name(name); + + if name == "message" { + "msg" + } else { + name + } +} + +/// Decorates field names in logs if the `ansi-logs` is on. +#[cfg(feature = "ansi-logs")] +#[inline] +fn decorate_field_name(name: &str) -> String { + ansi_term::Color::Fixed(GRAY) + .italic() + .paint(name) + .to_string() +} + +/// Decorates field names in logs when `ansi-logs` is not on. +#[cfg(not(feature = "ansi-logs"))] +#[inline] +fn decorate_field_name(name: &str) -> String { + name.to_string() +} + +/// Converts system time to `rfc3339` format. +fn to_rfc3339(st: &SystemTime) -> String { + st.duration_since(SystemTime::UNIX_EPOCH) + .ok() + .and_then(|duration| TryFrom::try_from(duration).ok()) + .and_then(|duration| OffsetDateTime::UNIX_EPOCH.checked_add(duration)) + .and_then(|dt| dt.format(&Rfc3339).ok()) + .unwrap_or_default() +} + +/// Return true if the string value already starts/ends with quotes and is +/// already properly escaped (all spaces escaped). +fn needs_quotes_and_escaping(value: &str) -> bool { + // mismatches beginning / end quotes + if value.starts_with('"') != value.ends_with('"') { + return true; + } + + // ignore beginning/ending quotes, if any + let pre_quoted = value.len() >= 2 && value.starts_with('"') && value.ends_with('"'); + + let value = if pre_quoted { + &value[1..value.len() - 1] + } else { + value + }; + + // unescaped quotes + let c0 = value.chars(); + let c1 = value.chars().skip(1); + if c0.zip(c1).any(|(c0, c1)| c0 != '\\' && c1 == '"') { + return true; + } + + // Quote any strings that contain a literal '=' which the logfmt parser + // interprets as a key/value separator. + if value.chars().any(|c| c == '=') && !pre_quoted { + return true; + } + + if value.bytes().any(|b| b <= b' ') && !pre_quoted { + return true; + } + + false +} + +/// Escape any characters in name as needed, otherwise return string as is. +fn quote_and_escape(value: &'_ str) -> Cow<'_, str> { + if needs_quotes_and_escaping(value) { + Cow::Owned(format!("{value:?}")) + } else { + Cow::Borrowed(value) + } +} + +/// slice / cut fields with a `.`. +fn slice_field_name(name: &str) -> &str { + match name { + name if name.starts_with("log.") => &name[4..], + name => name, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn quote_and_escape_len0() { + assert_eq!(quote_and_escape(""), ""); + } + + #[test] + fn quote_and_escape_len1() { + assert_eq!(quote_and_escape("f"), "f"); + } + + #[test] + fn quote_and_escape_len2() { + assert_eq!(quote_and_escape("fo"), "fo"); + } + + #[test] + fn quote_and_escape_len3() { + assert_eq!(quote_and_escape("foo"), "foo"); + } + + #[test] + fn quote_and_escape_len3_1quote_start() { + assert_eq!(quote_and_escape("\"foo"), "\"\\\"foo\""); + } + + #[test] + fn quote_and_escape_len3_1quote_end() { + assert_eq!(quote_and_escape("foo\""), "\"foo\\\"\""); + } + + #[test] + fn quote_and_escape_len3_2quote() { + assert_eq!(quote_and_escape("\"foo\""), "\"foo\""); + } + + #[test] + fn quote_and_escape_space() { + assert_eq!(quote_and_escape("foo bar"), "\"foo bar\""); + } + + #[test] + fn quote_and_escape_space_prequoted() { + assert_eq!(quote_and_escape("\"foo bar\""), "\"foo bar\""); + } + + #[test] + fn quote_and_escape_space_prequoted_but_not_escaped() { + assert_eq!(quote_and_escape("\"foo \"bar\""), "\"\\\"foo \\\"bar\\\"\""); + } + + #[test] + fn quote_and_escape_quoted_quotes() { + assert_eq!(quote_and_escape("foo:\"bar\""), "\"foo:\\\"bar\\\"\""); + } + + #[test] + fn quote_and_escape_nested_1() { + assert_eq!(quote_and_escape(r#"a "b" c"#), r#""a \"b\" c""#); + } + + #[test] + fn quote_and_escape_nested_2() { + assert_eq!( + quote_and_escape(r#"a "0 \"1\" 2" c"#), + r#""a \"0 \\\"1\\\" 2\" c""# + ); + } + + #[test] + fn quote_not_printable() { + assert_eq!(quote_and_escape("foo\nbar"), r#""foo\nbar""#); + assert_eq!(quote_and_escape("foo\r\nbar"), r#""foo\r\nbar""#); + assert_eq!(quote_and_escape("foo\0bar"), r#""foo\0bar""#); + } + + #[test] + fn not_quote_unicode_unnecessarily() { + assert_eq!(quote_and_escape("mikuličić"), "mikuličić"); + } + + #[test] + fn test_uri_quoted() { + assert_eq!(quote_and_escape("/api/v2/write?bucket=06fddb4f912a0d7f&org=9df0256628d1f506&orgID=9df0256628d1f506&precision=ns"), + r#""/api/v2/write?bucket=06fddb4f912a0d7f&org=9df0256628d1f506&orgID=9df0256628d1f506&precision=ns""#); + } +} diff --git a/src/tracing_layers/metrics_layer.rs b/src/tracing_layers/metrics_layer.rs new file mode 100644 index 0000000..cae1451 --- /dev/null +++ b/src/tracing_layers/metrics_layer.rs @@ -0,0 +1,84 @@ +//! Metrics layer. + +use crate::tracing_layers::storage_layer::Storage; +use std::{borrow::Cow, time::Instant}; +use tracing::{Id, Subscriber}; +use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer}; + +const PREFIX_LABEL: &str = "metric_label_"; +const METRIC_NAME: &str = "metric_name"; +const OK: &str = "ok"; +const ERROR: &str = "error"; +const LABEL: &str = "label"; +const RESULT_LABEL: &str = "result"; +const SPAN_LABEL: &str = "span_name"; + +/// Prefix used for capturing metric spans/instrumentations. +pub const METRIC_META_PREFIX: &str = "record."; + +/// Metrics layer for automatically deriving metrics for record.* events. +/// +/// Append to custom [LogFmtLayer](crate::tracing_layers::format_layer::LogFmtLayer). +#[derive(Debug)] +pub struct MetricsLayer; + +impl Layer for MetricsLayer +where + S: Subscriber + for<'span> LookupSpan<'span>, +{ + fn on_close(&self, id: Id, ctx: Context<'_, S>) { + let span = ctx.span(&id).expect("Span not found"); + let mut extensions = span.extensions_mut(); + + let elapsed_secs_f64 = extensions + .get_mut::() + .map(|i| i.elapsed().as_secs_f64()) + .unwrap_or(0.0); + + if let Some(visitor) = extensions.get_mut::>() { + let mut labels = vec![]; + for (key, value) in visitor.values() { + if key.starts_with(PREFIX_LABEL) { + labels.push(( + key.strip_prefix(PREFIX_LABEL).unwrap_or(LABEL), + value.to_string(), + )) + } + } + + let span_name = span + .name() + .strip_prefix(METRIC_META_PREFIX) + .unwrap_or_else(|| span.name()); + + labels.push((SPAN_LABEL, span_name.to_string())); + + let name = visitor + .values() + .get(METRIC_NAME) + .unwrap_or(&Cow::from(span_name)) + .to_string(); + + if visitor.values().contains_key(ERROR) { + labels.push((RESULT_LABEL, String::from(ERROR))) + } else { + labels.push((RESULT_LABEL, String::from(OK))) + } + + // Need to sort labels to remain the same across all metrics. + labels.sort_unstable(); + + metrics::increment_counter!(format!("{name}_total"), &labels); + metrics::histogram!( + format!("{name}_duration_seconds"), + elapsed_secs_f64, + &labels + ); + + // Remove storage as this is the last layer. + extensions + .remove::>() + .expect("Visitor not found on 'close'"); + } + } +} diff --git a/src/tracing_layers/mod.rs b/src/tracing_layers/mod.rs new file mode 100644 index 0000000..2aa7799 --- /dev/null +++ b/src/tracing_layers/mod.rs @@ -0,0 +1,9 @@ +//! Custom [tracing_subscriber::layer::Layer]s for formatting log events, +//! deriving metrics from instrumentation calls, and for storage to augment +//! layers. For more information, please read [Composing an observable Rust application]. +//! +//! [Composing an observable Rust application]: + +pub mod format_layer; +pub mod metrics_layer; +pub mod storage_layer; diff --git a/src/tracing_layers/storage_layer.rs b/src/tracing_layers/storage_layer.rs new file mode 100644 index 0000000..1ab1609 --- /dev/null +++ b/src/tracing_layers/storage_layer.rs @@ -0,0 +1,195 @@ +//! Storage layer. + +use std::{borrow::Cow, collections::HashMap, fmt, time::Instant}; +use tracing::{ + field::{Field, Visit}, + span::{Attributes, Record}, + Event, Id, Subscriber, +}; +use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer}; + +/// Storage fields for events. +const ON_EVENT_KEEP_FIELDS: [&str; 1] = ["error"]; + +const TRACE_ID: &str = "trace_id"; +const PARENT_SPAN: &str = "parent_span"; +const FOLLOWS_FROM_TRACE_ID: &str = "follows_from.trace_id"; +const FOLLOWS_FROM_FIELD: &str = "follows_from"; +const LATENCY_FIELD: &str = "latency_ms"; + +/// Storage layer for contextual trace information. +/// +/// Prepend to custom [LogFmtLayer](crate::tracing_layers::format_layer::LogFmtLayer). +#[derive(Clone, Debug)] +pub struct StorageLayer; + +#[derive(Clone, Debug, Default)] +pub(crate) struct Storage<'a> { + values: HashMap<&'a str, Cow<'a, str>>, +} + +impl<'a> Storage<'a> { + pub(crate) fn values(&self) -> &HashMap<&'a str, Cow<'a, str>> { + &self.values + } +} + +impl Visit for Storage<'_> { + /// Visit a signed 64-bit integer value. + fn record_i64(&mut self, field: &Field, value: i64) { + self.values + .insert(field.name(), Cow::from(value.to_string())); + } + + /// Visit an unsigned 64-bit integer value. + fn record_u64(&mut self, field: &Field, value: u64) { + self.values + .insert(field.name(), Cow::from(value.to_string())); + } + + /// Visit a boolean value. + fn record_bool(&mut self, field: &Field, value: bool) { + self.values + .insert(field.name(), Cow::from(value.to_string())); + } + + /// Visit a string value. + fn record_str(&mut self, field: &Field, value: &str) { + self.values + .insert(field.name(), Cow::from(value.to_string())); + } + + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + // Note this appears to be invoked via `debug!` and `info! macros + match field.name() { + name if name.starts_with("log.") => (), + _ => { + let debug_formatted = format!("{value:?}"); + self.values.insert(field.name(), Cow::from(debug_formatted)); + } + } + } + + fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { + match field.name() { + name if name.starts_with("log.") => (), + _ => { + let display_formatted = format!("{value}"); + self.values + .insert(field.name(), Cow::from(display_formatted)); + } + } + } +} + +impl Layer for StorageLayer +where + S: Subscriber + for<'span> LookupSpan<'span>, +{ + fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { + let span = ctx.span(id).expect("Span not found"); + + // We want to inherit the fields from the parent span, if there is one. + let mut visitor = if let Some(parent_span) = span.parent() { + let mut extensions = parent_span.extensions_mut(); + + let mut inner = extensions + .get_mut::>() + .map(|v| v.to_owned()) + .unwrap_or_default(); + + inner.values.insert( + PARENT_SPAN, + Cow::from(parent_span.id().into_u64().to_string()), + ); + inner + } else { + Storage::default() + }; + + let mut extensions = span.extensions_mut(); + + attrs.record(&mut visitor); + extensions.insert(visitor); + } + + fn on_record(&self, span: &Id, values: &Record<'_>, ctx: Context<'_, S>) { + let span = ctx.span(span).expect("Span not found"); + + let mut extensions = span.extensions_mut(); + let visitor = extensions + .get_mut::>() + .expect("Visitor not found on 'record'!"); + + values.record(visitor); + } + + fn on_follows_from(&self, span: &Id, follows: &Id, ctx: Context<'_, S>) { + let span = ctx.span(span).expect("Span not found"); + let follows_span = ctx.span(follows).expect("Span not found"); + + let mut extensions = span.extensions_mut(); + let follows_extensions = follows_span.extensions(); + + if let Some((visitor, follows_visitor)) = extensions + .get_mut::>() + .zip(follows_extensions.get::>()) + { + // insert "follows_from" span name + visitor + .values + .insert(FOLLOWS_FROM_FIELD, Cow::from(follows_span.name())); + + // insert "follows_from" trace_id + let follows_trace = follows_visitor + .values + .get(TRACE_ID) + .unwrap_or(&Cow::from(format!("{follows:?}"))) + .to_string(); + visitor + .values + .insert(FOLLOWS_FROM_TRACE_ID, Cow::from(follows_trace)); + }; + } + + fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) { + ctx.lookup_current().map(|current_span| { + let mut extensions = current_span.extensions_mut(); + extensions.get_mut::>().map(|visitor| { + if event + .fields() + .any(|f| ON_EVENT_KEEP_FIELDS.contains(&f.name())) + { + event.record(visitor); + } + }) + }); + } + + fn on_enter(&self, span: &Id, ctx: Context<'_, S>) { + let span = ctx.span(span).expect("Span not found"); + + let mut extensions = span.extensions_mut(); + if extensions.get_mut::().is_none() { + extensions.insert(Instant::now()); + } + } + fn on_close(&self, id: Id, ctx: Context<'_, S>) { + let span = ctx.span(&id).expect("Span not found"); + + let mut extensions = span.extensions_mut(); + + let elapsed_milliseconds = extensions + .get_mut::() + .map(|i| i.elapsed().as_millis()) + .unwrap_or(0); + + let visitor = extensions + .get_mut::>() + .expect("Visitor not found on 'record'"); + + visitor + .values + .insert(LATENCY_FIELD, Cow::from(format!("{elapsed_milliseconds}"))); + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 0000000..2ec3e27 --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,118 @@ +use http::Uri; +use reqwest::Client; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_retry::policies::ExponentialBackoff; +use reqwest_tracing::TracingMiddleware; +use serde::Deserialize; +use serde_with::serde_as; +use std::time::Duration; +use wiremock::{ + matchers::{method, path}, + Mock, MockServer, ResponseTemplate, +}; +use efected_coto_emmory::{ + middleware::{ + client::metrics::Metrics, logging::Logger, reqwest_retry::RetryTransientMiddleware, + reqwest_tracing::ExtendedTrace, + }, + settings::{AppEnvironment, HttpClient, HttpClientRetryOptions, Settings}, +}; + +/// Test loading settings. +#[test] +fn test_settings() { + let settings = Settings::load().unwrap(); + assert_eq!(settings.environment(), AppEnvironment::Local); +} + +#[serde_as] +#[derive(Debug, Deserialize)] +struct ClientSettings { + #[serde(default)] + pub http_client: HttpClient, + #[serde(with = "http_serde::uri")] + pub url: Uri, +} + +/// A reqwest-based HTTP client for all Sentilink API operations. +/// +/// 500s are retried. +#[derive(Debug)] +struct AClient { + client: ClientWithMiddleware, + url: String, +} + +impl AClient { + fn load(settings: ClientSettings) -> anyhow::Result { + let retry_policy = ExponentialBackoff::builder() + .retry_bounds( + Duration::from_millis(settings.http_client.retry_options.bounds_low_ms), + Duration::from_millis(settings.http_client.retry_options.bounds_high_ms), + ) + .build_with_max_retries(settings.http_client.retry_options.count.into()); + + // reqwest::Client by default has a timeout of 30s + let reqwest_client = Client::builder() + .pool_idle_timeout(settings.http_client.pool_idle_timeout()) + .timeout(Duration::from_millis(settings.http_client.timeout_ms)) + .build(); + + Ok(Self { + client: ClientBuilder::new(reqwest_client?) + .with(TracingMiddleware::::new()) + .with(Logger) + .with(RetryTransientMiddleware::new_with_policy( + retry_policy, + "AClient".to_string(), + )) + .with(Metrics { + name: "AClient".to_string(), + }) + .build(), + + url: settings.url.to_string(), + }) + } + + async fn query(&self) -> anyhow::Result { + // Send the actual http request. + let response = self + .client + .get(format!("{}query", self.url.to_owned())) + .send() + .await?; + Ok(response) + } +} + +/// Test example reqwest-client call via wiremock. +#[tokio::test] +async fn test_client() { + let mock_server = MockServer::start().await; + + let settings = ClientSettings { + http_client: HttpClient { + pool_idle_timeout_ms: Some(5000), + retry_options: HttpClientRetryOptions { + bounds_low_ms: 100, + bounds_high_ms: 5000, + count: 3, + }, + timeout_ms: 100, + }, + url: mock_server.uri().parse::().unwrap(), + }; + + let client = AClient::load(settings).unwrap(); + + Mock::given(method("GET")) + .and(path("/query")) + .respond_with(ResponseTemplate::new(200)) + .expect(1) // number of expected requests + .mount(&mock_server) + .await; + + let res = client.query().await.unwrap(); + assert_eq!(res.status().as_u16(), 200); +} From 81736a2c56cacdead6ba869aee270c726a0c7c0c Mon Sep 17 00:00:00 2001 From: 50^2 <36756030+aleeusgr@users.noreply.github.com> Date: Fri, 29 Dec 2023 09:14:19 +0200 Subject: [PATCH 02/25] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e6f5c5..0938866 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # efected-cotoemory -a PBL in project management. +the first project in the PBL: Project Management. +Implement a cloud service in Rust to leverage its advantages for Performance, Memory Safety and Concurrency. ## [What is PBL?](https://www.pblworks.org/what-is-pbl) Project Based Learning (PBL) is a teaching method in which students learn by actively engaging in real-world and personally meaningful projects. @@ -11,4 +12,4 @@ TODO, describe UAT: cargo `run` makes a Swagger UI accessible in the browser. F docs: https://medium.com/@alexeusgr/optimizing-python-service-on-ae-cloud-using-rust-b189ced94309 https://github.com/fission-codes/rust-template -TODO: add more links. \ No newline at end of file +TODO: add more links. From a742f4beca61d02e0cf66f004c276da5739b32c5 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 31 Dec 2023 09:14:56 +0200 Subject: [PATCH 03/25] direnv and precommit --- .gitignore | 1 + .pre-commit-config.yaml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.gitignore b/.gitignore index b630a4c..45cc071 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # will have compiled files and executables debug/ target/ +.direnv # 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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..2b032e0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.1.0 + hooks: + - id: check-byte-order-marker + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace +- repo: https://github.com/pre-commit/pre-commit + rev: v2.5.1 + hooks: + - id: validate_manifest From dc225dac847e800cb7f1a8772a13653ac7400e7f Mon Sep 17 00:00:00 2001 From: 50^2 <36756030+aleeusgr@users.noreply.github.com> Date: Sun, 31 Dec 2023 11:42:25 +0200 Subject: [PATCH 04/25] Update README.md --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0938866..3ac1c0f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,20 @@ # efected-cotoemory -the first project in the PBL: Project Management. +the first project in the PBL: Project Management.
Implement a cloud service in Rust to leverage its advantages for Performance, Memory Safety and Concurrency. -## [What is PBL?](https://www.pblworks.org/what-is-pbl) +## [What is a PBL?](https://www.pblworks.org/what-is-pbl) Project Based Learning (PBL) is a teaching method in which students learn by actively engaging in real-world and personally meaningful projects. ## How to run? -nix won't `run` so use the long way: `clone`, nix `develop`, cargo `test | run`,nix `flake update`, -TODO, describe UAT: cargo `run` makes a Swagger UI accessible in the browser. Find the port and link to the doc. +1. `git clone` +2. nix `develop` +3. cargo `test` (optional) +4. cargo `run` -docs: -https://medium.com/@alexeusgr/optimizing-python-service-on-ae-cloud-using-rust-b189ced94309 -https://github.com/fission-codes/rust-template -TODO: add more links. +UAT: Swagger UI is accessible [in the browser](http://localhost:3000/swagger-ui/).
+ +Maybe try nix `flake update`? + +docs:
+1. https://medium.com/@alexeusgr/optimizing-python-service-on-ae-cloud-using-rust-b189ced94309 +2. https://github.com/fission-codes/rust-template From 05ba04a68b7500c433e7a6daf9c42cd262198446 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 3 Jan 2024 16:08:09 +0200 Subject: [PATCH 05/25] review main --- src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index f02a8af..3974f06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,6 +77,9 @@ async fn main() -> Result<()> { serve("Metrics", router, settings.server().metrics_port).await }; + // the app is defined by an id (so we can have an API?) + // a router adds monitoring + // let app = async { let req_id = HeaderName::from_static(REQUEST_ID); let router = router::setup_app_router() @@ -104,6 +107,9 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) + // here is openAPI with UAT: + // TODO: link docs on merge + // Here is where I add new functionality, right? .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); serve("Application", router, settings.server().port).await @@ -112,7 +118,7 @@ async fn main() -> Result<()> { tokio::try_join!(app, app_metrics)?; Ok(()) } - +// to serve means to run with a Router app at a port and address. async fn serve(name: &str, app: Router, port: u16) -> Result<()> { let bind_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port); info!( @@ -123,6 +129,7 @@ async fn serve(name: &str, app: Router, port: u16) -> Result<()> { bind_addr ); + // server binds an address to serve the app at it axum::Server::bind(&bind_addr) .serve(app.into_make_service_with_connect_info::()) .with_graceful_shutdown(shutdown()) From a8c8523cbaf04fd44d2e89102118281e0e3d6512 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 4 Jan 2024 08:44:55 +0200 Subject: [PATCH 06/25] comments --- src/main.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3974f06..8af9f3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,11 +47,14 @@ const REQUEST_ID: &str = "request_id"; #[tokio::main] async fn main() -> Result<()> { + // IO let (stdout_writer, _stdout_guard) = tracing_appender::non_blocking(io::stdout()); + // sui ex let settings = Settings::load()?; setup_tracing(stdout_writer, settings.otel())?; + // tracing? info!( subject = "app_settings", category = "init", @@ -77,13 +80,12 @@ async fn main() -> Result<()> { serve("Metrics", router, settings.server().metrics_port).await }; - // the app is defined by an id (so we can have an API?) - // a router adds monitoring - // let app = async { - let req_id = HeaderName::from_static(REQUEST_ID); + let req_id = HeaderName::from_static(REQUEST_ID); // used by the ServiceBuilder + // Router is used to set up which paths goes to which services: let router = router::setup_app_router() .route_layer(axum::middleware::from_fn(middleware::metrics::track)) + // layer adds additional processing to a request for a group of routes .layer(Extension(env)) // Include trace context as header into the response. .layer(response_with_trace_layer()) @@ -91,6 +93,7 @@ async fn main() -> Result<()> { // This returns a `TraceLayer` configured to use // OpenTelemetry’s conventional span field names. .layer(opentelemetry_tracing_layer()) + // TODO: search ulid // Set and propagate "request_id" (as a ulid) per request. .layer( ServiceBuilder::new() @@ -107,10 +110,10 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - // here is openAPI with UAT: - // TODO: link docs on merge // Here is where I add new functionality, right? + // adds the following router to the self: .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); + //TODO: test layers. serve("Application", router, settings.server().port).await }; From 8c22fd486ffb05f3f48d6f49b2074aa53be26499 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 4 Jan 2024 08:47:37 +0200 Subject: [PATCH 07/25] add an endpoint --- src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.rs b/src/main.rs index 8af9f3c..16496e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,6 +110,7 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) + .route("/say-hello", get(say_hello)) // Here is where I add new functionality, right? // adds the following router to the self: .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); @@ -121,6 +122,9 @@ async fn main() -> Result<()> { tokio::try_join!(app, app_metrics)?; Ok(()) } +async fn say_hello() -> String { + return "Hello!".to_string(); +} // to serve means to run with a Router app at a port and address. async fn serve(name: &str, app: Router, port: u16) -> Result<()> { let bind_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port); From 4b1bfa6bd827e974333a93b02a375ad9cee40589 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 4 Jan 2024 10:09:31 +0200 Subject: [PATCH 08/25] change comments --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 16496e5..0f834f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,7 +111,6 @@ async fn main() -> Result<()> { // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) .route("/say-hello", get(say_hello)) - // Here is where I add new functionality, right? // adds the following router to the self: .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); //TODO: test layers. @@ -122,6 +121,8 @@ async fn main() -> Result<()> { tokio::try_join!(app, app_metrics)?; Ok(()) } +// this is a handler, a function that is used in a Router +// integrate new functionality here: async fn say_hello() -> String { return "Hello!".to_string(); } From d43538009902903d6f09ee056aa48ff5802f106b Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 4 Jan 2024 17:50:15 +0200 Subject: [PATCH 09/25] comment --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0f834f8..f3fa399 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,7 +110,7 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - .route("/say-hello", get(say_hello)) + .route("/say-hello", get(say_hello)) // change handler // adds the following router to the self: .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); //TODO: test layers. @@ -123,6 +123,7 @@ async fn main() -> Result<()> { } // this is a handler, a function that is used in a Router // integrate new functionality here: +// TODO: A | B example: serve a stream. roadmap - show webcam on laptop screen. async fn say_hello() -> String { return "Hello!".to_string(); } From c23f86a4bc140c06db141015f25f98ba3cc96e84 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 08:51:13 +0200 Subject: [PATCH 10/25] add handlers --- handlers/log.rs | 7 +++++++ src/main.rs | 10 +++------- 2 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 handlers/log.rs diff --git a/handlers/log.rs b/handlers/log.rs new file mode 100644 index 0000000..5f4302e --- /dev/null +++ b/handlers/log.rs @@ -0,0 +1,7 @@ + +// this is a handler, a function that is used in a Router +// integrate new functionality here: +// TODO: A | B example: serve a stream. roadmap - show webcam on laptop screen. +pub async fn say_hello() -> String { + return "Hello!".to_string(); +} diff --git a/src/main.rs b/src/main.rs index f3fa399..b4ec913 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,8 @@ use efected_coto_emmory::{ }, }; +pub mod handlers; // notice this line + /// Request identifier field. const REQUEST_ID: &str = "request_id"; @@ -110,7 +112,7 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - .route("/say-hello", get(say_hello)) // change handler + .route("/say-hello", get(handlers::log::say_hello)) // change handler // adds the following router to the self: .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); //TODO: test layers. @@ -121,12 +123,6 @@ async fn main() -> Result<()> { tokio::try_join!(app, app_metrics)?; Ok(()) } -// this is a handler, a function that is used in a Router -// integrate new functionality here: -// TODO: A | B example: serve a stream. roadmap - show webcam on laptop screen. -async fn say_hello() -> String { - return "Hello!".to_string(); -} // to serve means to run with a Router app at a port and address. async fn serve(name: &str, app: Router, port: u16) -> Result<()> { let bind_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port); From 45e8238a2a2cf844926bb01d0f247cfc01aad002 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 08:53:06 +0200 Subject: [PATCH 11/25] create mod.rs --- handlers/mod.rs | 1 + 1 file changed, 1 insertion(+) create mode 100644 handlers/mod.rs diff --git a/handlers/mod.rs b/handlers/mod.rs new file mode 100644 index 0000000..f4ee9bc --- /dev/null +++ b/handlers/mod.rs @@ -0,0 +1 @@ +pub mod log; From fa3b42209b05ed74a714f02efb604fa30b7eaef7 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 09:06:54 +0200 Subject: [PATCH 12/25] fix errors --- {handlers => src/handlers}/log.rs | 0 {handlers => src/handlers}/mod.rs | 0 src/main.rs | 1 + 3 files changed, 1 insertion(+) rename {handlers => src/handlers}/log.rs (100%) rename {handlers => src/handlers}/mod.rs (100%) diff --git a/handlers/log.rs b/src/handlers/log.rs similarity index 100% rename from handlers/log.rs rename to src/handlers/log.rs diff --git a/handlers/mod.rs b/src/handlers/mod.rs similarity index 100% rename from handlers/mod.rs rename to src/handlers/mod.rs diff --git a/src/main.rs b/src/main.rs index b4ec913..ab16ccb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,7 @@ use efected_coto_emmory::{ }, }; +use handlers::log; pub mod handlers; // notice this line /// Request identifier field. From 86094d84b542a8d6c10fb7691dfda6ad11a67a5a Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 16:55:48 +0200 Subject: [PATCH 13/25] change the name for handler file --- src/handlers/mod.rs | 2 +- src/handlers/{log.rs => my.rs} | 3 +-- src/main.rs | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) rename src/handlers/{log.rs => my.rs} (86%) diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index f4ee9bc..d5c1c84 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1 +1 @@ -pub mod log; +pub mod my; diff --git a/src/handlers/log.rs b/src/handlers/my.rs similarity index 86% rename from src/handlers/log.rs rename to src/handlers/my.rs index 5f4302e..53c7b00 100644 --- a/src/handlers/log.rs +++ b/src/handlers/my.rs @@ -1,7 +1,6 @@ - // this is a handler, a function that is used in a Router // integrate new functionality here: // TODO: A | B example: serve a stream. roadmap - show webcam on laptop screen. pub async fn say_hello() -> String { - return "Hello!".to_string(); + return "Hello!!!".to_string(); } diff --git a/src/main.rs b/src/main.rs index ab16ccb..6148d37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ use efected_coto_emmory::{ }, }; -use handlers::log; +use handlers::my; pub mod handlers; // notice this line /// Request identifier field. @@ -113,7 +113,7 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - .route("/say-hello", get(handlers::log::say_hello)) // change handler + .route("/say-hello", get(handlers::my::say_hello)) // change handler // adds the following router to the self: .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); //TODO: test layers. From dbc3e680cf9414487a699f0a01fb43d67946afc5 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 17:15:17 +0200 Subject: [PATCH 14/25] add handlers --- src/handlers/my.rs | 16 ++++++++++++++-- src/main.rs | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/handlers/my.rs b/src/handlers/my.rs index 53c7b00..b146432 100644 --- a/src/handlers/my.rs +++ b/src/handlers/my.rs @@ -1,6 +1,18 @@ +use axum::{ + Json, + response::{Html, IntoResponse}, + http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}}, +}; + // this is a handler, a function that is used in a Router -// integrate new functionality here: -// TODO: A | B example: serve a stream. roadmap - show webcam on laptop screen. +// A handler is an async function that accepts zero or more “extractors” as arguments and returns something that can be converted into a response. pub async fn say_hello() -> String { + // integrate new functionality here: return "Hello!!!".to_string(); } + +pub async fn html() -> Html<&'static str> { + Html("

Hello, World!

") +} +// stream. +// roadmap - show webcam on laptop screen. diff --git a/src/main.rs b/src/main.rs index 6148d37..78bb53c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,7 +113,7 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - .route("/say-hello", get(handlers::my::say_hello)) // change handler + .route("/say-hello", get(handlers::my::html)) // change handler // adds the following router to the self: .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); //TODO: test layers. From 6c02e9e847555e8c77af37fd028d677f42f57d47 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 17:21:26 +0200 Subject: [PATCH 15/25] clean main --- src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 78bb53c..052ed02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,10 +113,8 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - .route("/say-hello", get(handlers::my::html)) // change handler - // adds the following router to the self: + .route("/say-hello", get(handlers::my::html)) .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); - //TODO: test layers. serve("Application", router, settings.server().port).await }; From 9675529fe2763267df599ef88342693e6c49cfca Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 17:39:03 +0200 Subject: [PATCH 16/25] get_image --- src/handlers/my.rs | 10 ++++++++++ src/main.rs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/handlers/my.rs b/src/handlers/my.rs index b146432..5a5af74 100644 --- a/src/handlers/my.rs +++ b/src/handlers/my.rs @@ -15,4 +15,14 @@ pub async fn html() -> Html<&'static str> { Html("

Hello, World!

") } // stream. +pub async fn get_image() -> impl axum::response::IntoResponse { + //let img_path = PathBuf::from("").join("ferris.png"); + //let image = image::io::Reader::open(&img_path).unwrap().decode().unwrap(); + + ( + axum::response::AppendHeaders([(header::CONTENT_TYPE, "image/png")]), + "Hello!!!".to_string() + // image.into_bytes() + ) +} // roadmap - show webcam on laptop screen. diff --git a/src/main.rs b/src/main.rs index 052ed02..39f2ea7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,7 +113,7 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - .route("/say-hello", get(handlers::my::html)) + .route("/say-hello", get(handlers::my::get_image)) .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); serve("Application", router, settings.server().port).await From 8559281eccc962c198f7cd4232211be60f8b0338 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 17:49:20 +0200 Subject: [PATCH 17/25] encode image --- src/handlers/my.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/handlers/my.rs b/src/handlers/my.rs index 5a5af74..b2570a4 100644 --- a/src/handlers/my.rs +++ b/src/handlers/my.rs @@ -3,6 +3,9 @@ use axum::{ response::{Html, IntoResponse}, http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}}, }; +use std::path::PathBuf; +use std::io::{BufWriter, Cursor}; +use image::ImageFormat; // this is a handler, a function that is used in a Router // A handler is an async function that accepts zero or more “extractors” as arguments and returns something that can be converted into a response. @@ -16,12 +19,14 @@ pub async fn html() -> Html<&'static str> { } // stream. pub async fn get_image() -> impl axum::response::IntoResponse { - //let img_path = PathBuf::from("").join("ferris.png"); - //let image = image::io::Reader::open(&img_path).unwrap().decode().unwrap(); - + let img_path = PathBuf::from("assets/").join("a_logo.png"); + let image = image::io::Reader::open(&img_path).unwrap().decode().unwrap(); + let mut buffer = BufWriter::new(Cursor::new(Vec::new())); + image.write_to(&mut buffer, ImageFormat::Png).unwrap(); + let bytes: Vec = buffer.into_inner().unwrap().into_inner(); ( axum::response::AppendHeaders([(header::CONTENT_TYPE, "image/png")]), - "Hello!!!".to_string() + bytes // image.into_bytes() ) } From d61aee230886260c02deb0ca3f89a91745447b16 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 5 Jan 2024 17:49:50 +0200 Subject: [PATCH 18/25] add deps --- Cargo.lock | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + 2 files changed, 183 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e830733..1b2a733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,6 +272,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -299,6 +305,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + [[package]] name = "byteorder" version = "1.5.0" @@ -369,7 +381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", - "half", + "half 1.8.2", ] [[package]] @@ -393,6 +405,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "concurrent-queue" version = "2.4.0" @@ -595,6 +613,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -751,6 +775,7 @@ dependencies = [ "http", "http-serde", "hyper", + "image", "metrics", "metrics-exporter-prometheus", "metrics-util", @@ -836,6 +861,22 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half 2.2.1", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -851,6 +892,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd" +dependencies = [ + "simd-adler32", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -867,6 +917,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1041,6 +1100,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.1" @@ -1072,6 +1141,15 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1325,6 +1403,25 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1386,6 +1483,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.66" @@ -1415,6 +1521,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.151" @@ -1587,6 +1699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1700,6 +1813,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -2165,6 +2289,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "portable-atomic" version = "0.3.20" @@ -2309,6 +2446,15 @@ dependencies = [ "prost", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quanta" version = "0.10.1" @@ -3034,6 +3180,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "sketches-ddsketch" version = "0.2.1" @@ -3076,6 +3228,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -3221,6 +3376,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.31" @@ -3987,6 +4153,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "which" version = "4.4.2" @@ -4229,3 +4401,12 @@ dependencies = [ "crossbeam-utils", "flate2", ] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 3f15390..9869595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ console-subscriber = { version = "0.1", default-features = false, features = [ " const_format = "0.2" futures = "0.3" headers = "0.3" +image = "0.24" http = "0.2" http-serde = "1.1" hyper = "0.14" From 1763cf6dd71667968369923f73f55dd77476cdbb Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 7 Jan 2024 09:34:04 +0200 Subject: [PATCH 19/25] refactor cmm to lib --- src/handlers/my.rs | 26 -------------------------- src/lib.rs | 27 +++++++++++++++++++++++++++ src/main.rs | 7 ++++--- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/handlers/my.rs b/src/handlers/my.rs index b2570a4..66ce4a4 100644 --- a/src/handlers/my.rs +++ b/src/handlers/my.rs @@ -1,11 +1,3 @@ -use axum::{ - Json, - response::{Html, IntoResponse}, - http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}}, -}; -use std::path::PathBuf; -use std::io::{BufWriter, Cursor}; -use image::ImageFormat; // this is a handler, a function that is used in a Router // A handler is an async function that accepts zero or more “extractors” as arguments and returns something that can be converted into a response. @@ -13,21 +5,3 @@ pub async fn say_hello() -> String { // integrate new functionality here: return "Hello!!!".to_string(); } - -pub async fn html() -> Html<&'static str> { - Html("

Hello, World!

") -} -// stream. -pub async fn get_image() -> impl axum::response::IntoResponse { - let img_path = PathBuf::from("assets/").join("a_logo.png"); - let image = image::io::Reader::open(&img_path).unwrap().decode().unwrap(); - let mut buffer = BufWriter::new(Cursor::new(Vec::new())); - image.write_to(&mut buffer, ImageFormat::Png).unwrap(); - let bytes: Vec = buffer.into_inner().unwrap().into_inner(); - ( - axum::response::AppendHeaders([(header::CONTENT_TYPE, "image/png")]), - bytes - // image.into_bytes() - ) -} -// roadmap - show webcam on laptop screen. diff --git a/src/lib.rs b/src/lib.rs index dd42ac6..a4cffb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,15 @@ pub mod settings; pub mod tracer; pub mod tracing_layers; +use axum::{ + Json, + response::{Html, IntoResponse}, + http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}}, +}; +use std::path::PathBuf; +use std::io::{BufWriter, Cursor}; +use image::ImageFormat; + /// Test utilities. #[cfg(any(test, feature = "test_utils"))] #[cfg_attr(docsrs, doc(cfg(feature = "test_utils")))] @@ -24,3 +33,21 @@ pub mod test_utils; pub fn add(a: i32, b: i32) -> i32 { a + b } +// stream. +pub async fn get_image() -> impl axum::response::IntoResponse { + let img_path = PathBuf::from("assets/").join("a_logo.png"); + let image = image::io::Reader::open(&img_path).unwrap().decode().unwrap(); + let mut buffer = BufWriter::new(Cursor::new(Vec::new())); + image.write_to(&mut buffer, ImageFormat::Png).unwrap(); + let bytes: Vec = buffer.into_inner().unwrap().into_inner(); + ( + axum::response::AppendHeaders([(header::CONTENT_TYPE, "image/png")]), + bytes + // image.into_bytes() + ) +} + +pub async fn html() -> Html<&'static str> { + Html("

Hello, World!

") +} +// roadmap - show webcam on laptop screen. diff --git a/src/main.rs b/src/main.rs index 39f2ea7..95e6f2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,8 +42,9 @@ use efected_coto_emmory::{ }, }; +use efected_coto_emmory::get_image; use handlers::my; -pub mod handlers; // notice this line +pub mod handlers; /// Request identifier field. const REQUEST_ID: &str = "request_id"; @@ -96,7 +97,6 @@ async fn main() -> Result<()> { // This returns a `TraceLayer` configured to use // OpenTelemetry’s conventional span field names. .layer(opentelemetry_tracing_layer()) - // TODO: search ulid // Set and propagate "request_id" (as a ulid) per request. .layer( ServiceBuilder::new() @@ -113,7 +113,8 @@ async fn main() -> Result<()> { .layer(CatchPanicLayer::custom(runtime::catch_panic)) // Mark headers as sensitive on both requests and responses. .layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION])) - .route("/say-hello", get(handlers::my::get_image)) + .route("/say-hello", get(handlers::my::say_hello)) + .route("/test-lib", get(get_image)) .merge(SwaggerUi::new("/swagger-ui").url("/api-doc/openapi.json", ApiDoc::openapi())); serve("Application", router, settings.server().port).await From 88936cdc1784f585e865fd965c9f94610ebfc71b Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 8 Jan 2024 09:53:17 +0200 Subject: [PATCH 20/25] add openssl --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 1b2a733..a67f878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,6 +783,7 @@ dependencies = [ "num_cpus", "once_cell", "openssl", + "openssl-sys", "opentelemetry 0.18.0", "opentelemetry-otlp", "opentelemetry-semantic-conventions", diff --git a/Cargo.toml b/Cargo.toml index 9869595..90c84b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ mime = "0.3" num_cpus = "1.0" once_cell = "1.14" openssl = { version = "0.10", features = ["vendored"], default-features = false } +openssl-sys = "0.9.98" opentelemetry = { version = "0.18", features = ["rt-tokio", "trace"] } opentelemetry-otlp = { version = "0.11", features = ["metrics", "grpc-tonic", "tls-roots"], default-features = false } opentelemetry-semantic-conventions = "0.10" From bd442de87964d34828ef93abdd87a7975f149f27 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 8 Jan 2024 10:17:32 +0200 Subject: [PATCH 21/25] add openssl and pkg-config --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 14b56f2..a9a3826 100644 --- a/flake.nix +++ b/flake.nix @@ -61,6 +61,8 @@ # because native build inputs are added to $PATH in the order they're listed here. nightly-rustfmt rust-toolchain + openssl + pkg-config pre-commit protobuf direnv From bd95504edee4601953207c74c7b1b12f3e182c02 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 9 Jan 2024 08:24:57 +0200 Subject: [PATCH 22/25] add testing tools --- flake.nix | 1 + test_rhea.sh | 1 + 2 files changed, 2 insertions(+) create mode 100755 test_rhea.sh diff --git a/flake.nix b/flake.nix index a9a3826..b0886aa 100644 --- a/flake.nix +++ b/flake.nix @@ -63,6 +63,7 @@ rust-toolchain openssl pkg-config + ffmpeg pre-commit protobuf direnv diff --git a/test_rhea.sh b/test_rhea.sh new file mode 100755 index 0000000..a646d74 --- /dev/null +++ b/test_rhea.sh @@ -0,0 +1 @@ +~/.cargo/bin/rhea --url http://localhost:3000/say-hello --concurrency 100 --requests 1000 From 7aaf3c96f30d451438a0135e6eac69fc308cdd67 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 9 Jan 2024 11:45:14 +0200 Subject: [PATCH 23/25] refactor main --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 95e6f2c..4110189 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; use efected_coto_emmory::{ docs::ApiDoc, + get_image, metrics::{process, prom::setup_metrics_recorder}, middleware::{self, request_ulid::MakeRequestUlid, runtime}, router, @@ -42,7 +43,6 @@ use efected_coto_emmory::{ }, }; -use efected_coto_emmory::get_image; use handlers::my; pub mod handlers; From 7a17318beacc20d6b57236a9c7b7c5615dd231ee Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 9 Jan 2024 11:46:04 +0200 Subject: [PATCH 24/25] add static video server --- test_svs.sh | 1 + 1 file changed, 1 insertion(+) create mode 100755 test_svs.sh diff --git a/test_svs.sh b/test_svs.sh new file mode 100755 index 0000000..ada1e1a --- /dev/null +++ b/test_svs.sh @@ -0,0 +1 @@ +UST_LOG="info" /home/alex/.cargo/bin/static-video-server --assets-root "/home/alex/Videos" --port 9092 --host "0.0.0.0" From ea594951857c33d7d84b2a8573145ac0b4edb7db Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 10 Jan 2024 08:46:33 +0200 Subject: [PATCH 25/25] update readme and flake --- README.md | 2 +- flake.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3ac1c0f..41c1810 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Project Based Learning (PBL) is a teaching method in which students learn by act 3. cargo `test` (optional) 4. cargo `run` -UAT: Swagger UI is accessible [in the browser](http://localhost:3000/swagger-ui/).
+Swagger UI is accessible [in the browser](http://localhost:3000/swagger-ui/) or http://localhost:3000/test-lib Maybe try nix `flake update`? diff --git a/flake.lock b/flake.lock index 1682adf..9324f4a 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1703134684, - "narHash": "sha256-SQmng1EnBFLzS7WSRyPM9HgmZP2kLJcPAz+Ug/nug6o=", + "lastModified": 1704626572, + "narHash": "sha256-VwRTEKzK4wSSv64G+g3RLF3t6yBHrhR2VK3kZ5UWisU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d6863cbcbbb80e71cecfc03356db1cda38919523", + "rev": "24fe8bb4f552ad3926274d29e083b79d84707da6", "type": "github" }, "original": { @@ -68,11 +68,11 @@ ] }, "locked": { - "lastModified": 1703470538, - "narHash": "sha256-NVFMSr99F0TIqVWBwDqMH6lWoM4PVyzMtI+CPGRIscg=", + "lastModified": 1704853054, + "narHash": "sha256-xD87M7isL2XqlFr+2f+j86jy8s5lfIaAEWO4TpQQZUA=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "f2b937756343365f9b1ba66ec7a1ca489aef745c", + "rev": "6dea03e0c8a81cf28340564259d4762b6d6f01de", "type": "github" }, "original": {