[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\n\nenv:\n  CARGO_TERM_COLOR: always\n\non:\n  push:\n    branches: [main]\n  pull_request: {}\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n          components: clippy, rustfmt\n      - uses: Swatinem/rust-cache@v2\n      - name: run clippy\n        run: cargo clippy --all-features -- -D warnings\n      - name: run formatter checks\n        run: cargo fmt --all --check\n\n  test:\n    needs: check\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n      - uses: Swatinem/rust-cache@v2\n      - name: run tests\n        run: cargo test --all-features\n"
  },
  {
    "path": ".gitignore",
    "content": "# Directories\n.cargo/\n.turbo/\nassets/\nbuild/\ndata/\ndist/\nnode_modules/\npublic/\ntarget/\n\n# Files\n.env\n.env.development\n.env.production\n.log\nCargo.lock\npnpm-lock.yaml\n\n# User Settings\n.idea\n.vscode"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## v0.8.1\n\n- Revert an accidental breaking change introducting a nightly-only feature.\n\n## v0.8.0\n\n- Change `HxLocation`, `HxPushUrl`, `HxRedirect`, and `HxReplaceUrl` to take `String` instead of `Uri` for better flexibility and ease-of-use. _([@skwee357](https://github.com/skwee357))_\n    - `HxCurrentUrl` remains unchanged in order to align with axum.\n- Changed how `LocationOptions` is handled internally with regard to non-exhaustiveness,\nallowing external crates to use the functional record update syntax.\n\n## v0.7.0\n\n- Support axum v0.8. _([@kakalos12](https://github.com/kakalos12))_\n\n## v0.6.0\n\n- Added support for Vary headers in responses via the `VaryHxRequest`, `VaryHxTarget`, `VaryHxTrigger`, and `VaryHxTriggerName` responders. _([@imbolc](https://github.com/imbolc))_\n- Header names/values are now typed as `HeaderName` and `HeaderValue` instead of `&str`. _([@imbolc](https://github.com/imbolc))_\n- `HxError` now implements source on `error::Error`. _([@imbolc](https://github.com/imbolc))_\n- Added `AutoVaryLayer` middleware to automatically manage `Vary` headers when using corresponding extractors. The middleware is behind the `auto-vary` feature. [See this section of the README for more details.](https://github.com/robertwayne/axum-htmx?tab=readme-ov-file#vary-responders). _([@imbolc](https://github.com/imbolc))_\n\n## v0.5.0\n\nThere are some several breaking changes in this release. Big thanks to [@ItsEthra](https://github.com/ItsEthra) for their work in several PRs!\n\n- All responders now take an `HxEvent` instead of a `String | HxEvent`. When the `serde` flag is enabled, it will expose additional data fields.\n- `HxResponseTrigger` is now a simple struct containing an `TriggerMode` and a `Vec<HxEvent>`. There are several methods to make constructing these easier: `HxResponseTrigger::normal`, `HxResponseTrigger::after_settle`, and `HxResponseTrigger::after_swap`.\n- The `HxCurrentUrl` extractor now returns an `Option<axum::http::Uri>` instead of a `String`. If the Uri cannot be parsed, it will return `None`.\n- All Uri-related responders now impl `TryFrom<&str>`.\n- `HxError::Serialization` has been renamed to `HxError::Json`.\n- The `HxResponseTrigger*` header will not be added to the response if the event list is empty.\n- Added feature flag badges and made additional updates to the docs.rs pages.\n- Reduced dependency count / compile time by swapping `axum` out for the `axum-core`, `async-trait`, and `http` crates.\n\n## v0.4.0\n\n- Added support for all [htmx response headers](https://htmx.org/reference/#response_headers) via a type implementing `IntoResponseParts`. These \"responders\" allow you to simply and safely apply the HX-* headers to any of your responses. Thanks to [@pfz4](https://github.com/pfz4) for the implementation work! ([#5](https://github.com/robertwayne/axum-htmx/pull/5))\n\n## v0.3.1\n\n- Rebuild docs with features enabled so `HxRequestGuardLayer` is visible on docs.rs.\n\n## v0.3.0\n\n- `HxRequestGuardLayer` now redirects on failures instead of returning a 403\\. By default, it will redirect to \"/\", but you can specify a different route to redirect to with `HxRequestGuardLayer::new(\"/your-route-here\")`.\n\n## v0.2.0\n\n- Added `HxRequestGuardLayer`, allowing you to protect an entire router from non-htmx requests.\n\n## v0.1.0\n\n- Initial release.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"axum-htmx\"\nauthors = [\"Rob Wagner <rob@sombia.com>\"]\nlicense = \"MIT OR Apache-2.0\"\ndescription = \"A set of htmx extractors, responders, and request guards for axum.\"\nrepository = \"https://github.com/robertwayne/axum-htmx\"\ncategories = [\"web-programming\"]\nkeywords = [\"axum\", \"htmx\"]\nreadme = \"README.md\"\nversion = \"0.8.1\"\nedition = \"2024\"\nrust-version = \"1.87\"\n\n[features]\ndefault = []\nunstable = []\nguards = [\"tower\", \"futures-core\", \"pin-project-lite\"]\nserde = [\"dep:serde\", \"dep:serde_json\"]\nauto-vary = [\"futures\", \"tokio\", \"tower\"]\n\n[dependencies]\naxum-core = \"0.5\"\nhttp = { version = \"1\", default-features = false }\n\n# Optional dependencies required for the `guards` feature.\ntower = { version = \"0.5\", default-features = false, optional = true }\nfutures-core = { version = \"0.3\", optional = true }\npin-project-lite = { version = \"0.2\", optional = true }\n\n# Optional dependencies required for the `serde` feature.\nserde = { version = \"1\", features = [\"derive\"], optional = true }\nserde_json = { version = \"1\", optional = true }\n\n# Optional dependencies required for the `auto-vary` feature.\ntokio = { version = \"1\", features = [\"sync\"], optional = true }\nfutures = { version = \"0.3\", default-features = false, features = [\n    \"alloc\",\n], optional = true }\n\n[dev-dependencies]\naxum = { version = \"0.8\", default-features = false }\naxum-test = \"18\"\ntokio = { version = \"1\", features = [\"full\"] }\ntokio-test = \"0.4\"\n\n[package.metadata.docs.rs]\nall-features = true\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.\n\n\"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.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.\n\n\"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.\n\n\"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).\n\n\"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.\n\n\"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.\"\n\n\"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.\n\n2. 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.\n\n3. 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.\n\n4. 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:\n\nYou must give any other recipients of the Work or Derivative Works a copy of this License; and\nYou must cause any modified files to carry prominent notices stating that You changed the files; and\nYou 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\nIf 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.\n\nYou 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.\n5. 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.\n\n6. 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.\n\n7. 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.\n\n8. 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.\n\n9. 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.\n\nEND OF TERMS AND CONDITIONS"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Copyright 2023 Rob Wagner\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# axum-htmx\n\n<!-- markdownlint-disable -->\n<div align=\"right\">\n<a href=\"https://crates.io/crates/axum-htmx\">\n    <img src=\"https://img.shields.io/crates/v/axum-htmx?style=flat-square\" alt=\"crates.io badge\">\n</a>\n<a href=\"https://docs.rs/axum-htmx/latest/\">\n    <img src=\"https://img.shields.io/docsrs/axum-htmx?style=flat-square\" alt=\"docs.rs badge\">\n</a>\n</div>\n<br>\n<!-- markdownlint-enable -->\n\n`axum-htmx` is a small extension library providing extractors, responders, and\n request guards for [htmx](https://htmx.org/) headers within\n [axum](https://github.com/tokio-rs/axum).\n\n## Table of Contents\n\n  - [Getting Started](#getting-started)\n  - [Extractors](#extractors)\n  - [Responders](#responders)\n    - [Vary Responders](#vary-responders)\n  - [Auto Caching Management](#auto-caching-management)\n  - [Request Guards](#request-guards)\n  - [Examples](#examples)\n    - [Example: Extractors](#example-extractors)\n    - [Example: Responders](#example-responders)\n    - [Example: Router Guard](#example-router-guard)\n  - [Feature Flags](#feature-flags)\n  - [Contributing](#contributing)\n    - [Testing](#testing)\n  - [License](#license)\n\n## Getting Started\n\nRun `cargo add axum-htmx` to add the library to your project.\n\n## Extractors\n\nAll of the [htmx request headers](https://htmx.org/reference/#request_headers)\nhave a supported extractor. Extractors are infallible, meaning they will always\nsucceed and never return an error. In the case where a header is not present,\nthe extractor will return `None` or `false` dependant on the expected return\ntype.\n\n| Header                       | Extractor                 | Value                     |\n|------------------------------|---------------------------|---------------------------|\n| `HX-Boosted`                 | `HxBoosted`               | `bool`                    |\n| `HX-Current-URL`             | `HxCurrentUrl`            | `Option<axum::http::Uri>` |\n| `HX-History-Restore-Request` | `HxHistoryRestoreRequest` | `bool`                    |\n| `HX-Prompt`                  | `HxPrompt`                | `Option<String>`          |\n| `HX-Request`                 | `HxRequest`               | `bool`                    |\n| `HX-Target`                  | `HxTarget`                | `Option<String>`          |\n| `HX-Trigger-Name`            | `HxTriggerName`           | `Option<String>`          |\n| `HX-Trigger`                 | `HxTrigger`               | `Option<String>`          |\n\n## Responders\n\nAll of the [htmx response headers](https://htmx.org/reference/#response_headers)\nhave a supported responder. A responder is a basic type that implements\n`IntoResponseParts`, allowing you to simply and safely apply the HX-* headers to\nany of your responses.\n\n| Header                    | Responder           | Value                               |\n|---------------------------|---------------------|-------------------------------------|\n| `HX-Location`             | `HxLocation`        | `String`                   |\n| `HX-Push-Url`             | `HxPushUrl`         | `String`                   |\n| `HX-Redirect`             | `HxRedirect`        | `String`                   |\n| `HX-Refresh`              | `HxRefresh`         | `bool`                              |\n| `HX-Replace-Url`          | `HxReplaceUrl`      | `String`                   |\n| `HX-Reswap`               | `HxReswap`          | `axum_htmx::responders::SwapOption` |\n| `HX-Retarget`             | `HxRetarget`        | `String`                            |\n| `HX-Reselect`             | `HxReselect`        | `String`                            |\n| `HX-Trigger`              | `HxResponseTrigger` | `axum_htmx::serde::HxEvent`         |\n| `HX-Trigger-After-Settle` | `HxResponseTrigger` | `axum_htmx::serde::HxEvent`         |\n| `HX-Trigger-After-Swap`   | `HxResponseTrigger` | `axum_htmx::serde::HxEvent`         |\n\n### Vary Responders\n\nAlso, there are corresponding cache-related headers, which you may want to add to\n`GET` responses, depending on the htmx headers.\n\n_For example, if your server renders the full HTML when the `HX-Request` header is\nmissing or `false`, and it renders a fragment of that HTML when `HX-Request: true`,\nyou need to add `Vary: HX-Request`. That causes the cache to be keyed based on a\ncomposite of the response URL and the `HX-Request` request header - rather than\nbeing based just on the response URL._\n\nRefer to [caching htmx docs section][htmx-caching] for details.\n\n| Header                  | Responder           |\n|-------------------------|---------------------|\n| `Vary: HX-Request`      | `VaryHxRequest`     |\n| `Vary: HX-Target`       | `VaryHxTarget`      |\n| `Vary: HX-Trigger`      | `VaryHxTrigger`     |\n| `Vary: HX-Trigger-Name` | `VaryHxTriggerName` |\n\nLook at the [Auto Caching Management](#auto-caching-management) section for\nautomatic `Vary` headers management.\n\n## Auto Caching Management\n\n__Requires feature `auto-vary`.__\n\nManual use of [Vary Reponders](#vary-responders) adds fragility to the code,\nbecause of the need to manually control correspondence between used extractors\nand the responders.\n\nWe provide a [middleware](crate::AutoVaryLayer) to address this issue by\nautomatically adding `Vary` headers when corresponding extractors are used.\nFor example, on extracting [`HxRequest`], the middleware automatically adds \n`Vary: hx-request` header to the response.\n\nLook at the usage [example][auto-vary-example].\n\n## Request Guards\n\n__Requires feature `guards`.__\n\nIn addition to the extractors, there is also a route-wide layer request guard\nfor the `HX-Request` header. This will redirect any requests without the header\nto \"/\" by default.\n\n_It should be noted that this is NOT a replacement for an auth guard. A user can\ntrivially set the `HX-Request` header themselves. This is merely a convenience\nfor preventing users from receiving partial responses without context. If you\nneed to secure an endpoint you should be using a proper auth system._\n\n## Examples\n\n### Example: Extractors\n\nIn this example, we'll look for the `HX-Boosted` header, which is set when\napplying the [hx-boost](https://htmx.org/attributes/hx-boost/) attribute to an\nelement. In our case, we'll use it to determine what kind of response we send.\n\nWhen is this useful? When using a templating engine, like\n[minijinja](https://github.com/mitsuhiko/minijinja), it is common to extend\ndifferent templates from a `_base.html` template. However, htmx works by sending\npartial responses, so extending our `_base.html` would result in lots of extra\ndata being sent over the wire.\n\nIf we wanted to swap between pages, we would need to support both full template\nresponses and partial responses _(as the page can be accessed directly or\nthrough a boosted anchor)_, so we look for the `HX-Boosted` header and extend\nfrom a `_partial.html` template instead.\n\n```rust\nuse axum::response::IntoResponse;\nuse axum_htmx::HxBoosted;\n\nasync fn get_index(HxBoosted(boosted): HxBoosted) -> impl IntoResponse {\n    if boosted {\n        // Send a template extending from _partial.html\n    } else {\n        // Send a template extending from _base.html\n    }\n}\n```\n\n### Example: Responders\n\nWe can trigger any event being listened to by the DOM using an [htmx\ntrigger](https://htmx.org/attributes/hx-trigger/) header.\n\n```rust\nuse axum_htmx::HxResponseTrigger;\n\n// When we load our page, we will trigger any event listeners for \"my-event.\nasync fn index() -> (HxResponseTrigger, &'static str) {\n    // Note: As HxResponseTrigger only implements `IntoResponseParts`, we must\n    // return our trigger first here.\n    (\n        HxResponseTrigger::normal([\"my-event\", \"second-event\"]),\n        \"Hello, world!\",\n    )\n}\n```\n\n`htmx` also allows arbitrary data to be sent along with the event, which we can\nuse via the `serde` feature flag and the `HxEvent` type.\n\n```rust\nuse serde_json::json;\n\n// Note that we are using `HxResponseTrigger` from the `axum_htmx::serde` module\n// instead of the root module.\nuse axum_htmx::{HxEvent, HxResponseTrigger};\n\nasync fn index() -> (HxResponseTrigger, &'static str) {\n    let event = HxEvent::new_with_data(\n        \"my-event\",\n        // May be any object that implements `serde::Serialize`\n        json!({\"level\": \"info\", \"message\": {\n            \"title\": \"Hello, world!\",\n            \"body\": \"This is a test message.\",\n        }}),\n    )\n    .unwrap();\n\n    // Note: As HxResponseTrigger only implements `IntoResponseParts`, we must\n    // return our trigger first here.\n    (HxResponseTrigger::normal([event]), \"Hello, world!\")\n}\n```\n\n### Example: Router Guard\n\n```rust\nuse axum::Router;\nuse axum_htmx::HxRequestGuardLayer;\n\nfn router_one() -> Router {\n    Router::new()\n        // Redirects to \"/\" if the HX-Request header is not present\n        .layer(HxRequestGuardLayer::default())\n}\n\nfn router_two() -> Router {\n    Router::new()\n        .layer(HxRequestGuardLayer::new(\"/redirect-to-this-route\"))\n}\n```\n\n## Feature Flags\n\n<!-- markdownlint-disable -->\n| Flag        | Default  | Description                                                | Dependencies                                |\n|-------------|----------|------------------------------------------------------------|---------------------------------------------|\n| `auto-vary` | Disabled | A middleware to address [htmx caching issue][htmx-caching] | `futures`, `tokio`, `tower`                 |\n| `guards`    | Disabled | Adds request guard layers.                                 | `tower`, `futures-core`, `pin-project-lite` |\n| `serde`     | Disabled | Adds serde support for the `HxEvent` and `LocationOptions` | `serde`, `serde_json`                       |\n<!-- markdownlint-enable -->\n\n## Contributing\n\nContributions are always welcome! If you have an idea for a feature or find a\nbug, let me know. PR's are appreciated, but if it's not a small change, please\nopen an issue first so we're all on the same page!\n\n### Testing\n\n```sh\ncargo +nightly test --all-features\n```\n\n## License\n\n`axum-htmx` is dual-licensed under either\n\n- **[MIT License](/LICENSE-MIT)**\n- **[Apache License, Version 2.0](/LICENSE-APACHE)**\n\nat your option.\n\n[htmx-caching]: https://htmx.org/docs/#caching\n[auto-vary-example]: https://github.com/robertwayne/axum-htmx/blob/main/examples/auto-vary.rs\n"
  },
  {
    "path": "examples/auto-vary.rs",
    "content": "//! Using `auto-vary` middleware\n//!\n//! Don't forget about the feature while running it:\n//! `cargo run --features auto-vary --example auto-vary`\nuse std::time::Duration;\n\nuse axum::{Router, response::Html, routing::get, serve};\nuse axum_htmx::{AutoVaryLayer, HxRequest};\nuse tokio::{net::TcpListener, time::sleep};\n\n#[tokio::main]\nasync fn main() {\n    let app = Router::new()\n        .route(\"/\", get(handler))\n        // Add the middleware\n        .layer(AutoVaryLayer);\n\n    let listener = TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    serve(listener, app).await.unwrap();\n}\n\n// Our handler differentiates full-page GET requests from htmx-based ones by looking at the `hx-request`\n// requestheader.\n//\n// The middleware sees the usage of the `HxRequest` extractor and automatically adds the\n// `Vary: hx-request` response header.\nasync fn handler(HxRequest(hx_request): HxRequest) -> Html<&'static str> {\n    if hx_request {\n        // For htmx-based GET request, it returns a partial page update\n        sleep(Duration::from_secs(3)).await;\n        return Html(\"htmx response\");\n    }\n    // While for a normal GET request, it returns the whole page\n    Html(\n        r#\"\n        <script src=\"https://unpkg.com/htmx.org@1\"></script>\n        <p hx-get=\"/\" hx-trigger=\"load\">Loading ...</p>\n        \"#,\n    )\n}\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "group_imports = \"StdExternalCrate\"\nimports_granularity = \"Crate\"\nreorder_imports = true\n"
  },
  {
    "path": "src/auto_vary.rs",
    "content": "//! A middleware to automatically add a `Vary` header when needed to address\n//! [htmx caching issue](https://htmx.org/docs/#caching)\n\nuse std::{\n    sync::Arc,\n    task::{Context, Poll},\n};\n\nuse axum_core::{\n    extract::Request,\n    response::{IntoResponse, Response},\n};\nuse futures::future::{BoxFuture, join_all};\nuse http::{\n    Extensions,\n    header::{HeaderValue, VARY},\n};\nuse tokio::sync::oneshot::{self, Receiver, Sender};\nuse tower::{Layer, Service};\n\nuse crate::{\n    HxError,\n    headers::{HX_REQUEST_STR, HX_TARGET_STR, HX_TRIGGER_NAME_STR, HX_TRIGGER_STR},\n};\n#[cfg(doc)]\nuse crate::{HxRequest, HxTarget, HxTrigger, HxTriggerName};\n\nconst MIDDLEWARE_DOUBLE_USE: &str =\n    \"Configuration error: `axum_httpx::vary_middleware` is used twice\";\n\n/// Addresses [htmx caching issues](https://htmx.org/docs/#caching)\n/// by automatically adding a corresponding `Vary` header when\n/// [`HxRequest`], [`HxTarget`], [`HxTrigger`], [`HxTriggerName`]\n/// or their combination is used.\n#[derive(Clone)]\npub struct AutoVaryLayer;\n\n/// Tower service for [`AutoVaryLayer`]\n#[derive(Clone)]\npub struct AutoVaryMiddleware<S> {\n    inner: S,\n}\n\npub(crate) trait Notifier {\n    fn sender(&mut self) -> Option<Sender<()>>;\n\n    fn notify(&mut self) {\n        if let Some(sender) = self.sender() {\n            sender.send(()).ok();\n        }\n    }\n\n    fn insert(extensions: &mut Extensions) -> Receiver<()>;\n}\n\nmacro_rules! define_notifiers {\n    ($($name:ident),*) => {\n        $(\n            #[derive(Clone)]\n            pub(crate) struct $name(Option<Arc<Sender<()>>>);\n\n            impl Notifier for $name {\n                fn sender(&mut self) -> Option<Sender<()>> {\n                    self.0.take().and_then(Arc::into_inner)\n                }\n\n                fn insert(extensions: &mut Extensions) -> Receiver<()> {\n                    let (tx, rx) = oneshot::channel();\n                    if extensions.insert(Self(Some(Arc::new(tx)))).is_some() {\n                        panic!(\"{}\", MIDDLEWARE_DOUBLE_USE);\n                    }\n                    rx\n                }\n            }\n        )*\n    }\n}\n\ndefine_notifiers!(\n    HxRequestExtracted,\n    HxTargetExtracted,\n    HxTriggerExtracted,\n    HxTriggerNameExtracted\n);\n\nimpl<S> Layer<S> for AutoVaryLayer {\n    type Service = AutoVaryMiddleware<S>;\n\n    fn layer(&self, inner: S) -> Self::Service {\n        AutoVaryMiddleware { inner }\n    }\n}\n\nimpl<S> Service<Request> for AutoVaryMiddleware<S>\nwhere\n    S: Service<Request, Response = Response> + Send + 'static,\n    S::Future: Send + 'static,\n{\n    type Response = S::Response;\n    type Error = S::Error;\n    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;\n\n    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        self.inner.poll_ready(cx)\n    }\n\n    fn call(&mut self, mut request: Request) -> Self::Future {\n        let exts = request.extensions_mut();\n        let rx_header = [\n            (HxRequestExtracted::insert(exts), HX_REQUEST_STR),\n            (HxTargetExtracted::insert(exts), HX_TARGET_STR),\n            (HxTriggerExtracted::insert(exts), HX_TRIGGER_STR),\n            (HxTriggerNameExtracted::insert(exts), HX_TRIGGER_NAME_STR),\n        ];\n        let future = self.inner.call(request);\n        Box::pin(async move {\n            let mut response: Response = future.await?;\n            let used_headers: Vec<_> = join_all(\n                rx_header\n                    .into_iter()\n                    .map(|(rx, header)| async move { rx.await.ok().map(|_| header) }),\n            )\n            .await\n            .into_iter()\n            .flatten()\n            .collect();\n\n            if used_headers.is_empty() {\n                return Ok(response);\n            }\n\n            let value = match HeaderValue::from_str(&used_headers.join(\", \")) {\n                Ok(x) => x,\n                Err(e) => return Ok(HxError::from(e).into_response()),\n            };\n\n            if let Err(e) = response.headers_mut().try_append(VARY, value) {\n                return Ok(HxError::from(e).into_response());\n            }\n\n            Ok(response)\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use axum::{Router, routing::get};\n\n    use super::*;\n    use crate::{HxRequest, HxTarget, HxTrigger, HxTriggerName};\n\n    fn vary_headers(resp: &axum_test::TestResponse) -> Vec<HeaderValue> {\n        resp.iter_headers_by_name(\"vary\").cloned().collect()\n    }\n\n    fn server() -> axum_test::TestServer {\n        let app = Router::new()\n            .route(\"/no-extractors\", get(|| async { () }))\n            .route(\"/hx-request\", get(|_: HxRequest| async { () }))\n            .route(\"/hx-target\", get(|_: HxTarget| async { () }))\n            .route(\"/hx-trigger\", get(|_: HxTrigger| async { () }))\n            .route(\"/hx-trigger-name\", get(|_: HxTriggerName| async { () }))\n            .route(\n                \"/repeated-extractor\",\n                get(|_: HxRequest, _: HxRequest| async { () }),\n            )\n            .route(\n                \"/multiple-extractors\",\n                get(|_: HxRequest, _: HxTarget, _: HxTrigger, _: HxTriggerName| async { () }),\n            )\n            .layer(AutoVaryLayer);\n        axum_test::TestServer::new(app).unwrap()\n    }\n\n    #[tokio::test]\n    async fn no_extractors() {\n        assert!(vary_headers(&server().get(\"/no-extractors\").await).is_empty());\n    }\n\n    #[tokio::test]\n    async fn single_hx_request() {\n        assert_eq!(\n            vary_headers(&server().get(\"/hx-request\").await),\n            [\"hx-request\"]\n        );\n    }\n\n    #[tokio::test]\n    async fn single_hx_target() {\n        assert_eq!(\n            vary_headers(&server().get(\"/hx-target\").await),\n            [\"hx-target\"]\n        );\n    }\n\n    #[tokio::test]\n    async fn single_hx_trigger() {\n        assert_eq!(\n            vary_headers(&server().get(\"/hx-trigger\").await),\n            [\"hx-trigger\"]\n        );\n    }\n\n    #[tokio::test]\n    async fn single_hx_trigger_name() {\n        assert_eq!(\n            vary_headers(&server().get(\"/hx-trigger-name\").await),\n            [\"hx-trigger-name\"]\n        );\n    }\n\n    #[tokio::test]\n    async fn repeated_extractor() {\n        assert_eq!(\n            vary_headers(&server().get(\"/repeated-extractor\").await),\n            [\"hx-request\"]\n        );\n    }\n\n    // Extractors can be used multiple times e.g. in middlewares\n    #[tokio::test]\n    async fn multiple_extractors() {\n        assert_eq!(\n            vary_headers(&server().get(\"/multiple-extractors\").await),\n            [\"hx-request, hx-target, hx-trigger, hx-trigger-name\"],\n        );\n    }\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "use std::{error, fmt};\n\nuse axum_core::response::IntoResponse;\nuse http::{\n    StatusCode,\n    header::{InvalidHeaderValue, MaxSizeReached},\n};\n\n#[derive(Debug)]\npub enum HxError {\n    InvalidHeaderValue(InvalidHeaderValue),\n    TooManyResponseHeaders(MaxSizeReached),\n\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\n    Json(serde_json::Error),\n}\n\nimpl From<InvalidHeaderValue> for HxError {\n    fn from(value: InvalidHeaderValue) -> Self {\n        Self::InvalidHeaderValue(value)\n    }\n}\n\nimpl From<MaxSizeReached> for HxError {\n    fn from(value: MaxSizeReached) -> Self {\n        Self::TooManyResponseHeaders(value)\n    }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\nimpl From<serde_json::Error> for HxError {\n    fn from(value: serde_json::Error) -> Self {\n        Self::Json(value)\n    }\n}\n\nimpl fmt::Display for HxError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            HxError::InvalidHeaderValue(_) => write!(f, \"Invalid header value\"),\n            HxError::TooManyResponseHeaders(_) => write!(f, \"Too many response headers\"),\n            #[cfg(feature = \"serde\")]\n            HxError::Json(_) => write!(f, \"Json\"),\n        }\n    }\n}\n\nimpl error::Error for HxError {\n    fn source(&self) -> Option<&(dyn error::Error + 'static)> {\n        match self {\n            HxError::InvalidHeaderValue(e) => Some(e),\n            HxError::TooManyResponseHeaders(e) => Some(e),\n            #[cfg(feature = \"serde\")]\n            HxError::Json(e) => Some(e),\n        }\n    }\n}\n\nimpl IntoResponse for HxError {\n    fn into_response(self) -> axum_core::response::Response {\n        (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()\n    }\n}\n"
  },
  {
    "path": "src/extractors.rs",
    "content": "//! Axum extractors for htmx request headers.\n\nuse axum_core::extract::FromRequestParts;\nuse http::request::Parts;\n\nuse crate::{\n    HX_BOOSTED, HX_CURRENT_URL, HX_HISTORY_RESTORE_REQUEST, HX_PROMPT, HX_REQUEST, HX_TARGET,\n    HX_TRIGGER, HX_TRIGGER_NAME,\n};\n\n/// The `HX-Boosted` header.\n///\n/// This is set when a request is made from an element where its parent has the\n/// `hx-boost` attribute set to `true`.\n///\n/// This extractor will always return a value. If the header is not present, it\n/// will return `false`.\n///\n/// See <https://htmx.org/attributes/hx-boost/> for more information.\n#[derive(Debug, Clone, Copy)]\npub struct HxBoosted(pub bool);\n\nimpl<S> FromRequestParts<S> for HxBoosted\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        if parts.headers.contains_key(HX_BOOSTED) {\n            Ok(HxBoosted(true))\n        } else {\n            Ok(HxBoosted(false))\n        }\n    }\n}\n\n/// The `HX-Current-Url` header.\n///\n/// This is set on every request made by htmx itself. As its name implies, it\n/// just contains the current url.\n///\n/// This extractor will always return a value. If the header is not present, or\n/// extractor fails to parse the url it will return `None`.\n#[derive(Debug, Clone)]\npub struct HxCurrentUrl(pub Option<http::Uri>);\n\nimpl<S> FromRequestParts<S> for HxCurrentUrl\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        if let Some(url) = parts.headers.get(HX_CURRENT_URL) {\n            let url = url\n                .to_str()\n                .ok()\n                .and_then(|url| url.parse::<http::Uri>().ok());\n\n            return Ok(HxCurrentUrl(url));\n        }\n\n        Ok(HxCurrentUrl(None))\n    }\n}\n\n/// The `HX-History-Restore-Request` header.\n///\n/// This extractor will always return a value. If the header is not present, it\n/// will return `false`.\n#[derive(Debug, Clone, Copy)]\npub struct HxHistoryRestoreRequest(pub bool);\n\nimpl<S> FromRequestParts<S> for HxHistoryRestoreRequest\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        if parts.headers.contains_key(HX_HISTORY_RESTORE_REQUEST) {\n            Ok(HxHistoryRestoreRequest(true))\n        } else {\n            Ok(HxHistoryRestoreRequest(false))\n        }\n    }\n}\n\n/// The `HX-Prompt` header.\n///\n/// This is set when a request is made from an element that has the `hx-prompt`\n/// attribute set. The value will contain the string input by the user.\n///\n/// This extractor will always return a value. If the header is not present, it\n/// will return `None`.\n#[derive(Debug, Clone)]\npub struct HxPrompt(pub Option<String>);\n\nimpl<S> FromRequestParts<S> for HxPrompt\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        if let Some(prompt) = parts.headers.get(HX_PROMPT) {\n            if let Ok(prompt) = prompt.to_str() {\n                return Ok(HxPrompt(Some(prompt.to_string())));\n            }\n        }\n\n        Ok(HxPrompt(None))\n    }\n}\n\n/// The `HX-Request` header.\n///\n/// This is set on every request made by htmx itself. It won't be present on\n/// requests made manually, or by other libraries.\n///\n/// This extractor will always return a value. If the header is not present, it\n/// will return `false`.\n#[derive(Debug, Clone, Copy)]\npub struct HxRequest(pub bool);\n\nimpl<S> FromRequestParts<S> for HxRequest\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        #[cfg(feature = \"auto-vary\")]\n        parts\n            .extensions\n            .get_mut::<crate::auto_vary::HxRequestExtracted>()\n            .map(crate::auto_vary::Notifier::notify);\n\n        if parts.headers.contains_key(HX_REQUEST) {\n            Ok(HxRequest(true))\n        } else {\n            Ok(HxRequest(false))\n        }\n    }\n}\n\n/// The `HX-Target` header.\n///\n/// This is set when a request is made from an element that has the `hx-target`\n/// attribute set. The value will contain the target element's id. If the id\n/// does not exist on the page, the value will be None.\n///\n/// This extractor will always return a value. If the header is not present, it\n/// will return `None`.\n#[derive(Debug, Clone)]\npub struct HxTarget(pub Option<String>);\n\nimpl<S> FromRequestParts<S> for HxTarget\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        #[cfg(feature = \"auto-vary\")]\n        parts\n            .extensions\n            .get_mut::<crate::auto_vary::HxTargetExtracted>()\n            .map(crate::auto_vary::Notifier::notify);\n\n        if let Some(target) = parts.headers.get(HX_TARGET) {\n            if let Ok(target) = target.to_str() {\n                return Ok(HxTarget(Some(target.to_string())));\n            }\n        }\n\n        Ok(HxTarget(None))\n    }\n}\n\n/// The `HX-Trigger-Name` header.\n///\n/// This is set when a request is made from an element that has the `hx-trigger`\n/// attribute set. The value will contain the trigger element's name. If the\n/// name does not exist on the page, the value will be None.\n///\n/// This extractor will always return a value. If the header is not present, it\n/// will return `None`.\n#[derive(Debug, Clone)]\npub struct HxTriggerName(pub Option<String>);\n\nimpl<S> FromRequestParts<S> for HxTriggerName\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        #[cfg(feature = \"auto-vary\")]\n        parts\n            .extensions\n            .get_mut::<crate::auto_vary::HxTriggerNameExtracted>()\n            .map(crate::auto_vary::Notifier::notify);\n\n        if let Some(trigger_name) = parts.headers.get(HX_TRIGGER_NAME) {\n            if let Ok(trigger_name) = trigger_name.to_str() {\n                return Ok(HxTriggerName(Some(trigger_name.to_string())));\n            }\n        }\n\n        Ok(HxTriggerName(None))\n    }\n}\n\n/// The `HX-Trigger` header.\n///\n/// This is set when a request is made from an element that has the `hx-trigger`\n/// attribute set. The value will contain the trigger element's id. If the id\n/// does not exist on the page, the value will be None.\n///\n/// This extractor will always return a value. If the header is not present, it\n/// will return `None`.\n#[derive(Debug, Clone)]\npub struct HxTrigger(pub Option<String>);\n\nimpl<S> FromRequestParts<S> for HxTrigger\nwhere\n    S: Send + Sync,\n{\n    type Rejection = std::convert::Infallible;\n\n    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {\n        #[cfg(feature = \"auto-vary\")]\n        parts\n            .extensions\n            .get_mut::<crate::auto_vary::HxTriggerExtracted>()\n            .map(crate::auto_vary::Notifier::notify);\n\n        if let Some(trigger) = parts.headers.get(HX_TRIGGER) {\n            if let Ok(trigger) = trigger.to_str() {\n                return Ok(HxTrigger(Some(trigger.to_string())));\n            }\n        }\n\n        Ok(HxTrigger(None))\n    }\n}\n"
  },
  {
    "path": "src/guard.rs",
    "content": "//! Request guard for protecting a router against non-htmx requests.\n\nuse std::{\n    future::Future,\n    pin::Pin,\n    task::{Context, Poll},\n};\n\nuse futures_core::ready;\nuse http::{Request, StatusCode, header::LOCATION, response::Response};\nuse pin_project_lite::pin_project;\nuse tower::{Layer, Service};\n\nuse crate::HX_REQUEST;\n\n/// Checks if the request contains the `HX-Request` header, redirecting to the\n/// given location if not.\n///\n/// This can be useful for preventing users from accidently ending up on a route\n/// which would otherwise return only partial HTML data.\n#[derive(Debug, Clone)]\npub struct HxRequestGuardLayer<'a> {\n    redirect_to: &'a str,\n}\n\nimpl<'a> HxRequestGuardLayer<'a> {\n    pub fn new(redirect_to: &'a str) -> Self {\n        Self { redirect_to }\n    }\n}\n\nimpl Default for HxRequestGuardLayer<'_> {\n    fn default() -> Self {\n        Self { redirect_to: \"/\" }\n    }\n}\n\nimpl<'a, S> Layer<S> for HxRequestGuardLayer<'a> {\n    type Service = HxRequestGuard<'a, S>;\n\n    fn layer(&self, inner: S) -> Self::Service {\n        HxRequestGuard {\n            inner,\n            hx_request: false,\n            layer: self.clone(),\n        }\n    }\n}\n\n/// Tower service that implements redirecting to non-partial routes.\n#[derive(Debug, Clone)]\npub struct HxRequestGuard<'a, S> {\n    inner: S,\n    hx_request: bool,\n    layer: HxRequestGuardLayer<'a>,\n}\n\nimpl<'a, S, T, U> Service<Request<T>> for HxRequestGuard<'a, S>\nwhere\n    S: Service<Request<T>, Response = Response<U>>,\n    U: Default,\n{\n    type Response = S::Response;\n    type Error = S::Error;\n    type Future = private::ResponseFuture<'a, S::Future>;\n\n    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n        self.inner.poll_ready(cx)\n    }\n\n    fn call(&mut self, req: Request<T>) -> Self::Future {\n        // This will always contain a \"true\" value.\n        if req.headers().contains_key(HX_REQUEST) {\n            self.hx_request = true;\n        }\n\n        let response_future = self.inner.call(req);\n\n        private::ResponseFuture {\n            response_future,\n            hx_request: self.hx_request,\n            layer: self.layer.clone(),\n        }\n    }\n}\n\nmod private {\n    use super::*;\n\n    pin_project! {\n        pub struct ResponseFuture<'a, F> {\n            #[pin]\n            pub(super) response_future: F,\n            pub(super) hx_request: bool,\n            pub(super) layer: HxRequestGuardLayer<'a>,\n        }\n    }\n\n    impl<F, B, E> Future for ResponseFuture<'_, F>\n    where\n        F: Future<Output = Result<Response<B>, E>>,\n        B: Default,\n    {\n        type Output = Result<Response<B>, E>;\n\n        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n            let this = self.project();\n            let response: Response<B> = ready!(this.response_future.poll(cx))?;\n\n            match *this.hx_request {\n                true => Poll::Ready(Ok(response)),\n                false => {\n                    let res = Response::builder()\n                        .status(StatusCode::SEE_OTHER)\n                        .header(LOCATION, this.layer.redirect_to)\n                        .body(B::default())\n                        .expect(\"failed to build response\");\n\n                    Poll::Ready(Ok(res))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/headers.rs",
    "content": "//! HTTP headers used by htmx.\n\nuse http::HeaderName;\n\n/// Indicates that the request is via an element using `hx-boost` attribute.\n///\n/// See <https://htmx.org/attributes/hx-boost/> for more information.\npub const HX_BOOSTED: HeaderName = HeaderName::from_static(\"hx-boosted\");\n\n/// The current URL of the browser.\npub const HX_CURRENT_URL: HeaderName = HeaderName::from_static(\"hx-current-url\");\n\n/// `true` if the request is for history restoration after a miss in the local\n/// history cache.\npub const HX_HISTORY_RESTORE_REQUEST: HeaderName =\n    HeaderName::from_static(\"hx-history-restore-request\");\n\n/// The user response to an `hx-prompt`\n///\n/// See <https://htmx.org/attributes/hx-prompt/> for more information.\npub const HX_PROMPT: HeaderName = HeaderName::from_static(\"hx-prompt\");\n\npub(crate) const HX_REQUEST_STR: &str = \"hx-request\";\n\n/// Always `true`.\npub const HX_REQUEST: HeaderName = HeaderName::from_static(HX_REQUEST_STR);\n\npub(crate) const HX_TARGET_STR: &str = \"hx-target\";\n\n/// The `id` of the target element, if it exists.\npub const HX_TARGET: HeaderName = HeaderName::from_static(HX_TARGET_STR);\n\npub(crate) const HX_TRIGGER_NAME_STR: &str = \"hx-trigger-name\";\n\n/// The `name` of the triggered element, if it exists.\npub const HX_TRIGGER_NAME: HeaderName = HeaderName::from_static(HX_TRIGGER_NAME_STR);\n\n/// Allows you to do a client-side redirect that does not do a full page reload.\npub const HX_LOCATION: HeaderName = HeaderName::from_static(\"hx-location\");\n\n/// Pushes a new URL onto the history stack.\npub const HX_PUSH_URL: HeaderName = HeaderName::from_static(\"hx-push-url\");\n\n/// Can be used to do a client-side redirect to a new location.\npub const HX_REDIRECT: HeaderName = HeaderName::from_static(\"hx-redirect\");\n\n/// If set to `true`, the client will do a full refresh on the page.\npub const HX_REFRESH: HeaderName = HeaderName::from_static(\"hx-refresh\");\n\n/// Replaces the currelt URL in the location bar.\npub const HX_REPLACE_URL: HeaderName = HeaderName::from_static(\"hx-replace-url\");\n\n/// Allows you to specify how the response value will be swapped.\n///\n/// See <https://htmx.org/attributes/hx-swap/> for more information.\npub const HX_RESWAP: HeaderName = HeaderName::from_static(\"hx-reswap\");\n\n/// A CSS selector that update the target of the content update to a different\n/// element on the page.\npub const HX_RETARGET: HeaderName = HeaderName::from_static(\"hx-retarget\");\n\n/// A CSS selector that allows you to choose which part of the response is used\n/// to be swapped in. Overrides an existing `hx-select` on the triggering\n/// element\npub const HX_RESELECT: HeaderName = HeaderName::from_static(\"hx-reselect\");\n\npub(crate) const HX_TRIGGER_STR: &str = \"hx-trigger\";\n\n/// Can be set as a request or response header.\n///\n/// In a request, it contains the `id` of the element that triggered the\n/// request.\n///\n/// In a response, it can be used to trigger client-side events.\n///\n/// See <https://htmx.org/headers/hx-trigger/> for more information.\npub const HX_TRIGGER: HeaderName = HeaderName::from_static(HX_TRIGGER_STR);\n\n/// Allows you to trigger client-side events.\n///\n/// See <https://htmx.org/headers/hx-trigger/> for more information.\npub const HX_TRIGGER_AFTER_SETTLE: HeaderName = HeaderName::from_static(\"hx-trigger-after-settle\");\n\n/// Allows you to trigger client-side events.\n///\n/// See <https://htmx.org/headers/hx-trigger/> for more information.\npub const HX_TRIGGER_AFTER_SWAP: HeaderName = HeaderName::from_static(\"hx-trigger-after-swap\");\n"
  },
  {
    "path": "src/lib.rs",
    "content": "#![cfg_attr(feature = \"unstable\", feature(doc_cfg))]\n#![doc = include_str!(\"../README.md\")]\n#![forbid(unsafe_code)]\n#![allow(clippy::too_long_first_doc_paragraph)]\n\nmod error;\npub use error::*;\n\n#[cfg(feature = \"auto-vary\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"auto-vary\")))]\npub mod auto_vary;\npub mod extractors;\n#[cfg(feature = \"guards\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"guards\")))]\npub mod guard;\npub mod headers;\npub mod responders;\n\n#[cfg(feature = \"auto-vary\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"auto-vary\")))]\n#[doc(inline)]\npub use auto_vary::*;\n#[doc(inline)]\npub use extractors::*;\n#[cfg(feature = \"guards\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"guards\")))]\n#[doc(inline)]\npub use guard::*;\n#[doc(inline)]\npub use headers::*;\n#[doc(inline)]\npub use responders::*;\n"
  },
  {
    "path": "src/responders/location.rs",
    "content": "use axum_core::response::{IntoResponseParts, ResponseParts};\nuse http::HeaderValue;\n\nuse crate::{HxError, headers};\n\n/// The `HX-Location` header.\n///\n/// This response header can be used to trigger a client side redirection\n/// without reloading the whole page. If you intend to redirect to a specific\n/// target on the page, you must enable the `serde` feature flag and specify\n/// [`LocationOptions`].\n///\n/// Will fail if the supplied uri contains characters that are not visible ASCII\n/// (32-127).\n///\n/// See <https://htmx.org/headers/hx-location/> for more information.\n#[derive(Debug, Clone)]\npub struct HxLocation {\n    /// Uri of the new location.\n    pub uri: String,\n    /// Extra options.\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\n    pub options: LocationOptions,\n}\n\nimpl HxLocation {\n    /// Parses `uri` and sets it as location.\n    #[allow(clippy::should_implement_trait)]\n    pub fn from_str(uri: impl AsRef<str>) -> Self {\n        Self {\n            #[cfg(feature = \"serde\")]\n            options: LocationOptions::default(),\n            uri: uri.as_ref().to_string(),\n        }\n    }\n\n    /// Parses `uri` and sets it as location with additional options.\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\n    pub fn from_str_with_options(uri: impl AsRef<str>, options: LocationOptions) -> Self {\n        Self {\n            options,\n            uri: uri.as_ref().to_string(),\n        }\n    }\n\n    #[cfg(feature = \"serde\")]\n    fn into_header_with_options(self) -> Result<String, HxError> {\n        if self.options.is_default() {\n            return Ok(self.uri.to_string());\n        }\n\n        #[derive(::serde::Serialize)]\n        struct LocWithOpts {\n            path: String,\n            #[serde(flatten)]\n            opts: LocationOptions,\n        }\n\n        let loc_with_opts = LocWithOpts {\n            path: self.uri.to_string(),\n            opts: self.options,\n        };\n\n        Ok(serde_json::to_string(&loc_with_opts)?)\n    }\n}\n\nimpl<'a> From<&'a str> for HxLocation {\n    fn from(uri: &'a str) -> Self {\n        Self::from_str(uri)\n    }\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\nimpl<'a> From<(&'a str, LocationOptions)> for HxLocation {\n    fn from((uri, options): (&'a str, LocationOptions)) -> Self {\n        Self::from_str_with_options(uri, options)\n    }\n}\n\nimpl IntoResponseParts for HxLocation {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        #[cfg(feature = \"serde\")]\n        let header = self.into_header_with_options()?;\n        #[cfg(not(feature = \"serde\"))]\n        let header = self.uri.to_string();\n\n        res.headers_mut().insert(\n            headers::HX_LOCATION,\n            HeaderValue::from_maybe_shared(header)?,\n        );\n\n        Ok(res)\n    }\n}\n\n/// More options for `HX-Location` header.\n///\n/// - `source` - the source element of the request\n/// - `event` - an event that “triggered” the request\n/// - `handler` - a callback that will handle the response HTML\n/// - `target` - the target to swap the response into\n/// - `swap` - how the response will be swapped in relative to the target\n/// - `values` - values to submit with the request\n/// - `headers` - headers to submit with the request\n/// - `select` - allows you to select the content you want swapped from a\n///   response\n#[cfg(feature = \"serde\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\n#[derive(Debug, Clone, serde::Serialize, Default)]\npub struct LocationOptions {\n    /// The source element of the request.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub source: Option<String>,\n    /// An event that \"triggered\" the request.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub event: Option<String>,\n    /// A callback that will handle the response HTML.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub handler: Option<String>,\n    /// The target to swap the response into.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub target: Option<String>,\n    /// How the response will be swapped in relative to the target.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub swap: Option<crate::SwapOption>,\n    /// Values to submit with the request.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub values: Option<serde_json::Value>,\n    /// Headers to submit with the request.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub headers: Option<serde_json::Value>,\n    // Hacky way of making this struct non-exhaustive.\n    // See <https://rust-lang.github.io/rfcs/2008-non-exhaustive.html> and <https://github.com/robertwayne/axum-htmx/issues/29> for reasoning.\n    #[serde(skip)]\n    pub non_exhaustive: (),\n}\n\n#[cfg(feature = \"serde\")]\n#[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\nimpl LocationOptions {\n    pub(super) fn is_default(&self) -> bool {\n        let Self {\n            source: None,\n            event: None,\n            handler: None,\n            target: None,\n            swap: None,\n            values: None,\n            headers: None,\n            non_exhaustive: (),\n        } = self\n        else {\n            return false;\n        };\n\n        true\n    }\n}\n\n#[cfg(test)]\n#[cfg(feature = \"serde\")]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_serialize_location() {\n        use crate::SwapOption;\n\n        let loc = HxLocation::from(\"/foo\");\n        assert_eq!(loc.into_header_with_options().unwrap(), \"/foo\");\n\n        let loc = HxLocation::from_str_with_options(\n            \"/foo\",\n            LocationOptions {\n                event: Some(\"click\".into()),\n                swap: Some(SwapOption::InnerHtml),\n                ..Default::default()\n            },\n        );\n        assert_eq!(\n            loc.into_header_with_options().unwrap(),\n            r#\"{\"path\":\"/foo\",\"event\":\"click\",\"swap\":\"innerHTML\"}\"#\n        );\n    }\n}\n"
  },
  {
    "path": "src/responders/trigger.rs",
    "content": "use axum_core::response::{IntoResponseParts, ResponseParts};\n\nuse crate::{HxError, headers};\n\n/// Represents a client-side event carrying optional data.\n#[derive(Debug, Clone)]\n#[cfg_attr(feature = \"serde\", derive(serde::Serialize))]\npub struct HxEvent {\n    pub name: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\n    pub data: Option<serde_json::Value>,\n}\n\nimpl HxEvent {\n    /// Creates new event with no associated data.\n    pub fn new(name: impl AsRef<str>) -> Self {\n        Self {\n            name: name.as_ref().to_owned(),\n            #[cfg(feature = \"serde\")]\n            data: None,\n        }\n    }\n\n    /// Creates new event with data.\n    #[cfg(feature = \"serde\")]\n    #[cfg_attr(feature = \"unstable\", doc(cfg(feature = \"serde\")))]\n    pub fn new_with_data<T: ::serde::Serialize>(\n        name: impl AsRef<str>,\n        data: T,\n    ) -> Result<Self, serde_json::Error> {\n        let data = serde_json::to_value(data)?;\n\n        Ok(Self {\n            name: name.as_ref().to_owned(),\n            #[cfg(feature = \"serde\")]\n            data: Some(data),\n        })\n    }\n}\n\nimpl<N: AsRef<str>> From<N> for HxEvent {\n    fn from(name: N) -> Self {\n        Self {\n            name: name.as_ref().to_owned(),\n            #[cfg(feature = \"serde\")]\n            data: None,\n        }\n    }\n}\n\n#[cfg(not(feature = \"serde\"))]\nfn events_to_header_value(events: Vec<HxEvent>) -> Result<http::HeaderValue, HxError> {\n    let header = events\n        .into_iter()\n        .map(|HxEvent { name }| name)\n        .collect::<Vec<_>>()\n        .join(\", \");\n\n    http::HeaderValue::from_str(&header).map_err(Into::into)\n}\n\n#[cfg(feature = \"serde\")]\nfn events_to_header_value(events: Vec<HxEvent>) -> Result<http::HeaderValue, HxError> {\n    use std::collections::HashMap;\n\n    use http::HeaderValue;\n    use serde_json::Value;\n\n    let with_data = events.iter().any(|e| e.data.is_some());\n\n    let header_value = if with_data {\n        // at least one event contains data so the header_value needs to be json\n        // encoded.\n        let header_value = events\n            .into_iter()\n            .map(|e| (e.name, e.data.unwrap_or_default()))\n            .collect::<HashMap<String, Value>>();\n\n        serde_json::to_string(&header_value)?\n    } else {\n        // no event contains data, the event names can be put in the header\n        // value separated by a comma.\n        events\n            .into_iter()\n            .map(|e| e.name)\n            .reduce(|acc, e| acc + \", \" + &e)\n            .unwrap_or_default()\n    };\n\n    HeaderValue::from_maybe_shared(header_value).map_err(HxError::from)\n}\n\n/// Describes when should event be triggered.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n#[non_exhaustive]\npub enum TriggerMode {\n    Normal,\n    AfterSettle,\n    AfterSwap,\n}\n\n/// The `HX-Trigger*` header.\n///\n/// Allows you to trigger client-side events. Corresponds to `HX-Trigger`,\n/// `HX-Trigger-After-Settle` and `HX-Trigger-After-Swap` headers. To change\n/// when events trigger use appropriate `mode`.\n///\n/// Will fail if the supplied events contain or produce characters that are not\n/// visible ASCII (32-127) when serializing to JSON.\n///\n/// See <https://htmx.org/headers/hx-trigger/> for more information.\n///\n/// Note: An `HxResponseTrigger` implements `IntoResponseParts` and should be\n/// used before any other response object would consume the response parts.\n#[derive(Debug, Clone)]\npub struct HxResponseTrigger {\n    pub mode: TriggerMode,\n    pub events: Vec<HxEvent>,\n}\n\nimpl HxResponseTrigger {\n    /// Creates new [trigger](https://htmx.org/headers/hx-trigger/) with\n    /// specified mode and events.\n    pub fn new<T: Into<HxEvent>>(mode: TriggerMode, events: impl IntoIterator<Item = T>) -> Self {\n        Self {\n            mode,\n            events: events.into_iter().map(Into::into).collect(),\n        }\n    }\n\n    /// Creates new [normal](https://htmx.org/headers/hx-trigger/) trigger from\n    /// events.\n    ///\n    /// See `HxResponseTrigger` for more information.\n    pub fn normal<T: Into<HxEvent>>(events: impl IntoIterator<Item = T>) -> Self {\n        Self::new(TriggerMode::Normal, events)\n    }\n\n    /// Creates new [after settle](https://htmx.org/headers/hx-trigger/) trigger\n    /// from events.\n    ///\n    /// See `HxResponseTrigger` for more information.\n    pub fn after_settle<T: Into<HxEvent>>(events: impl IntoIterator<Item = T>) -> Self {\n        Self::new(TriggerMode::AfterSettle, events)\n    }\n\n    /// Creates new [after swap](https://htmx.org/headers/hx-trigger/) trigger\n    /// from events.\n    ///\n    /// See `HxResponseTrigger` for more information.\n    pub fn after_swap<T: Into<HxEvent>>(events: impl IntoIterator<Item = T>) -> Self {\n        Self::new(TriggerMode::AfterSwap, events)\n    }\n}\n\nimpl<T> From<(TriggerMode, T)> for HxResponseTrigger\nwhere\n    T: IntoIterator,\n    T::Item: Into<HxEvent>,\n{\n    fn from((mode, events): (TriggerMode, T)) -> Self {\n        Self {\n            mode,\n            events: events.into_iter().map(Into::into).collect(),\n        }\n    }\n}\n\nimpl IntoResponseParts for HxResponseTrigger {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        if !self.events.is_empty() {\n            let header = match self.mode {\n                TriggerMode::Normal => headers::HX_TRIGGER,\n                TriggerMode::AfterSettle => headers::HX_TRIGGER_AFTER_SETTLE,\n                TriggerMode::AfterSwap => headers::HX_TRIGGER_AFTER_SETTLE,\n            };\n\n            res.headers_mut()\n                .insert(header, events_to_header_value(self.events)?);\n        }\n\n        Ok(res)\n    }\n}\n\n#[cfg(test)]\n#[cfg(feature = \"serde\")]\nmod tests {\n    use http::HeaderValue;\n    use serde_json::json;\n\n    use super::*;\n\n    #[test]\n    fn valid_event_to_header_encoding() {\n        let evt = HxEvent::new_with_data(\n            \"my-event\",\n            json!({\"level\": \"info\", \"message\": {\n                \"body\": \"This is a test message.\",\n                \"title\": \"Hello, world!\",\n            }}),\n        )\n        .unwrap();\n\n        let header_value = events_to_header_value(vec![evt]).unwrap();\n\n        let expected_value = r#\"{\"my-event\":{\"level\":\"info\",\"message\":{\"body\":\"This is a test message.\",\"title\":\"Hello, world!\"}}}\"#;\n\n        assert_eq!(header_value, HeaderValue::from_static(expected_value));\n\n        let value =\n            events_to_header_value(HxResponseTrigger::normal([\"foo\", \"bar\"]).events).unwrap();\n        assert_eq!(value, HeaderValue::from_static(\"foo, bar\"));\n    }\n}\n"
  },
  {
    "path": "src/responders/vary.rs",
    "content": "use axum_core::response::{IntoResponseParts, ResponseParts};\nuse http::header::{HeaderValue, VARY};\n\nuse crate::{HxError, extractors, headers};\n\nconst HX_REQUEST: HeaderValue = HeaderValue::from_static(headers::HX_REQUEST_STR);\nconst HX_TARGET: HeaderValue = HeaderValue::from_static(headers::HX_TARGET_STR);\nconst HX_TRIGGER: HeaderValue = HeaderValue::from_static(headers::HX_TRIGGER_STR);\nconst HX_TRIGGER_NAME: HeaderValue = HeaderValue::from_static(headers::HX_TRIGGER_NAME_STR);\n\n/// The `Vary: HX-Request` header.\n///\n/// You may want to add this header to the response if your handler responds\n/// differently based on the `HX-Request` request header.\n///\n/// For example, if your server renders the full HTML when the `HX-Request`\n/// header is missing or `false`, and it renders a fragment of that HTML when\n/// `HX-Request: true`.\n///\n/// You probably need this only for `GET` requests, as other HTTP methods are\n/// not cached by default.\n///\n/// See <https://htmx.org/docs/#caching> for more information.\n#[derive(Debug, Clone)]\npub struct VaryHxRequest;\n\nimpl IntoResponseParts for VaryHxRequest {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().try_append(VARY, HX_REQUEST)?;\n\n        Ok(res)\n    }\n}\n\nimpl extractors::HxRequest {\n    /// Convenience method to create the corresponding `Vary` response header\n    pub fn vary_response() -> VaryHxRequest {\n        VaryHxRequest\n    }\n}\n\n/// The `Vary: HX-Target` header.\n///\n/// You may want to add this header to the response if your handler responds\n/// differently based on the `HX-Target` request header.\n///\n/// You probably need this only for `GET` requests, as other HTTP methods are\n/// not cached by default.\n///\n/// See <https://htmx.org/docs/#caching> for more information.\n#[derive(Debug, Clone)]\npub struct VaryHxTarget;\n\nimpl IntoResponseParts for VaryHxTarget {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().try_append(VARY, HX_TARGET)?;\n\n        Ok(res)\n    }\n}\n\nimpl extractors::HxTarget {\n    /// Convenience method to create the corresponding `Vary` response header\n    pub fn vary_response() -> VaryHxTarget {\n        VaryHxTarget\n    }\n}\n\n/// The `Vary: HX-Trigger` header.\n///\n/// You may want to add this header to the response if your handler responds\n/// differently based on the `HX-Trigger` request header.\n///\n/// You probably need this only for `GET` requests, as other HTTP methods are\n/// not cached by default.\n///\n/// See <https://htmx.org/docs/#caching> for more information.\n#[derive(Debug, Clone)]\npub struct VaryHxTrigger;\n\nimpl IntoResponseParts for VaryHxTrigger {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().try_append(VARY, HX_TRIGGER)?;\n\n        Ok(res)\n    }\n}\n\nimpl extractors::HxTrigger {\n    /// Convenience method to create the corresponding `Vary` response header\n    pub fn vary_response() -> VaryHxTrigger {\n        VaryHxTrigger\n    }\n}\n\n/// The `Vary: HX-Trigger-Name` header.\n///\n/// You may want to add this header to the response if your handler responds\n/// differently based on the `HX-Trigger-Name` request header.\n///\n/// You probably need this only for `GET` requests, as other HTTP methods are\n/// not cached by default.\n///\n/// See <https://htmx.org/docs/#caching> for more information.\n#[derive(Debug, Clone)]\npub struct VaryHxTriggerName;\n\nimpl IntoResponseParts for VaryHxTriggerName {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().try_append(VARY, HX_TRIGGER_NAME)?;\n\n        Ok(res)\n    }\n}\n\nimpl extractors::HxTriggerName {\n    /// Convenience method to create the corresponding `Vary` response header\n    pub fn vary_response() -> VaryHxTriggerName {\n        VaryHxTriggerName\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::hash_set::HashSet;\n\n    use axum::{Router, routing::get};\n\n    use super::*;\n\n    #[tokio::test]\n    async fn multiple_headers() {\n        let app = Router::new().route(\"/\", get(|| async { (VaryHxRequest, VaryHxTarget, \"foo\") }));\n        let server = axum_test::TestServer::new(app).unwrap();\n\n        let resp = server.get(\"/\").await;\n        let values: HashSet<HeaderValue> = resp.iter_headers_by_name(\"vary\").cloned().collect();\n        assert_eq!(values, HashSet::from([HX_REQUEST, HX_TARGET]));\n    }\n}\n"
  },
  {
    "path": "src/responders.rs",
    "content": "//! Axum responses for htmx response headers.\n\nuse std::convert::Infallible;\n\nuse axum_core::response::{IntoResponseParts, ResponseParts};\nuse http::HeaderValue;\n\nuse crate::{HxError, headers};\n\nmod location;\npub use location::*;\nmod trigger;\npub use trigger::*;\nmod vary;\npub use vary::*;\n\nconst HX_SWAP_INNER_HTML: &str = \"innerHTML\";\nconst HX_SWAP_OUTER_HTML: &str = \"outerHTML\";\nconst HX_SWAP_BEFORE_BEGIN: &str = \"beforebegin\";\nconst HX_SWAP_AFTER_BEGIN: &str = \"afterbegin\";\nconst HX_SWAP_BEFORE_END: &str = \"beforeend\";\nconst HX_SWAP_AFTER_END: &str = \"afterend\";\nconst HX_SWAP_DELETE: &str = \"delete\";\nconst HX_SWAP_NONE: &str = \"none\";\n\n/// The `HX-Push-Url` header.\n///\n/// Pushes a new url into the history stack.\n///\n/// Will fail if the supplied Uri contains characters that are not visible ASCII\n/// (32-127).\n///\n/// See <https://htmx.org/headers/hx-push-url/> for more information.\n#[derive(Debug, Clone)]\npub struct HxPushUrl(pub String);\n\nimpl IntoResponseParts for HxPushUrl {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().insert(\n            headers::HX_PUSH_URL,\n            HeaderValue::from_maybe_shared(self.0)?,\n        );\n\n        Ok(res)\n    }\n}\n\nimpl<'a> From<&'a str> for HxPushUrl {\n    fn from(value: &'a str) -> Self {\n        Self(value.to_string())\n    }\n}\n\n/// The `HX-Redirect` header.\n///\n/// Can be used to do a client-side redirect to a new location.\n///\n/// Will fail if the supplied Uri contains characters that are not visible ASCII\n/// (32-127).\n#[derive(Debug, Clone)]\npub struct HxRedirect(pub String);\n\nimpl IntoResponseParts for HxRedirect {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().insert(\n            headers::HX_REDIRECT,\n            HeaderValue::from_maybe_shared(self.0)?,\n        );\n\n        Ok(res)\n    }\n}\n\nimpl<'a> From<&'a str> for HxRedirect {\n    fn from(value: &'a str) -> Self {\n        Self(value.to_string())\n    }\n}\n\n/// The `HX-Refresh`header.\n///\n/// If set to `true` the client-side will do a full refresh of the page.\n///\n/// This responder will never fail.\n#[derive(Debug, Copy, Clone)]\npub struct HxRefresh(pub bool);\n\nimpl From<bool> for HxRefresh {\n    fn from(value: bool) -> Self {\n        Self(value)\n    }\n}\n\nimpl IntoResponseParts for HxRefresh {\n    type Error = Infallible;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().insert(\n            headers::HX_REFRESH,\n            if self.0 {\n                HeaderValue::from_static(\"true\")\n            } else {\n                HeaderValue::from_static(\"false\")\n            },\n        );\n\n        Ok(res)\n    }\n}\n\n/// The `HX-Replace-Url` header.\n///\n/// Replaces the currelt URL in the location bar.\n///\n/// Will fail if the supplied Uri contains characters that are not visible ASCII\n/// (32-127).\n///\n/// See <https://htmx.org/headers/hx-replace-url/> for more information.\n#[derive(Debug, Clone)]\npub struct HxReplaceUrl(pub String);\n\nimpl IntoResponseParts for HxReplaceUrl {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().insert(\n            headers::HX_REPLACE_URL,\n            HeaderValue::from_maybe_shared(self.0)?,\n        );\n\n        Ok(res)\n    }\n}\n\nimpl<'a> From<&'a str> for HxReplaceUrl {\n    fn from(value: &'a str) -> Self {\n        Self(value.to_string())\n    }\n}\n\n/// The `HX-Reswap` header.\n///\n/// Allows you to specidy how the response will be swapped.\n///\n/// This responder will never fail.\n#[derive(Debug, Copy, Clone)]\npub struct HxReswap(pub SwapOption);\n\nimpl IntoResponseParts for HxReswap {\n    type Error = Infallible;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().insert(headers::HX_RESWAP, self.0.into());\n\n        Ok(res)\n    }\n}\n\nimpl From<SwapOption> for HxReswap {\n    fn from(value: SwapOption) -> Self {\n        Self(value)\n    }\n}\n\n/// The `HX-Retarget` header.\n///\n/// A CSS selector that updates the target of the content update to a different\n/// element on the page.\n///\n/// Will fail if the supplied String contains characters that are not visible\n/// ASCII (32-127).\n#[derive(Debug, Clone)]\npub struct HxRetarget(pub String);\n\nimpl IntoResponseParts for HxRetarget {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().insert(\n            headers::HX_RETARGET,\n            HeaderValue::from_maybe_shared(self.0)?,\n        );\n\n        Ok(res)\n    }\n}\n\nimpl<T: Into<String>> From<T> for HxRetarget {\n    fn from(value: T) -> Self {\n        Self(value.into())\n    }\n}\n\n/// The `HX-Reselect` header.\n///\n/// A CSS selector that allows you to choose which part of the response is used\n/// to be swapped in. Overrides an existing hx-select on the triggering element.\n///\n/// Will fail if the supplied String contains characters that are not visible\n/// ASCII (32-127).\n#[derive(Debug, Clone)]\npub struct HxReselect(pub String);\n\nimpl IntoResponseParts for HxReselect {\n    type Error = HxError;\n\n    fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Self::Error> {\n        res.headers_mut().insert(\n            headers::HX_RESELECT,\n            HeaderValue::from_maybe_shared(self.0)?,\n        );\n\n        Ok(res)\n    }\n}\n\nimpl<T: Into<String>> From<T> for HxReselect {\n    fn from(value: T) -> Self {\n        Self(value.into())\n    }\n}\n\n/// Values of the `hx-swap` attribute.\n// serde::Serialize is implemented in responders/serde.rs\n#[derive(Debug, Copy, Clone)]\npub enum SwapOption {\n    /// Replace the inner html of the target element.\n    InnerHtml,\n    /// Replace the entire target element with the response.\n    OuterHtml,\n    /// Insert the response before the target element.\n    BeforeBegin,\n    /// Insert the response before the first child of the target element.\n    AfterBegin,\n    /// Insert the response after the last child of the target element\n    BeforeEnd,\n    /// Insert the response after the target element\n    AfterEnd,\n    /// Deletes the target element regardless of the response\n    Delete,\n    /// Does not append content from response (out of band items will still be\n    /// processed).\n    None,\n}\n\n// can be removed  and automatically derived when\n// https://github.com/serde-rs/serde/issues/2485 is implemented\n#[cfg(feature = \"serde\")]\nimpl ::serde::Serialize for SwapOption {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: ::serde::Serializer,\n    {\n        const UNIT_NAME: &str = \"SwapOption\";\n        match self {\n            Self::InnerHtml => serializer.serialize_unit_variant(UNIT_NAME, 0, HX_SWAP_INNER_HTML),\n            Self::OuterHtml => serializer.serialize_unit_variant(UNIT_NAME, 1, HX_SWAP_OUTER_HTML),\n            Self::BeforeBegin => {\n                serializer.serialize_unit_variant(UNIT_NAME, 2, HX_SWAP_BEFORE_BEGIN)\n            }\n            Self::AfterBegin => {\n                serializer.serialize_unit_variant(UNIT_NAME, 3, HX_SWAP_AFTER_BEGIN)\n            }\n            Self::BeforeEnd => serializer.serialize_unit_variant(UNIT_NAME, 4, HX_SWAP_BEFORE_END),\n            Self::AfterEnd => serializer.serialize_unit_variant(UNIT_NAME, 5, HX_SWAP_AFTER_END),\n            Self::Delete => serializer.serialize_unit_variant(UNIT_NAME, 6, HX_SWAP_DELETE),\n            Self::None => serializer.serialize_unit_variant(UNIT_NAME, 7, HX_SWAP_NONE),\n        }\n    }\n}\n\nimpl From<SwapOption> for HeaderValue {\n    fn from(value: SwapOption) -> Self {\n        match value {\n            SwapOption::InnerHtml => HeaderValue::from_static(HX_SWAP_INNER_HTML),\n            SwapOption::OuterHtml => HeaderValue::from_static(HX_SWAP_OUTER_HTML),\n            SwapOption::BeforeBegin => HeaderValue::from_static(HX_SWAP_BEFORE_BEGIN),\n            SwapOption::AfterBegin => HeaderValue::from_static(HX_SWAP_AFTER_BEGIN),\n            SwapOption::BeforeEnd => HeaderValue::from_static(HX_SWAP_BEFORE_END),\n            SwapOption::AfterEnd => HeaderValue::from_static(HX_SWAP_AFTER_END),\n            SwapOption::Delete => HeaderValue::from_static(HX_SWAP_DELETE),\n            SwapOption::None => HeaderValue::from_static(HX_SWAP_NONE),\n        }\n    }\n}\n"
  }
]